[
  {
    "path": ".gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Please answer these questions before submitting your issue. Thanks!\n\n### What version of pprof are you using?\n\nIf you are using pprof via `go tool pprof`, what's your `go env` output?\nIf you run pprof from GitHub, what's the Git revision?\n\n\n### What operating system and processor architecture are you using?\n\n\n### What did you do?\n\nIf possible, provide a recipe for reproducing the error.\nAttaching a profile you are trying to analyze is good.\n\n\n### What did you expect to see?\n\n\n### What did you see instead?\n\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: ci\non:\n  push:\n    branches:\n      - main\n  pull_request:\n  schedule:\n    - cron: '0 2 * * *' # Run every day, at 2AM UTC.\nenv:\n  GOPATH: ${{ github.workspace }}\n  WORKING_DIR: ./src/github.com/google/pprof/\njobs:\n  test-mac:\n    runs-on: ${{ matrix.os }}\n    defaults:\n      run:\n        working-directory: ${{ env.WORKING_DIR }}\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.24', '1.25', 'tip']\n        # Supported macOS versions can be found in\n        # https://github.com/actions/virtual-environments#available-environments.\n        os: ['macos-14', 'macos-15']\n        # Supported Xcode versions can be found in:\n        # - https://github.com/actions/virtual-environments/blob/main/images/macos/macos-14-Readme.md#xcode\n        # - https://github.com/actions/virtual-environments/blob/main/images/macos/macos-15-Readme.md#xcode\n        xcode-version: ['26.0', '16.4', '16.3', '16.2', '16.1', '16.0', '15.4', '15.3', '15.2', '15.1', '15.0.1']\n        exclude:\n          - os: 'macos-14'\n            xcode-version: '26.0'\n          - os: 'macos-14'\n            xcode-version: '16.4'\n          - os: 'macos-14'\n            xcode-version: '16.3'\n          - os: 'macos-14'\n            xcode-version: '16.0'\n          - os: 'macos-15'\n            xcode-version: '15.4'\n          - os: 'macos-15'\n            xcode-version: '15.3'\n          - os: 'macos-15'\n            xcode-version: '15.2'\n          - os: 'macos-15'\n            xcode-version: '15.1'\n          - os: 'macos-15'\n            xcode-version: '15.0.1'\n\n    steps:\n      - name: Checkout the repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: ${{ env.WORKING_DIR }}\n\n      - name: Update Go version using setup-go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        if: matrix.go != 'tip'\n        with:\n          # Include cache directives to allow proper caching. Without them, we\n          # get setup-go \"Restore cache failed\" warnings.\n          go-version: ${{ matrix.go }}\n          cache: true\n          cache-dependency-path: '**/go.sum'\n\n      - name: Install Go bootstrap compiler\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        if: matrix.go == 'tip'\n        with:\n          # Bootstrapping go tip requires 1.24\n          # Include cache directives to allow proper caching. Without them, we\n          # get setup-go \"Restore cache failed\" warnings.\n          go-version: 1.24\n          cache: true\n          cache-dependency-path: '**/go.sum'\n\n      - name: Update Go version manually\n        if: matrix.go == 'tip'\n        working-directory: ${{ github.workspace }}\n        run: |\n          git clone https://go.googlesource.com/go $HOME/gotip\n          cd $HOME/gotip/src\n          ./make.bash\n          echo \"GOROOT=$HOME/gotip\" >> $GITHUB_ENV\n          echo \"RUN_STATICCHECK=false\" >> $GITHUB_ENV\n          echo \"RUN_GOLANGCI_LINTER=false\" >> $GITHUB_ENV\n          echo \"$HOME/gotip/bin:$PATH\" >> $GITHUB_PATH\n\n      - name: Set up Xcode\n        uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0\n        with:\n          xcode-version: ${{ matrix.xcode-version }}\n\n      - name: Fetch dependencies\n        run: |\n          brew install graphviz\n          # Do not let tools interfere with the main module's go.mod.\n          cd && go mod init tools\n          go install honnef.co/go/tools/cmd/staticcheck@2025.1.1\n          go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0\n          # Add PATH for installed tools.\n          echo \"$GOPATH/bin:$PATH\" >> $GITHUB_PATH\n\n      - name: Run the script\n        run: |\n          go version\n          ./test.sh\n\n      - name: Code coverage\n        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2\n        with:\n          files: ${{ env.WORKING_DIR }}/coverage.txt\n          fail_ci_if_error: true\n          token: ${{ secrets.CODECOV_TOKEN }}\n\n  test-linux:\n    runs-on: ${{ matrix.os }}\n    defaults:\n      run:\n        working-directory: ${{ env.WORKING_DIR }}\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.24', '1.25', 'tip']\n        os: ['ubuntu-24.04', 'ubuntu-22.04']\n    steps:\n      - name: Checkout the repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: ${{ env.WORKING_DIR }}\n\n      - name: Update Go version using setup-go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        if: matrix.go != 'tip'\n        with:\n          # Include cache directives to allow proper caching. Without them, we\n          # get setup-go \"Restore cache failed\" warnings.\n          go-version: ${{ matrix.go }}\n          cache: true\n          cache-dependency-path: '**/go.sum'\n\n      - name: Install Go bootstrap compiler\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        if: matrix.go == 'tip'\n        with:\n          # Bootstrapping go tip requires 1.24\n          # Include cache directives to allow proper caching. Without them, we\n          # get setup-go \"Restore cache failed\" warnings.\n          go-version: 1.24\n          cache: true\n          cache-dependency-path: '**/go.sum'\n\n      - name: Update Go version manually\n        if: matrix.go == 'tip'\n        working-directory: ${{ github.workspace }}\n        run: |\n          git clone https://go.googlesource.com/go $HOME/gotip\n          cd $HOME/gotip/src\n          ./make.bash\n          echo \"GOROOT=$HOME/gotip\" >> $GITHUB_ENV\n          echo \"RUN_STATICCHECK=false\" >> $GITHUB_ENV\n          echo \"RUN_GOLANGCI_LINTER=false\" >> $GITHUB_ENV\n          echo \"$HOME/gotip/bin\" >> $GITHUB_PATH\n\n      - name: Check chrome for browser tests\n        run: |\n          google-chrome --version\n          which google-chrome\n\n      - name: Add LLVM 14.0 repository to ensure llvm-symbolizer 14.0.0+ on Ubuntu 20.04\n        if: matrix.os == 'ubuntu-20.04'\n        run: |\n          wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -\n          sudo add-apt-repository \"deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main\"\n          sudo apt-get update\n\n      - name: Install llvm-symbolizer\n        run: |\n          if [ \"${{ matrix.os }}\" = \"ubuntu-20.04\" ]; then\n            sudo apt-get install -y llvm-14 clang-14\n            sudo update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-14 100\n          else\n            sudo apt-get update\n            sudo apt-get install -y llvm clang\n          fi\n\n      - name: Fetch dependencies\n        run: |\n          sudo apt-get install graphviz\n          # Do not let tools interfere with the main module's go.mod.\n          cd && go mod init tools\n          go install honnef.co/go/tools/cmd/staticcheck@2025.1.1\n          go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0\n          # Add PATH for installed tools.\n          echo \"$GOPATH/bin:$PATH\" >> $GITHUB_PATH\n\n      - name: Check llvm-symbolizer installation\n        run: |\n          llvm-symbolizer --version\n\n      - name: Run the script\n        run: |\n          go version\n          ./test.sh\n\n      - name: Code coverage\n        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2\n        with:\n          files: ${{ env.WORKING_DIR }}/coverage.txt\n          fail_ci_if_error: true\n          token: ${{ secrets.CODECOV_TOKEN }}\n\n  test-windows:\n    runs-on: windows-2022\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.24', '1.25']\n    steps:\n      - name: Checkout the repo\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          path: ${{ env.WORKING_DIR }}\n\n      - name: Update Go version using setup-go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          # Include cache directives to allow proper caching. Without them, we\n          # get setup-go \"Restore cache failed\" warnings.\n          go-version: ${{ matrix.go }}\n          cache: true\n          cache-dependency-path: '**/go.sum'\n\n      - name: Fetch Windows dependency\n        uses: crazy-max/ghaction-chocolatey@dff3862348493b11fba2fbc49147b6d2dfe09b66 # v4.0.0\n        with:\n          args: install graphviz llvm\n\n      - name: Run the test\n        run: |\n          go version\n          # This is a workaround to make graphviz installed through choco work.\n          # It generates a config file to tell dot what layout engine and\n          # format types are available. See\n          # https://github.com/google/pprof/issues/585 for more details.\n          dot -c\n          go env\n          go build github.com/google/pprof\n          go test -v ./...\n        working-directory: ${{ env.WORKING_DIR }}\n\n  check:\n    if: always()\n    runs-on: ubuntu-latest\n    needs:\n    - test-mac\n    - test-linux\n    - test-windows\n    steps:\n    - name: Decide whether the needed jobs succeeded or failed\n      uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2\n      with:\n        jobs: ${{ toJSON(needs) }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*~\n*.orig\n*.exe\n.*.swp\ncore\ncoverage.txt\npprof\n"
  },
  {
    "path": "AUTHORS",
    "content": "# This is the official list of pprof authors for copyright purposes.\n# This file is distinct from the CONTRIBUTORS files.\n# See the latter for an explanation.\n# Names should be added to this file as:\n# Name or Organization <email address>\n# The email address is not required for organizations.\nGoogle Inc."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Want to contribute? Great: read the page (including the small print at the end).\n\n# Before you contribute\n\nAs an individual, sign the [Google Individual Contributor License\nAgreement](https://cla.developers.google.com/about/google-individual) (CLA)\nonline. This is required for any of your code to be accepted.\n\nBefore you start working on a larger contribution, get in touch with us first\nthrough the issue tracker with your idea so that we can help out and possibly\nguide you. Coordinating up front makes it much easier to avoid frustration later\non.\n\n# What to expect\n\nAll submissions (including by project members) are done via GitHub pull requests\nand require a code review by a project member.\n\nWe expect contributions to be good, clean code following style and practices for\nthe language the contribution is in. The pprof source code is in Go with a bit\nof JavaScript, CSS and HTML. If you are new to Go, read [Effective\nGo](https://golang.org/doc/effective_go.html) and the [summary on typical\ncomments during Go code\nreviews](https://github.com/golang/go/wiki/CodeReviewComments).\n\nAll contributions should include automated tests for the change. We are\ncontinuously improving pprof automated testing and we can't accept changes that\nare not helping that direction. Code coverage numbers are automatically\npublished in each pull request - we expect that number to go up.  Note that\nadding a good test often requires more time than the fix itself - this is\nexpected and you should be prepared for that time investment.\n\nContributions that do not meet the above guidelines will get less attention and\nwill be slow to get accepted or won't be accepted at all. We will also likely\nrefuse to accept changes that have fairly limited audience but will require us\nto commit to maintain them for foreseeable future. This includes support for\nspecific platforms, making internal pprof APIs public, etc.\n\n# Development\n\nThe commands below assume `/tmp/pprof` as the location for the source code.\nYou can change it to a directory of your choice.\n\nTo get the source code, run\n\n```\ncd /tmp\ngit clone git@github.com:google/pprof.git\ncd pprof\n```\n\nTo run the tests, do\n\n```\ncd /tmp/pprof\ngo test -v ./...\n(cd browsertests && go test)\n```\n\nWhen you wish to work with your own fork of the source (which is required to be\nable to create a pull request), you'll want to get your fork repo as another Git\nremote in the same `github.com/google/pprof` directory. Otherwise, if you'll `go\nget` your fork directly, you'll be getting errors like `use of internal package\nnot allowed` when running tests.  To set up the remote do something like\n\n```\ncd /tmp/pprof\ngit remote add aalexand git@github.com:aalexand/pprof.git\ngit fetch aalexand\ngit checkout -b my-new-feature\n# hack hack hack\ngo test -v ./...\n(cd browsertests && go test)\ngit commit -a -m \"Add new feature.\"\ngit push aalexand\n```\n\nwhere `aalexand` is your GitHub user ID. Then proceed to the GitHub UI to send a\ncode review.\n\n# The small print\n\nContributions made by corporations are covered by a different agreement than the\none above, the [Software Grant and Corporate Contributor License\nAgreement](https://cla.developers.google.com/about/google-corporate).\n"
  },
  {
    "path": "CONTRIBUTORS",
    "content": "# People who have agreed to one of the CLAs and can contribute patches.\n# The AUTHORS file lists the copyright holders; this file\n# lists people.  For example, Google employees are listed here\n# but not in AUTHORS, because Google holds the copyright.\n#\n# https://developers.google.com/open-source/cla/individual\n# https://developers.google.com/open-source/cla/corporate\n#\n# Names should be added to this file as:\n#     Name <email address>\nRaul Silvera <rsilvera@google.com>\nTipp Moseley <tipp@google.com>\nHyoun Kyu Cho <netforce@google.com>\nMartin Spier <spiermar@gmail.com>\nTaco de Wolff <tacodewolff@gmail.com>\nAndrew Hunter <andrewhhunter@gmail.com>\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[![Github Action CI](https://github.com/google/pprof/workflows/ci/badge.svg)](https://github.com/google/pprof/actions)\n[![Codecov](https://codecov.io/gh/google/pprof/graph/badge.svg)](https://codecov.io/gh/google/pprof)\n[![Go Reference](https://pkg.go.dev/badge/github.com/google/pprof/profile.svg)](https://pkg.go.dev/github.com/google/pprof/profile)\n\n# Introduction\n\npprof is a tool for visualization and analysis of profiling data.\n\npprof reads a collection of profiling samples in profile.proto format and\ngenerates reports to visualize and help analyze the data. It can generate both\ntext and graphical reports (through the use of the dot visualization package).\n\nprofile.proto is a protocol buffer that describes a set of callstacks\nand symbolization information. A common usage is to represent a set of\nsampled callstacks from statistical profiling. The format is\ndescribed on the [proto/profile.proto](./proto/profile.proto) file. For details on protocol\nbuffers, see https://developers.google.com/protocol-buffers\n\nProfiles can be read from a local file, or over http. Multiple\nprofiles of the same type can be aggregated or compared.\n\nIf the profile samples contain machine addresses, pprof can symbolize\nthem through the use of the native binutils tools (addr2line and nm).\n\n**This is not an official Google product.**\n\n# Building pprof\n\nPrerequisites:\n\n- Go development kit of a [supported version](https://golang.org/doc/devel/release.html#policy).\n  Follow [these instructions](http://golang.org/doc/code.html) to prepare\n  the environment.\n\n- Graphviz: http://www.graphviz.org/\n  Optional, used to generate graphic visualizations of profiles\n\nTo build and install it:\n\n    go install github.com/google/pprof@latest\n\nThe binary will be installed `$GOPATH/bin` (`$HOME/go/bin` by default).\n\n# Basic usage\n\npprof can read a profile from a file or directly from a server via http.\nSpecify the profile input(s) in the command line, and use options to\nindicate how to format the report.\n\n## Generate a text report of the profile, sorted by hotness:\n\n```\n% pprof -top [main_binary] profile.pb.gz\nWhere\n    main_binary:  Local path to the main program binary, to enable symbolization\n    profile.pb.gz: Local path to the profile in a compressed protobuf, or\n                   URL to the http service that serves a profile.\n```\n\n## Generate a graph in an SVG file, and open it with a web browser:\n\n```\npprof -web [main_binary] profile.pb.gz\n```\n\n## Run pprof on interactive mode:\n\nIf no output formatting option is specified, pprof runs on interactive mode,\nwhere reads the profile and accepts interactive commands for visualization and\nrefinement of the profile.\n\n```\npprof [main_binary] profile.pb.gz\n\nThis will open a simple shell that takes pprof commands to generate reports.\nType 'help' for available commands/options.\n```\n\n## Run pprof via a web interface\n\nIf the `-http` flag is specified, pprof starts a web server at\nthe specified host:port that provides an interactive web-based interface to pprof.\nHost is optional, and is \"localhost\" by default. Port is optional, and is a\nrandom available port by default. `-http=\":\"` starts a server locally at\na random port.\n\n```\npprof -http=[host]:[port] [main_binary] profile.pb.gz\n```\n\nThe preceding command should automatically open your web browser at\nthe right page; if not, you can manually visit the specified port in\nyour web browser.\n\n## Using pprof with Linux Perf\n\npprof can read `perf.data` files generated by the\n[Linux perf](https://perf.wiki.kernel.org/index.php/Main_Page) tool by using the\n`perf_to_profile` program from the\n[perf_data_converter](https://github.com/google/perf_data_converter) package.\n\n## Viewing disassembly on Windows\n\nTo view disassembly of profiles collected from Go programs compiled as Windows executables,\nthe executable must be built with `go build -buildmode=exe`. LLVM or GCC must be installed,\nso required tools like `addr2line` and `nm` are available to `pprof`.\n\n## Further documentation\n\nSee [doc/README.md](doc/README.md) for more detailed end-user documentation.\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for contribution documentation.\n\nSee [proto/README.md](proto/README.md) for a description of the profile.proto format.\n"
  },
  {
    "path": "browsertests/README.md",
    "content": "Browser tests are separated out into a module of their own to avoid\npolluting pprof dependencies with chromedp.\n\nThese tests can be run by executing the following in the top-level\nof the pprof directory:\n\n```shell\n(cd browsertests && go test ./...)\n```\n"
  },
  {
    "path": "browsertests/browser_test.go",
    "content": "// Copyright 2023 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage browsertests\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t_ \"embed\"\n\n\t\"github.com/chromedp/chromedp\"\n)\n\nfunc maybeSkipBrowserTest(t *testing.T) {\n\t// Limit to just Linux for now since this is expensive and the\n\t// browser interactions should be platform agnostic.  If we ever\n\t// see a benefit from wider testing, we can relax this.\n\tif runtime.GOOS != \"linux\" || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86-64 Linux\")\n\t}\n\n\t// Check that browser is available.\n\tif _, err := exec.LookPath(\"google-chrome\"); err == nil {\n\t\treturn\n\t}\n\tif _, err := exec.LookPath(\"chrome\"); err == nil {\n\t\treturn\n\t}\n\tt.Skip(\"chrome not available\")\n}\n\nfunc TestTopTable(t *testing.T) {\n\tmaybeSkipBrowserTest(t)\n\n\tprof := makeFakeProfile()\n\tserver := makeTestServer(t, prof)\n\tctx := newContext(context.Background(), t)\n\n\terr := chromedp.Run(ctx,\n\t\tchromedp.Navigate(server.URL+\"/top\"),\n\t\tchromedp.WaitVisible(`#toptable`, chromedp.ByID),\n\n\t\t// Check that fake profile entries show up in the right order.\n\t\tmatchRegexp(t, \"#node0\", `200ms.*F2`),\n\t\tmatchInOrder(t, \"#toptable\", \"F2\", \"F3\", \"F1\"),\n\n\t\t// Check sorting by cumulative count.\n\t\tchromedp.Click(`#cumhdr1`, chromedp.ByID),\n\t\tmatchInOrder(t, \"#toptable\", \"F1\", \"F2\", \"F3\"),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFlameGraph(t *testing.T) {\n\tmaybeSkipBrowserTest(t)\n\n\tprof := makeFakeProfile()\n\tserver := makeTestServer(t, prof)\n\tctx := newContext(context.Background(), t)\n\n\tvar ignored []byte // Some chromedp.Evaluate() versions wants non-nil result argument\n\terr := chromedp.Run(ctx,\n\t\tchromedp.Navigate(server.URL),\n\t\tchromedp.Evaluate(jsTestFixture, &ignored),\n\t\teval(t, jsCheckFlame),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n//go:embed testdata/testflame.js\nvar jsCheckFlame string\n\nfunc TestSource(t *testing.T) {\n\tmaybeSkipBrowserTest(t)\n\n\tprof := makeFakeProfile()\n\tserver := makeTestServer(t, prof)\n\tctx := newContext(context.Background(), t)\n\n\terr := chromedp.Run(ctx,\n\t\tchromedp.Navigate(server.URL+\"/source?f=F3\"),\n\t\tchromedp.WaitVisible(`#content`, chromedp.ByID),\n\t\tmatchRegexp(t, \"#content\", `F3`),            // Header\n\t\tmatchRegexp(t, \"#content\", `Total:.*100ms`), // Total for function\n\t\tmatchRegexp(t, \"#content\", `\\b22\\b.*100ms`), // Line 22\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc newContext(ctx context.Context, t *testing.T) context.Context {\n\topts := append(chromedp.DefaultExecAllocatorOptions[:],\n\t\t// Ubuntu 23+ enables AppArmor in a way that conflicts with Chrome's usage\n\t\t// of unprivileged user namespaces as part of the sandboxing. Since our\n\t\t// test does not visit any external websites, we don't really need the\n\t\t// sandbox, so disable it.\n\t\tchromedp.NoSandbox,\n\t)\n\n\t// browserDeadline is the deadline to use for browser tests. This is long to\n\t// reduce flakiness in CI workflows.\n\tconst browserDeadline = time.Second * 90\n\n\tctx, cancel := chromedp.NewExecAllocator(ctx, opts...)\n\tt.Cleanup(cancel)\n\tctx, cancel = context.WithTimeout(ctx, browserDeadline)\n\tt.Cleanup(cancel)\n\tctx, cancel = chromedp.NewContext(ctx)\n\tt.Cleanup(cancel)\n\treturn ctx\n}\n\n// matchRegexp is a chromedp.Action that fetches the text of the first\n// node that matched query and checks that the text matches regexp re.\nfunc matchRegexp(t *testing.T, query, re string) chromedp.ActionFunc {\n\treturn func(ctx context.Context) error {\n\t\tvar value string\n\t\terr := chromedp.Text(query, &value, chromedp.ByQuery).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"text %s: %v\", query, err)\n\t\t}\n\t\tt.Logf(\"text %s:\\n%s\", query, value)\n\t\tm, err := regexp.MatchString(re, value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !m {\n\t\t\treturn fmt.Errorf(\"%s: did not find %q in\\n%s\", query, re, value)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// matchInOrder is a chromedp.Action that fetches the text of the first\n// node that matched query and checks that the supplied sequence of\n// strings occur in order in the text.\nfunc matchInOrder(t *testing.T, query string, sequence ...string) chromedp.ActionFunc {\n\treturn func(ctx context.Context) error {\n\t\tvar value string\n\t\terr := chromedp.Text(query, &value, chromedp.ByQuery).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"text %s: %v\", query, err)\n\t\t}\n\t\tt.Logf(\"text %s:\\n%s\", query, value)\n\t\tremaining := value\n\t\tfor _, s := range sequence {\n\t\t\tpos := strings.Index(remaining, s)\n\t\t\tif pos < 0 {\n\t\t\t\treturn fmt.Errorf(\"%s: did not find %q in expected order %v  in\\n%s\", query, s, sequence, value)\n\t\t\t}\n\t\t\tremaining = remaining[pos+len(s):]\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// eval runs the specified javascript in the browser. The javascript must\n// return an [][]any, where each of the []any starts with either \"LOG\" or\n// \"ERROR\" (see testdata/testfixture.js).\nfunc eval(t *testing.T, js string) chromedp.ActionFunc {\n\treturn func(ctx context.Context) error {\n\t\tvar result [][]any\n\t\terr := chromedp.Evaluate(js, &result).Do(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, s := range result {\n\t\t\tif len(s) > 0 && s[0] == \"LOG\" {\n\t\t\t\tt.Log(s[1:]...)\n\t\t\t} else if len(s) > 0 && s[0] == \"ERROR\" {\n\t\t\t\tt.Error(s[1:]...)\n\t\t\t} else {\n\t\t\t\tt.Error(s...) // Treat missing prefix as an error.\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n//go:embed testdata/testfixture.js\nvar jsTestFixture string\n"
  },
  {
    "path": "browsertests/go.mod",
    "content": "module github.com/google/pprof/browsertests\n\ngo 1.24.0\n\ntoolchain go1.24.9\n\n// Use the version of pprof in this directory tree.\nreplace github.com/google/pprof => ../\n\nrequire (\n\tgithub.com/chromedp/chromedp v0.13.6\n\tgithub.com/google/pprof v0.0.0\n)\n\nrequire (\n\tgithub.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b // indirect\n\tgithub.com/chromedp/sysutil v1.1.0 // indirect\n\tgithub.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // indirect\n\tgithub.com/gobwas/httphead v0.1.0 // indirect\n\tgithub.com/gobwas/pool v0.2.1 // indirect\n\tgithub.com/gobwas/ws v1.4.0 // indirect\n\tgithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect\n\tgolang.org/x/sys v0.32.0 // indirect\n)\n"
  },
  {
    "path": "browsertests/go.sum",
    "content": "github.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b h1:jJmiCljLNTaq/O1ju9Bzz2MPpFlmiTn0F7LwCoeDZVw=\ngithub.com/chromedp/cdproto v0.0.0-20250403032234-65de8f5d025b/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=\ngithub.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk=\ngithub.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A=\ngithub.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=\ngithub.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=\ngithub.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=\ngithub.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=\ngithub.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=\ngithub.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA=\ngithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=\ngolang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\n"
  },
  {
    "path": "browsertests/testdata/testfixture.js",
    "content": "// TestFixture records log messages and errors in an array that will\n// be returned to Go code. Each element in the result array is either\n// an array of the form [\"LOG\", ...], or [\"ERROR\", ...].\nclass TestFixture {\n  constructor() {\n    this.context = \"\";  // Added to front of all log and error messages.\n    this.result = [];\n  }\n\n  run(name, subtest) {\n    this.result.push([\"LOG\", \"===\", name]);\n    this.context = \"\";\n    subtest();\n    this.context = \"\";\n  }\n\n  // setContext arranges to add name(args) to all messages added\n  // until the next setContext or run call.\n  setContext(name, ...args) {\n    this.context = name + \"(\" + args.join(\",\") + \")\";\n  }\n\n  log(...args) {\n    this.result.push([\"LOG\", this.context, ...args]);\n  }\n\n  err(...args) {\n    this.result.push([\"ERROR\", this.context, ...args]);\n  }\n}\n"
  },
  {
    "path": "browsertests/testdata/testflame.js",
    "content": "function TestFlame() {\n  const PADDING = 2; // Matches PADDING in stackViewer\n\n  const test = new TestFixture();\n  const chart = document.getElementById(\"stack-chart\");\n  if (!chart) {\n    test.err(\"could not find stack-chart\");\n    return;\n  }\n  const chartRect = chart.getBoundingClientRect();\n\n  // Create map from box text to DOM element.\n  // TODO: Generalize to support multiple boxes for a given piece of text.\n  let boxMap;\n  fetchBoxes();\n  function fetchBoxes() {\n    boxMap = new Map();\n    const boxes = document.querySelectorAll(\".boxbg\");\n    for (let box of boxes) {\n      const text = box.innerText;\n      boxMap.set(text, box);\n    }\n  }\n\n  // rect gets the bounding box for box with text t.\n  function rect(t) {\n    const elem = boxMap.get(t);\n    if (!elem) {\n      test.err(\"did not find\", t);\n      return null;\n    }\n    return elem.getBoundingClientRect();\n  }\n\n  // checkCalls checks that box with text a is positioned w.r.t. box with\n  // text b to indicate a call from a to b.  Expect a gap of the specified\n  // number of rows.\n  function checkCalls(a, b, gap = 0) {\n    test.setContext(\"checkCalls\", a, b, gap);\n    const ra = rect(a);\n    const rb = rect(b);\n    if (!ra || !rb) return;\n\n    const pixelGap = gap * rb.height;\n    if (rb.top != ra.bottom + pixelGap) {\n      test.err(\"not above\");\n    }\n    // TODO: Allow checking boxes above pivots.\n    if (rb.left < ra.left || rb.right > ra.right) {\n      test.err(\"horizontal span of\", a, \"is not nested inside horizontal span of\", b);\n    }\n  }\n\n  // checkWidth checks that the width of the box with text t is approximately\n  // the specified fraction of the total width.\n  function checkWidth(t, fraction) {\n    test.setContext(\"checkWidth\", t, fraction);\n    const r = rect(t);\n    if (!r) return;\n    const expect = (chartRect.width - 2*PADDING) * fraction;\n    if (r.width < expect*0.95 || r.width > expect*1.05) {\n      test.err(\"bad width\", r.width, \"expecting ~\", expect);\n    }\n  }\n\n  // Fake profile has the following stacks:\n  //    100 F1 F2 F3\n  //    200 F1 F2\n  test.run(\"initial\", function() {\n    checkCalls(\"root\", \"F1\");\n    checkCalls(\"F1\", \"F2\");\n    checkCalls(\"F2\", \"F3\");\n    checkWidth(\"root\", 300/300);\n    checkWidth(\"F1\", 300/300);\n    checkWidth(\"F2\", 300/300);\n    checkWidth(\"F3\", 100/300);\n  });\n\n  test.run(\"Pivot F3\", function() {\n    boxMap.get(\"F3\").click();\n    fetchBoxes();\n    checkCalls(\"root\", \"F1\");\n    checkCalls(\"F1\", \"F2\");\n    checkCalls(\"F2\", \"F3\", 1);\n    checkWidth(\"root\", 100/100);\n    checkWidth(\"F1\", 100/100);\n    checkWidth(\"F2\", 100/100);\n    checkWidth(\"F3\", 100/100);\n  });\n\n  test.run(\"NavigateWithoutPivot\", function() {\n    // Clear pivot\n    boxMap.get(\"root\").click();\n\n    // Trigger link update.\n    const btn = document.getElementById(\"graphbtn\");\n    if (!btn) {\n      test.err(\"no graph button on page\");\n      return;\n    }\n    const event = new Event(\"mouseenter\");\n    btn.dispatchEvent(event);\n\n    // Check that URL does not contain a focus parameter.\n    test.log(btn.href);\n    const url = new URL(btn.href);\n    if (url.searchParams.has('f')) {\n      test.err(\"unexpected focus parameter in URL\", btn.href);\n    }\n  });\n\n  test.run(\"Units\", function() {\n    function checkUnitText(unit, v, expect) {\n      const result = pprofUnitText(v, unit);\n      if (result != expect) {\n        test.err(\"bad text for\", v, unit, \":\", result, \"expecting:\", expect);\n      }\n    }\n\n    // Time units, plus logic tests.\n    checkUnitText(\"s\", 0.51e-9, \"0.51ns\");\n    checkUnitText(\"s\", 3e-9, \"3ns\");\n    checkUnitText(\"s\", 1.23e-6, \"1.23us\");\n    checkUnitText(\"s\", 0.04, \"40ms\");\n    checkUnitText(\"s\", 1, \"1s\");\n    checkUnitText(\"s\", 3599, \"3599s\");\n    checkUnitText(\"s\", 3600, \"1hrs\");\n\n    // Sanity check for byte units.\n    checkUnitText(\"B\", 2*1048576, \"2MB\");\n\n    // Unknown unit.\n    checkUnitText(\"cm\", 100, \"100cm\");\n  });\n\n  return test.result;\n}\nTestFlame();\n"
  },
  {
    "path": "browsertests/testutils.go",
    "content": "package browsertests\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/pprof/driver\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc makeTestServer(t testing.TB, prof *profile.Profile) *httptest.Server {\n\tif runtime.GOOS == \"nacl\" || runtime.GOOS == \"js\" {\n\t\tt.Skip(\"test assumes tcp available\")\n\t}\n\n\t// Custom http server creator\n\tvar server *httptest.Server\n\tserverCreated := make(chan bool)\n\tcreator := func(a *driver.HTTPServerArgs) error {\n\t\tserver = httptest.NewServer(http.HandlerFunc(\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif h := a.Handlers[r.URL.Path]; h != nil {\n\t\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\t}\n\t\t\t}))\n\t\tserverCreated <- true\n\t\treturn nil\n\t}\n\n\t// Start server and wait for it to be initialized\n\tgo func() {\n\t\terr := driver.PProf(&driver.Options{\n\t\t\tObj:        fakeObjTool{},\n\t\t\tUI:         testUI{t},\n\t\t\tFetch:      testFetcher{prof},\n\t\t\tHTTPServer: creator,\n\t\t\tFlagset: testFlags{\n\t\t\t\t\"http\":       \"unused:1234\",\n\t\t\t\t\"no_browser\": true,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\t<-serverCreated\n\n\t// Close the server when the test is done.\n\tt.Cleanup(server.Close)\n\n\treturn server\n}\n\n// Fake test implementations of types needed by pprof driver.\n\nconst addrBase = 0x1000\nconst fakeSource = \"testdata/file1000.src\"\n\ntype fakeObj struct{}\n\nfunc (f fakeObj) Close() error                        { return nil }\nfunc (f fakeObj) Name() string                        { return \"testbin\" }\nfunc (f fakeObj) ObjAddr(addr uint64) (uint64, error) { return addr, nil }\nfunc (f fakeObj) BuildID() string                     { return \"\" }\nfunc (f fakeObj) SourceLine(addr uint64) ([]driver.Frame, error) {\n\treturn nil, fmt.Errorf(\"SourceLine unimplemented\")\n}\nfunc (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*driver.Sym, error) {\n\treturn []*driver.Sym{\n\t\t{\n\t\t\tName: []string{\"F1\"}, File: fakeSource,\n\t\t\tStart: addrBase, End: addrBase + 10,\n\t\t},\n\t\t{\n\t\t\tName: []string{\"F2\"}, File: fakeSource,\n\t\t\tStart: addrBase + 10, End: addrBase + 20,\n\t\t},\n\t\t{\n\t\t\tName: []string{\"F3\"}, File: fakeSource,\n\t\t\tStart: addrBase + 20, End: addrBase + 30,\n\t\t},\n\t}, nil\n}\n\ntype fakeObjTool struct{}\n\nfunc (obj fakeObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (driver.ObjFile, error) {\n\treturn fakeObj{}, nil\n}\n\nfunc (obj fakeObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]driver.Inst, error) {\n\treturn []driver.Inst{\n\t\t{Addr: addrBase + 10, Text: \"f1:asm\", Function: \"F1\", Line: 3},\n\t\t{Addr: addrBase + 20, Text: \"f2:asm\", Function: \"F2\", Line: 11},\n\t\t{Addr: addrBase + 30, Text: \"d3:asm\", Function: \"F3\", Line: 22},\n\t}, nil\n}\n\nfunc makeFakeProfile() *profile.Profile {\n\t// Three functions: F1, F2, F3 with three lines, 11, 22, 33.\n\tfuncs := []*profile.Function{\n\t\t{ID: 1, Name: \"F1\", Filename: fakeSource, StartLine: 3},\n\t\t{ID: 2, Name: \"F2\", Filename: fakeSource, StartLine: 5},\n\t\t{ID: 3, Name: \"F3\", Filename: fakeSource, StartLine: 7},\n\t}\n\tlines := []profile.Line{\n\t\t{Function: funcs[0], Line: 11},\n\t\t{Function: funcs[1], Line: 22},\n\t\t{Function: funcs[2], Line: 33},\n\t}\n\tmapping := []*profile.Mapping{\n\t\t{\n\t\t\tID:             1,\n\t\t\tStart:          addrBase,\n\t\t\tLimit:          addrBase + 100,\n\t\t\tOffset:         0,\n\t\t\tFile:           \"testbin\",\n\t\t\tHasFunctions:   true,\n\t\t\tHasFilenames:   true,\n\t\t\tHasLineNumbers: true,\n\t\t},\n\t}\n\n\t// Three interesting addresses: base+{10,20,30}\n\tlocs := []*profile.Location{\n\t\t{ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]},\n\t\t{ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]},\n\t\t{ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]},\n\t}\n\n\t// Two stack traces.\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locs[2], locs[1], locs[0]},\n\t\t\t\tValue:    []int64{100},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locs[1], locs[0]},\n\t\t\t\tValue:    []int64{200},\n\t\t\t},\n\t\t},\n\t\tLocation: locs,\n\t\tFunction: funcs,\n\t\tMapping:  mapping,\n\t}\n}\n\ntype testFlags map[string]any\n\nfunc (flags testFlags) Bool(name string, def bool, usage string) *bool {\n\treturn getFlag(flags, name, def)\n}\nfunc (flags testFlags) Int(name string, def int, usage string) *int {\n\treturn getFlag(flags, name, def)\n}\nfunc (flags testFlags) Float64(name string, def float64, usage string) *float64 {\n\treturn getFlag(flags, name, def)\n}\nfunc (flags testFlags) String(name string, def string, usage string) *string {\n\treturn getFlag(flags, name, def)\n}\nfunc (flags testFlags) StringList(name string, def string, usage string) *[]*string {\n\treturn getFlag(flags, name, []*string{}) // Not supported, so return an empty list.\n}\nfunc (flags testFlags) ExtraUsage() string          { return \"\" }\nfunc (flags testFlags) AddExtraUsage(eu string)     {}\nfunc (flags testFlags) Parse(usage func()) []string { return []string{\"test\", \"bin\"} }\n\nvar _ driver.FlagSet = testFlags{}\n\nfunc getFlag[T any](flags testFlags, name string, def T) *T {\n\tresult := &def\n\tif v, ok := flags[name]; ok {\n\t\t*result = v.(T)\n\t}\n\treturn result\n}\n\ntype testUI struct {\n\tT testing.TB\n}\n\nfunc (ui testUI) ReadLine(_ string) (string, error)     { return \"\", io.EOF }\nfunc (ui testUI) IsTerminal() bool                      { return false }\nfunc (ui testUI) WantBrowser() bool                     { return false }\nfunc (ui testUI) SetAutoComplete(_ func(string) string) {}\nfunc (ui testUI) Print(args ...interface{})             {} // discard\nfunc (ui testUI) PrintErr(args ...interface{}) {\n\tui.T.Error(\"unexpected error: \" + fmt.Sprint(args...))\n}\n\nvar _ driver.UI = testUI{}\n\ntype testFetcher struct {\n\tprofile *profile.Profile\n}\n\nfunc (f testFetcher) Fetch(source string, duration, timeout time.Duration) (*profile.Profile, string, error) {\n\t// http://pproftest.local prevents file from being saved.\n\treturn f.profile, \"http://pproftest.local\", nil\n}\n\nvar _ driver.Fetcher = testFetcher{}\n"
  },
  {
    "path": "doc/README.md",
    "content": "# pprof\n\npprof is a tool for visualization and analysis of profiling data.\n\npprof reads a collection of profiling samples in profile.proto format and\ngenerates reports to visualize and help analyze the data. It can generate both\ntext and graphical reports (through the use of the dot visualization package).\n\nprofile.proto is a protocol buffer that describes a set of callstacks\nand symbolization information. A common usage is to represent a set of\nsampled callstacks from statistical profiling. The format is\ndescribed on the proto/profile.proto file. For details on protocol\nbuffers, see https://developers.google.com/protocol-buffers\n\nProfiles can be read from a local file, or over http. Multiple\nprofiles of the same type can be aggregated or compared.\n\nIf the profile samples contain machine addresses, pprof can symbolize\nthem through the use of the native binutils tools (addr2line and nm).\n\n# pprof profiles\n\npprof operates on data in the profile.proto format. Each profile is a collection\nof samples, where each sample is associated to a point in a location hierarchy,\none or more numeric values, and a set of labels. Often these profiles represents\ndata collected through statistical sampling of a program, so each sample\ndescribes a program call stack and a number or value of samples collected at a\nlocation. pprof is agnostic to the profile semantics, so other uses are\npossible. The interpretation of the reports generated by pprof depends on the\nsemantics defined by the source of the profile.\n\n# Usage modes\n\nThere are few different ways of using `pprof`.\n\n## Report generation\n\nIf a report format is requested on the command line:\n\n    pprof <format> [options] source\n\npprof will generate a report in the specified format and exit.\nFormats can be either text, or graphical. See below for details about\nsupported formats, options, and sources.\n\n## Interactive terminal use\n\nWithout a format specifier:\n\n    pprof [options] source\n\npprof will start an interactive shell in which the user can type\ncommands.  Type `help` to get online help.\n\n## Web interface\n\nIf a host:port is specified on the command line:\n\n    pprof -http=[host]:[port] [options] source\n\npprof will start serving HTTP requests on the specified port.  Visit\nthe HTTP url corresponding to the port (typically `http://<host>:<port>/`)\nin a browser to see the interface.\n\n# Details\n\nThe objective of pprof is to generate a report for a profile. The report is\ngenerated from a location hierarchy, which is reconstructed from the profile\nsamples. Each location contains two values:\n\n* *flat*: the value of the location itself.\n* *cum*: the value of the location plus all its descendants.\n\nSamples that include a location multiple times (e.g. for recursive functions)\nare counted only once per location.\n\n## Options\n\n*options* configure the contents of a report. Each option has a value,\nwhich can be boolean, numeric, or strings. While only one format can\nbe specified, most options can be selected independently of each\nother.\n\nSome common pprof options are:\n\n* **-flat** [default], **-cum**: Sort entries based on their flat or cumulative\n  value respectively, on text reports.\n* **-functions** [default], **-filefunctions**, **-files**, **-lines**,\n  **-addresses**: Generate the report using the specified granularity.\n* **-noinlines**: Attribute inlined functions to their first out-of-line caller.\n  For example, a command like `pprof -list foo -noinlines profile.pb.gz` can be\n  used to produce the annotated source listing attributing the metrics in the\n  inlined functions to the out-of-line calling line.\n* **-nodecount= _int_:** Maximum number of entries in the report. pprof will\n  only print this many entries and will use heuristics to select which entries\n  to trim.\n* **-focus= _regex_:** Only include samples that include a report entry matching\n  *regex*.\n* **-ignore= _regex_:** Do not include samples that include a report entry\n  matching *regex*.\n* **-show\\_from= _regex_:** Do not show entries above the first one that\n  matches *regex*.\n* **-show= _regex_:** Only show entries that match *regex*.\n* **-hide= _regex_:** Do not show entries that match *regex*.\n\nEach sample in a profile may include multiple values, representing different\nentities associated to the sample. pprof reports include a single sample value,\nwhich by convention is the last one specified in the report. The `sample_index=`\noption selects which value to use, and can be set to a number (from 0 to the\nnumber of values - 1) or the name of the sample value.\n\nSample values are numeric values associated to a unit. If pprof can recognize\nthese units, it will attempt to scale the values to a suitable unit for\nvisualization. The `unit=` option will force the use of a specific unit. For\nexample, `unit=sec` will force any time values to be reported in\nseconds. pprof recognizes most common time and memory size units.\n\n## Tags\n\nSamples in a profile may have tags. These tags have a name and a value. The\nvalue can be either numeric or a string; the numeric values can be associated\nwith a unit. Tags are used as additional dimensions that the sample values can\nbe broken by. The most common use of tags is selecting samples from a profile\nbased on the tag values. pprof also supports tags at the visualization time.\n\n### Tag filtering\n\nThe `-tagfocus` option is the most used option for selecting data in a profile\nbased on tag values. It has the syntax of **-tagfocus=_regex_** or\n**-tagfocus=_range_:** which will restrict the data to samples with tags matched\nby regexp or in range. The `-tagignore` option has the identical syntax and can\nbe used to filter out the samples that have matching tags. If both `-tagignore`\nand `-tagfocus` are specified and match a given sample, then the sample will be\ndiscarded.\n\nWhen using `-tagfocus=regex` and `-tagignore=regex`, the regex will be compared\nto each value associated with each tag. If one specifies a value\nlike `regex1,regex2`, then only samples with a tag value matching `regex1`\nand a tag value matching `regex2` will be kept.\n\nIn addition to being able to filter on tag values, one can specify the name of\nthe tag which a certain value must be associated with using the notation\n`-tagfocus=tagName=value`. Here, the `tagName` must match the tag's name\nexactly, and the value can be either a regex or a range. If one specifies\na value like `regex1,regex2`, then samples with a tag value (paired with the\nspecified tag name) matching either `regex1` or matching `regex2` will match.\n\nHere are examples explaining how `-tagfocus` can be used:\n\n* `-tagfocus 128kb:512kb` accepts a sample iff it has any numeric tag with\n  memory value in the specified range.\n* `-tagfocus mytag=128kb:512kb` accepts a sample iff it has a numeric tag\n  `mytag` with memory value in the specified range. There isn't a way to say\n   `-tagfocus mytag=128kb:512kb,16kb:32kb`\n   or `-tagfocus mytag=128kb:512kb,mytag2=128kb:512kb`. Just single value or\n   range for numeric tags.\n* `-tagfocus someregex` accepts a sample iff it has any string tag with\n  `tagName:tagValue` string matching specified regexp. In the future, this\n  will change to accept sample iff it has any string tag with `tagValue` string\n  matching specified regexp.\n* `-tagfocus mytag=myvalue1,myvalue2` matches if either of the two tag values\n  are present.\n\n### Tag visualization\n\nTo list the tags and their values available in a profile use **-tags** option.\nIt will output the available tags and their values as well as the breakdown of\nthe sample value by the values of each tag.\n\nThe pprof callgraph reports, such as `-web` or raw `-dot`, will automatically\nvisualize the values for all tags as pseudo nodes in the graph. Use `-tagshow`\nand `-taghide` options to limit what tags are displayed. The options accept a\nregular expression that is matched against the tag name to show or hide it\nrespectively.\n\nOptions `-tagroot` and `-tagleaf` can be used to create pseudo stack frames to\nthe profile samples. For example, `-tagroot=mytag` will add stack frames at the\nroot of the profile call tree with the value of the tag for the corresponding\nsamples. Similarly, `-tagleaf=mytag` will add such stack frames as leaf nodes of\neach sample. These options are useful when visualizing a profile in tree formats\nsuch as the tree view in the `-http` mode web UI.\n\n## Text reports\n\npprof text reports show the location hierarchy in text format.\n\n* **-text:** Prints the location entries, one per line, including the flat and\n  cum values.\n* **-tree:** Prints each location entry with its predecessors and successors.\n* **-peek= _regex_:** Print the location entry with all its predecessors and\n  successors, without trimming any entries.\n* **-traces:** Prints each sample with a location per line.\n\n## Graphical reports\n\npprof can generate graphical reports on the DOT format, and convert them to\nmultiple formats using the graphviz package.\n\nThese reports represent the location hierarchy as a graph, with a report entry\nrepresented as a node. Nodes are removed using heuristics to limit the size of\nthe graph, controlled by the *nodecount* option.\n\n* **-dot:** Generates a report in .dot format. All other formats are generated\n  from this one.\n* **-svg:** Generates a report in SVG format.\n* **-web:** Generates a report in SVG format on a temp file, and starts a web\n  browser to view it.\n* **-png, -jpg, -gif, -pdf:** Generates a report in these formats.\n\n### Interpreting the Callgraph\n\n* **Node Color**:\n  * large positive cum values are red.\n  * large negative cum values are green; negative values are most likely to\n    appear during profile comparison, see [this section](#comparing-profiles)\n    for details.\n  * cum values close to zero are grey.\n\n* **Node Font Size**:\n  * larger font size means larger absolute flat values.\n  * smaller font size means smaller absolute flat values.\n\n* **Edge Weight**:\n  * thicker edges indicate more resources were used along that path.\n  * thinner edges indicate fewer resources were used along that path.\n\n* **Edge Color**:\n  * large positive values are red.\n  * large negative values are green.\n  * values close to zero are grey.\n\n* **Dashed Edges**: some locations between the two connected locations were\n  removed.\n\n* **Solid Edges**: one location directly calls the other.\n\n* **\"(inline)\" Edge Marker**: the call has been inlined into the caller.\n\nLet's consider the following example graph:\n\n![callgraph](images/callgraph.png)\n\n* For nodes:\n  * `(*Rand).Read` has a small flat value and a small cum value because the\n    the font is small and the node is grey.\n  * `(*compressor).deflate` has a large flat value and a large cum value because the font\n    is large and the node is red.\n  * `(*Writer).Flush` has a small flat value and a large cum value because the font is\n    small and the node is red.\n\n* For edges:\n  * the edge between `(*Writer).Write` and `(*compressor).write`:\n    * Since it is a dashed edge, some nodes were removed between those two.\n    * Since it is thick and red, more resources were used in call stacks between\n    those two nodes.\n  * the edge between `(*Rand).Read` and `read`:\n    * Since it is a dashed edge, some nodes were removed between those two.\n    * Since it is thin and grey, fewer resources were used in call stacks\n    between those two nodes.\n  * the edge between `read` and `(*rngSource).Int63`:\n    * Since it is a solid edge, there are no nodes between those two (i.e. it\n      was a direct call).\n    * Since it is thin and grey, fewer resources were used in call stacks\n      between those two nodes.\n\n## Annotated code\n\npprof can also generate reports of annotated source with samples associated to\nthem. For these, the source or binaries must be locally available, and the\nprofile must contain data with the appropriate level of detail.\n\npprof will look for source files on its current working directory and all its\nancestors. pprof will look for binaries on the directories specified in the\n`$PPROF_BINARY_PATH` environment variable, by default `$HOME/pprof/binaries`\n(`%USERPROFILE%\\pprof\\binaries` on Windows). It will look binaries up by name,\nand if the profile includes linker build ids, it will also search for them in\na directory named as the build id.\n\npprof uses the binutils tools to examine and disassemble the binaries. By\ndefault it will search for those tools in the current path, but it can also\nsearch for them in a directory pointed to by the environment variable\n`$PPROF_TOOLS`.\n\n* **-list= _regex_:** Generates an annotated source listing for functions\n  matching *regex*, with flat/cum values for each source line.\n* **-disasm= _regex_:** Generates an annotated disassembly listing for\n  functions matching *regex*.\n* **-weblist= _regex_:** Generates a source/assembly combined annotated listing\n  for functions matching *regex*, and starts a web browser to display it.\n\n## Comparing profiles\n\npprof can subtract one profile from another, provided the profiles are of\ncompatible types (i.e. two heap profiles). pprof has two options which can be\nused to specify the filename or URL for a profile to be subtracted from the\nsource profile:\n\n* **-diff_base= _profile_:** useful for comparing two profiles. Percentages in\nthe output are relative to the total of samples in the diff base profile.\n\n* **-base= _profile_:** useful for subtracting a cumulative profile, like a\n[golang block profile](https://golang.org/doc/diagnostics.html#profiling),\nfrom another cumulative profile collected from the same program at a later time.\nWhen comparing cumulative profiles collected on the same program, percentages in\nthe output are relative to the difference between the total for the source\nprofile and the total for the base profile.\n\nThe **-normalize** flag can be used when a base profile is specified with either\nthe `-diff_base` or the `-base` option. This flag scales the source profile so\nthat the total of samples in the source profile is equal to the total of samples\nin the base profile prior to subtracting the base profile from the source\nprofile. Useful for determining the relative differences between profiles, for\nexample, which profile has a larger percentage of CPU time used in a particular\nfunction.\n\nWhen using the **-diff_base** option, some report entries may have negative\nvalues. If the merged profile is output as a protocol buffer, all samples in the\ndiff base profile will have a label with the key \"pprof::base\" and a value of\n\"true\". If pprof is then used to look at the merged profile, it will behave as\nif separate source and base profiles were passed in.\n\nWhen using the **-base** option to subtract one cumulative profile from another\ncollected on the same program at a later time, percentages will be relative to\nthe difference between the total for the source profile and the total for\nthe base profile, and all values will be positive. In the general case, some\nreport entries may have negative values and percentages will be relative to the\ntotal of the absolute value of all samples when aggregated at the address level.\n\n# Fetching profiles\n\npprof can read profiles from a file or directly from a URL over http or https.\nIts native format is a gzipped profile.proto file, but it can\nalso accept some legacy formats generated by\n[gperftools](https://github.com/gperftools/gperftools).\n\nWhen fetching from a URL handler, pprof accepts options to indicate how much to\nwait for the profile.\n\n* **-seconds= _int_:** Makes pprof request for a profile with the specified\n  duration in seconds. Only makes sense for profiles based on elapsed time, such\n  as CPU profiles.\n* **-timeout= _int_:** Makes pprof wait for the specified timeout when\n  retrieving a profile over http. If not specified, pprof will use heuristics to\n  determine a reasonable timeout.\n\npprof also accepts options which allow a user to specify TLS certificates to\nuse when fetching or symbolizing a profile from a protected endpoint. For more\ninformation about generating these certificates, see\nhttps://docs.docker.com/engine/security/https/.\n\n* **-tls\\_cert= _/path/to/cert_:** File containing the TLS client certificate\n  to be used when fetching and symbolizing profiles.\n* **-tls\\_key= _/path/to/key_:** File containing the TLS private key to be used\n  when fetching and symbolizing profiles.\n* **-tls\\_ca= _/path/to/ca_:** File containing the certificate authority to be\n  used when fetching and symbolizing profiles.\n\npprof also supports skipping verification of the server's certificate chain and\nhost name when collecting or symbolizing a profile. To skip this verification,\nuse \"https+insecure\" in place of \"https\" in the URL.\n\nIf multiple profiles are specified, pprof will fetch them all and merge\nthem. This is useful to combine profiles from multiple processes of a\ndistributed job. The profiles may be from different programs but must be\ncompatible (for example, CPU profiles cannot be combined with heap profiles).\n\n## Symbolization\n\npprof can add symbol information to a profile that was collected only with\naddress information. This is useful for profiles for compiled languages, where\nit may not be easy or even possible for the profile source to include function\nnames or source coordinates.\n\npprof can extract the symbol information locally by examining the binaries using\nthe binutils tools, or it can ask running jobs that provide a symbolization\ninterface.\n\npprof will attempt symbolizing profiles by default, and its `-symbolize` option\nprovides some control over symbolization:\n\n* **-symbolize=none:** Disables any symbolization from pprof.\n\n* **-symbolize=local:** Only attempts symbolizing the profile from local\n  binaries using the binutils tools.\n\n* **-symbolize=remote:** Only attempts to symbolize running jobs by contacting\n  their symbolization handler.\n\nFor local symbolization, pprof will look for the binaries on the paths specified\nby the profile, and then it will search for them on the path specified by the\nenvironment variable `$PPROF_BINARY_PATH`. Also, the name of the main binary can\nbe passed directly to pprof as its first parameter, to override the name or\nlocation of the main binary of the profile, like this:\n\n    pprof /path/to/binary profile.pb.gz\n\nBy default pprof will attempt to demangle and simplify C++ names, to provide\nreadable names for C++ symbols. It will aggressively discard template and\nfunction parameters. This can be controlled with the `-symbolize=demangle`\noption. Note that for remote symbolization mangled names may not be provided by\nthe symbolization handler.\n\n* **-symbolize=demangle=none:** Do not perform any demangling. Show mangled\n  names if available.\n\n* **-symbolize=demangle=full:** Demangle, but do not perform any\n  simplification. Show full demangled names if available.\n\n* **-symbolize=demangle=templates:** Demangle, and trim function parameters, but\n  not template parameters.\n\n# Web Interface\n\nWhen the user requests a web interface (by supplying an `-http=[host]:[port]`\nargument on the command-line), pprof starts a web server and opens a browser\nwindow pointing at that server. The web interface provided by the server allows\nthe user to interactively view profile data in multiple formats.\n\n## Views\n\nThe top of the display is a header that contains some buttons and menus.  The\n`View` menu allows the user to switch between different visualizations of the\nprofile. The available views are described here:\n\n### Graph\n\nThe default view in the local web interface displays a graph where the nodes are\nfunctions, and edges indicate caller/callee relations.\n\nNote: You can drag the display around with the mouse button held down, or zoom\nin and out using a mouse scroll-wheel or pinch/expand touch gestures.\n\n![Graph view](images/webui/graph.png)\n\nE.g., `FormatPack` has an outgoing edge to `FormatUntyped` that indicates that\nthe former calls the latter. The number along the edge (5.72s) indicates the\namount of time that was spent in `FormatUntyped` (and its callees) when called\nfrom `FormatPack`.\n\nSee [earlier explanation](#interpreting-the-callgraph) for more details.\n\n### Flame graph\n\nSwitching to the `Flame graph` view (via the `View` menu) will display a [flame\ngraph](https://www.brendangregg.com/flamegraphs.html). This view provides a\ncompact representation of caller/callee relations:\n\n![Flame graph](images/webui/flame.png)\n\nBoxes on this view correspond to stack frames in the profile. Caller boxes are\ndirectly above callee boxes. The width of each box is proportional to the sum of\nthe sample value of profile samples where that frame was present on the call\nstack. Children of a particular box are laid out left to right in decreasing\nsize order.\n\nE.g., here we see that `FormatPack` is right above `FormatUntyped`, which\nindicates that the former calls the latter. The width of `FormatUntyped`\ncorresponds to the fraction of time accounted for by this call.\n\nNames displayed in different boxes may have different font sizes. These size\ndifferences are due to an attempt to fit as much of the name into the box as\npossible; no other interpretation should be placed on the size.\n\nBoxes are colored according to the name of the package in which the corresponding\nfunction occurs. E.g., in C++ profiles all frames corresponding to `std::` functions\nwill be assigned the same color.\n\n#### Viewing callers\n\nTraditional flame graphs provide a top-down view: it is easy to see the\nfunctions called by a particular function, but harder to find callers of a\nparticular function. E.g., in the linked example there are multiple occurrences\nof `FormatUntyped` since it has multiple callers.\n\nPprof's flame graph extend the traditional model: when a function is selected,\nthe graph changes to show call-stacks leading that function. Therefore, clicking\non any of the `FormatUntyped` boxes will show the call stacks that end up\ncalling `FormatUntyped`:\n\n![Flame graph showing multiple callers](images/webui/flame-multi.png)\n\n#### Diff mode\n\nWhen using the **--diff_base** option, box width is proportional to the sum of\nthe increases and decreases in the sub-tree rooted at box. E.g., if the cost of\none child of box decreases by 150 and the cost of another child increases by\n200, the box width will be proportional to 150+200. The net increase or decrease\n(the preceding example has a net increase of 200-150, i.e., 50) is indicated by\na shaded region. The size of the shaded region is proportional to the net\nincrease or net decrease. The shading is red for a net increase, and green for a\nnet decrease.\n\n#### Inlining\n\nInlining is indicated by the absence of a horizontal border between a caller and\na callee. E.g., suppose X calls Y calls Z and the call from Y to Z is inlined into\nY. There will be a black border between X and Y, but no border between Y and Z.\n\n### Annotated Source Code\n\nLet's try to dig into what is going on inside `FormatUntyped` by viewing its\nsource-code annotated with performance data. First, right-click on the box for\nthe function to get a context menu.\n\n![Flame menu](images/webui/flame-menu.png)\n\nSelect `Show source in new tab`. That will create a new tab that displays source\ncode for the function.\n\nNote: You can also display source code by selecting `Source` from the `View`\nmenu, but only do so if you are focused on just one or a few routines since\nsource code display can be very slow and voluminous when multiple functions are\nbeing viewed.\n\n![Source listing](images/webui/source.png)\n\nEach source line is annotated with the time spent in that source line. There are\ntwo numbers (e.g., 840ms and 6.17s on line 207 in the screenshot). The first\nnumber does not count time spent in functions called from the source line, the\nsecond number includes that time.\n\nLet's dig down a bit more by clicking on line 207. That will expand the display\nto include the source code for inlined function calls, as well as the\ncorresponding assembly code.\n\n![Expanded source listing](images/webui/source-expanded.png)\n\nThe assembly code is displayed in green. Source code for inlined functions is\ndisplayed in blue and is indented by its inlining level. For example, the\nindentation indicates that the `ConvertAll` call on line 207 is inlined, and it\nin turn has an inlined call to `has_parsed_conversion`, which in turn expands to\na `cmpq` instruction.\n\n### Disassembly\n\nSometimes it is helpful to view just the disassembly in instruction order\nwithout interleaving with source code. You can achieve this by selecting\n`Disassemble`\" from the `View` menu.\n\nNote: Do not select `Disassemble` unless you are focused on just one or a few\nroutines since disassembly can be very slow and voluminous when multiple\nfunctions are being viewed.\n\n![Disassembly](images/webui/disasm.png)\n\n### Top Functions\n\nYou may sometimes find a table that displays just the top functions in the\nprofile helpful.\n\n![Top functions](images/webui/top.png)\n\nThe table shows numbers (and percentages) for two different metrics:\n\n*   `flat`: profile samples in this function\n*   `cum`: (cumulative) profile samples in this function and its callees\n\nThe table is initially sorted in decreasing order of `flat`. Clicking on the\n`Cum` table header will sort it in decreasing order of samples in the function\nand its callees.\n\n### Peek\n\nThis view shows callers / callees per function in a simple textual format.\nThe Flame graph view is typically more helpful.\n\n## Config\n\nThe `Config` menu allows the user to save the current refinement\nsettings (e.g., the focus and hide list) as a named configuration. A\nsaved configuration can later be re-applied to reinstitue the saved\nrefinements. The `Config` menu contains:\n\n**Save as ...**: shows a dialog where the user can type in a\nconfiguration name. The current refinement settings are saved under\nthe specified name.\n\n**Default**: switches back to the default view by removing all refinements.\n\nThe `Config` menu also contains an entry per named\nconfiguration. Selecting such an entry applies that configuration. The\ncurrently selected entry is marked with a ✓. Clicking on the 🗙 on the\nright-hand side of such an entry deletes the configuration (after\nprompting the user to confirm).\n\n## TODO: cover the following issues:\n\n*   Overall layout\n*   Other menu entries\n"
  },
  {
    "path": "driver/driver.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package driver provides an external entry point to the pprof driver.\npackage driver\n\nimport (\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"time\"\n\n\tinternaldriver \"github.com/google/pprof/internal/driver\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// PProf acquires a profile, and symbolizes it using a profile\n// manager. Then it generates a report formatted according to the\n// options selected through the flags package.\nfunc PProf(o *Options) error {\n\treturn internaldriver.PProf(o.internalOptions())\n}\n\nfunc (o *Options) internalOptions() *plugin.Options {\n\tvar obj plugin.ObjTool\n\tif o.Obj != nil {\n\t\tobj = &internalObjTool{o.Obj}\n\t}\n\tvar sym plugin.Symbolizer\n\tif o.Sym != nil {\n\t\tsym = &internalSymbolizer{o.Sym}\n\t}\n\tvar httpServer func(args *plugin.HTTPServerArgs) error\n\tif o.HTTPServer != nil {\n\t\thttpServer = func(args *plugin.HTTPServerArgs) error {\n\t\t\treturn o.HTTPServer(((*HTTPServerArgs)(args)))\n\t\t}\n\t}\n\treturn &plugin.Options{\n\t\tWriter:        o.Writer,\n\t\tFlagset:       o.Flagset,\n\t\tFetch:         o.Fetch,\n\t\tSym:           sym,\n\t\tObj:           obj,\n\t\tUI:            o.UI,\n\t\tHTTPServer:    httpServer,\n\t\tHTTPTransport: o.HTTPTransport,\n\t}\n}\n\n// HTTPServerArgs contains arguments needed by an HTTP server that\n// is exporting a pprof web interface.\ntype HTTPServerArgs plugin.HTTPServerArgs\n\n// Options groups all the optional plugins into pprof.\ntype Options struct {\n\tWriter        Writer\n\tFlagset       FlagSet\n\tFetch         Fetcher\n\tSym           Symbolizer\n\tObj           ObjTool\n\tUI            UI\n\tHTTPServer    func(*HTTPServerArgs) error\n\tHTTPTransport http.RoundTripper\n}\n\n// Writer provides a mechanism to write data under a certain name,\n// typically a filename.\ntype Writer interface {\n\tOpen(name string) (io.WriteCloser, error)\n}\n\n// A FlagSet creates and parses command-line flags.\n// It is similar to the standard flag.FlagSet.\ntype FlagSet interface {\n\t// Bool, Int, Float64, and String define new flags,\n\t// like the functions of the same name in package flag.\n\tBool(name string, def bool, usage string) *bool\n\tInt(name string, def int, usage string) *int\n\tFloat64(name string, def float64, usage string) *float64\n\tString(name string, def string, usage string) *string\n\n\t// StringList is similar to String but allows multiple values for a\n\t// single flag\n\tStringList(name string, def string, usage string) *[]*string\n\n\t// ExtraUsage returns any additional text that should be printed after the\n\t// standard usage message. The extra usage message returned includes all text\n\t// added with AddExtraUsage().\n\t// The typical use of ExtraUsage is to show any custom flags defined by the\n\t// specific pprof plugins being used.\n\tExtraUsage() string\n\n\t// AddExtraUsage appends additional text to the end of the extra usage message.\n\tAddExtraUsage(eu string)\n\n\t// Parse initializes the flags with their values for this run\n\t// and returns the non-flag command line arguments.\n\t// If an unknown flag is encountered or there are no arguments,\n\t// Parse should call usage and return nil.\n\tParse(usage func()) []string\n}\n\n// A Fetcher reads and returns the profile named by src, using\n// the specified duration and timeout. It returns the fetched\n// profile and a string indicating a URL from where the profile\n// was fetched, which may be different than src.\ntype Fetcher interface {\n\tFetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error)\n}\n\n// A Symbolizer introduces symbol information into a profile.\ntype Symbolizer interface {\n\tSymbolize(mode string, srcs MappingSources, prof *profile.Profile) error\n}\n\n// MappingSources map each profile.Mapping to the source of the profile.\n// The key is either Mapping.File or Mapping.BuildId.\ntype MappingSources map[string][]struct {\n\tSource string // URL of the source the mapping was collected from\n\tStart  uint64 // delta applied to addresses from this source (to represent Merge adjustments)\n}\n\n// An ObjTool inspects shared libraries and executable files.\ntype ObjTool interface {\n\t// Open opens the named object file. If the object is a shared\n\t// library, start/limit/offset are the addresses where it is mapped\n\t// into memory in the address space being inspected. If the object\n\t// is a linux kernel, relocationSymbol is the name of the symbol\n\t// corresponding to the start address.\n\tOpen(file string, start, limit, offset uint64, relocationSymbol string) (ObjFile, error)\n\n\t// Disasm disassembles the named object file, starting at\n\t// the start address and stopping at (before) the end address.\n\tDisasm(file string, start, end uint64, intelSyntax bool) ([]Inst, error)\n}\n\n// An Inst is a single instruction in an assembly listing.\ntype Inst struct {\n\tAddr     uint64 // virtual address of instruction\n\tText     string // instruction text\n\tFunction string // function name\n\tFile     string // source file\n\tLine     int    // source line\n}\n\n// An ObjFile is a single object file: a shared library or executable.\ntype ObjFile interface {\n\t// Name returns the underlying file name, if available.\n\tName() string\n\n\t// ObjAddr returns the objdump address corresponding to a runtime address.\n\tObjAddr(addr uint64) (uint64, error)\n\n\t// BuildID returns the GNU build ID of the file, or an empty string.\n\tBuildID() string\n\n\t// SourceLine reports the source line information for a given\n\t// address in the file. Due to inlining, the source line information\n\t// is in general a list of positions representing a call stack,\n\t// with the leaf function first.\n\tSourceLine(addr uint64) ([]Frame, error)\n\n\t// Symbols returns a list of symbols in the object file.\n\t// If r is not nil, Symbols restricts the list to symbols\n\t// with names matching the regular expression.\n\t// If addr is not zero, Symbols restricts the list to symbols\n\t// containing that address.\n\tSymbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)\n\n\t// Close closes the file, releasing associated resources.\n\tClose() error\n}\n\n// A Frame describes a single line in a source file.\ntype Frame struct {\n\tFunc      string // name of function\n\tFile      string // source file name\n\tLine      int    // line in file\n\tColumn    int    // column in file\n\tStartLine int    // start line of function (if available)\n}\n\n// A Sym describes a single symbol in an object file.\ntype Sym struct {\n\tName  []string // names of symbol (many if symbol was dedup'ed)\n\tFile  string   // object file containing symbol\n\tStart uint64   // start virtual address\n\tEnd   uint64   // virtual address of last byte in sym (Start+size-1)\n}\n\n// A UI manages user interactions.\ntype UI interface {\n\t// ReadLine returns a line of text (a command) read from the user.\n\t// prompt is printed before reading the command.\n\tReadLine(prompt string) (string, error)\n\n\t// Print shows a message to the user.\n\t// It formats the text as fmt.Print would and adds a final \\n if not already present.\n\t// For line-based UI, Print writes to standard error.\n\t// (Standard output is reserved for report data.)\n\tPrint(...interface{})\n\n\t// PrintErr shows an error message to the user.\n\t// It formats the text as fmt.Print would and adds a final \\n if not already present.\n\t// For line-based UI, PrintErr writes to standard error.\n\tPrintErr(...interface{})\n\n\t// IsTerminal returns whether the UI is known to be tied to an\n\t// interactive terminal (as opposed to being redirected to a file).\n\tIsTerminal() bool\n\n\t// WantBrowser indicates whether browser should be opened with the -http option.\n\tWantBrowser() bool\n\n\t// SetAutoComplete instructs the UI to call complete(cmd) to obtain\n\t// the auto-completion of cmd, if the UI supports auto-completion at all.\n\tSetAutoComplete(complete func(string) string)\n}\n\n// internalObjTool is a wrapper to map from the pprof external\n// interface to the internal interface.\ntype internalObjTool struct {\n\tObjTool\n}\n\nfunc (o *internalObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\tf, err := o.ObjTool.Open(file, start, limit, offset, relocationSymbol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &internalObjFile{f}, err\n}\n\ntype internalObjFile struct {\n\tObjFile\n}\n\nfunc (f *internalObjFile) SourceLine(frame uint64) ([]plugin.Frame, error) {\n\tframes, err := f.ObjFile.SourceLine(frame)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar pluginFrames []plugin.Frame\n\tfor _, f := range frames {\n\t\tpluginFrames = append(pluginFrames, plugin.Frame(f))\n\t}\n\treturn pluginFrames, nil\n}\n\nfunc (f *internalObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {\n\tsyms, err := f.ObjFile.Symbols(r, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar pluginSyms []*plugin.Sym\n\tfor _, s := range syms {\n\t\tps := plugin.Sym(*s)\n\t\tpluginSyms = append(pluginSyms, &ps)\n\t}\n\treturn pluginSyms, nil\n}\n\nfunc (o *internalObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\tinsts, err := o.ObjTool.Disasm(file, start, end, intelSyntax)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar pluginInst []plugin.Inst\n\tfor _, inst := range insts {\n\t\tpluginInst = append(pluginInst, plugin.Inst(inst))\n\t}\n\treturn pluginInst, nil\n}\n\n// internalSymbolizer is a wrapper to map from the pprof external\n// interface to the internal interface.\ntype internalSymbolizer struct {\n\tSymbolizer\n}\n\nfunc (s *internalSymbolizer) Symbolize(mode string, srcs plugin.MappingSources, prof *profile.Profile) error {\n\tisrcs := MappingSources{}\n\tmaps.Copy(isrcs, srcs)\n\treturn s.Symbolizer.Symbolize(mode, isrcs, prof)\n}\n"
  },
  {
    "path": "fuzz/README.md",
    "content": "This is an explanation of how to do fuzzing of ParseData. This uses github.com/dvyukov/go-fuzz/ for fuzzing.\n\n# How to use\nFirst, get go-fuzz \n```\n$ go get github.com/dvyukov/go-fuzz/go-fuzz\n$ go get github.com/dvyukov/go-fuzz/go-fuzz-build\n```\n\nBuild the test program by calling the following command \n(assuming you have files for pprof located in github.com/google/pprof within go's src folder)\n\n```\n$ go-fuzz-build github.com/google/pprof/fuzz\n```\nThe above command will produce pprof-fuzz.zip \n\n\nNow you can run the fuzzer by calling\n\n```\n$ go-fuzz -bin=./pprof-fuzz.zip -workdir=fuzz\n```\n\nThis will save a corpus of files used by the fuzzer in ./fuzz/corpus, and\nall files that caused ParseData to crash in ./fuzz/crashers.\n\nFor more details on the usage, see github.com/dvyukov/go-fuzz/\n\n# About the to corpus\n\nRight now, fuzz/corpus contains the corpus initially given to the fuzzer\n\nIf using the above commands, fuzz/corpus will be used to generate the initial corpus during fuzz testing.\n\nOne can add profiles into the corpus by placing these files in the corpus directory (fuzz/corpus)\nprior to calling go-fuzz-build.\n"
  },
  {
    "path": "fuzz/corpus/empty",
    "content": ""
  },
  {
    "path": "fuzz/fuzz_test.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage pprof\n\nimport (\n\t\"os\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestParseData(t *testing.T) {\n\tif runtime.GOOS == \"nacl\" {\n\t\tt.Skip(\"no direct filesystem access on Nacl\")\n\t}\n\n\tconst path = \"testdata/\"\n\tfiles, err := os.ReadDir(path)\n\tif err != nil {\n\t\tt.Errorf(\"Problem reading directory %s : %v\", path, err)\n\t}\n\tfor _, f := range files {\n\t\tfile := path + f.Name()\n\t\tinbytes, err := os.ReadFile(file)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Problem reading file: %s : %v\", file, err)\n\t\t\tcontinue\n\t\t}\n\t\tprofile.ParseData(inbytes)\n\t}\n}\n"
  },
  {
    "path": "fuzz/main.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package pprof is used in conjunction with github.com/dvyukov/go-fuzz/go-fuzz\n// to fuzz ParseData function.\npackage pprof\n\nimport (\n\t\"github.com/google/pprof/profile\"\n)\n\n// Fuzz can be used with https://github.com/dvyukov/go-fuzz to do fuzz testing on ParseData\nfunc Fuzz(data []byte) int {\n\tprofile.ParseData(data)\n\treturn 0\n}\n"
  },
  {
    "path": "fuzz/testdata/7e3c92482f6f39fc502b822ded792c589849cca8",
    "content": "--- heapz 1 ---\n0 0 @ 0\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/google/pprof\n\ngo 1.24.0\n\ntoolchain go1.24.9\n\nrequire (\n\tgithub.com/chzyer/readline v1.5.1\n\tgithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b\n)\n\nrequire golang.org/x/sys v0.32.0 // indirect\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=\ngithub.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=\ngithub.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=\ngithub.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=\ngithub.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=\ngithub.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=\ngithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b h1:ogbOPx86mIhFy764gGkqnkFC8m5PJA7sPzlk9ppLVQA=\ngithub.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=\ngolang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\n"
  },
  {
    "path": "internal/binutils/addr2liner.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\nconst (\n\tdefaultAddr2line = \"addr2line\"\n\n\t// addr2line may produce multiple lines of output. We\n\t// use this sentinel to identify the end of the output.\n\tsentinel = ^uint64(0)\n)\n\n// addr2Liner is a connection to an addr2line command for obtaining\n// address and line number information from a binary.\ntype addr2Liner struct {\n\tmu   sync.Mutex\n\trw   lineReaderWriter\n\tbase uint64\n\n\t// nm holds an addr2Liner using nm tool. Certain versions of addr2line\n\t// produce incomplete names due to\n\t// https://sourceware.org/bugzilla/show_bug.cgi?id=17541. As a workaround,\n\t// the names from nm are used when they look more complete. See addrInfo()\n\t// code below for the exact heuristic.\n\tnm *addr2LinerNM\n}\n\n// lineReaderWriter is an interface to abstract the I/O to an addr2line\n// process. It writes a line of input to the job, and reads its output\n// one line at a time.\ntype lineReaderWriter interface {\n\twrite(string) error\n\treadLine() (string, error)\n\tclose()\n}\n\ntype addr2LinerJob struct {\n\tcmd *exec.Cmd\n\tin  io.WriteCloser\n\tout *bufio.Reader\n}\n\nfunc (a *addr2LinerJob) write(s string) error {\n\t_, err := fmt.Fprint(a.in, s+\"\\n\")\n\treturn err\n}\n\nfunc (a *addr2LinerJob) readLine() (string, error) {\n\ts, err := a.out.ReadString('\\n')\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimSpace(s), nil\n}\n\n// close releases any resources used by the addr2liner object.\nfunc (a *addr2LinerJob) close() {\n\ta.in.Close()\n\ta.cmd.Wait()\n}\n\n// newAddr2Liner starts the given addr2liner command reporting\n// information about the given executable file. If file is a shared\n// library, base should be the address at which it was mapped in the\n// program under consideration.\nfunc newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) {\n\tif cmd == \"\" {\n\t\tcmd = defaultAddr2line\n\t}\n\n\tj := &addr2LinerJob{\n\t\tcmd: exec.Command(cmd, \"-aif\", \"-e\", file),\n\t}\n\n\tvar err error\n\tif j.in, err = j.cmd.StdinPipe(); err != nil {\n\t\treturn nil, err\n\t}\n\n\toutPipe, err := j.cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tj.out = bufio.NewReader(outPipe)\n\tif err := j.cmd.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\n\ta := &addr2Liner{\n\t\trw:   j,\n\t\tbase: base,\n\t}\n\n\treturn a, nil\n}\n\n// readFrame parses the addr2line output for a single address. It\n// returns a populated plugin.Frame and whether it has reached the end of the\n// data.\nfunc (d *addr2Liner) readFrame() (plugin.Frame, bool) {\n\tfuncname, err := d.rw.readLine()\n\tif err != nil {\n\t\treturn plugin.Frame{}, true\n\t}\n\tif strings.HasPrefix(funcname, \"0x\") {\n\t\t// If addr2line returns a hex address we can assume it is the\n\t\t// sentinel. Read and ignore next two lines of output from\n\t\t// addr2line\n\t\td.rw.readLine()\n\t\td.rw.readLine()\n\t\treturn plugin.Frame{}, true\n\t}\n\n\tfileline, err := d.rw.readLine()\n\tif err != nil {\n\t\treturn plugin.Frame{}, true\n\t}\n\n\tlinenumber := 0\n\n\tif funcname == \"??\" {\n\t\tfuncname = \"\"\n\t}\n\n\tif fileline == \"??:0\" {\n\t\tfileline = \"\"\n\t} else {\n\t\tif i := strings.LastIndex(fileline, \":\"); i >= 0 {\n\t\t\t// Remove discriminator, if present\n\t\t\tif disc := strings.Index(fileline, \" (discriminator\"); disc > 0 {\n\t\t\t\tfileline = fileline[:disc]\n\t\t\t}\n\t\t\t// If we cannot parse a number after the last \":\", keep it as\n\t\t\t// part of the filename.\n\t\t\tif line, err := strconv.Atoi(fileline[i+1:]); err == nil {\n\t\t\t\tlinenumber = line\n\t\t\t\tfileline = fileline[:i]\n\t\t\t}\n\t\t}\n\t}\n\n\treturn plugin.Frame{\n\t\tFunc: funcname,\n\t\tFile: fileline,\n\t\tLine: linenumber}, false\n}\n\nfunc (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\tif err := d.rw.write(fmt.Sprintf(\"%x\", addr-d.base)); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := d.rw.write(fmt.Sprintf(\"%x\", sentinel)); err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := d.rw.readLine()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !strings.HasPrefix(resp, \"0x\") {\n\t\treturn nil, fmt.Errorf(\"unexpected addr2line output: %s\", resp)\n\t}\n\n\tvar stack []plugin.Frame\n\tfor {\n\t\tframe, end := d.readFrame()\n\t\tif end {\n\t\t\tbreak\n\t\t}\n\n\t\tif frame != (plugin.Frame{}) {\n\t\t\tstack = append(stack, frame)\n\t\t}\n\t}\n\treturn stack, err\n}\n\n// addrInfo returns the stack frame information for a specific program\n// address. It returns nil if the address could not be identified.\nfunc (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {\n\tstack, err := d.rawAddrInfo(addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Certain versions of addr2line produce incomplete names due to\n\t// https://sourceware.org/bugzilla/show_bug.cgi?id=17541. Attempt to replace\n\t// the name with a better one from nm.\n\tif len(stack) > 0 && d.nm != nil {\n\t\tnm, err := d.nm.addrInfo(addr)\n\t\tif err == nil && len(nm) > 0 {\n\t\t\t// Last entry in frame list should match since it is non-inlined. As a\n\t\t\t// simple heuristic, we only switch to the nm-based name if it is longer\n\t\t\t// by 2 or more characters. We consider nm names that are longer by 1\n\t\t\t// character insignificant to avoid replacing foo with _foo on MacOS (for\n\t\t\t// unknown reasons read2line produces the former and nm produces the\n\t\t\t// latter on MacOS even though both tools are asked to produce mangled\n\t\t\t// names).\n\t\t\tnmName := nm[len(nm)-1].Func\n\t\t\ta2lName := stack[len(stack)-1].Func\n\t\t\tif len(nmName) > len(a2lName)+1 {\n\t\t\t\tstack[len(stack)-1].Func = nmName\n\t\t\t}\n\t\t}\n\t}\n\n\treturn stack, nil\n}\n"
  },
  {
    "path": "internal/binutils/addr2liner_llvm.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\nconst (\n\tdefaultLLVMSymbolizer = \"llvm-symbolizer\"\n)\n\n// llvmSymbolizer is a connection to an llvm-symbolizer command for\n// obtaining address and line number information from a binary.\ntype llvmSymbolizer struct {\n\tsync.Mutex\n\tfilename string\n\trw       lineReaderWriter\n\tbase     uint64\n\tisData   bool\n}\n\ntype llvmSymbolizerJob struct {\n\tcmd *exec.Cmd\n\tin  io.WriteCloser\n\tout *bufio.Reader\n\t// llvm-symbolizer requires the symbol type, CODE or DATA, for symbolization.\n\tsymType string\n}\n\nfunc (a *llvmSymbolizerJob) write(s string) error {\n\t_, err := fmt.Fprintln(a.in, a.symType, s)\n\treturn err\n}\n\nfunc (a *llvmSymbolizerJob) readLine() (string, error) {\n\ts, err := a.out.ReadString('\\n')\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimSpace(s), nil\n}\n\n// close releases any resources used by the llvmSymbolizer object.\nfunc (a *llvmSymbolizerJob) close() {\n\ta.in.Close()\n\ta.cmd.Wait()\n}\n\n// newLLVMSymbolizer starts the given llvmSymbolizer command reporting\n// information about the given executable file. If file is a shared\n// library, base should be the address at which it was mapped in the\n// program under consideration.\nfunc newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymbolizer, error) {\n\tif cmd == \"\" {\n\t\tcmd = defaultLLVMSymbolizer\n\t}\n\n\tj := &llvmSymbolizerJob{\n\t\tcmd:     exec.Command(cmd, \"--inlining\", \"-demangle=false\", \"--output-style=JSON\"),\n\t\tsymType: \"CODE\",\n\t}\n\tif isData {\n\t\tj.symType = \"DATA\"\n\t}\n\n\tvar err error\n\tif j.in, err = j.cmd.StdinPipe(); err != nil {\n\t\treturn nil, err\n\t}\n\n\toutPipe, err := j.cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tj.out = bufio.NewReader(outPipe)\n\tif err := j.cmd.Start(); err != nil {\n\t\treturn nil, err\n\t}\n\n\ta := &llvmSymbolizer{\n\t\tfilename: file,\n\t\trw:       j,\n\t\tbase:     base,\n\t\tisData:   isData,\n\t}\n\n\treturn a, nil\n}\n\n// readDataFrames parses the llvm-symbolizer DATA output for a single address. It\n// returns a populated plugin.Frame array with a single entry.\nfunc (d *llvmSymbolizer) readDataFrames() ([]plugin.Frame, error) {\n\tline, err := d.rw.readLine()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar frame struct {\n\t\tAddress    string `json:\"Address\"`\n\t\tModuleName string `json:\"ModuleName\"`\n\t\tData       struct {\n\t\t\tStart string `json:\"Start\"`\n\t\t\tSize  string `json:\"Size\"`\n\t\t\tName  string `json:\"Name\"`\n\t\t} `json:\"Data\"`\n\t}\n\tif err := json.Unmarshal([]byte(line), &frame); err != nil {\n\t\treturn nil, err\n\t}\n\t// Match non-JSON output behaviour of stuffing the start/size into the filename of a single frame,\n\t// with the size being a decimal value.\n\tsize, err := strconv.ParseInt(frame.Data.Size, 0, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar stack []plugin.Frame\n\tstack = append(stack, plugin.Frame{Func: frame.Data.Name, File: fmt.Sprintf(\"%s %d\", frame.Data.Start, size)})\n\treturn stack, nil\n}\n\n// readCodeFrames parses the llvm-symbolizer CODE output for a single address. It\n// returns a populated plugin.Frame array.\nfunc (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) {\n\tline, err := d.rw.readLine()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar frame struct {\n\t\tAddress    string `json:\"Address\"`\n\t\tModuleName string `json:\"ModuleName\"`\n\t\tSymbol     []struct {\n\t\t\tLine         int    `json:\"Line\"`\n\t\t\tColumn       int    `json:\"Column\"`\n\t\t\tFunctionName string `json:\"FunctionName\"`\n\t\t\tFileName     string `json:\"FileName\"`\n\t\t\tStartLine    int    `json:\"StartLine\"`\n\t\t} `json:\"Symbol\"`\n\t}\n\tif err := json.Unmarshal([]byte(line), &frame); err != nil {\n\t\treturn nil, err\n\t}\n\tvar stack []plugin.Frame\n\tfor _, s := range frame.Symbol {\n\t\tstack = append(stack, plugin.Frame{Func: s.FunctionName, File: s.FileName, Line: s.Line, Column: s.Column, StartLine: s.StartLine})\n\t}\n\treturn stack, nil\n}\n\n// addrInfo returns the stack frame information for a specific program\n// address. It returns nil if the address could not be identified.\nfunc (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {\n\td.Lock()\n\tdefer d.Unlock()\n\n\tif err := d.rw.write(fmt.Sprintf(\"%s 0x%x\", d.filename, addr-d.base)); err != nil {\n\t\treturn nil, err\n\t}\n\tif d.isData {\n\t\treturn d.readDataFrames()\n\t}\n\treturn d.readCodeFrames()\n}\n"
  },
  {
    "path": "internal/binutils/addr2liner_nm.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"io\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\nconst (\n\tdefaultNM = \"nm\"\n)\n\n// addr2LinerNM is a connection to an nm command for obtaining symbol\n// information from a binary.\ntype addr2LinerNM struct {\n\tm []symbolInfo // Sorted list of symbol addresses from binary.\n}\n\ntype symbolInfo struct {\n\taddress uint64\n\tsize    uint64\n\tname    string\n\tsymType string\n}\n\n// isData returns if the symbol has a known data object symbol type.\nfunc (s *symbolInfo) isData() bool {\n\t// The following symbol types are taken from https://linux.die.net/man/1/nm:\n\t// Lowercase letter means local symbol, uppercase denotes a global symbol.\n\t// - b or B: the symbol is in the uninitialized data section, e.g. .bss;\n\t// - d or D: the symbol is in the initialized data section;\n\t// - r or R: the symbol is in a read only data section;\n\t// - v or V: the symbol is a weak object;\n\t// - W: the symbol is a weak symbol that has not been specifically tagged as a\n\t//      weak object symbol. Experiments with some binaries, showed these to be\n\t//      mostly data objects.\n\treturn strings.ContainsAny(s.symType, \"bBdDrRvVW\")\n}\n\n// newAddr2LinerNM starts the given nm command reporting information about the\n// given executable file. If file is a shared library, base should be the\n// address at which it was mapped in the program under consideration.\nfunc newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {\n\tif cmd == \"\" {\n\t\tcmd = defaultNM\n\t}\n\tvar b bytes.Buffer\n\tc := exec.Command(cmd, \"--numeric-sort\", \"--print-size\", \"--format=posix\", file)\n\tc.Stdout = &b\n\tif err := c.Run(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn parseAddr2LinerNM(base, &b)\n}\n\nfunc parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) {\n\ta := &addr2LinerNM{\n\t\tm: []symbolInfo{},\n\t}\n\n\t// Parse nm output and populate symbol map.\n\t// Skip lines we fail to parse.\n\tbuf := bufio.NewReader(nm)\n\tfor {\n\t\tline, err := buf.ReadString('\\n')\n\t\tif line == \"\" && err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tline = strings.TrimSpace(line)\n\t\tfields := strings.Split(line, \" \")\n\t\tif len(fields) != 4 {\n\t\t\tcontinue\n\t\t}\n\t\taddress, err := strconv.ParseUint(fields[2], 16, 64)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tsize, err := strconv.ParseUint(fields[3], 16, 64)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\ta.m = append(a.m, symbolInfo{\n\t\t\taddress: address + base,\n\t\t\tsize:    size,\n\t\t\tname:    fields[0],\n\t\t\tsymType: fields[1],\n\t\t})\n\t}\n\n\treturn a, nil\n}\n\n// addrInfo returns the stack frame information for a specific program\n// address. It returns nil if the address could not be identified.\nfunc (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) {\n\tif len(a.m) == 0 || addr < a.m[0].address || addr >= (a.m[len(a.m)-1].address+a.m[len(a.m)-1].size) {\n\t\treturn nil, nil\n\t}\n\n\t// Binary search. Search until low, high are separated by 1.\n\tlow, high := 0, len(a.m)\n\tfor low+1 < high {\n\t\tmid := (low + high) / 2\n\t\tv := a.m[mid].address\n\t\tif addr == v {\n\t\t\tlow = mid\n\t\t\tbreak\n\t\t} else if addr > v {\n\t\t\tlow = mid\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\t// Address is between a.m[low] and a.m[high]. Pick low, as it represents\n\t// [low, high). For data symbols, we use a strict check that the address is in\n\t// the [start, start + size) range of a.m[low].\n\tif a.m[low].isData() && addr >= (a.m[low].address+a.m[low].size) {\n\t\treturn nil, nil\n\t}\n\treturn []plugin.Frame{{Func: a.m[low].name}}, nil\n}\n"
  },
  {
    "path": "internal/binutils/binutils.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package binutils provides access to the GNU binutils.\npackage binutils\n\nimport (\n\t\"debug/elf\"\n\t\"debug/macho\"\n\t\"debug/pe\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/google/pprof/internal/elfexec\"\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\n// A Binutils implements plugin.ObjTool by invoking the GNU binutils.\ntype Binutils struct {\n\tmu  sync.Mutex\n\trep *binrep\n}\n\nvar (\n\tobjdumpLLVMVerRE = regexp.MustCompile(`LLVM version (?:(\\d*)\\.(\\d*)\\.(\\d*)|.*(trunk).*)`)\n\n\t// Defined for testing\n\telfOpen = elf.Open\n)\n\n// binrep is an immutable representation for Binutils.  It is atomically\n// replaced on every mutation to provide thread-safe access.\ntype binrep struct {\n\t// Commands to invoke.\n\tllvmSymbolizer      string\n\tllvmSymbolizerFound bool\n\taddr2line           string\n\taddr2lineFound      bool\n\tnm                  string\n\tnmFound             bool\n\tobjdump             string\n\tobjdumpFound        bool\n\tisLLVMObjdump       bool\n\n\t// if fast, perform symbolization using nm (symbol names only),\n\t// instead of file-line detail from the slower addr2line.\n\tfast bool\n}\n\n// get returns the current representation for bu, initializing it if necessary.\nfunc (bu *Binutils) get() *binrep {\n\tbu.mu.Lock()\n\tr := bu.rep\n\tif r == nil {\n\t\tr = &binrep{}\n\t\tinitTools(r, \"\")\n\t\tbu.rep = r\n\t}\n\tbu.mu.Unlock()\n\treturn r\n}\n\n// update modifies the rep for bu via the supplied function.\nfunc (bu *Binutils) update(fn func(r *binrep)) {\n\tr := &binrep{}\n\tbu.mu.Lock()\n\tdefer bu.mu.Unlock()\n\tif bu.rep == nil {\n\t\tinitTools(r, \"\")\n\t} else {\n\t\t*r = *bu.rep\n\t}\n\tfn(r)\n\tbu.rep = r\n}\n\n// String returns string representation of the binutils state for debug logging.\nfunc (bu *Binutils) String() string {\n\tr := bu.get()\n\tvar llvmSymbolizer, addr2line, nm, objdump string\n\tif r.llvmSymbolizerFound {\n\t\tllvmSymbolizer = r.llvmSymbolizer\n\t}\n\tif r.addr2lineFound {\n\t\taddr2line = r.addr2line\n\t}\n\tif r.nmFound {\n\t\tnm = r.nm\n\t}\n\tif r.objdumpFound {\n\t\tobjdump = r.objdump\n\t}\n\treturn fmt.Sprintf(\"llvm-symbolizer=%q addr2line=%q nm=%q objdump=%q fast=%t\",\n\t\tllvmSymbolizer, addr2line, nm, objdump, r.fast)\n}\n\n// SetFastSymbolization sets a toggle that makes binutils use fast\n// symbolization (using nm), which is much faster than addr2line but\n// provides only symbol name information (no file/line).\nfunc (bu *Binutils) SetFastSymbolization(fast bool) {\n\tbu.update(func(r *binrep) { r.fast = fast })\n}\n\n// SetTools processes the contents of the tools option. It\n// expects a set of entries separated by commas; each entry is a pair\n// of the form t:path, where cmd will be used to look only for the\n// tool named t. If t is not specified, the path is searched for all\n// tools.\nfunc (bu *Binutils) SetTools(config string) {\n\tbu.update(func(r *binrep) { initTools(r, config) })\n}\n\nfunc initTools(b *binrep, config string) {\n\t// paths collect paths per tool; Key \"\" contains the default.\n\tpaths := make(map[string][]string)\n\tfor _, t := range strings.Split(config, \",\") {\n\t\tname, path := \"\", t\n\t\tif ct := strings.SplitN(t, \":\", 2); len(ct) == 2 {\n\t\t\tname, path = ct[0], ct[1]\n\t\t}\n\t\tpaths[name] = append(paths[name], path)\n\t}\n\n\tdefaultPath := paths[\"\"]\n\tb.llvmSymbolizer, b.llvmSymbolizerFound = chooseExe([]string{\"llvm-symbolizer\"}, []string{}, append(paths[\"llvm-symbolizer\"], defaultPath...))\n\tb.addr2line, b.addr2lineFound = chooseExe([]string{\"addr2line\"}, []string{\"gaddr2line\"}, append(paths[\"addr2line\"], defaultPath...))\n\t// The \"-n\" option is supported by LLVM since 2011. The output of llvm-nm\n\t// and GNU nm with \"-n\" option is interchangeable for our purposes, so we do\n\t// not need to differrentiate them.\n\tb.nm, b.nmFound = chooseExe([]string{\"llvm-nm\", \"nm\"}, []string{\"gnm\"}, append(paths[\"nm\"], defaultPath...))\n\tb.objdump, b.objdumpFound, b.isLLVMObjdump = findObjdump(append(paths[\"objdump\"], defaultPath...))\n}\n\n// findObjdump finds and returns path to preferred objdump binary.\n// Order of preference is: llvm-objdump, objdump.\n// On MacOS only, also looks for gobjdump with least preference.\n// Accepts a list of paths and returns:\n// a string with path to the preferred objdump binary if found,\n// or an empty string if not found;\n// a boolean if any acceptable objdump was found;\n// a boolean indicating if it is an LLVM objdump.\nfunc findObjdump(paths []string) (string, bool, bool) {\n\tobjdumpNames := []string{\"llvm-objdump\", \"objdump\"}\n\tif runtime.GOOS == \"darwin\" {\n\t\tobjdumpNames = append(objdumpNames, \"gobjdump\")\n\t}\n\n\tfor _, objdumpName := range objdumpNames {\n\t\tif objdump, objdumpFound := findExe(objdumpName, paths); objdumpFound {\n\t\t\tcmdOut, err := exec.Command(objdump, \"--version\").Output()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif isLLVMObjdump(string(cmdOut)) {\n\t\t\t\treturn objdump, true, true\n\t\t\t}\n\t\t\tif isBuObjdump(string(cmdOut)) {\n\t\t\t\treturn objdump, true, false\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", false, false\n}\n\n// chooseExe finds and returns path to preferred binary. names is a list of\n// names to search on both Linux and OSX. osxNames is a list of names specific\n// to OSX. names always has a higher priority than osxNames. The order of\n// the name within each list decides its priority (e.g. the first name has a\n// higher priority than the second name in the list).\n//\n// It returns a string with path to the binary and a boolean indicating if any\n// acceptable binary was found.\nfunc chooseExe(names, osxNames []string, paths []string) (string, bool) {\n\tif runtime.GOOS == \"darwin\" {\n\t\tnames = append(names, osxNames...)\n\t}\n\tfor _, name := range names {\n\t\tif binary, found := findExe(name, paths); found {\n\t\t\treturn binary, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\n// isLLVMObjdump accepts a string with path to an objdump binary,\n// and returns a boolean indicating if the given binary is an LLVM\n// objdump binary of an acceptable version.\nfunc isLLVMObjdump(output string) bool {\n\tfields := objdumpLLVMVerRE.FindStringSubmatch(output)\n\tif len(fields) != 5 {\n\t\treturn false\n\t}\n\tif fields[4] == \"trunk\" {\n\t\treturn true\n\t}\n\tverMajor, err := strconv.Atoi(fields[1])\n\tif err != nil {\n\t\treturn false\n\t}\n\tverPatch, err := strconv.Atoi(fields[3])\n\tif err != nil {\n\t\treturn false\n\t}\n\tif runtime.GOOS == \"linux\" && verMajor >= 8 {\n\t\t// Ensure LLVM objdump is at least version 8.0 on Linux.\n\t\t// Some flags, like --demangle, and double dashes for options are\n\t\t// not supported by previous versions.\n\t\treturn true\n\t}\n\tif runtime.GOOS == \"darwin\" {\n\t\t// Ensure LLVM objdump is at least version 10.0.1 on MacOS.\n\t\treturn verMajor > 10 || (verMajor == 10 && verPatch >= 1)\n\t}\n\treturn false\n}\n\n// isBuObjdump accepts a string with path to an objdump binary,\n// and returns a boolean indicating if the given binary is a GNU\n// binutils objdump binary. No version check is performed.\nfunc isBuObjdump(output string) bool {\n\treturn strings.Contains(output, \"GNU objdump\")\n}\n\n// findExe looks for an executable command on a set of paths.\n// If it cannot find it, returns cmd.\nfunc findExe(cmd string, paths []string) (string, bool) {\n\tfor _, p := range paths {\n\t\tcp := filepath.Join(p, cmd)\n\t\tif c, err := exec.LookPath(cp); err == nil {\n\t\t\treturn c, true\n\t\t}\n\t}\n\treturn cmd, false\n}\n\n// Disasm returns the assembly instructions for the specified address range\n// of a binary.\nfunc (bu *Binutils) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\tb := bu.get()\n\tif !b.objdumpFound {\n\t\treturn nil, errors.New(\"cannot disasm: no objdump tool available\")\n\t}\n\targs := []string{\"--disassemble\", \"--demangle\", \"--no-show-raw-insn\",\n\t\t\"--line-numbers\", fmt.Sprintf(\"--start-address=%#x\", start),\n\t\tfmt.Sprintf(\"--stop-address=%#x\", end)}\n\n\tif intelSyntax {\n\t\tif b.isLLVMObjdump {\n\t\t\targs = append(args, \"--x86-asm-syntax=intel\")\n\t\t} else {\n\t\t\targs = append(args, \"-M\", \"intel\")\n\t\t}\n\t}\n\n\targs = append(args, file)\n\tcmd := exec.Command(b.objdump, args...)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", cmd.Args, err)\n\t}\n\n\treturn disassemble(out)\n}\n\n// Open satisfies the plugin.ObjTool interface.\nfunc (bu *Binutils) Open(name string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\tb := bu.get()\n\n\t// Make sure file is a supported executable.\n\t// This uses magic numbers, mainly to provide better error messages but\n\t// it should also help speed.\n\n\tif _, err := os.Stat(name); err != nil {\n\t\t// For testing, do not require file name to exist.\n\t\tif strings.Contains(b.addr2line, \"testdata/\") {\n\t\t\treturn &fileAddr2Line{file: file{b: b, name: name}}, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\t// Read the first 4 bytes of the file.\n\n\tf, err := os.Open(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error opening %s: %v\", name, err)\n\t}\n\tdefer f.Close()\n\n\tvar header [4]byte\n\tif _, err = io.ReadFull(f, header[:]); err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading magic number from %s: %v\", name, err)\n\t}\n\n\telfMagic := string(header[:])\n\n\t// Match against supported file types.\n\tif elfMagic == elf.ELFMAG {\n\t\tf, err := b.openELF(name, start, limit, offset, relocationSymbol)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading ELF file %s: %v\", name, err)\n\t\t}\n\t\treturn f, nil\n\t}\n\n\t// Mach-O magic numbers can be big or little endian.\n\tmachoMagicLittle := binary.LittleEndian.Uint32(header[:])\n\tmachoMagicBig := binary.BigEndian.Uint32(header[:])\n\n\tif machoMagicLittle == macho.Magic32 || machoMagicLittle == macho.Magic64 ||\n\t\tmachoMagicBig == macho.Magic32 || machoMagicBig == macho.Magic64 {\n\t\tf, err := b.openMachO(name, start, limit, offset)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading Mach-O file %s: %v\", name, err)\n\t\t}\n\t\treturn f, nil\n\t}\n\tif machoMagicLittle == macho.MagicFat || machoMagicBig == macho.MagicFat {\n\t\tf, err := b.openFatMachO(name, start, limit, offset)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading fat Mach-O file %s: %v\", name, err)\n\t\t}\n\t\treturn f, nil\n\t}\n\n\tpeMagic := string(header[:2])\n\tif peMagic == \"MZ\" {\n\t\tf, err := b.openPE(name, start, limit, offset)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading PE file %s: %v\", name, err)\n\t\t}\n\t\treturn f, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"unrecognized binary format: %s\", name)\n}\n\nfunc (b *binrep) openMachOCommon(name string, of *macho.File, start, limit, offset uint64) (plugin.ObjFile, error) {\n\n\t// Subtract the load address of the __TEXT section. Usually 0 for shared\n\t// libraries or 0x100000000 for executables. You can check this value by\n\t// running `objdump -private-headers <file>`.\n\n\ttextSegment := of.Segment(\"__TEXT\")\n\tif textSegment == nil {\n\t\treturn nil, fmt.Errorf(\"could not identify base for %s: no __TEXT segment\", name)\n\t}\n\tif textSegment.Addr > start {\n\t\treturn nil, fmt.Errorf(\"could not identify base for %s: __TEXT segment address (0x%x) > mapping start address (0x%x)\",\n\t\t\tname, textSegment.Addr, start)\n\t}\n\n\tbase := start - textSegment.Addr\n\n\tif b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {\n\t\treturn &fileNM{file: file{b: b, name: name, base: base}}, nil\n\t}\n\treturn &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil\n}\n\nfunc (b *binrep) openFatMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {\n\tof, err := macho.OpenFat(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing %s: %v\", name, err)\n\t}\n\tdefer of.Close()\n\n\tif len(of.Arches) == 0 {\n\t\treturn nil, fmt.Errorf(\"empty fat Mach-O file: %s\", name)\n\t}\n\n\tvar arch macho.Cpu\n\t// Use the host architecture.\n\t// TODO: This is not ideal because the host architecture may not be the one\n\t// that was profiled. E.g. an amd64 host can profile a 386 program.\n\tswitch runtime.GOARCH {\n\tcase \"386\":\n\t\tarch = macho.Cpu386\n\tcase \"amd64\", \"amd64p32\":\n\t\tarch = macho.CpuAmd64\n\tcase \"arm\", \"armbe\", \"arm64\", \"arm64be\":\n\t\tarch = macho.CpuArm\n\tcase \"ppc\":\n\t\tarch = macho.CpuPpc\n\tcase \"ppc64\", \"ppc64le\":\n\t\tarch = macho.CpuPpc64\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported host architecture for %s: %s\", name, runtime.GOARCH)\n\t}\n\tfor i := range of.Arches {\n\t\tif of.Arches[i].Cpu == arch {\n\t\t\treturn b.openMachOCommon(name, of.Arches[i].File, start, limit, offset)\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"architecture not found in %s: %s\", name, runtime.GOARCH)\n}\n\nfunc (b *binrep) openMachO(name string, start, limit, offset uint64) (plugin.ObjFile, error) {\n\tof, err := macho.Open(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing %s: %v\", name, err)\n\t}\n\tdefer of.Close()\n\n\treturn b.openMachOCommon(name, of, start, limit, offset)\n}\n\nfunc (b *binrep) openELF(name string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\tef, err := elfOpen(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing %s: %v\", name, err)\n\t}\n\tdefer ef.Close()\n\n\tbuildID := \"\"\n\tif id, err := elfexec.GetBuildID(ef); err == nil {\n\t\tbuildID = fmt.Sprintf(\"%x\", id)\n\t}\n\n\tvar (\n\t\tkernelOffset *uint64\n\t\tpageAligned  = func(addr uint64) bool { return addr%4096 == 0 }\n\t)\n\tif strings.Contains(name, \"vmlinux\") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {\n\t\t// Reading all Symbols is expensive, and we only rarely need it so\n\t\t// we don't want to do it every time. But if _stext happens to be\n\t\t// page-aligned but isn't the same as Vaddr, we would symbolize\n\t\t// wrong. So if the name the addresses aren't page aligned, or if\n\t\t// the name is \"vmlinux\" we read _stext. We can be wrong if: (1)\n\t\t// someone passes a kernel path that doesn't contain \"vmlinux\" AND\n\t\t// (2) _stext is page-aligned AND (3) _stext is not at Vaddr\n\t\tsymbols, err := ef.Symbols()\n\t\tif err != nil && err != elf.ErrNoSymbols {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// The kernel relocation symbol (the mapping start address) can be either\n\t\t// _text or _stext. When profiles are generated by `perf`, which one was used is\n\t\t// distinguished by the mapping name for the kernel image:\n\t\t// '[kernel.kallsyms]_text' or '[kernel.kallsyms]_stext', respectively. If we haven't\n\t\t// been able to parse it from the mapping, we default to _stext.\n\t\tif relocationSymbol == \"\" {\n\t\t\trelocationSymbol = \"_stext\"\n\t\t}\n\t\tfor _, s := range symbols {\n\t\t\tif s.Name == relocationSymbol {\n\t\t\t\tkernelOffset = &s.Value\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check that we can compute a base for the binary. This may not be the\n\t// correct base value, so we don't save it. We delay computing the actual base\n\t// value until we have a sample address for this mapping, so that we can\n\t// correctly identify the associated program segment that is needed to compute\n\t// the base.\n\tif _, err := elfexec.GetBase(&ef.FileHeader, elfexec.FindTextProgHeader(ef), kernelOffset, start, limit, offset); err != nil {\n\t\treturn nil, fmt.Errorf(\"could not identify base for %s: %v\", name, err)\n\t}\n\n\tif b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {\n\t\treturn &fileNM{file: file{\n\t\t\tb:       b,\n\t\t\tname:    name,\n\t\t\tbuildID: buildID,\n\t\t\tm:       &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset},\n\t\t}}, nil\n\t}\n\treturn &fileAddr2Line{file: file{\n\t\tb:       b,\n\t\tname:    name,\n\t\tbuildID: buildID,\n\t\tm:       &elfMapping{start: start, limit: limit, offset: offset, kernelOffset: kernelOffset},\n\t}}, nil\n}\n\nfunc (b *binrep) openPE(name string, start, limit, offset uint64) (plugin.ObjFile, error) {\n\tpf, err := pe.Open(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing %s: %v\", name, err)\n\t}\n\tdefer pf.Close()\n\n\tvar imageBase uint64\n\tswitch h := pf.OptionalHeader.(type) {\n\tcase *pe.OptionalHeader32:\n\t\timageBase = uint64(h.ImageBase)\n\tcase *pe.OptionalHeader64:\n\t\timageBase = uint64(h.ImageBase)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown OptionalHeader %T\", pf.OptionalHeader)\n\t}\n\n\tvar base uint64\n\tif start > 0 {\n\t\tbase = start - imageBase\n\t}\n\tif b.fast || (!b.addr2lineFound && !b.llvmSymbolizerFound) {\n\t\treturn &fileNM{file: file{b: b, name: name, base: base}}, nil\n\t}\n\treturn &fileAddr2Line{file: file{b: b, name: name, base: base}}, nil\n}\n\n// elfMapping stores the parameters of a runtime mapping that are needed to\n// identify the ELF segment associated with a mapping.\ntype elfMapping struct {\n\t// Runtime mapping parameters.\n\tstart, limit, offset uint64\n\t// Offset of kernel relocation symbol. Only defined for kernel images, nil otherwise.\n\tkernelOffset *uint64\n}\n\n// findProgramHeader returns the program segment that matches the current\n// mapping and the given address, or an error if it cannot find a unique program\n// header.\nfunc (m *elfMapping) findProgramHeader(ef *elf.File, addr uint64) (*elf.ProgHeader, error) {\n\t// For user space executables, we try to find the actual program segment that\n\t// is associated with the given mapping. Skip this search if limit <= start.\n\t// We cannot use just a check on the start address of the mapping to tell if\n\t// it's a kernel / .ko module mapping, because with quipper address remapping\n\t// enabled, the address would be in the lower half of the address space.\n\n\tif m.kernelOffset != nil || m.start >= m.limit || m.limit >= (uint64(1)<<63) {\n\t\t// For the kernel, find the program segment that includes the .text section.\n\t\treturn elfexec.FindTextProgHeader(ef), nil\n\t}\n\n\t// Fetch all the loadable segments.\n\tvar phdrs []elf.ProgHeader\n\tfor i := range ef.Progs {\n\t\tif ef.Progs[i].Type == elf.PT_LOAD {\n\t\t\tphdrs = append(phdrs, ef.Progs[i].ProgHeader)\n\t\t}\n\t}\n\t// Some ELF files don't contain any loadable program segments, e.g. .ko\n\t// kernel modules. It's not an error to have no header in such cases.\n\tif len(phdrs) == 0 {\n\t\treturn nil, nil\n\t}\n\t// Get all program headers associated with the mapping.\n\theaders := elfexec.ProgramHeadersForMapping(phdrs, m.offset, m.limit-m.start)\n\tif len(headers) == 0 {\n\t\treturn nil, errors.New(\"no program header matches mapping info\")\n\t}\n\tif len(headers) == 1 {\n\t\treturn headers[0], nil\n\t}\n\n\t// Use the file offset corresponding to the address to symbolize, to narrow\n\t// down the header.\n\treturn elfexec.HeaderForFileOffset(headers, addr-m.start+m.offset)\n}\n\n// file implements the binutils.ObjFile interface.\ntype file struct {\n\tb       *binrep\n\tname    string\n\tbuildID string\n\n\tbaseOnce sync.Once // Ensures the base, baseErr and isData are computed once.\n\tbase     uint64\n\tbaseErr  error // Any eventual error while computing the base.\n\tisData   bool\n\t// Mapping information. Relevant only for ELF files, nil otherwise.\n\tm *elfMapping\n}\n\n// computeBase computes the relocation base for the given binary file only if\n// the elfMapping field is set. It populates the base and isData fields and\n// returns an error.\nfunc (f *file) computeBase(addr uint64) error {\n\tif f == nil || f.m == nil {\n\t\treturn nil\n\t}\n\tif addr < f.m.start || addr >= f.m.limit {\n\t\treturn fmt.Errorf(\"specified address %x is outside the mapping range [%x, %x] for file %q\", addr, f.m.start, f.m.limit, f.name)\n\t}\n\tef, err := elfOpen(f.name)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing %s: %v\", f.name, err)\n\t}\n\tdefer ef.Close()\n\n\tph, err := f.m.findProgramHeader(ef, addr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to find program header for file %q, ELF mapping %#v, address %x: %v\", f.name, *f.m, addr, err)\n\t}\n\n\tbase, err := elfexec.GetBase(&ef.FileHeader, ph, f.m.kernelOffset, f.m.start, f.m.limit, f.m.offset)\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.base = base\n\tf.isData = ph != nil && ph.Flags&elf.PF_X == 0\n\treturn nil\n}\n\nfunc (f *file) Name() string {\n\treturn f.name\n}\n\nfunc (f *file) ObjAddr(addr uint64) (uint64, error) {\n\tf.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })\n\tif f.baseErr != nil {\n\t\treturn 0, f.baseErr\n\t}\n\treturn addr - f.base, nil\n}\n\nfunc (f *file) BuildID() string {\n\treturn f.buildID\n}\n\nfunc (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\tf.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })\n\tif f.baseErr != nil {\n\t\treturn nil, f.baseErr\n\t}\n\treturn nil, nil\n}\n\nfunc (f *file) Close() error {\n\treturn nil\n}\n\nfunc (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {\n\t// Get from nm a list of symbols sorted by address.\n\tcmd := exec.Command(f.b.nm, \"-n\", f.name)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", cmd.Args, err)\n\t}\n\n\treturn findSymbols(out, f.name, r, addr)\n}\n\n// fileNM implements the binutils.ObjFile interface, using 'nm' to map\n// addresses to symbols (without file/line number information). It is\n// faster than fileAddr2Line.\ntype fileNM struct {\n\tfile\n\taddr2linernm *addr2LinerNM\n}\n\nfunc (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\tf.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })\n\tif f.baseErr != nil {\n\t\treturn nil, f.baseErr\n\t}\n\tif f.addr2linernm == nil {\n\t\taddr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tf.addr2linernm = addr2liner\n\t}\n\treturn f.addr2linernm.addrInfo(addr)\n}\n\n// fileAddr2Line implements the binutils.ObjFile interface, using\n// llvm-symbolizer, if that's available, or addr2line to map addresses to\n// symbols (with file/line number information). It can be slow for large\n// binaries with debug information.\ntype fileAddr2Line struct {\n\tonce sync.Once\n\tfile\n\taddr2liner     *addr2Liner\n\tllvmSymbolizer *llvmSymbolizer\n\tisData         bool\n}\n\nfunc (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\tf.baseOnce.Do(func() { f.baseErr = f.computeBase(addr) })\n\tif f.baseErr != nil {\n\t\treturn nil, f.baseErr\n\t}\n\tf.once.Do(f.init)\n\tif f.llvmSymbolizer != nil {\n\t\treturn f.llvmSymbolizer.addrInfo(addr)\n\t}\n\tif f.addr2liner != nil {\n\t\treturn f.addr2liner.addrInfo(addr)\n\t}\n\treturn nil, fmt.Errorf(\"could not find local addr2liner\")\n}\n\nfunc (f *fileAddr2Line) init() {\n\tif llvmSymbolizer, err := newLLVMSymbolizer(f.b.llvmSymbolizer, f.name, f.base, f.isData); err == nil {\n\t\tf.llvmSymbolizer = llvmSymbolizer\n\t\treturn\n\t}\n\n\tif addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base); err == nil {\n\t\tf.addr2liner = addr2liner\n\n\t\t// When addr2line encounters some gcc compiled binaries, it\n\t\t// drops interesting parts of names in anonymous namespaces.\n\t\t// Fallback to NM for better function names.\n\t\tif nm, err := newAddr2LinerNM(f.b.nm, f.name, f.base); err == nil {\n\t\t\tf.addr2liner.nm = nm\n\t\t}\n\t}\n}\n\nfunc (f *fileAddr2Line) Close() error {\n\tif f.llvmSymbolizer != nil {\n\t\tf.llvmSymbolizer.rw.close()\n\t\tf.llvmSymbolizer = nil\n\t}\n\tif f.addr2liner != nil {\n\t\tf.addr2liner.rw.close()\n\t\tf.addr2liner = nil\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/binutils/binutils_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"bytes\"\n\t\"debug/elf\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\nvar testAddrMap = map[int]string{\n\t1000: \"_Z3fooid.clone2\",\n\t2000: \"_ZNSaIiEC1Ev.clone18\",\n\t3000: \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\",\n}\n\nfunc functionName(level int) (name string) {\n\tif name = testAddrMap[level]; name != \"\" {\n\t\treturn name\n\t}\n\treturn fmt.Sprintf(\"fun%d\", level)\n}\n\nfunc TestAddr2Liner(t *testing.T) {\n\tconst offset = 0x500\n\n\ta := addr2Liner{rw: &mockAddr2liner{}, base: offset}\n\tfor i := 1; i < 8; i++ {\n\t\taddr := i*0x1000 + offset\n\t\ts, err := a.addrInfo(uint64(addr))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"addrInfo(%#x): %v\", addr, err)\n\t\t}\n\t\tif len(s) != i {\n\t\t\tt.Fatalf(\"addrInfo(%#x): got len==%d, want %d\", addr, len(s), i)\n\t\t}\n\t\tfor l, f := range s {\n\t\t\tlevel := (len(s) - l) * 1000\n\t\t\twant := plugin.Frame{Func: functionName(level), File: fmt.Sprintf(\"file%d\", level), Line: level}\n\n\t\t\tif f != want {\n\t\t\t\tt.Errorf(\"AddrInfo(%#x)[%d]: = %+v, want %+v\", addr, l, f, want)\n\t\t\t}\n\t\t}\n\t}\n\ts, err := a.addrInfo(0xFFFF)\n\tif err != nil {\n\t\tt.Fatalf(\"addrInfo(0xFFFF): %v\", err)\n\t}\n\tif len(s) != 0 {\n\t\tt.Fatalf(\"AddrInfo(0xFFFF): got len==%d, want 0\", len(s))\n\t}\n\ta.rw.close()\n}\n\ntype mockAddr2liner struct {\n\toutput []string\n}\n\nfunc (a *mockAddr2liner) write(s string) error {\n\tvar lines []string\n\tswitch s {\n\tcase \"1000\":\n\t\tlines = []string{\"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"2000\":\n\t\tlines = []string{\"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"3000\":\n\t\tlines = []string{\"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"4000\":\n\t\tlines = []string{\"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"5000\":\n\t\tlines = []string{\"fun5000\", \"file5000:5000\", \"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"6000\":\n\t\tlines = []string{\"fun6000\", \"file6000:6000\", \"fun5000\", \"file5000:5000\", \"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"7000\":\n\t\tlines = []string{\"fun7000\", \"file7000:7000\", \"fun6000\", \"file6000:6000\", \"fun5000\", \"file5000:5000\", \"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"8000\":\n\t\tlines = []string{\"fun8000\", \"file8000:8000\", \"fun7000\", \"file7000:7000\", \"fun6000\", \"file6000:6000\", \"fun5000\", \"file5000:5000\", \"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tcase \"9000\":\n\t\tlines = []string{\"fun9000\", \"file9000:9000\", \"fun8000\", \"file8000:8000\", \"fun7000\", \"file7000:7000\", \"fun6000\", \"file6000:6000\", \"fun5000\", \"file5000:5000\", \"fun4000\", \"file4000:4000\", \"_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm\", \"file3000:3000\", \"_ZNSaIiEC1Ev.clone18\", \"file2000:2000\", \"_Z3fooid.clone2\", \"file1000:1000\"}\n\tdefault:\n\t\tlines = []string{\"??\", \"??:0\"}\n\t}\n\ta.output = append(a.output, \"0x\"+s)\n\ta.output = append(a.output, lines...)\n\treturn nil\n}\n\nfunc (a *mockAddr2liner) readLine() (string, error) {\n\tif len(a.output) == 0 {\n\t\treturn \"\", fmt.Errorf(\"end of file\")\n\t}\n\tnext := a.output[0]\n\ta.output = a.output[1:]\n\treturn next, nil\n}\n\nfunc (a *mockAddr2liner) close() {\n}\n\nfunc TestAddr2LinerLookup(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc             string\n\t\tnmOutput         string\n\t\twantSymbolized   map[uint64]string\n\t\twantUnsymbolized []uint64\n\t}{\n\t\t{\n\t\t\tdesc: \"odd symbol count\",\n\t\t\tnmOutput: `\n0x1000 T 1000 100\n0x2000 T 2000 120\n0x3000 T 3000 130\n`,\n\t\t\twantSymbolized: map[uint64]string{\n\t\t\t\t0x1000: \"0x1000\",\n\t\t\t\t0x1001: \"0x1000\",\n\t\t\t\t0x1FFF: \"0x1000\",\n\t\t\t\t0x2000: \"0x2000\",\n\t\t\t\t0x2001: \"0x2000\",\n\t\t\t\t0x3000: \"0x3000\",\n\t\t\t\t0x312f: \"0x3000\",\n\t\t\t},\n\t\t\twantUnsymbolized: []uint64{0x0fff, 0x3130},\n\t\t},\n\t\t{\n\t\t\tdesc: \"even symbol count\",\n\t\t\tnmOutput: `\n0x1000 T 1000 100\n0x2000 T 2000 120\n0x3000 T 3000 130\n0x4000 T 4000 140\n`,\n\t\t\twantSymbolized: map[uint64]string{\n\t\t\t\t0x1000: \"0x1000\",\n\t\t\t\t0x1001: \"0x1000\",\n\t\t\t\t0x1FFF: \"0x1000\",\n\t\t\t\t0x2000: \"0x2000\",\n\t\t\t\t0x2fff: \"0x2000\",\n\t\t\t\t0x3000: \"0x3000\",\n\t\t\t\t0x3fff: \"0x3000\",\n\t\t\t\t0x4000: \"0x4000\",\n\t\t\t\t0x413f: \"0x4000\",\n\t\t\t},\n\t\t\twantUnsymbolized: []uint64{0x0fff, 0x4140},\n\t\t},\n\t\t{\n\t\t\tdesc: \"different symbol types\",\n\t\t\tnmOutput: `\nabsolute_0x100 a 100\nabsolute_0x200 A 200\ntext_0x1000 t 1000 100\nbss_0x2000 b 2000 120\ndata_0x3000 d 3000 130\nrodata_0x4000 r 4000 140\nweak_0x5000 v 5000 150\ntext_0x6000 T 6000 160\nbss_0x7000 B 7000 170\ndata_0x8000 D 8000 180\nrodata_0x9000 R 9000 190\nweak_0xa000 V a000 1a0\nweak_0xb000 W b000 1b0\n`,\n\t\t\twantSymbolized: map[uint64]string{\n\t\t\t\t0x1000: \"text_0x1000\",\n\t\t\t\t0x1FFF: \"text_0x1000\",\n\t\t\t\t0x2000: \"bss_0x2000\",\n\t\t\t\t0x211f: \"bss_0x2000\",\n\t\t\t\t0x3000: \"data_0x3000\",\n\t\t\t\t0x312f: \"data_0x3000\",\n\t\t\t\t0x4000: \"rodata_0x4000\",\n\t\t\t\t0x413f: \"rodata_0x4000\",\n\t\t\t\t0x5000: \"weak_0x5000\",\n\t\t\t\t0x514f: \"weak_0x5000\",\n\t\t\t\t0x6000: \"text_0x6000\",\n\t\t\t\t0x6fff: \"text_0x6000\",\n\t\t\t\t0x7000: \"bss_0x7000\",\n\t\t\t\t0x716f: \"bss_0x7000\",\n\t\t\t\t0x8000: \"data_0x8000\",\n\t\t\t\t0x817f: \"data_0x8000\",\n\t\t\t\t0x9000: \"rodata_0x9000\",\n\t\t\t\t0x918f: \"rodata_0x9000\",\n\t\t\t\t0xa000: \"weak_0xa000\",\n\t\t\t\t0xa19f: \"weak_0xa000\",\n\t\t\t\t0xb000: \"weak_0xb000\",\n\t\t\t\t0xb1af: \"weak_0xb000\",\n\t\t\t},\n\t\t\twantUnsymbolized: []uint64{0x100, 0x200, 0x0fff, 0x2120, 0x3130, 0x4140, 0x5150, 0x7170, 0x8180, 0x9190, 0xa1a0, 0xb1b0},\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\ta, err := parseAddr2LinerNM(0, bytes.NewBufferString(tc.nmOutput))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"nm parse error: %v\", err)\n\t\t\t}\n\t\t\tfor address, want := range tc.wantSymbolized {\n\t\t\t\tif got, _ := a.addrInfo(address); !checkAddress(got, address, want) {\n\t\t\t\t\tt.Errorf(\"%x: got %v, want %s\", address, got, want)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, unknown := range tc.wantUnsymbolized {\n\t\t\t\tif got, _ := a.addrInfo(unknown); got != nil {\n\t\t\t\t\tt.Errorf(\"%x: got %v, want nil\", unknown, got)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkAddress(got []plugin.Frame, address uint64, want string) bool {\n\tif len(got) != 1 {\n\t\treturn false\n\t}\n\treturn got[0].Func == want\n}\n\nfunc TestSetTools(t *testing.T) {\n\t// Test that multiple calls work.\n\tbu := &Binutils{}\n\tbu.SetTools(\"\")\n\tbu.SetTools(\"\")\n}\n\nfunc TestSetFastSymbolization(t *testing.T) {\n\t// Test that multiple calls work.\n\tbu := &Binutils{}\n\tbu.SetFastSymbolization(true)\n\tbu.SetFastSymbolization(false)\n}\n\nfunc skipUnlessLinuxAmd64(t *testing.T) {\n\tif runtime.GOOS != \"linux\" || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86-64 Linux\")\n\t}\n}\n\nfunc skipUnlessDarwinAmd64(t *testing.T) {\n\tif runtime.GOOS != \"darwin\" || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86-64 macOS\")\n\t}\n}\n\nfunc skipUnlessWindowsAmd64(t *testing.T) {\n\tif runtime.GOOS != \"windows\" || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86-64 Windows\")\n\t}\n}\n\nfunc testDisasm(t *testing.T, intelSyntax bool) {\n\t_, llvmObjdump, buObjdump := findObjdump([]string{\"\"})\n\tif !llvmObjdump && !buObjdump {\n\t\tt.Skip(\"cannot disasm: no objdump tool available\")\n\t}\n\n\tbu := &Binutils{}\n\tvar testexe string\n\tswitch runtime.GOOS {\n\tcase \"linux\":\n\t\ttestexe = \"exe_linux_64\"\n\tcase \"darwin\":\n\t\ttestexe = \"exe_mac_64\"\n\tcase \"windows\":\n\t\ttestexe = \"exe_windows_64.exe\"\n\tdefault:\n\t\tt.Skipf(\"unsupported OS %q\", runtime.GOOS)\n\t}\n\n\tinsts, err := bu.Disasm(filepath.Join(\"testdata\", testexe), 0, math.MaxUint64, intelSyntax)\n\tif err != nil {\n\t\tt.Fatalf(\"Disasm: unexpected error %v\", err)\n\t}\n\tmainCount := 0\n\tfor _, x := range insts {\n\t\t// macOS symbols have a leading underscore.\n\t\tif x.Function == \"main\" || x.Function == \"_main\" {\n\t\t\tmainCount++\n\t\t}\n\t}\n\tif mainCount == 0 {\n\t\tt.Error(\"Disasm: found no main instructions\")\n\t}\n}\n\nfunc TestDisasm(t *testing.T) {\n\tif (runtime.GOOS != \"linux\" && runtime.GOOS != \"darwin\" && runtime.GOOS != \"windows\") || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86-64 Linux, macOS or Windows\")\n\t}\n\ttestDisasm(t, false)\n}\n\nfunc TestDisasmIntelSyntax(t *testing.T) {\n\tif (runtime.GOOS != \"linux\" && runtime.GOOS != \"darwin\" && runtime.GOOS != \"windows\") || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"This test only works on x86_64 Linux, macOS or Windows as it tests Intel asm syntax\")\n\t}\n\ttestDisasm(t, true)\n}\n\nfunc findSymbol(syms []*plugin.Sym, name string) *plugin.Sym {\n\tfor _, s := range syms {\n\t\tif slices.Contains(s.Name, name) {\n\t\t\treturn s\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc TestObjFile(t *testing.T) {\n\t// If this test fails, check the address for main function in testdata/exe_linux_64\n\t// using the command 'nm -n '. Update the hardcoded addresses below to match\n\t// the addresses from the output.\n\tskipUnlessLinuxAmd64(t)\n\tfor _, tc := range []struct {\n\t\tdesc                 string\n\t\tstart, limit, offset uint64\n\t\taddr                 uint64\n\t}{\n\t\t{\"fixed load address\", 0x400000, 0x4006fc, 0, 0x40052d},\n\t\t// True user-mode ASLR binaries are ET_DYN rather than ET_EXEC so this case\n\t\t// is a bit artificial except that it approximates the\n\t\t// vmlinux-with-kernel-ASLR case where the binary *is* ET_EXEC.\n\t\t{\"simulated ASLR address\", 0x500000, 0x5006fc, 0, 0x50052d},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tbu := &Binutils{}\n\t\t\tf, err := bu.Open(filepath.Join(\"testdata\", \"exe_linux_64\"), tc.start, tc.limit, tc.offset, \"\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Open: unexpected error %v\", err)\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\tsyms, err := f.Symbols(regexp.MustCompile(\"main\"), 0)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Symbols: unexpected error %v\", err)\n\t\t\t}\n\n\t\t\tm := findSymbol(syms, \"main\")\n\t\t\tif m == nil {\n\t\t\t\tt.Fatalf(\"Symbols: did not find main\")\n\t\t\t}\n\t\t\taddr, err := f.ObjAddr(tc.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"ObjAddr(%x) failed: %v\", tc.addr, err)\n\t\t\t}\n\t\t\tif addr != m.Start {\n\t\t\t\tt.Errorf(\"ObjAddr(%x) got %x, want %x\", tc.addr, addr, m.Start)\n\t\t\t}\n\t\t\tgotFrames, err := f.SourceLine(tc.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"SourceLine: unexpected error %v\", err)\n\t\t\t}\n\t\t\twantFrames := []plugin.Frame{\n\t\t\t\t{Func: \"main\", File: \"/tmp/hello.c\", Line: 3, StartLine: 3},\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotFrames, wantFrames) {\n\t\t\t\tt.Fatalf(\"SourceLine for main: got %v; want %v\\n\", gotFrames, wantFrames)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMachoFiles(t *testing.T) {\n\t// If this test fails, check the address for main function in testdata/exe_mac_64\n\t// and testdata/lib_mac_64 using addr2line or gaddr2line. Update the\n\t// hardcoded addresses below to match the addresses from the output.\n\tskipUnlessDarwinAmd64(t)\n\n\t// Load `file`, pretending it was mapped at `start`. Then get the symbol\n\t// table. Check that it contains the symbol `sym` and that the address\n\t// `addr` gives the `expected` stack trace.\n\tfor _, tc := range []struct {\n\t\tdesc                 string\n\t\tfile                 string\n\t\tstart, limit, offset uint64\n\t\taddr                 uint64\n\t\tsym                  string\n\t\texpected             []plugin.Frame\n\t}{\n\t\t{\"normal mapping\", \"exe_mac_64\", 0x100000000, math.MaxUint64, 0,\n\t\t\t0x100000f50, \"_main\",\n\t\t\t[]plugin.Frame{\n\t\t\t\t{Func: \"main\", File: \"/tmp/hello.c\", Line: 3, StartLine: 3},\n\t\t\t}},\n\t\t{\"other mapping\", \"exe_mac_64\", 0x200000000, math.MaxUint64, 0,\n\t\t\t0x200000f50, \"_main\",\n\t\t\t[]plugin.Frame{\n\t\t\t\t{Func: \"main\", File: \"/tmp/hello.c\", Line: 3, StartLine: 3},\n\t\t\t}},\n\t\t{\"lib normal mapping\", \"lib_mac_64\", 0, math.MaxUint64, 0,\n\t\t\t0xfa0, \"_bar\",\n\t\t\t[]plugin.Frame{\n\t\t\t\t{Func: \"bar\", File: \"/tmp/lib.c\", Line: 5, StartLine: 5},\n\t\t\t}},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tbu := &Binutils{}\n\t\t\tf, err := bu.Open(filepath.Join(\"testdata\", tc.file), tc.start, tc.limit, tc.offset, \"\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Open: unexpected error %v\", err)\n\t\t\t}\n\t\t\tt.Logf(\"binutils: %v\", bu)\n\t\t\tif runtime.GOOS == \"darwin\" && !bu.rep.addr2lineFound && !bu.rep.llvmSymbolizerFound {\n\t\t\t\t// On macOS, user needs to install gaddr2line or llvm-symbolizer with\n\t\t\t\t// Homebrew, skip the test when the environment doesn't have it\n\t\t\t\t// installed.\n\t\t\t\tt.Skip(\"couldn't find addr2line or gaddr2line\")\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\tsyms, err := f.Symbols(nil, 0)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Symbols: unexpected error %v\", err)\n\t\t\t}\n\n\t\t\tm := findSymbol(syms, tc.sym)\n\t\t\tif m == nil {\n\t\t\t\tt.Fatalf(\"Symbols: could not find symbol %v\", tc.sym)\n\t\t\t}\n\t\t\tgotFrames, err := f.SourceLine(tc.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"SourceLine: unexpected error %v\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotFrames, tc.expected) {\n\t\t\t\tt.Fatalf(\"SourceLine for main: got %v; want %v\\n\", gotFrames, tc.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLLVMSymbolizer(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\tt.Skip(\"testtdata/llvm-symbolizer has only been tested on linux\")\n\t}\n\n\tcmd := filepath.Join(\"testdata\", \"fake-llvm-symbolizer\")\n\tfor _, c := range []struct {\n\t\taddr   uint64\n\t\tisData bool\n\t\tframes []plugin.Frame\n\t}{\n\t\t{0x10, false, []plugin.Frame{\n\t\t\t{Func: \"Inlined_0x10\", File: \"foo.h\", Line: 0, Column: 0, StartLine: 0},\n\t\t\t{Func: \"Func_0x10\", File: \"foo.c\", Line: 2, Column: 1, StartLine: 2},\n\t\t}},\n\t\t{0x20, true, []plugin.Frame{\n\t\t\t{Func: \"foo_0x20\", File: \"0x20 8\"},\n\t\t}},\n\t} {\n\t\tdesc := fmt.Sprintf(\"Code %x\", c.addr)\n\t\tif c.isData {\n\t\t\tdesc = fmt.Sprintf(\"Data %x\", c.addr)\n\t\t}\n\t\tt.Run(desc, func(t *testing.T) {\n\t\t\tsymbolizer, err := newLLVMSymbolizer(cmd, \"foo\", 0, c.isData)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newLLVMSymbolizer: unexpected error %v\", err)\n\t\t\t}\n\t\t\tdefer symbolizer.rw.close()\n\n\t\t\tframes, err := symbolizer.addrInfo(c.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"LLVM: unexpected error %v\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(frames, c.frames) {\n\t\t\t\tt.Errorf(\"LLVM: expect %v; got %v\\n\", c.frames, frames)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPEFile(t *testing.T) {\n\t// If this test fails, check the address for main function in testdata/exe_windows_64.exe\n\t// using the command 'nm -n '. Update the hardcoded addresses below to match\n\t// the addresses from the output.\n\tskipUnlessWindowsAmd64(t)\n\tfor _, tc := range []struct {\n\t\tdesc                 string\n\t\tstart, limit, offset uint64\n\t\taddr                 uint64\n\t}{\n\t\t{\"fake mapping\", 0, math.MaxUint64, 0, 0x140001594},\n\t\t{\"fixed load address\", 0x140000000, 0x140002000, 0, 0x140001594},\n\t\t{\"simulated ASLR address\", 0x150000000, 0x150002000, 0, 0x150001594},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tbu := &Binutils{}\n\t\t\tf, err := bu.Open(filepath.Join(\"testdata\", \"exe_windows_64.exe\"), tc.start, tc.limit, tc.offset, \"\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Open: unexpected error %v\", err)\n\t\t\t}\n\t\t\tdefer f.Close()\n\t\t\tsyms, err := f.Symbols(regexp.MustCompile(\"main\"), 0)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Symbols: unexpected error %v\", err)\n\t\t\t}\n\n\t\t\tm := findSymbol(syms, \"main\")\n\t\t\tif m == nil {\n\t\t\t\tt.Fatalf(\"Symbols: did not find main\")\n\t\t\t}\n\t\t\taddr, err := f.ObjAddr(tc.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"ObjAddr(%x) failed: %v\", tc.addr, err)\n\t\t\t}\n\t\t\tif addr != m.Start {\n\t\t\t\tt.Errorf(\"ObjAddr(%x) got %x, want %x\", tc.addr, addr, m.Start)\n\t\t\t}\n\t\t\tgotFrames, err := f.SourceLine(tc.addr)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"SourceLine: unexpected error %v\", err)\n\t\t\t}\n\t\t\twantFrames := []plugin.Frame{\n\t\t\t\t{Func: \"main\", File: \"hello.c\", Line: 3, Column: 12, StartLine: 3},\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotFrames, wantFrames) {\n\t\t\t\tt.Fatalf(\"SourceLine for main: got %v; want %v\\n\", gotFrames, wantFrames)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestOpenMalformedELF(t *testing.T) {\n\t// Test that opening a malformed ELF file will report an error containing\n\t// the word \"ELF\".\n\tbu := &Binutils{}\n\t_, err := bu.Open(filepath.Join(\"testdata\", \"malformed_elf\"), 0, 0, 0, \"\")\n\tif err == nil {\n\t\tt.Fatalf(\"Open: unexpected success\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"ELF\") {\n\t\tt.Errorf(\"Open: got %v, want error containing 'ELF'\", err)\n\t}\n}\n\nfunc TestOpenMalformedMachO(t *testing.T) {\n\t// Test that opening a malformed Mach-O file will report an error containing\n\t// the word \"Mach-O\".\n\tbu := &Binutils{}\n\t_, err := bu.Open(filepath.Join(\"testdata\", \"malformed_macho\"), 0, 0, 0, \"\")\n\tif err == nil {\n\t\tt.Fatalf(\"Open: unexpected success\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"Mach-O\") {\n\t\tt.Errorf(\"Open: got %v, want error containing 'Mach-O'\", err)\n\t}\n}\n\nfunc TestObjdumpVersionChecks(t *testing.T) {\n\t// Test that the objdump version strings are parsed properly.\n\ttype testcase struct {\n\t\tdesc string\n\t\tos   string\n\t\tver  string\n\t\twant bool\n\t}\n\n\tfor _, tc := range []testcase{\n\t\t{\n\t\t\tdesc: \"Valid Apple LLVM version string with usable version\",\n\t\t\tos:   \"darwin\",\n\t\t\tver:  \"Apple LLVM version 11.0.3 (clang-1103.0.32.62)\\nOptimized build.\",\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Valid Apple LLVM version string with unusable version\",\n\t\t\tos:   \"darwin\",\n\t\t\tver:  \"Apple LLVM version 10.0.0 (clang-1000.11.45.5)\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Invalid Apple LLVM version string with usable version\",\n\t\t\tos:   \"darwin\",\n\t\t\tver:  \"Apple LLVM versions 11.0.3 (clang-1103.0.32.62)\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Valid LLVM version string with usable version\",\n\t\t\tos:   \"linux\",\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nLLVM version 9.0.1\\n\\nOptimized build.\",\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Valid LLVM version string with unusable version\",\n\t\t\tos:   \"linux\",\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nLLVM version 6.0.1\\n\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Invalid LLVM version string with usable version\",\n\t\t\tos:   \"linux\",\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nLLVM versions 9.0.1\\n\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Valid LLVM objdump version string with trunk\",\n\t\t\tos:   runtime.GOOS,\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nLLVM version custom-trunk 124ffeb592a00bfe\\nOptimized build.\",\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Invalid LLVM objdump version string with trunk\",\n\t\t\tos:   runtime.GOOS,\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nLLVM version custom-trank 124ffeb592a00bfe\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Invalid LLVM objdump version string with trunk\",\n\t\t\tos:   runtime.GOOS,\n\t\t\tver:  \"LLVM (http://llvm.org/):\\nllvm version custom-trunk 124ffeb592a00bfe\\nOptimized build.\",\n\t\t\twant: false,\n\t\t},\n\t} {\n\t\tif runtime.GOOS == tc.os {\n\t\t\tif got := isLLVMObjdump(tc.ver); got != tc.want {\n\t\t\t\tt.Errorf(\"%v: got %v, want %v\", tc.desc, got, tc.want)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, tc := range []testcase{\n\t\t{\n\t\t\tdesc: \"Valid GNU objdump version string\",\n\t\t\tver:  \"GNU objdump (GNU Binutils) 2.34\\nCopyright (C) 2020 Free Software Foundation, Inc.\",\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"Invalid GNU objdump version string\",\n\t\t\tver:  \"GNU nm (GNU Binutils) 2.34\\nCopyright (C) 2020 Free Software Foundation, Inc.\",\n\t\t\twant: false,\n\t\t},\n\t} {\n\t\tif got := isBuObjdump(tc.ver); got != tc.want {\n\t\t\tt.Errorf(\"%v: got %v, want %v\", tc.desc, got, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestComputeBase(t *testing.T) {\n\trealELFOpen := elfOpen\n\tdefer func() {\n\t\telfOpen = realELFOpen\n\t}()\n\n\ttinyExecFile := &elf.File{\n\t\tFileHeader: elf.FileHeader{Type: elf.ET_EXEC},\n\t\tProgs: []*elf.Prog{\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_PHDR, Flags: elf.PF_R | elf.PF_X, Off: 0x40, Vaddr: 0x400040, Paddr: 0x400040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x400238, Paddr: 0x400238, Filesz: 0x1c, Memsz: 0x1c, Align: 1}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000}},\n\t\t},\n\t}\n\ttinyBadBSSExecFile := &elf.File{\n\t\tFileHeader: elf.FileHeader{Type: elf.ET_EXEC},\n\t\tProgs: []*elf.Prog{\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_PHDR, Flags: elf.PF_R | elf.PF_X, Off: 0x40, Vaddr: 0x400040, Paddr: 0x400040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x400238, Paddr: 0x400238, Filesz: 0x1c, Memsz: 0x1c, Align: 1}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000}},\n\t\t\t{ProgHeader: elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x90, Memsz: 0x90, Align: 0x200000}},\n\t\t},\n\t}\n\n\tfor _, tc := range []struct {\n\t\tdesc       string\n\t\tfile       *elf.File\n\t\topenErr    error\n\t\tmapping    *elfMapping\n\t\taddr       uint64\n\t\twantError  bool\n\t\twantBase   uint64\n\t\twantIsData bool\n\t}{\n\t\t{\n\t\t\tdesc:       \"no elf mapping, no error\",\n\t\t\tmapping:    nil,\n\t\t\taddr:       0x1000,\n\t\t\twantBase:   0,\n\t\t\twantIsData: false,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"address outside mapping bounds means error\",\n\t\t\tfile:      &elf.File{},\n\t\t\tmapping:   &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},\n\t\t\taddr:      0x1000,\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"elf.Open failing means error\",\n\t\t\tfile:      &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_EXEC}},\n\t\t\topenErr:   errors.New(\"elf.Open failed\"),\n\t\t\tmapping:   &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},\n\t\t\taddr:      0x4000,\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc:       \"no loadable segments, no error\",\n\t\t\tfile:       &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_EXEC}},\n\t\t\tmapping:    &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},\n\t\t\taddr:       0x4000,\n\t\t\twantBase:   0,\n\t\t\twantIsData: false,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"unsupported executable type, Get Base returns error\",\n\t\t\tfile:      &elf.File{FileHeader: elf.FileHeader{Type: elf.ET_NONE}},\n\t\t\tmapping:   &elfMapping{start: 0x2000, limit: 0x5000, offset: 0x1000},\n\t\t\taddr:      0x4000,\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc:       \"tiny file select executable segment by offset\",\n\t\t\tfile:       tinyExecFile,\n\t\t\tmapping:    &elfMapping{start: 0x5000000, limit: 0x5001000, offset: 0x0},\n\t\t\taddr:       0x5000c00,\n\t\t\twantBase:   0x5000000,\n\t\t\twantIsData: false,\n\t\t},\n\t\t{\n\t\t\tdesc:       \"tiny file select data segment by offset\",\n\t\t\tfile:       tinyExecFile,\n\t\t\tmapping:    &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},\n\t\t\taddr:       0x5200c80,\n\t\t\twantBase:   0x5000000,\n\t\t\twantIsData: true,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"tiny file offset outside any segment means error\",\n\t\t\tfile:      tinyExecFile,\n\t\t\tmapping:   &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},\n\t\t\taddr:      0x5200e70,\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc:       \"tiny file with bad BSS segment selects data segment by offset in initialized section\",\n\t\t\tfile:       tinyBadBSSExecFile,\n\t\t\tmapping:    &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},\n\t\t\taddr:       0x5200d79,\n\t\t\twantBase:   0x5000000,\n\t\t\twantIsData: true,\n\t\t},\n\t\t{\n\t\t\tdesc:      \"tiny file with bad BSS segment with offset in uninitialized section means error\",\n\t\t\tfile:      tinyBadBSSExecFile,\n\t\t\tmapping:   &elfMapping{start: 0x5200000, limit: 0x5201000, offset: 0x0},\n\t\t\taddr:      0x5200d80,\n\t\t\twantError: true,\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\telfOpen = func(_ string) (*elf.File, error) {\n\t\t\t\treturn tc.file, tc.openErr\n\t\t\t}\n\t\t\tf := file{m: tc.mapping}\n\t\t\terr := f.computeBase(tc.addr)\n\t\t\tif (err != nil) != tc.wantError {\n\t\t\t\tt.Errorf(\"got error %v, want any error=%v\", err, tc.wantError)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif f.base != tc.wantBase {\n\t\t\t\tt.Errorf(\"got base %x, want %x\", f.base, tc.wantBase)\n\t\t\t}\n\t\t\tif f.isData != tc.wantIsData {\n\t\t\t\tt.Errorf(\"got isData %v, want %v\", f.isData, tc.wantIsData)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestELFObjAddr(t *testing.T) {\n\t// The exe_linux_64 has two loadable program headers:\n\t//  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000\n\t//                 0x00000000000006fc 0x00000000000006fc  R E    0x200000\n\t//  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10\n\t//                 0x0000000000000230 0x0000000000000238  RW     0x200000\n\tname := filepath.Join(\"testdata\", \"exe_linux_64\")\n\n\tfor _, tc := range []struct {\n\t\tdesc                 string\n\t\tstart, limit, offset uint64\n\t\twantOpenError        bool\n\t\taddr                 uint64\n\t\twantObjAddr          uint64\n\t\twantAddrError        bool\n\t}{\n\t\t{\"exec mapping, good address\", 0x5400000, 0x5401000, 0, false, 0x5400400, 0x400400, false},\n\t\t{\"exec mapping, address outside segment\", 0x5400000, 0x5401000, 0, false, 0x5400800, 0, true},\n\t\t{\"short data mapping, good address\", 0x5600e00, 0x5602000, 0xe00, false, 0x5600e10, 0x600e10, false},\n\t\t{\"short data mapping, address outside segment\", 0x5600e00, 0x5602000, 0xe00, false, 0x5600e00, 0x600e00, false},\n\t\t{\"page aligned data mapping, good address\", 0x5600000, 0x5602000, 0, false, 0x5601000, 0x601000, false},\n\t\t{\"page aligned data mapping, address outside segment\", 0x5600000, 0x5602000, 0, false, 0x5601048, 0, true},\n\t\t{\"bad file offset, no matching segment\", 0x5600000, 0x5602000, 0x2000, false, 0x5600e10, 0, true},\n\t\t{\"large mapping size, match by sample offset\", 0x5600000, 0x5603000, 0, false, 0x5600e10, 0x600e10, false},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tb := binrep{}\n\t\t\to, err := b.openELF(name, tc.start, tc.limit, tc.offset, \"\")\n\t\t\tif (err != nil) != tc.wantOpenError {\n\t\t\t\tt.Errorf(\"openELF got error %v, want any error=%v\", err, tc.wantOpenError)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgot, err := o.ObjAddr(tc.addr)\n\t\t\tif (err != nil) != tc.wantAddrError {\n\t\t\t\tt.Errorf(\"ObjAddr got error %v, want any error=%v\", err, tc.wantAddrError)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tc.wantObjAddr {\n\t\t\t\tt.Errorf(\"got ObjAddr %x; want %x\\n\", got, tc.wantObjAddr)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype buf struct {\n\tdata []byte\n}\n\n// write appends a null-terminated string and returns its starting index.\nfunc (b *buf) write(s string) uint32 {\n\tres := uint32(len(b.data))\n\tb.data = append(b.data, s...)\n\tb.data = append(b.data, '\\x00')\n\treturn res\n}\n\n// fakeELFFile generates a minimal valid ELF file, with fake .head.text and\n// .text sections, and their corresponding _text and _stext start symbols,\n// mimicking a kernel vmlinux image.\nfunc fakeELFFile(t *testing.T) *elf.File {\n\tvar (\n\t\tsizeHeader64  = binary.Size(elf.Header64{})\n\t\tsizeProg64    = binary.Size(elf.Prog64{})\n\t\tsizeSection64 = binary.Size(elf.Section64{})\n\t)\n\n\tconst (\n\t\ttextAddr  = 0xffff000010080000\n\t\tstextAddr = 0xffff000010081000\n\t)\n\n\t// Generate magic to identify as an ELF file.\n\tvar ident [16]uint8\n\tident[0] = '\\x7f'\n\tident[1] = 'E'\n\tident[2] = 'L'\n\tident[3] = 'F'\n\tident[elf.EI_CLASS] = uint8(elf.ELFCLASS64)\n\tident[elf.EI_DATA] = uint8(elf.ELFDATA2LSB)\n\tident[elf.EI_VERSION] = uint8(elf.EV_CURRENT)\n\tident[elf.EI_OSABI] = uint8(elf.ELFOSABI_NONE)\n\n\t// A single program header, containing code and starting at the _text address.\n\tprogs := []elf.Prog64{{\n\t\tType: uint32(elf.PT_LOAD), Flags: uint32(elf.PF_R | elf.PF_X), Off: 0x10000, Vaddr: textAddr, Paddr: textAddr, Filesz: 0x1234567, Memsz: 0x1234567, Align: 0x10000}}\n\n\tsymNames := buf{}\n\tsyms := []elf.Sym64{\n\t\t{}, // first symbol empty by convention\n\t\t{Name: symNames.write(\"_text\"), Info: 0, Other: 0, Shndx: 0, Value: textAddr, Size: 0},\n\t\t{Name: symNames.write(\"_stext\"), Info: 0, Other: 0, Shndx: 0, Value: stextAddr, Size: 0},\n\t}\n\n\tconst numSections = 5\n\t// We'll write `textSize` zero bytes as contents of the .head.text and .text sections.\n\tconst textSize = 16\n\t// Offset of section contents in the byte stream -- after header, program headers, and section headers.\n\tsectionsStart := uint64(sizeHeader64 + len(progs)*sizeProg64 + numSections*sizeSection64)\n\n\tsecNames := buf{}\n\tsections := [numSections]elf.Section64{\n\t\t{Name: secNames.write(\".head.text\"), Type: uint32(elf.SHT_PROGBITS), Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), Addr: textAddr, Off: sectionsStart, Size: textSize, Link: 0, Info: 0, Addralign: 2048, Entsize: 0},\n\t\t{Name: secNames.write(\".text\"), Type: uint32(elf.SHT_PROGBITS), Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), Addr: stextAddr, Off: sectionsStart + textSize, Size: textSize, Link: 0, Info: 0, Addralign: 2048, Entsize: 0},\n\t\t{Name: secNames.write(\".symtab\"), Type: uint32(elf.SHT_SYMTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize, Size: uint64(len(syms) * elf.Sym64Size), Link: 3 /*index of .strtab*/, Info: 0, Addralign: 8, Entsize: elf.Sym64Size},\n\t\t{Name: secNames.write(\".strtab\"), Type: uint32(elf.SHT_STRTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize + uint64(len(syms)*elf.Sym64Size), Size: uint64(len(symNames.data)), Link: 0, Info: 0, Addralign: 1, Entsize: 0},\n\t\t{Name: secNames.write(\".shstrtab\"), Type: uint32(elf.SHT_STRTAB), Flags: 0, Addr: 0, Off: sectionsStart + 2*textSize + uint64(len(syms)*elf.Sym64Size+len(symNames.data)), Size: uint64(len(secNames.data)), Link: 0, Info: 0, Addralign: 1, Entsize: 0},\n\t}\n\n\thdr := elf.Header64{\n\t\tIdent:     ident,\n\t\tType:      uint16(elf.ET_DYN),\n\t\tMachine:   uint16(elf.EM_AARCH64),\n\t\tVersion:   uint32(elf.EV_CURRENT),\n\t\tEntry:     textAddr,\n\t\tPhoff:     uint64(sizeHeader64),\n\t\tShoff:     uint64(sizeHeader64 + len(progs)*sizeProg64),\n\t\tFlags:     0,\n\t\tEhsize:    uint16(sizeHeader64),\n\t\tPhentsize: uint16(sizeProg64),\n\t\tPhnum:     uint16(len(progs)),\n\t\tShentsize: uint16(sizeSection64),\n\t\tShnum:     uint16(len(sections)),\n\t\tShstrndx:  4, // index of .shstrtab\n\t}\n\n\t// Serialize all headers and sections into a single binary stream.\n\tvar data bytes.Buffer\n\tfor i, b := range []interface{}{hdr, progs, sections, [textSize]byte{}, [textSize]byte{}, syms, symNames.data, secNames.data} {\n\t\terr := binary.Write(&data, binary.LittleEndian, b)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Write(%v) got err %v, want nil\", i, err)\n\t\t}\n\t}\n\n\t// ... and parse it as and ELF file.\n\tef, err := elf.NewFile(bytes.NewReader(data.Bytes()))\n\tif err != nil {\n\t\tt.Fatalf(\"elf.NewFile got err %v, want nil\", err)\n\t}\n\treturn ef\n}\n\nfunc TestELFKernelOffset(t *testing.T) {\n\trealELFOpen := elfOpen\n\tdefer func() {\n\t\telfOpen = realELFOpen\n\t}()\n\n\twantAddr := uint64(0xffff000010082000)\n\telfOpen = func(_ string) (*elf.File, error) {\n\t\treturn fakeELFFile(t), nil\n\t}\n\n\tfor _, tc := range []struct {\n\t\tname             string\n\t\trelocationSymbol string\n\t\tstart            uint64\n\t}{\n\t\t{\"text\", \"_text\", 0xffff000020080000},\n\t\t{\"stext\", \"_stext\", 0xffff000020081000},\n\t} {\n\n\t\tb := binrep{}\n\t\to, err := b.openELF(\"vmlinux\", tc.start, 0xffffffffffffffff, tc.start, tc.relocationSymbol)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%v: openELF got error %v, want nil\", tc.name, err)\n\t\t\tcontinue\n\t\t}\n\n\t\taddr, err := o.ObjAddr(0xffff000020082000)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%v: ObjAddr got err %v, want nil\", tc.name, err)\n\t\t\tcontinue\n\t\t}\n\t\tif addr != wantAddr {\n\t\t\tt.Errorf(\"%v: ObjAddr got %x, want %x\", tc.name, addr, wantAddr)\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "internal/binutils/disasm.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/ianlancetaylor/demangle\"\n)\n\nvar (\n\tnmOutputRE                = regexp.MustCompile(`^\\s*([[:xdigit:]]+)\\s+(.)\\s+(.*)`)\n\tobjdumpAsmOutputRE        = regexp.MustCompile(`^\\s*([[:xdigit:]]+):\\s+(.*)`)\n\tobjdumpOutputFileLine     = regexp.MustCompile(`^;?\\s?(.*):([0-9]+)`)\n\tobjdumpOutputFunction     = regexp.MustCompile(`^;?\\s?(\\S.*)\\(\\):`)\n\tobjdumpOutputFunctionLLVM = regexp.MustCompile(`^([[:xdigit:]]+)?\\s?(.*):`)\n)\n\nfunc findSymbols(syms []byte, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {\n\t// Collect all symbols from the nm output, grouping names mapped to\n\t// the same address into a single symbol.\n\n\t// The symbols to return.\n\tvar symbols []*plugin.Sym\n\n\t// The current group of symbol names, and the address they are all at.\n\tnames, start := []string{}, uint64(0)\n\n\tbuf := bytes.NewBuffer(syms)\n\n\tfor {\n\t\tsymAddr, name, err := nextSymbol(buf)\n\t\tif err == io.EOF {\n\t\t\t// Done. If there was an unfinished group, append it.\n\t\t\tif len(names) != 0 {\n\t\t\t\tif match := matchSymbol(names, start, symAddr-1, r, address); match != nil {\n\t\t\t\t\tsymbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// And return the symbols.\n\t\t\treturn symbols, nil\n\t\t}\n\n\t\tif err != nil {\n\t\t\t// There was some kind of serious error reading nm's output.\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// If this symbol is at the same address as the current group, add it to the group.\n\t\tif symAddr == start {\n\t\t\tnames = append(names, name)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Otherwise append the current group to the list of symbols.\n\t\tif match := matchSymbol(names, start, symAddr-1, r, address); match != nil {\n\t\t\tsymbols = append(symbols, &plugin.Sym{Name: match, File: file, Start: start, End: symAddr - 1})\n\t\t}\n\n\t\t// And start a new group.\n\t\tnames, start = []string{name}, symAddr\n\t}\n}\n\n// matchSymbol checks if a symbol is to be selected by checking its\n// name to the regexp and optionally its address. It returns the name(s)\n// to be used for the matched symbol, or nil if no match\nfunc matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address uint64) []string {\n\tif address != 0 && address >= start && address <= end {\n\t\treturn names\n\t}\n\tfor _, name := range names {\n\t\tif r == nil || r.MatchString(name) {\n\t\t\treturn []string{name}\n\t\t}\n\n\t\t// Match all possible demangled versions of the name.\n\t\tfor _, o := range [][]demangle.Option{\n\t\t\t{demangle.NoClones},\n\t\t\t{demangle.NoParams, demangle.NoEnclosingParams},\n\t\t\t{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams},\n\t\t} {\n\t\t\tif demangled, err := demangle.ToString(name, o...); err == nil && r.MatchString(demangled) {\n\t\t\t\treturn []string{demangled}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// disassemble parses the output of the objdump command and returns\n// the assembly instructions in a slice.\nfunc disassemble(asm []byte) ([]plugin.Inst, error) {\n\tbuf := bytes.NewBuffer(asm)\n\tfunction, file, line := \"\", \"\", 0\n\tvar assembly []plugin.Inst\n\tfor {\n\t\tinput, err := buf.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tif err != io.EOF {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif input == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tinput = strings.TrimSpace(input)\n\n\t\tif fields := objdumpAsmOutputRE.FindStringSubmatch(input); len(fields) == 3 {\n\t\t\tif address, err := strconv.ParseUint(fields[1], 16, 64); err == nil {\n\t\t\t\tassembly = append(assembly,\n\t\t\t\t\tplugin.Inst{\n\t\t\t\t\t\tAddr:     address,\n\t\t\t\t\t\tText:     fields[2],\n\t\t\t\t\t\tFunction: function,\n\t\t\t\t\t\tFile:     file,\n\t\t\t\t\t\tLine:     line,\n\t\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif fields := objdumpOutputFileLine.FindStringSubmatch(input); len(fields) == 3 {\n\t\t\tif l, err := strconv.ParseUint(fields[2], 10, 32); err == nil {\n\t\t\t\tfile, line = fields[1], int(l)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif fields := objdumpOutputFunction.FindStringSubmatch(input); len(fields) == 2 {\n\t\t\tfunction = fields[1]\n\t\t\tcontinue\n\t\t} else {\n\t\t\tif fields := objdumpOutputFunctionLLVM.FindStringSubmatch(input); len(fields) == 3 {\n\t\t\t\tfunction = fields[2]\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\t// Reset on unrecognized lines.\n\t\tfunction, file, line = \"\", \"\", 0\n\t}\n\n\treturn assembly, nil\n}\n\n// nextSymbol parses the nm output to find the next symbol listed.\n// Skips over any output it cannot recognize.\nfunc nextSymbol(buf *bytes.Buffer) (uint64, string, error) {\n\tfor {\n\t\tline, err := buf.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tif err != io.EOF || line == \"\" {\n\t\t\t\treturn 0, \"\", err\n\t\t\t}\n\t\t}\n\t\tline = strings.TrimSpace(line)\n\n\t\tif fields := nmOutputRE.FindStringSubmatch(line); len(fields) == 4 {\n\t\t\tif address, err := strconv.ParseUint(fields[1], 16, 64); err == nil {\n\t\t\t\treturn address, fields[3], nil\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/binutils/disasm_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage binutils\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\n// TestFindSymbols tests the FindSymbols routine using a hardcoded nm output.\nfunc TestFindSymbols(t *testing.T) {\n\ttype testcase struct {\n\t\tquery, syms string\n\t\twant        []plugin.Sym\n\t}\n\n\ttestsyms := `0000000000001000 t lineA001\n0000000000001000 t lineA002\n0000000000001000 t line1000\n0000000000002000 t line200A\n0000000000002000 t line2000\n0000000000002000 t line200B\n0000000000003000 t line3000\n0000000000003000 t _ZNK4DumbclEPKc\n0000000000003000 t lineB00C\n0000000000003000 t line300D\n0000000000004000 t _the_end\n\t`\n\ttestcases := []testcase{\n\t\t{\n\t\t\t\"line.*[AC]\",\n\t\t\ttestsyms,\n\t\t\t[]plugin.Sym{\n\t\t\t\t{Name: []string{\"lineA001\"}, File: \"object.o\", Start: 0x1000, End: 0x1FFF},\n\t\t\t\t{Name: []string{\"line200A\"}, File: \"object.o\", Start: 0x2000, End: 0x2FFF},\n\t\t\t\t{Name: []string{\"lineB00C\"}, File: \"object.o\", Start: 0x3000, End: 0x3FFF},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"Dumb::operator\",\n\t\t\ttestsyms,\n\t\t\t[]plugin.Sym{\n\t\t\t\t{Name: []string{\"Dumb::operator()(char const*) const\"}, File: \"object.o\", Start: 0x3000, End: 0x3FFF},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tsyms, err := findSymbols([]byte(tc.syms), \"object.o\", regexp.MustCompile(tc.query), 0)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%q: findSymbols: %v\", tc.query, err)\n\t\t}\n\t\tif err := checkSymbol(syms, tc.want); err != nil {\n\t\t\tt.Errorf(\"%q: %v\", tc.query, err)\n\t\t}\n\t}\n}\n\nfunc checkSymbol(got []*plugin.Sym, want []plugin.Sym) error {\n\tif len(got) != len(want) {\n\t\treturn fmt.Errorf(\"unexpected number of symbols %d (want %d)\", len(got), len(want))\n\t}\n\n\tfor i, g := range got {\n\t\tw := want[i]\n\t\tif len(g.Name) != len(w.Name) {\n\t\t\treturn fmt.Errorf(\"names, got %d, want %d\", len(g.Name), len(w.Name))\n\t\t}\n\t\tfor n := range g.Name {\n\t\t\tif g.Name[n] != w.Name[n] {\n\t\t\t\treturn fmt.Errorf(\"name %d, got %q, want %q\", n, g.Name[n], w.Name[n])\n\t\t\t}\n\t\t}\n\t\tif g.File != w.File {\n\t\t\treturn fmt.Errorf(\"filename, got %q, want %q\", g.File, w.File)\n\t\t}\n\t\tif g.Start != w.Start {\n\t\t\treturn fmt.Errorf(\"start address, got %#x, want %#x\", g.Start, w.Start)\n\t\t}\n\t\tif g.End != w.End {\n\t\t\treturn fmt.Errorf(\"end address, got %#x, want %#x\", g.End, w.End)\n\t\t}\n\t}\n\treturn nil\n}\n\n// TestFunctionAssembly tests the FunctionAssembly routine by using a\n// fake objdump script.\nfunc TestFunctionAssembly(t *testing.T) {\n\ttype testcase struct {\n\t\ts    plugin.Sym\n\t\tasm  string\n\t\twant []plugin.Inst\n\t}\n\ttestcases := []testcase{\n\t\t{\n\t\t\tplugin.Sym{Name: []string{\"symbol1\"}, Start: 0x1000, End: 0x1FFF},\n\t\t\t\"  1000: instruction one\\n  1001: instruction two\\n  1002: instruction three\\n  1003: instruction four\",\n\t\t\t[]plugin.Inst{\n\t\t\t\t{Addr: 0x1000, Text: \"instruction one\"},\n\t\t\t\t{Addr: 0x1001, Text: \"instruction two\"},\n\t\t\t\t{Addr: 0x1002, Text: \"instruction three\"},\n\t\t\t\t{Addr: 0x1003, Text: \"instruction four\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tplugin.Sym{Name: []string{\"symbol2\"}, Start: 0x2000, End: 0x2FFF},\n\t\t\t\"  2000: instruction one\\n  2001: instruction two\",\n\t\t\t[]plugin.Inst{\n\t\t\t\t{Addr: 0x2000, Text: \"instruction one\"},\n\t\t\t\t{Addr: 0x2001, Text: \"instruction two\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tplugin.Sym{Name: []string{\"_main\"}, Start: 0x30000, End: 0x3FFF},\n\t\t\t\"_main:\\n; /tmp/hello.c:3\\n30001:\tpush   %rbp\",\n\t\t\t[]plugin.Inst{\n\t\t\t\t{Addr: 0x30001, Text: \"push   %rbp\", Function: \"_main\", File: \"/tmp/hello.c\", Line: 3},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tplugin.Sym{Name: []string{\"main\"}, Start: 0x4000, End: 0x4FFF},\n\t\t\t\"000000000040052d <main>:\\nmain():\\n/tmp/hello.c:3\\n40001:\tpush   %rbp\",\n\t\t\t[]plugin.Inst{\n\t\t\t\t{Addr: 0x40001, Text: \"push   %rbp\", Function: \"main\", File: \"/tmp/hello.c\", Line: 3},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tinsts, err := disassemble([]byte(tc.asm))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"FunctionAssembly: %v\", err)\n\t\t}\n\n\t\tif len(insts) != len(tc.want) {\n\t\t\tt.Errorf(\"Unexpected number of assembly instructions %d (want %d)\\n\", len(insts), len(tc.want))\n\t\t}\n\t\tfor i := range insts {\n\t\t\tif insts[i] != tc.want[i] {\n\t\t\t\tt.Errorf(\"Expected symbol %v, got %v\\n\", tc.want[i], insts[i])\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/binutils/testdata/build_binaries.go",
    "content": "// Copyright 2019 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This is a script that generates the test executables for MacOS and Linux\n// in this directory. It should be needed very rarely to run this script.\n// It is mostly provided as a future reference on how the original binary\n// set was created.\n\n// When a new executable is generated, hardcoded addresses in the\n// functions TestObjFile, TestMachoFiles, TestPEFile in binutils_test.go must be updated.\npackage main\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n)\n\nfunc main() {\n\twd, err := os.Getwd()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tswitch runtime.GOOS {\n\tcase \"linux\":\n\t\tif err := removeGlob(\"exe_linux_64*\"); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tout, err := exec.Command(\"cc\", \"-g\", \"-ffile-prefix-map=\"+wd+\"=\"+\"/tmp\", \"-o\", \"exe_linux_64\", \"hello.c\").CombinedOutput()\n\t\tlog.Println(string(out))\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\tcase \"darwin\":\n\t\tif err := removeGlob(\"exe_mac_64*\", \"lib_mac_64\"); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tout, err := exec.Command(\"clang\", \"-g\", \"-ffile-prefix-map=\"+wd+\"=\"+\"/tmp\", \"-o\", \"exe_mac_64\", \"hello.c\").CombinedOutput()\n\t\tlog.Println(string(out))\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tout, err = exec.Command(\"clang\", \"-g\", \"-ffile-prefix-map=\"+wd+\"=\"+\"/tmp\", \"-o\", \"lib_mac_64\", \"-dynamiclib\", \"lib.c\").CombinedOutput()\n\t\tlog.Println(string(out))\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\tcase \"windows\":\n\t\t// Many gcc environments may create binaries that trigger false-positives\n\t\t// in antiviruses. MSYS2 with gcc 10.2.0 is a working environment for\n\t\t// compiling. To setup the environment follow the guide at\n\t\t// https://www.msys2.org/ and install gcc with `pacman -S gcc`.\n\t\tout, err := exec.Command(\"gcc\", \"-g\", \"-ffile-prefix-map=\"+wd+\"=\", \"-o\", \"exe_windows_64.exe\", \"hello.c\").CombinedOutput()\n\t\tlog.Println(string(out))\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tlog.Println(\"Please verify that exe_windows_64.exe does not trigger any antivirus on `virustotal.com`.\")\n\tdefault:\n\t\tlog.Fatalf(\"Unsupported OS %q\", runtime.GOOS)\n\t}\n}\n\nfunc removeGlob(globs ...string) error {\n\tfor _, glob := range globs {\n\t\tmatches, err := filepath.Glob(glob)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, p := range matches {\n\t\t\tos.Remove(p)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/binutils/testdata/exe_mac_64.dSYM/Contents/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>English</string>\n    <key>CFBundleIdentifier</key>\n    <string>com.apple.xcode.dsym.exe_mac_64</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundlePackageType</key>\n    <string>dSYM</string>\n    <key>CFBundleSignature</key>\n    <string>????</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0</string>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n  </dict>\n</plist>\n"
  },
  {
    "path": "internal/binutils/testdata/fake-llvm-symbolizer",
    "content": "#!/bin/sh\n#\n# Copyright 2014 Google Inc. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# Fake llvm-symbolizer to use in tests\n\nset -f\nIFS=\" \"\n\nwhile read line; do\n  # line has form:\n  #    filename 0xaddr\n  # Emit dummy output that matches llvm-symbolizer JSON output format.\n  set -- ${line}\n  kind=$1\n  fname=$2\n  addr=$3\n  case ${kind} in\n  CODE)\n    echo \"{\\\"Address\\\":\\\"${addr}\\\",\\\"ModuleName\\\":\\\"${fname}\\\",\\\"Symbol\\\":[{\\\"Column\\\":0,\\\"FileName\\\":\\\"${fname}.h\\\",\\\"FunctionName\\\":\\\"Inlined_${addr}\\\",\\\"Line\\\":0,\\\"StartLine\\\":0},{\\\"Column\\\":1,\\\"FileName\\\":\\\"${fname}.c\\\",\\\"FunctionName\\\":\\\"Func_${addr}\\\",\\\"Line\\\":2,\\\"StartLine\\\":2}]}\"\n    ;;\n  DATA)\n    echo \"{\\\"Address\\\":\\\"${addr}\\\",\\\"ModuleName\\\":\\\"${fname}\\\",\\\"Data\\\":{\\\"Name\\\":\\\"${fname}_${addr}\\\",\\\"Size\\\":\\\"0x8\\\",\\\"Start\\\":\\\"${addr}\\\"}}\"\n    ;;\n  *) exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "internal/binutils/testdata/hello.c",
    "content": "#include <stdio.h>\n\nint main() {\n  printf(\"Hello, world!\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "internal/binutils/testdata/lib.c",
    "content": "int foo() {\n  return 1;\n}\n\nint bar() {\n  return 2;\n}\n"
  },
  {
    "path": "internal/binutils/testdata/lib_mac_64.dSYM/Contents/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>CFBundleDevelopmentRegion</key>\n    <string>English</string>\n    <key>CFBundleIdentifier</key>\n    <string>com.apple.xcode.dsym.lib_mac_64</string>\n    <key>CFBundleInfoDictionaryVersion</key>\n    <string>6.0</string>\n    <key>CFBundlePackageType</key>\n    <string>dSYM</string>\n    <key>CFBundleSignature</key>\n    <string>????</string>\n    <key>CFBundleShortVersionString</key>\n    <string>1.0</string>\n    <key>CFBundleVersion</key>\n    <string>1</string>\n  </dict>\n</plist>\n"
  },
  {
    "path": "internal/binutils/testdata/malformed_elf",
    "content": "ELF"
  },
  {
    "path": "internal/binutils/testdata/malformed_macho",
    "content": ""
  },
  {
    "path": "internal/driver/cli.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\ntype source struct {\n\tSources   []string\n\tExecName  string\n\tBuildID   string\n\tBase      []string\n\tDiffBase  bool\n\tNormalize bool\n\n\tSeconds            int\n\tTimeout            int\n\tSymbolize          string\n\tHTTPHostport       string\n\tHTTPDisableBrowser bool\n\tComment            string\n\tAllFrames          bool\n}\n\n// parseFlags parses the command lines through the specified flags package\n// and returns the source of the profile and optionally the command\n// for the kind of report to generate (nil for interactive use).\nfunc parseFlags(o *plugin.Options) (*source, []string, error) {\n\tflag := o.Flagset\n\t// Comparisons.\n\tflagDiffBase := flag.StringList(\"diff_base\", \"\", \"Source of base profile for comparison\")\n\tflagBase := flag.StringList(\"base\", \"\", \"Source of base profile for profile subtraction\")\n\t// Source options.\n\tflagSymbolize := flag.String(\"symbolize\", \"\", \"Options for profile symbolization\")\n\tflagBuildID := flag.String(\"buildid\", \"\", \"Override build id for first mapping\")\n\tflagTimeout := flag.Int(\"timeout\", -1, \"Timeout in seconds for fetching a profile\")\n\tflagAddComment := flag.String(\"add_comment\", \"\", \"Free-form annotation to add to the profile\")\n\tflagAllFrames := flag.Bool(\"all_frames\", false, \"Ignore drop_frames and keep_frames regexps\")\n\t// CPU profile options\n\tflagSeconds := flag.Int(\"seconds\", -1, \"Length of time for dynamic profiles\")\n\t// Heap profile options\n\tflagInUseSpace := flag.Bool(\"inuse_space\", false, \"Display in-use memory size\")\n\tflagInUseObjects := flag.Bool(\"inuse_objects\", false, \"Display in-use object counts\")\n\tflagAllocSpace := flag.Bool(\"alloc_space\", false, \"Display allocated memory size\")\n\tflagAllocObjects := flag.Bool(\"alloc_objects\", false, \"Display allocated object counts\")\n\t// Contention profile options\n\tflagTotalDelay := flag.Bool(\"total_delay\", false, \"Display total delay at each region\")\n\tflagContentions := flag.Bool(\"contentions\", false, \"Display number of delays at each region\")\n\tflagMeanDelay := flag.Bool(\"mean_delay\", false, \"Display mean delay at each region\")\n\tflagTools := flag.String(\"tools\", os.Getenv(\"PPROF_TOOLS\"), \"Path for object tool pathnames\")\n\n\tflagHTTP := flag.String(\"http\", \"\", \"Present interactive web UI at the specified http host:port\")\n\tflagNoBrowser := flag.Bool(\"no_browser\", false, \"Skip opening a browser for the interactive web UI\")\n\n\t// Flags that set configuration properties.\n\tcfg := currentConfig()\n\tconfigFlagSetter := installConfigFlags(flag, &cfg)\n\n\tflagCommands := make(map[string]*bool)\n\tflagParamCommands := make(map[string]*string)\n\tfor name, cmd := range pprofCommands {\n\t\tif cmd.hasParam {\n\t\t\tflagParamCommands[name] = flag.String(name, \"\", \"Generate a report in \"+name+\" format, matching regexp\")\n\t\t} else {\n\t\t\tflagCommands[name] = flag.Bool(name, false, \"Generate a report in \"+name+\" format\")\n\t\t}\n\t}\n\n\targs := flag.Parse(func() {\n\t\to.UI.Print(usageMsgHdr +\n\t\t\tusage(true) +\n\t\t\tusageMsgSrc +\n\t\t\tflag.ExtraUsage() +\n\t\t\tusageMsgVars)\n\t})\n\tif len(args) == 0 {\n\t\treturn nil, nil, errors.New(\"no profile source specified\")\n\t}\n\n\tvar execName string\n\t// Recognize first argument as an executable or buildid override.\n\tif len(args) > 1 {\n\t\targ0 := args[0]\n\t\tif file, err := o.Obj.Open(arg0, 0, ^uint64(0), 0, \"\"); err == nil {\n\t\t\tfile.Close()\n\t\t\texecName = arg0\n\t\t\targs = args[1:]\n\t\t}\n\t}\n\n\t// Apply any specified flags to cfg.\n\tif err := configFlagSetter(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcmd, err := outputFormat(flagCommands, flagParamCommands)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif cmd != nil && *flagHTTP != \"\" {\n\t\treturn nil, nil, errors.New(\"-http is not compatible with an output format on the command line\")\n\t}\n\n\tif *flagNoBrowser && *flagHTTP == \"\" {\n\t\treturn nil, nil, errors.New(\"-no_browser only makes sense with -http\")\n\t}\n\n\tsi := cfg.SampleIndex\n\tsi = sampleIndex(flagTotalDelay, si, \"delay\", \"-total_delay\", o.UI)\n\tsi = sampleIndex(flagMeanDelay, si, \"delay\", \"-mean_delay\", o.UI)\n\tsi = sampleIndex(flagContentions, si, \"contentions\", \"-contentions\", o.UI)\n\tsi = sampleIndex(flagInUseSpace, si, \"inuse_space\", \"-inuse_space\", o.UI)\n\tsi = sampleIndex(flagInUseObjects, si, \"inuse_objects\", \"-inuse_objects\", o.UI)\n\tsi = sampleIndex(flagAllocSpace, si, \"alloc_space\", \"-alloc_space\", o.UI)\n\tsi = sampleIndex(flagAllocObjects, si, \"alloc_objects\", \"-alloc_objects\", o.UI)\n\tcfg.SampleIndex = si\n\n\tif *flagMeanDelay {\n\t\tcfg.Mean = true\n\t}\n\n\tsource := &source{\n\t\tSources:            args,\n\t\tExecName:           execName,\n\t\tBuildID:            *flagBuildID,\n\t\tSeconds:            *flagSeconds,\n\t\tTimeout:            *flagTimeout,\n\t\tSymbolize:          *flagSymbolize,\n\t\tHTTPHostport:       *flagHTTP,\n\t\tHTTPDisableBrowser: *flagNoBrowser,\n\t\tComment:            *flagAddComment,\n\t\tAllFrames:          *flagAllFrames,\n\t}\n\n\tif err := source.addBaseProfiles(*flagBase, *flagDiffBase); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tnormalize := cfg.Normalize\n\tif normalize && len(source.Base) == 0 {\n\t\treturn nil, nil, errors.New(\"must have base profile to normalize by\")\n\t}\n\tsource.Normalize = normalize\n\n\tif bu, ok := o.Obj.(*binutils.Binutils); ok {\n\t\tbu.SetTools(*flagTools)\n\t}\n\n\tsetCurrentConfig(cfg)\n\treturn source, cmd, nil\n}\n\n// addBaseProfiles adds the list of base profiles or diff base profiles to\n// the source. This function will return an error if both base and diff base\n// profiles are specified.\nfunc (source *source) addBaseProfiles(flagBase, flagDiffBase []*string) error {\n\tbase, diffBase := dropEmpty(flagBase), dropEmpty(flagDiffBase)\n\tif len(base) > 0 && len(diffBase) > 0 {\n\t\treturn errors.New(\"-base and -diff_base flags cannot both be specified\")\n\t}\n\n\tsource.Base = base\n\tif len(diffBase) > 0 {\n\t\tsource.Base, source.DiffBase = diffBase, true\n\t}\n\treturn nil\n}\n\n// dropEmpty list takes a slice of string pointers, and outputs a slice of\n// non-empty strings associated with the flag.\nfunc dropEmpty(list []*string) []string {\n\tvar l []string\n\tfor _, s := range list {\n\t\tif *s != \"\" {\n\t\t\tl = append(l, *s)\n\t\t}\n\t}\n\treturn l\n}\n\n// installConfigFlags creates command line flags for configuration\n// fields and returns a function which can be called after flags have\n// been parsed to copy any flags specified on the command line to\n// *cfg.\nfunc installConfigFlags(flag plugin.FlagSet, cfg *config) func() error {\n\t// List of functions for setting the different parts of a config.\n\tvar setters []func()\n\tvar err error // Holds any errors encountered while running setters.\n\n\tfor _, field := range configFields {\n\t\tn := field.name\n\t\thelp := configHelp[n]\n\t\tvar setter func()\n\t\tswitch ptr := cfg.fieldPtr(field).(type) {\n\t\tcase *bool:\n\t\t\tf := flag.Bool(n, *ptr, help)\n\t\t\tsetter = func() { *ptr = *f }\n\t\tcase *int:\n\t\t\tf := flag.Int(n, *ptr, help)\n\t\t\tsetter = func() { *ptr = *f }\n\t\tcase *float64:\n\t\t\tf := flag.Float64(n, *ptr, help)\n\t\t\tsetter = func() { *ptr = *f }\n\t\tcase *string:\n\t\t\tif len(field.choices) == 0 {\n\t\t\t\tf := flag.String(n, *ptr, help)\n\t\t\t\tsetter = func() { *ptr = *f }\n\t\t\t} else {\n\t\t\t\t// Make a separate flag per possible choice.\n\t\t\t\t// Set all flags to initially false so we can\n\t\t\t\t// identify conflicts.\n\t\t\t\tbools := make(map[string]*bool)\n\t\t\t\tfor _, choice := range field.choices {\n\t\t\t\t\tbools[choice] = flag.Bool(choice, false, configHelp[choice])\n\t\t\t\t}\n\t\t\t\tsetter = func() {\n\t\t\t\t\tvar set []string\n\t\t\t\t\tfor k, v := range bools {\n\t\t\t\t\t\tif *v {\n\t\t\t\t\t\t\tset = append(set, k)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tswitch len(set) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\t// Leave as default value.\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\t*ptr = set[0]\n\t\t\t\t\tdefault:\n\t\t\t\t\t\terr = fmt.Errorf(\"conflicting options set: %v\", set)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsetters = append(setters, setter)\n\t}\n\n\treturn func() error {\n\t\t// Apply the setter for every flag.\n\t\tfor _, setter := range setters {\n\t\t\tsetter()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc sampleIndex(flag *bool, si string, sampleType, option string, ui plugin.UI) string {\n\tif *flag {\n\t\tif si == \"\" {\n\t\t\treturn sampleType\n\t\t}\n\t\tui.PrintErr(\"Multiple value selections, ignoring \", option)\n\t}\n\treturn si\n}\n\nfunc outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string, err error) {\n\tfor n, b := range bcmd {\n\t\tif *b {\n\t\t\tif cmd != nil {\n\t\t\t\treturn nil, errors.New(\"must set at most one output format\")\n\t\t\t}\n\t\t\tcmd = []string{n}\n\t\t}\n\t}\n\tfor n, s := range acmd {\n\t\tif *s != \"\" {\n\t\t\tif cmd != nil {\n\t\t\t\treturn nil, errors.New(\"must set at most one output format\")\n\t\t\t}\n\t\t\tcmd = []string{n, *s}\n\t\t}\n\t}\n\treturn cmd, nil\n}\n\nvar usageMsgHdr = `usage:\n\nProduce output in the specified format.\n\n   pprof <format> [options] [binary] <source> ...\n\nOmit the format to get an interactive shell whose commands can be used\nto generate various views of a profile\n\n   pprof [options] [binary] <source> ...\n\nOmit the format and provide the \"-http\" flag to get an interactive web\ninterface at the specified host:port that can be used to navigate through\nvarious views of a profile.\n\n   pprof -http [host]:[port] [options] [binary] <source> ...\n\nDetails:\n`\n\nvar usageMsgSrc = \"\\n\\n\" +\n\t\"  Source options:\\n\" +\n\t\"    -seconds              Duration for time-based profile collection\\n\" +\n\t\"    -timeout              Timeout in seconds for profile collection\\n\" +\n\t\"    -buildid              Override build id for main binary\\n\" +\n\t\"    -add_comment          Free-form annotation to add to the profile\\n\" +\n\t\"                          Displayed on some reports or with pprof -comments\\n\" +\n\t\"    -diff_base source     Source of base profile for comparison\\n\" +\n\t\"    -base source          Source of base profile for profile subtraction\\n\" +\n\t\"    profile.pb.gz         Profile in compressed protobuf format\\n\" +\n\t\"    legacy_profile        Profile in legacy pprof format\\n\" +\n\t\"    http://host/profile   URL for profile handler to retrieve\\n\" +\n\t\"    -symbolize=           Controls source of symbol information\\n\" +\n\t\"      none                  Do not attempt symbolization\\n\" +\n\t\"      local                 Examine only local binaries\\n\" +\n\t\"      fastlocal             Only get function names from local binaries\\n\" +\n\t\"      remote                Do not examine local binaries\\n\" +\n\t\"      force                 Force re-symbolization\\n\" +\n\t\"    Binary                  Local path or build id of binary for symbolization\\n\"\n\nvar usageMsgVars = \"\\n\\n\" +\n\t\"  Misc options:\\n\" +\n\t\"   -http              Provide web interface at host:port.\\n\" +\n\t\"                      Host is optional and 'localhost' by default.\\n\" +\n\t\"                      Port is optional and a randomly available port by default.\\n\" +\n\t\"   -no_browser        Skip opening a browser for the interactive web UI.\\n\" +\n\t\"   -tools             Search path for object tools\\n\" +\n\t\"   -all_frames        Ignore drop_frames and keep_frames regexps in the profile\\n\" +\n\t\"                      Rarely needed, mainly used for debugging pprof itself\\n\" +\n\t\"\\n\" +\n\t\"  Legacy convenience options:\\n\" +\n\t\"   -inuse_space           Same as -sample_index=inuse_space\\n\" +\n\t\"   -inuse_objects         Same as -sample_index=inuse_objects\\n\" +\n\t\"   -alloc_space           Same as -sample_index=alloc_space\\n\" +\n\t\"   -alloc_objects         Same as -sample_index=alloc_objects\\n\" +\n\t\"   -total_delay           Same as -sample_index=delay\\n\" +\n\t\"   -contentions           Same as -sample_index=contentions\\n\" +\n\t\"   -mean_delay            Same as -mean -sample_index=delay\\n\" +\n\t\"\\n\" +\n\t\"  Environment Variables:\\n\" +\n\t\"   PPROF_TMPDIR       Location for saved profiles (default $HOME/pprof)\\n\" +\n\t\"   PPROF_TOOLS        Search path for object-level tools\\n\" +\n\t\"   PPROF_BINARY_PATH  Search path for local binary files\\n\" +\n\t\"                      default: $HOME/pprof/binaries\\n\" +\n\t\"                      searches $buildid/$name, $buildid/*, $path/$buildid,\\n\" +\n\t\"                      ${buildid:0:2}/${buildid:2}.debug, $name, $path,\\n\" +\n\t\"                      ${name}.debug, $dir/.debug/${name}.debug,\\n\" +\n\t\"                      usr/lib/debug/$dir/${name}.debug\\n\" +\n\t\"   * On Windows, %USERPROFILE% is used instead of $HOME\"\n"
  },
  {
    "path": "internal/driver/commands.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/report\"\n)\n\n// commands describes the commands accepted by pprof.\ntype commands map[string]*command\n\n// command describes the actions for a pprof command. Includes a\n// function for command-line completion, the report format to use\n// during report generation, any postprocessing functions, and whether\n// the command expects a regexp parameter (typically a function name).\ntype command struct {\n\tformat      int           // report format to generate\n\tpostProcess PostProcessor // postprocessing to run on report\n\tvisualizer  PostProcessor // display output using some callback\n\thasParam    bool          // collect a parameter from the CLI\n\tdescription string        // single-line description text saying what the command does\n\tusage       string        // multi-line help text saying how the command is used\n}\n\n// help returns a help string for a command.\nfunc (c *command) help(name string) string {\n\tmessage := c.description + \"\\n\"\n\tif c.usage != \"\" {\n\t\tmessage += \"  Usage:\\n\"\n\t\tlines := strings.Split(c.usage, \"\\n\")\n\t\tfor _, line := range lines {\n\t\t\tmessage += fmt.Sprintf(\"    %s\\n\", line)\n\t\t}\n\t}\n\treturn message + \"\\n\"\n}\n\n// AddCommand adds an additional command to the set of commands\n// accepted by pprof. This enables extensions to add new commands for\n// specialized visualization formats. If the command specified already\n// exists, it is overwritten.\nfunc AddCommand(cmd string, format int, post PostProcessor, desc, usage string) {\n\tpprofCommands[cmd] = &command{format, post, nil, false, desc, usage}\n}\n\n// SetVariableDefault sets the default value for a pprof\n// variable. This enables extensions to set their own defaults.\nfunc SetVariableDefault(variable, value string) {\n\tconfigure(variable, value)\n}\n\n// PostProcessor is a function that applies post-processing to the report output\ntype PostProcessor func(input io.Reader, output io.Writer, ui plugin.UI) error\n\n// interactiveMode is true if pprof is running on interactive mode, reading\n// commands from its shell.\nvar interactiveMode = false\n\n// pprofCommands are the report generation commands recognized by pprof.\nvar pprofCommands = commands{\n\t// Commands that require no post-processing.\n\t\"comments\": {report.Comments, nil, nil, false, \"Output all profile comments\", \"\"},\n\t\"disasm\":   {report.Dis, nil, nil, true, \"Output assembly listings annotated with samples\", listHelp(\"disasm\", true)},\n\t\"dot\":      {report.Dot, nil, nil, false, \"Outputs a graph in DOT format\", reportHelp(\"dot\", false, true)},\n\t\"list\":     {report.List, nil, nil, true, \"Output annotated source for functions matching regexp\", listHelp(\"list\", false)},\n\t\"peek\":     {report.Tree, nil, nil, true, \"Output callers/callees of functions matching regexp\", \"peek func_regex\\nDisplay callers and callees of functions matching func_regex.\"},\n\t\"raw\":      {report.Raw, nil, nil, false, \"Outputs a text representation of the raw profile\", \"\"},\n\t\"tags\":     {report.Tags, nil, 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.\"},\n\t\"text\":     {report.Text, nil, nil, false, \"Outputs top entries in text form\", reportHelp(\"text\", true, true)},\n\t\"top\":      {report.Text, nil, nil, false, \"Outputs top entries in text form\", reportHelp(\"top\", true, true)},\n\t\"traces\":   {report.Traces, nil, nil, false, \"Outputs all profile samples in text form\", \"\"},\n\t\"tree\":     {report.Tree, nil, nil, false, \"Outputs a text rendering of call graph\", reportHelp(\"tree\", true, true)},\n\n\t// Save binary formats to a file\n\t\"callgrind\": {report.Callgrind, nil, awayFromTTY(\"callgraph.out\"), false, \"Outputs a graph in callgrind format\", reportHelp(\"callgrind\", false, true)},\n\t\"proto\":     {report.Proto, nil, awayFromTTY(\"pb.gz\"), false, \"Outputs the profile in compressed protobuf format\", \"\"},\n\t\"topproto\":  {report.TopProto, nil, awayFromTTY(\"pb.gz\"), false, \"Outputs top entries in compressed protobuf format\", \"\"},\n\n\t// Generate report in DOT format and postprocess with dot\n\t\"gif\": {report.Dot, invokeDot(\"gif\"), awayFromTTY(\"gif\"), false, \"Outputs a graph image in GIF format\", reportHelp(\"gif\", false, true)},\n\t\"pdf\": {report.Dot, invokeDot(\"pdf\"), awayFromTTY(\"pdf\"), false, \"Outputs a graph in PDF format\", reportHelp(\"pdf\", false, true)},\n\t\"png\": {report.Dot, invokeDot(\"png\"), awayFromTTY(\"png\"), false, \"Outputs a graph image in PNG format\", reportHelp(\"png\", false, true)},\n\t\"ps\":  {report.Dot, invokeDot(\"ps\"), awayFromTTY(\"ps\"), false, \"Outputs a graph in PS format\", reportHelp(\"ps\", false, true)},\n\n\t// Save SVG output into a file\n\t\"svg\": {report.Dot, massageDotSVG(), awayFromTTY(\"svg\"), false, \"Outputs a graph in SVG format\", reportHelp(\"svg\", false, true)},\n\n\t// Visualize postprocessed dot output\n\t\"eog\":    {report.Dot, invokeDot(\"svg\"), invokeVisualizer(\"svg\", []string{\"eog\"}), false, \"Visualize graph through eog\", reportHelp(\"eog\", false, false)},\n\t\"evince\": {report.Dot, invokeDot(\"pdf\"), invokeVisualizer(\"pdf\", []string{\"evince\"}), false, \"Visualize graph through evince\", reportHelp(\"evince\", false, false)},\n\t\"gv\":     {report.Dot, invokeDot(\"ps\"), invokeVisualizer(\"ps\", []string{\"gv --noantialias\"}), false, \"Visualize graph through gv\", reportHelp(\"gv\", false, false)},\n\t\"web\":    {report.Dot, massageDotSVG(), invokeVisualizer(\"svg\", browsers()), false, \"Visualize graph through web browser\", reportHelp(\"web\", false, false)},\n\n\t// Visualize callgrind output\n\t\"kcachegrind\": {report.Callgrind, nil, invokeVisualizer(\"grind\", kcachegrind), false, \"Visualize report in KCachegrind\", reportHelp(\"kcachegrind\", false, false)},\n\n\t// Visualize HTML directly generated by report.\n\t\"weblist\": {report.WebList, nil, invokeVisualizer(\"html\", browsers()), true, \"Display annotated source in a web browser\", listHelp(\"weblist\", false)},\n}\n\n// configHelp contains help text per configuration parameter.\nvar configHelp = map[string]string{\n\t// Filename for file-based output formats, stdout by default.\n\t\"output\": helpText(\"Output filename for file-based outputs\"),\n\n\t// Comparisons.\n\t\"drop_negative\": helpText(\n\t\t\"Ignore negative differences\",\n\t\t\"Do not show any locations with values <0.\"),\n\n\t// Graph handling options.\n\t\"call_tree\": helpText(\n\t\t\"Create a context-sensitive call tree\",\n\t\t\"Treat locations reached through different paths as separate.\"),\n\n\t// Display options.\n\t\"relative_percentages\": helpText(\n\t\t\"Show percentages relative to focused subgraph\",\n\t\t\"If unset, percentages are relative to full graph before focusing\",\n\t\t\"to facilitate comparison with original graph.\"),\n\t\"unit\": helpText(\n\t\t\"Measurement units to display\",\n\t\t\"Scale the sample values to this unit.\",\n\t\t\"For time-based profiles, use seconds, milliseconds, nanoseconds, etc.\",\n\t\t\"For memory profiles, use megabytes, kilobytes, bytes, etc.\",\n\t\t\"Using auto will scale each value independently to the most natural unit.\"),\n\t\"compact_labels\": \"Show minimal headers\",\n\t\"source_path\":    \"Search path for source files\",\n\t\"trim_path\":      \"Path to trim from source paths before search\",\n\t\"intel_syntax\": helpText(\n\t\t\"Show assembly in Intel syntax\",\n\t\t\"Only applicable to commands `disasm` and `weblist`\"),\n\n\t// Filtering options\n\t\"nodecount\": helpText(\n\t\t\"Max number of nodes to show\",\n\t\t\"Uses heuristics to limit the number of locations to be displayed.\",\n\t\t\"On graphs, dotted edges represent paths through nodes that have been removed.\"),\n\t\"nodefraction\": \"Hide nodes below <f>*total\",\n\t\"edgefraction\": \"Hide edges below <f>*total\",\n\t\"trim\": helpText(\n\t\t\"Honor nodefraction/edgefraction/nodecount defaults\",\n\t\t\"Set to false to get the full profile, without any trimming.\"),\n\t\"focus\": helpText(\n\t\t\"Restricts to samples going through a node matching regexp\",\n\t\t\"Discard samples that do not include a node matching this regexp.\",\n\t\t\"Matching includes the function name, filename or object name.\"),\n\t\"ignore\": helpText(\n\t\t\"Skips paths going through any nodes matching regexp\",\n\t\t\"If set, discard samples that include a node matching this regexp.\",\n\t\t\"Matching includes the function name, filename or object name.\"),\n\t\"prune_from\": helpText(\n\t\t\"Drops any functions below the matched frame.\",\n\t\t\"If set, any frames matching the specified regexp and any frames\",\n\t\t\"below it will be dropped from each sample.\"),\n\t\"hide\": helpText(\n\t\t\"Skips nodes matching regexp\",\n\t\t\"Discard nodes that match this location.\",\n\t\t\"Other nodes from samples that include this location will be shown.\",\n\t\t\"Matching includes the function name, filename or object name.\"),\n\t\"show\": helpText(\n\t\t\"Only show nodes matching regexp\",\n\t\t\"If set, only show nodes that match this location.\",\n\t\t\"Matching includes the function name, filename or object name.\"),\n\t\"show_from\": helpText(\n\t\t\"Drops functions above the highest matched frame.\",\n\t\t\"If set, all frames above the highest match are dropped from every sample.\",\n\t\t\"Matching includes the function name, filename or object name.\"),\n\t\"tagroot\": helpText(\n\t\t\"Adds pseudo stack frames for labels key/value pairs at the callstack root.\",\n\t\t\"A comma-separated list of label keys.\",\n\t\t\"The first key creates frames at the new root.\"),\n\t\"tagleaf\": helpText(\n\t\t\"Adds pseudo stack frames for labels key/value pairs at the callstack leaf.\",\n\t\t\"A comma-separated list of label keys.\",\n\t\t\"The last key creates frames at the new leaf.\"),\n\t\"tagfocus\": helpText(\n\t\t\"Restricts to samples with tags in range or matched by regexp\",\n\t\t\"Use name=value syntax to limit the matching to a specific tag.\",\n\t\t\"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:\",\n\t\t\"String tag filter examples: foo, foo.*bar, mytag=foo.*bar\"),\n\t\"tagignore\": helpText(\n\t\t\"Discard samples with tags in range or matched by regexp\",\n\t\t\"Use name=value syntax to limit the matching to a specific tag.\",\n\t\t\"Numeric tag filter examples: 1kb, 1kb:10kb, memory=32mb:\",\n\t\t\"String tag filter examples: foo, foo.*bar, mytag=foo.*bar\"),\n\t\"tagshow\": helpText(\n\t\t\"Only consider tags matching this regexp\",\n\t\t\"Discard tags that do not match this regexp\"),\n\t\"taghide\": helpText(\n\t\t\"Skip tags matching this regexp\",\n\t\t\"Discard tags that match this regexp\"),\n\t// Heap profile options\n\t\"divide_by\": helpText(\n\t\t\"Ratio to divide all samples before visualization\",\n\t\t\"Divide all samples values by a constant, eg the number of processors or jobs.\"),\n\t\"mean\": helpText(\n\t\t\"Average sample value over first value (count)\",\n\t\t\"For memory profiles, report average memory per allocation.\",\n\t\t\"For time-based profiles, report average time per event.\"),\n\t\"sample_index\": helpText(\n\t\t\"Sample value to report (0-based index or name)\",\n\t\t\"Profiles contain multiple values per sample.\",\n\t\t\"Use sample_index=i to select the ith value (starting at 0).\"),\n\t\"normalize\": helpText(\n\t\t\"Scales profile based on the base profile.\"),\n\n\t// Data sorting criteria\n\t\"flat\": helpText(\"Sort entries based on own weight\"),\n\t\"cum\":  helpText(\"Sort entries based on cumulative weight\"),\n\n\t// Output granularity\n\t\"functions\": helpText(\n\t\t\"Aggregate at the function level.\",\n\t\t\"Ignores the filename where the function was defined.\"),\n\t\"filefunctions\": helpText(\n\t\t\"Aggregate at the function level.\",\n\t\t\"Takes into account the filename where the function was defined.\"),\n\t\"files\": \"Aggregate at the file level.\",\n\t\"lines\": \"Aggregate at the source code line level.\",\n\t\"addresses\": helpText(\n\t\t\"Aggregate at the address level.\",\n\t\t\"Includes functions' addresses in the output.\"),\n\t\"noinlines\": helpText(\n\t\t\"Ignore inlines.\",\n\t\t\"Attributes inlined functions to their first out-of-line caller.\"),\n\t\"showcolumns\": helpText(\n\t\t\"Show column numbers at the source code line level.\"),\n}\n\nfunc helpText(s ...string) string {\n\treturn strings.Join(s, \"\\n\") + \"\\n\"\n}\n\n// usage returns a string describing the pprof commands and configuration\n// options.  if commandLine is set, the output reflect cli usage.\nfunc usage(commandLine bool) string {\n\tvar prefix string\n\tif commandLine {\n\t\tprefix = \"-\"\n\t}\n\tfmtHelp := func(c, d string) string {\n\t\treturn fmt.Sprintf(\"    %-16s %s\", c, strings.SplitN(d, \"\\n\", 2)[0])\n\t}\n\n\tvar commands []string\n\tfor name, cmd := range pprofCommands {\n\t\tcommands = append(commands, fmtHelp(prefix+name, cmd.description))\n\t}\n\tsort.Strings(commands)\n\n\tvar help string\n\tif commandLine {\n\t\thelp = \"  Output formats (select at most one):\\n\"\n\t} else {\n\t\thelp = \"  Commands:\\n\"\n\t\tcommands = append(commands, fmtHelp(\"o/options\", \"List options and their current values\"))\n\t\tcommands = append(commands, fmtHelp(\"q/quit/exit/^D\", \"Exit pprof\"))\n\t}\n\n\thelp = help + strings.Join(commands, \"\\n\") + \"\\n\\n\" +\n\t\t\"  Options:\\n\"\n\n\t// Print help for configuration options after sorting them.\n\t// Collect choices for multi-choice options print them together.\n\tvar variables []string\n\tvar radioStrings []string\n\tfor _, f := range configFields {\n\t\tif len(f.choices) == 0 {\n\t\t\tvariables = append(variables, fmtHelp(prefix+f.name, configHelp[f.name]))\n\t\t\tcontinue\n\t\t}\n\t\t// Format help for for this group.\n\t\ts := []string{fmtHelp(f.name, \"\")}\n\t\tfor _, choice := range f.choices {\n\t\t\ts = append(s, \"  \"+fmtHelp(prefix+choice, configHelp[choice]))\n\t\t}\n\t\tradioStrings = append(radioStrings, strings.Join(s, \"\\n\"))\n\t}\n\tsort.Strings(variables)\n\tsort.Strings(radioStrings)\n\treturn help + strings.Join(variables, \"\\n\") + \"\\n\\n\" +\n\t\t\"  Option groups (only set one per group):\\n\" +\n\t\tstrings.Join(radioStrings, \"\\n\")\n}\n\nfunc reportHelp(c string, cum, redirect bool) string {\n\th := []string{\n\t\tc + \" [n] [focus_regex]* [-ignore_regex]*\",\n\t\t\"Include up to n samples\",\n\t\t\"Include samples matching focus_regex, and exclude ignore_regex.\",\n\t}\n\tif cum {\n\t\th[0] += \" [-cum]\"\n\t\th = append(h, \"-cum sorts the output by cumulative weight\")\n\t}\n\tif redirect {\n\t\th[0] += \" >f\"\n\t\th = append(h, \"Optionally save the report on the file f\")\n\t}\n\treturn strings.Join(h, \"\\n\")\n}\n\nfunc listHelp(c string, redirect bool) string {\n\th := []string{\n\t\tc + \"<func_regex|address> [-focus_regex]* [-ignore_regex]*\",\n\t\t\"Include functions matching func_regex, or including the address specified.\",\n\t\t\"Include samples matching focus_regex, and exclude ignore_regex.\",\n\t}\n\tif redirect {\n\t\th[0] += \" >f\"\n\t\th = append(h, \"Optionally save the report on the file f\")\n\t}\n\treturn strings.Join(h, \"\\n\")\n}\n\n// browsers returns a list of commands to attempt for web visualization.\nfunc browsers() []string {\n\tvar cmds []string\n\tif userBrowser := os.Getenv(\"BROWSER\"); userBrowser != \"\" {\n\t\tcmds = append(cmds, userBrowser)\n\t}\n\tswitch runtime.GOOS {\n\tcase \"darwin\":\n\t\tcmds = append(cmds, \"/usr/bin/open\")\n\tcase \"windows\":\n\t\tcmds = append(cmds, \"cmd /c start\")\n\tdefault:\n\t\t// Commands opening browsers are prioritized over xdg-open, so browser()\n\t\t// command can be used on linux to open the .svg file generated by the -web\n\t\t// command (the .svg file includes embedded javascript so is best viewed in\n\t\t// a browser).\n\t\tcmds = append(cmds, []string{\"chrome\", \"google-chrome\", \"chromium\", \"firefox\", \"sensible-browser\"}...)\n\t\tif os.Getenv(\"DISPLAY\") != \"\" {\n\t\t\t// xdg-open is only for use in a desktop environment.\n\t\t\tcmds = append(cmds, \"xdg-open\")\n\t\t}\n\t}\n\treturn cmds\n}\n\nvar kcachegrind = []string{\"kcachegrind\"}\n\n// awayFromTTY saves the output in a file if it would otherwise go to\n// the terminal screen. This is used to avoid dumping binary data on\n// the screen.\nfunc awayFromTTY(format string) PostProcessor {\n\treturn func(input io.Reader, output io.Writer, ui plugin.UI) error {\n\t\tif output == os.Stdout && (ui.IsTerminal() || interactiveMode) {\n\t\t\ttempFile, err := newTempFile(\"\", \"profile\", \".\"+format)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tui.PrintErr(\"Generating report in \", tempFile.Name())\n\t\t\toutput = tempFile\n\t\t}\n\t\t_, err := io.Copy(output, input)\n\t\treturn err\n\t}\n}\n\nfunc invokeDot(format string) PostProcessor {\n\treturn func(input io.Reader, output io.Writer, ui plugin.UI) error {\n\t\tcmd := exec.Command(\"dot\", \"-T\"+format)\n\t\tcmd.Stdin, cmd.Stdout, cmd.Stderr = input, output, os.Stderr\n\t\tif err := cmd.Run(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to execute dot. Is Graphviz installed? Error: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// massageDotSVG invokes the dot tool to generate an SVG image and alters\n// the image to have panning capabilities when viewed in a browser.\nfunc massageDotSVG() PostProcessor {\n\tgenerateSVG := invokeDot(\"svg\")\n\treturn func(input io.Reader, output io.Writer, ui plugin.UI) error {\n\t\tbaseSVG := new(bytes.Buffer)\n\t\tif err := generateSVG(input, baseSVG, ui); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err := output.Write([]byte(massageSVG(baseSVG.String())))\n\t\treturn err\n\t}\n}\n\nfunc invokeVisualizer(suffix string, visualizers []string) PostProcessor {\n\treturn func(input io.Reader, output io.Writer, ui plugin.UI) error {\n\t\ttempFile, err := newTempFile(os.TempDir(), \"pprof\", \".\"+suffix)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdeferDeleteTempFile(tempFile.Name())\n\t\tif _, err := io.Copy(tempFile, input); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttempFile.Close()\n\t\t// Try visualizers until one is successful\n\t\tfor _, v := range visualizers {\n\t\t\t// Separate command and arguments for exec.Command.\n\t\t\targs := strings.Split(v, \" \")\n\t\t\tif len(args) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tviewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)\n\t\t\tviewer.Stderr = os.Stderr\n\t\t\tif err = viewer.Start(); err == nil {\n\t\t\t\t// Wait for a second so that the visualizer has a chance to\n\t\t\t\t// open the input file. This needs to be done even if we're\n\t\t\t\t// waiting for the visualizer as it can be just a wrapper that\n\t\t\t\t// spawns a browser tab and returns right away.\n\t\t\t\tdefer func(t <-chan time.Time) {\n\t\t\t\t\t<-t\n\t\t\t\t}(time.After(time.Second))\n\t\t\t\t// On interactive mode, let the visualizer run in the background\n\t\t\t\t// so other commands can be issued.\n\t\t\t\tif !interactiveMode {\n\t\t\t\t\treturn viewer.Wait()\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}\n}\n\n// stringToBool is a custom parser for bools. We avoid using strconv.ParseBool\n// to remain compatible with old pprof behavior (e.g., treating \"\" as true).\nfunc stringToBool(s string) (bool, error) {\n\tswitch strings.ToLower(s) {\n\tcase \"true\", \"t\", \"yes\", \"y\", \"1\", \"\":\n\t\treturn true, nil\n\tcase \"false\", \"f\", \"no\", \"n\", \"0\":\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(`illegal value \"%s\" for bool variable`, s)\n\t}\n}\n"
  },
  {
    "path": "internal/driver/config.go",
    "content": "package driver\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// config holds settings for a single named config.\n// The JSON tag name for a field is used both for JSON encoding and as\n// a named variable.\ntype config struct {\n\t// Filename for file-based output formats, stdout by default.\n\tOutput string `json:\"-\"`\n\n\t// Display options.\n\tCallTree            bool    `json:\"call_tree,omitempty\"`\n\tRelativePercentages bool    `json:\"relative_percentages,omitempty\"`\n\tUnit                string  `json:\"unit,omitempty\"`\n\tCompactLabels       bool    `json:\"compact_labels,omitempty\"`\n\tSourcePath          string  `json:\"-\"`\n\tTrimPath            string  `json:\"-\"`\n\tIntelSyntax         bool    `json:\"intel_syntax,omitempty\"`\n\tMean                bool    `json:\"mean,omitempty\"`\n\tSampleIndex         string  `json:\"-\"`\n\tDivideBy            float64 `json:\"-\"`\n\tNormalize           bool    `json:\"normalize,omitempty\"`\n\tSort                string  `json:\"sort,omitempty\"`\n\n\t// Label pseudo stack frame generation options\n\tTagRoot string `json:\"tagroot,omitempty\"`\n\tTagLeaf string `json:\"tagleaf,omitempty\"`\n\n\t// Filtering options\n\tDropNegative bool    `json:\"drop_negative,omitempty\"`\n\tNodeCount    int     `json:\"nodecount,omitempty\"`\n\tNodeFraction float64 `json:\"nodefraction,omitempty\"`\n\tEdgeFraction float64 `json:\"edgefraction,omitempty\"`\n\tTrim         bool    `json:\"trim,omitempty\"`\n\tFocus        string  `json:\"focus,omitempty\"`\n\tIgnore       string  `json:\"ignore,omitempty\"`\n\tPruneFrom    string  `json:\"prune_from,omitempty\"`\n\tHide         string  `json:\"hide,omitempty\"`\n\tShow         string  `json:\"show,omitempty\"`\n\tShowFrom     string  `json:\"show_from,omitempty\"`\n\tTagFocus     string  `json:\"tagfocus,omitempty\"`\n\tTagIgnore    string  `json:\"tagignore,omitempty\"`\n\tTagShow      string  `json:\"tagshow,omitempty\"`\n\tTagHide      string  `json:\"taghide,omitempty\"`\n\tNoInlines    bool    `json:\"noinlines,omitempty\"`\n\tShowColumns  bool    `json:\"showcolumns,omitempty\"`\n\n\t// Output granularity\n\tGranularity string `json:\"granularity,omitempty\"`\n}\n\n// defaultConfig returns the default configuration values; it is unaffected by\n// flags and interactive assignments.\nfunc defaultConfig() config {\n\treturn config{\n\t\tUnit:         \"minimum\",\n\t\tNodeCount:    -1,\n\t\tNodeFraction: 0.005,\n\t\tEdgeFraction: 0.001,\n\t\tTrim:         true,\n\t\tDivideBy:     1.0,\n\t\tSort:         \"flat\",\n\t\tGranularity:  \"\", // Default depends on the display format\n\t}\n}\n\n// currentConfig holds the current configuration values; it is affected by\n// flags and interactive assignments.\nvar currentCfg = defaultConfig()\nvar currentMu sync.Mutex\n\nfunc currentConfig() config {\n\tcurrentMu.Lock()\n\tdefer currentMu.Unlock()\n\treturn currentCfg\n}\n\nfunc setCurrentConfig(cfg config) {\n\tcurrentMu.Lock()\n\tdefer currentMu.Unlock()\n\tcurrentCfg = cfg\n}\n\n// configField contains metadata for a single configuration field.\ntype configField struct {\n\tname         string              // JSON field name/key in variables\n\turlparam     string              // URL parameter name\n\tsaved        bool                // Is field saved in settings?\n\tfield        reflect.StructField // Field in config\n\tchoices      []string            // Name Of variables in group\n\tdefaultValue string              // Default value for this field.\n}\n\nvar (\n\tconfigFields []configField // Precomputed metadata per config field\n\n\t// configFieldMap holds an entry for every config field as well as an\n\t// entry for every valid choice for a multi-choice field.\n\tconfigFieldMap map[string]configField\n)\n\nfunc init() {\n\t// Config names for fields that are not saved in settings and therefore\n\t// do not have a JSON name.\n\tnotSaved := map[string]string{\n\t\t// Not saved in settings, but present in URLs.\n\t\t\"SampleIndex\": \"sample_index\",\n\n\t\t// Following fields are also not placed in URLs.\n\t\t\"Output\":     \"output\",\n\t\t\"SourcePath\": \"source_path\",\n\t\t\"TrimPath\":   \"trim_path\",\n\t\t\"DivideBy\":   \"divide_by\",\n\t}\n\n\t// choices holds the list of allowed values for config fields that can\n\t// take on one of a bounded set of values.\n\tchoices := map[string][]string{\n\t\t\"sort\":        {\"cum\", \"flat\"},\n\t\t\"granularity\": {\"functions\", \"filefunctions\", \"files\", \"lines\", \"addresses\"},\n\t}\n\n\t// urlparam holds the mapping from a config field name to the URL\n\t// parameter used to hold that config field. If no entry is present for\n\t// a name, the corresponding field is not saved in URLs.\n\turlparam := map[string]string{\n\t\t\"drop_negative\":        \"dropneg\",\n\t\t\"call_tree\":            \"calltree\",\n\t\t\"relative_percentages\": \"rel\",\n\t\t\"unit\":                 \"unit\",\n\t\t\"compact_labels\":       \"compact\",\n\t\t\"intel_syntax\":         \"intel\",\n\t\t\"nodecount\":            \"n\",\n\t\t\"nodefraction\":         \"nf\",\n\t\t\"edgefraction\":         \"ef\",\n\t\t\"trim\":                 \"trim\",\n\t\t\"focus\":                \"f\",\n\t\t\"ignore\":               \"i\",\n\t\t\"prune_from\":           \"prunefrom\",\n\t\t\"hide\":                 \"h\",\n\t\t\"show\":                 \"s\",\n\t\t\"show_from\":            \"sf\",\n\t\t\"tagfocus\":             \"tf\",\n\t\t\"tagignore\":            \"ti\",\n\t\t\"tagshow\":              \"ts\",\n\t\t\"taghide\":              \"th\",\n\t\t\"mean\":                 \"mean\",\n\t\t\"sample_index\":         \"si\",\n\t\t\"normalize\":            \"norm\",\n\t\t\"sort\":                 \"sort\",\n\t\t\"granularity\":          \"g\",\n\t\t\"noinlines\":            \"noinlines\",\n\t\t\"showcolumns\":          \"showcolumns\",\n\t}\n\n\tdef := defaultConfig()\n\tconfigFieldMap = map[string]configField{}\n\tt := reflect.TypeFor[config]()\n\tfor i, n := 0, t.NumField(); i < n; i++ {\n\t\tfield := t.Field(i)\n\t\tjs := strings.Split(field.Tag.Get(\"json\"), \",\")\n\t\tif len(js) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\t// Get the configuration name for this field.\n\t\tname := js[0]\n\t\tif name == \"-\" {\n\t\t\tname = notSaved[field.Name]\n\t\t\tif name == \"\" {\n\t\t\t\t// Not a configurable field.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tf := configField{\n\t\t\tname:     name,\n\t\t\turlparam: urlparam[name],\n\t\t\tsaved:    (name == js[0]),\n\t\t\tfield:    field,\n\t\t\tchoices:  choices[name],\n\t\t}\n\t\tf.defaultValue = def.get(f)\n\t\tconfigFields = append(configFields, f)\n\t\tconfigFieldMap[f.name] = f\n\t\tfor _, choice := range f.choices {\n\t\t\tconfigFieldMap[choice] = f\n\t\t}\n\t}\n}\n\n// fieldPtr returns a pointer to the field identified by f in *cfg.\nfunc (cfg *config) fieldPtr(f configField) interface{} {\n\t// reflect.ValueOf: converts to reflect.Value\n\t// Elem: dereferences cfg to make *cfg\n\t// FieldByIndex: fetches the field\n\t// Addr: takes address of field\n\t// Interface: converts back from reflect.Value to a regular value\n\treturn reflect.ValueOf(cfg).Elem().FieldByIndex(f.field.Index).Addr().Interface()\n}\n\n// get returns the value of field f in cfg.\nfunc (cfg *config) get(f configField) string {\n\tswitch ptr := cfg.fieldPtr(f).(type) {\n\tcase *string:\n\t\treturn *ptr\n\tcase *int:\n\t\treturn fmt.Sprint(*ptr)\n\tcase *float64:\n\t\treturn fmt.Sprint(*ptr)\n\tcase *bool:\n\t\treturn fmt.Sprint(*ptr)\n\t}\n\tpanic(fmt.Sprintf(\"unsupported config field type %v\", f.field.Type))\n}\n\n// set sets the value of field f in cfg to value.\nfunc (cfg *config) set(f configField, value string) error {\n\tswitch ptr := cfg.fieldPtr(f).(type) {\n\tcase *string:\n\t\tif len(f.choices) > 0 {\n\t\t\t// Verify that value is one of the allowed choices.\n\t\t\tif slices.Contains(f.choices, value) {\n\t\t\t\t*ptr = value\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"invalid %q value %q\", f.name, value)\n\t\t}\n\t\t*ptr = value\n\tcase *int:\n\t\tv, err := strconv.Atoi(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*ptr = v\n\tcase *float64:\n\t\tv, err := strconv.ParseFloat(value, 64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*ptr = v\n\tcase *bool:\n\t\tv, err := stringToBool(value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*ptr = v\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unsupported config field type %v\", f.field.Type))\n\t}\n\treturn nil\n}\n\n// isConfigurable returns true if name is either the name of a config field, or\n// a valid value for a multi-choice config field.\nfunc isConfigurable(name string) bool {\n\t_, ok := configFieldMap[name]\n\treturn ok\n}\n\n// isBoolConfig returns true if name is either name of a boolean config field,\n// or a valid value for a multi-choice config field.\nfunc isBoolConfig(name string) bool {\n\tf, ok := configFieldMap[name]\n\tif !ok {\n\t\treturn false\n\t}\n\tif name != f.name {\n\t\treturn true // name must be one possible value for the field\n\t}\n\tvar cfg config\n\t_, ok = cfg.fieldPtr(f).(*bool)\n\treturn ok\n}\n\n// completeConfig returns the list of configurable names starting with prefix.\nfunc completeConfig(prefix string) []string {\n\tvar result []string\n\tfor v := range configFieldMap {\n\t\tif strings.HasPrefix(v, prefix) {\n\t\t\tresult = append(result, v)\n\t\t}\n\t}\n\treturn result\n}\n\n// configure stores the name=value mapping into the current config, correctly\n// handling the case when name identifies a particular choice in a field.\nfunc configure(name, value string) error {\n\tcurrentMu.Lock()\n\tdefer currentMu.Unlock()\n\tf, ok := configFieldMap[name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown config field %q\", name)\n\t}\n\tif f.name == name {\n\t\treturn currentCfg.set(f, value)\n\t}\n\t// name must be one of the choices. If value is true, set field-value\n\t// to name.\n\tif v, err := strconv.ParseBool(value); v && err == nil {\n\t\treturn currentCfg.set(f, name)\n\t}\n\treturn fmt.Errorf(\"unknown config field %q\", name)\n}\n\n// resetTransient sets all transient fields in *cfg to their currently\n// configured values.\nfunc (cfg *config) resetTransient() {\n\tcurrent := currentConfig()\n\tcfg.Output = current.Output\n\tcfg.SourcePath = current.SourcePath\n\tcfg.TrimPath = current.TrimPath\n\tcfg.DivideBy = current.DivideBy\n\tcfg.SampleIndex = current.SampleIndex\n}\n\n// applyURL updates *cfg based on params.\nfunc (cfg *config) applyURL(params url.Values) error {\n\tfor _, f := range configFields {\n\t\tvar value string\n\t\tif f.urlparam != \"\" {\n\t\t\tvalue = params.Get(f.urlparam)\n\t\t}\n\t\tif value == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif err := cfg.set(f, value); err != nil {\n\t\t\treturn fmt.Errorf(\"error setting config field %s: %v\", f.name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// makeURL returns a URL based on initialURL that contains the config contents\n// as parameters.  The second result is true iff a parameter value was changed.\nfunc (cfg *config) makeURL(initialURL url.URL) (url.URL, bool) {\n\tq := initialURL.Query()\n\tchanged := false\n\tfor _, f := range configFields {\n\t\tif f.urlparam == \"\" || !f.saved {\n\t\t\tcontinue\n\t\t}\n\t\tv := cfg.get(f)\n\t\tif v == f.defaultValue {\n\t\t\tv = \"\" // URL for of default value is the empty string.\n\t\t} else if f.field.Type.Kind() == reflect.Bool {\n\t\t\t// Shorten bool values to \"f\" or \"t\"\n\t\t\tv = v[:1]\n\t\t}\n\t\tif q.Get(f.urlparam) == v {\n\t\t\tcontinue\n\t\t}\n\t\tchanged = true\n\t\tif v == \"\" {\n\t\t\tq.Del(f.urlparam)\n\t\t} else {\n\t\t\tq.Set(f.urlparam, v)\n\t\t}\n\t}\n\tif changed {\n\t\tinitialURL.RawQuery = q.Encode()\n\t}\n\treturn initialURL, changed\n}\n"
  },
  {
    "path": "internal/driver/driver.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package driver implements the core pprof functionality. It can be\n// parameterized with a flag implementation, fetch and symbolize\n// mechanisms.\npackage driver\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/report\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// PProf acquires a profile, and symbolizes it using a profile\n// manager. Then it generates a report formatted according to the\n// options selected through the flags package.\nfunc PProf(eo *plugin.Options) error {\n\t// Remove any temporary files created during pprof processing.\n\tdefer cleanupTempFiles()\n\n\to := setDefaults(eo)\n\n\tsrc, cmd, err := parseFlags(o)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp, err := fetchProfiles(src, o)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif cmd != nil {\n\t\treturn generateReport(p, cmd, currentConfig(), o)\n\t}\n\n\tif src.HTTPHostport != \"\" {\n\t\treturn serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)\n\t}\n\treturn interactive(p, o)\n}\n\n// generateRawReport is allowed to modify p.\nfunc generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {\n\t// Identify units of numeric tags in profile.\n\tnumLabelUnits := identifyNumLabelUnits(p, o.UI)\n\n\t// Get report output format\n\tc := pprofCommands[cmd[0]]\n\tif c == nil {\n\t\tpanic(\"unexpected nil command\")\n\t}\n\n\tcfg = applyCommandOverrides(cmd[0], c.format, cfg)\n\n\t// Create label pseudo nodes before filtering, in case the filters use\n\t// the generated nodes.\n\tgenerateTagRootsLeaves(p, cfg, o.UI)\n\n\t// Delay focus after configuring report to get percentages on all samples.\n\trelative := cfg.RelativePercentages\n\tif relative {\n\t\tif err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\tropt, err := reportOptions(p, numLabelUnits, cfg)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tropt.OutputFormat = c.format\n\tif len(cmd) == 2 {\n\t\ts, err := regexp.Compile(cmd[1])\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"parsing argument regexp %s: %v\", cmd[1], err)\n\t\t}\n\t\tropt.Symbol = s\n\t}\n\n\trpt := report.New(p, ropt)\n\tif !relative {\n\t\tif err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\tif err := aggregate(p, cfg); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn c, rpt, nil\n}\n\n// generateReport is allowed to modify p.\nfunc generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {\n\tc, rpt, err := generateRawReport(p, cmd, cfg, o)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Generate the report.\n\tdst := new(bytes.Buffer)\n\tswitch rpt.OutputFormat() {\n\tcase report.WebList:\n\t\t// We need template expansion, so generate here instead of in report.\n\t\terr = printWebList(dst, rpt, o.Obj)\n\tdefault:\n\t\terr = report.Generate(dst, rpt, o.Obj)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\tsrc := dst\n\n\t// If necessary, perform any data post-processing.\n\tif c.postProcess != nil {\n\t\tdst = new(bytes.Buffer)\n\t\tif err := c.postProcess(src, dst, o.UI); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsrc = dst\n\t}\n\n\t// If no output is specified, use default visualizer.\n\toutput := cfg.Output\n\tif output == \"\" {\n\t\tif c.visualizer != nil {\n\t\t\treturn c.visualizer(src, os.Stdout, o.UI)\n\t\t}\n\t\t_, err := src.WriteTo(os.Stdout)\n\t\treturn err\n\t}\n\n\t// Output to specified file.\n\to.UI.PrintErr(\"Generating report in \", output)\n\tout, err := o.Writer.Open(output)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif _, err := src.WriteTo(out); err != nil {\n\t\tout.Close()\n\t\treturn err\n\t}\n\treturn out.Close()\n}\n\nfunc printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error {\n\tlisting, err := report.MakeWebList(rpt, obj, -1)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlegend := report.ProfileLabels(rpt)\n\treturn renderHTML(dst, \"sourcelisting\", rpt, nil, legend, webArgs{\n\t\tStandalone: true,\n\t\tListing:    listing,\n\t})\n}\n\nfunc applyCommandOverrides(cmd string, outputFormat int, cfg config) config {\n\t// Some report types override the trim flag to false below. This is to make\n\t// sure the default heuristics of excluding insignificant nodes and edges\n\t// from the call graph do not apply. One example where it is important is\n\t// annotated source or disassembly listing. Those reports run on a specific\n\t// function (or functions), but the trimming is applied before the function\n\t// data is selected. So, with trimming enabled, the report could end up\n\t// showing no data if the specified function is \"uninteresting\" as far as the\n\t// trimming is concerned.\n\ttrim := cfg.Trim\n\n\tswitch cmd {\n\tcase \"disasm\":\n\t\ttrim = false\n\t\tcfg.Granularity = \"addresses\"\n\t\t// Force the 'noinlines' mode so that source locations for a given address\n\t\t// collapse and there is only one for the given address. Without this\n\t\t// cumulative metrics would be double-counted when annotating the assembly.\n\t\t// This is because the merge is done by address and in case of an inlined\n\t\t// stack each of the inlined entries is a separate callgraph node.\n\t\tcfg.NoInlines = true\n\tcase \"weblist\":\n\t\ttrim = false\n\t\tcfg.Granularity = \"addresses\"\n\t\tcfg.NoInlines = false // Need inline info to support call expansion\n\tcase \"peek\":\n\t\ttrim = false\n\tcase \"list\":\n\t\ttrim = false\n\t\tcfg.Granularity = \"lines\"\n\t\t// Do not force 'noinlines' to be false so that specifying\n\t\t// \"-list foo -noinlines\" is supported and works as expected.\n\tcase \"text\", \"top\", \"topproto\":\n\t\tif cfg.NodeCount == -1 {\n\t\t\tcfg.NodeCount = 0\n\t\t}\n\tdefault:\n\t\tif cfg.NodeCount == -1 {\n\t\t\tcfg.NodeCount = 80\n\t\t}\n\t}\n\n\tswitch outputFormat {\n\tcase report.Proto, report.Raw, report.Callgrind:\n\t\ttrim = false\n\t\tcfg.Granularity = \"addresses\"\n\t}\n\n\tif !trim {\n\t\tcfg.NodeCount = 0\n\t\tcfg.NodeFraction = 0\n\t\tcfg.EdgeFraction = 0\n\t}\n\treturn cfg\n}\n\n// generateTagRootsLeaves generates extra nodes from the tagroot and tagleaf options.\nfunc generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) {\n\ttagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, \",\"))\n\ttagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, \",\"))\n\trootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit)\n\twarnNoMatches(cfg.TagRoot == \"\" || rootm, \"TagRoot\", ui)\n\twarnNoMatches(cfg.TagLeaf == \"\" || leafm, \"TagLeaf\", ui)\n}\n\n// dropEmptyStrings filters a slice to only non-empty strings\nfunc dropEmptyStrings(in []string) (out []string) {\n\tfor _, s := range in {\n\t\tif s != \"\" {\n\t\t\tout = append(out, s)\n\t\t}\n\t}\n\treturn\n}\n\nfunc aggregate(prof *profile.Profile, cfg config) error {\n\tvar function, filename, linenumber, address bool\n\tinlines := !cfg.NoInlines\n\tswitch cfg.Granularity {\n\tcase \"\":\n\t\tfunction = true // Default granularity is \"functions\"\n\tcase \"addresses\":\n\t\tif inlines {\n\t\t\treturn nil\n\t\t}\n\t\tfunction = true\n\t\tfilename = true\n\t\tlinenumber = true\n\t\taddress = true\n\tcase \"lines\":\n\t\tfunction = true\n\t\tfilename = true\n\t\tlinenumber = true\n\tcase \"files\":\n\t\tfilename = true\n\tcase \"functions\":\n\t\tfunction = true\n\tcase \"filefunctions\":\n\t\tfunction = true\n\t\tfilename = true\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected granularity\")\n\t}\n\treturn prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)\n}\n\nfunc reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {\n\tsi, mean := cfg.SampleIndex, cfg.Mean\n\tvalue, meanDiv, sample, err := sampleFormat(p, si, mean)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstype := sample.Type\n\tif mean {\n\t\tstype = \"mean_\" + stype\n\t}\n\n\tif cfg.DivideBy == 0 {\n\t\treturn nil, fmt.Errorf(\"zero divisor specified\")\n\t}\n\n\tvar filters []string\n\taddFilter := func(k string, v string) {\n\t\tif v != \"\" {\n\t\t\tfilters = append(filters, k+\"=\"+v)\n\t\t}\n\t}\n\taddFilter(\"focus\", cfg.Focus)\n\taddFilter(\"ignore\", cfg.Ignore)\n\taddFilter(\"hide\", cfg.Hide)\n\taddFilter(\"show\", cfg.Show)\n\taddFilter(\"show_from\", cfg.ShowFrom)\n\taddFilter(\"tagfocus\", cfg.TagFocus)\n\taddFilter(\"tagignore\", cfg.TagIgnore)\n\taddFilter(\"tagshow\", cfg.TagShow)\n\taddFilter(\"taghide\", cfg.TagHide)\n\n\tropt := &report.Options{\n\t\tCumSort:      cfg.Sort == \"cum\",\n\t\tCallTree:     cfg.CallTree,\n\t\tDropNegative: cfg.DropNegative,\n\n\t\tCompactLabels: cfg.CompactLabels,\n\t\tRatio:         1 / cfg.DivideBy,\n\n\t\tNodeCount:    cfg.NodeCount,\n\t\tNodeFraction: cfg.NodeFraction,\n\t\tEdgeFraction: cfg.EdgeFraction,\n\n\t\tActiveFilters: filters,\n\t\tNumLabelUnits: numLabelUnits,\n\n\t\tSampleValue:       value,\n\t\tSampleMeanDivisor: meanDiv,\n\t\tSampleType:        stype,\n\t\tSampleUnit:        sample.Unit,\n\n\t\tOutputUnit: cfg.Unit,\n\n\t\tSourcePath: cfg.SourcePath,\n\t\tTrimPath:   cfg.TrimPath,\n\n\t\tIntelSyntax: cfg.IntelSyntax,\n\t}\n\n\tif len(p.Mapping) > 0 && p.Mapping[0].File != \"\" {\n\t\tropt.Title = filepath.Base(p.Mapping[0].File)\n\t}\n\n\treturn ropt, nil\n}\n\n// identifyNumLabelUnits returns a map of numeric label keys to the units\n// associated with those keys.\nfunc identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {\n\tnumLabelUnits, ignoredUnits := p.NumLabelUnits()\n\n\t// Print errors for tags with multiple units associated with\n\t// a single key.\n\tfor k, units := range ignoredUnits {\n\t\tui.PrintErr(fmt.Sprintf(\"For tag %s used unit %s, also encountered unit(s) %s\", k, numLabelUnits[k], strings.Join(units, \", \")))\n\t}\n\treturn numLabelUnits\n}\n\ntype sampleValueFunc func([]int64) int64\n\n// sampleFormat returns a function to extract values out of a profile.Sample,\n// and the type/units of those values.\nfunc sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {\n\tif len(p.SampleType) == 0 {\n\t\treturn nil, nil, nil, fmt.Errorf(\"profile has no samples\")\n\t}\n\tindex, err := p.SampleIndexByName(sampleIndex)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tvalue = valueExtractor(index)\n\tif mean {\n\t\tmeanDiv = valueExtractor(0)\n\t}\n\tv = p.SampleType[index]\n\treturn\n}\n\nfunc valueExtractor(ix int) sampleValueFunc {\n\treturn func(v []int64) int64 {\n\t\treturn v[ix]\n\t}\n}\n\n// profileCopier can be used to obtain a fresh copy of a profile.\n// It is useful since reporting code may mutate the profile handed to it.\ntype profileCopier []byte\n\nfunc makeProfileCopier(src *profile.Profile) profileCopier {\n\t// Pre-serialize the profile. We will deserialize every time a fresh copy is needed.\n\tvar buf bytes.Buffer\n\tsrc.WriteUncompressed(&buf)\n\treturn profileCopier(buf.Bytes())\n}\n\n// newCopy returns a new copy of the profile.\nfunc (c profileCopier) newCopy() *profile.Profile {\n\tp, err := profile.ParseUncompressed([]byte(c))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "internal/driver/driver_focus.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\nvar tagFilterRangeRx = regexp.MustCompile(\"([+-]?[[:digit:]]+)([[:alpha:]]+)?\")\n\n// applyFocus filters samples based on the focus/ignore options\nfunc applyFocus(prof *profile.Profile, numLabelUnits map[string]string, cfg config, ui plugin.UI) error {\n\tfocus, err := compileRegexOption(\"focus\", cfg.Focus, nil)\n\tignore, err := compileRegexOption(\"ignore\", cfg.Ignore, err)\n\thide, err := compileRegexOption(\"hide\", cfg.Hide, err)\n\tshow, err := compileRegexOption(\"show\", cfg.Show, err)\n\tshowfrom, err := compileRegexOption(\"show_from\", cfg.ShowFrom, err)\n\ttagfocus, err := compileTagFilter(\"tagfocus\", cfg.TagFocus, numLabelUnits, ui, err)\n\ttagignore, err := compileTagFilter(\"tagignore\", cfg.TagIgnore, numLabelUnits, ui, err)\n\tprunefrom, err := compileRegexOption(\"prune_from\", cfg.PruneFrom, err)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfm, im, hm, hnm := prof.FilterSamplesByName(focus, ignore, hide, show)\n\twarnNoMatches(focus == nil || fm, \"Focus\", ui)\n\twarnNoMatches(ignore == nil || im, \"Ignore\", ui)\n\twarnNoMatches(hide == nil || hm, \"Hide\", ui)\n\twarnNoMatches(show == nil || hnm, \"Show\", ui)\n\n\tsfm := prof.ShowFrom(showfrom)\n\twarnNoMatches(showfrom == nil || sfm, \"ShowFrom\", ui)\n\n\ttfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore)\n\twarnNoMatches(tagfocus == nil || tfm, \"TagFocus\", ui)\n\twarnNoMatches(tagignore == nil || tim, \"TagIgnore\", ui)\n\n\ttagshow, err := compileRegexOption(\"tagshow\", cfg.TagShow, err)\n\ttaghide, err := compileRegexOption(\"taghide\", cfg.TagHide, err)\n\ttns, tnh := prof.FilterTagsByName(tagshow, taghide)\n\twarnNoMatches(tagshow == nil || tns, \"TagShow\", ui)\n\twarnNoMatches(taghide == nil || tnh, \"TagHide\", ui)\n\n\tif prunefrom != nil {\n\t\tprof.PruneFrom(prunefrom)\n\t}\n\treturn err\n}\n\nfunc compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {\n\tif value == \"\" || err != nil {\n\t\treturn nil, err\n\t}\n\trx, err := regexp.Compile(value)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing %s regexp: %v\", name, err)\n\t}\n\treturn rx, nil\n}\n\nfunc compileTagFilter(name, value string, numLabelUnits map[string]string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) {\n\tif value == \"\" || err != nil {\n\t\treturn nil, err\n\t}\n\n\ttagValuePair := strings.SplitN(value, \"=\", 2)\n\tvar wantKey string\n\tif len(tagValuePair) == 2 {\n\t\twantKey = tagValuePair[0]\n\t\tvalue = tagValuePair[1]\n\t}\n\n\tif numFilter := parseTagFilterRange(value); numFilter != nil {\n\t\tui.PrintErr(name, \":Interpreted '\", value, \"' as range, not regexp\")\n\t\tlabelFilter := func(vals []int64, unit string) bool {\n\t\t\tfor _, val := range vals {\n\t\t\t\tif numFilter(val, unit) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\tnumLabelUnit := func(key string) string {\n\t\t\treturn numLabelUnits[key]\n\t\t}\n\t\tif wantKey == \"\" {\n\t\t\treturn func(s *profile.Sample) bool {\n\t\t\t\tfor key, vals := range s.NumLabel {\n\t\t\t\t\tif labelFilter(vals, numLabelUnit(key)) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t}, nil\n\t\t}\n\t\treturn func(s *profile.Sample) bool {\n\t\t\tif vals, ok := s.NumLabel[wantKey]; ok {\n\t\t\t\treturn labelFilter(vals, numLabelUnit(wantKey))\n\t\t\t}\n\t\t\treturn false\n\t\t}, nil\n\t}\n\n\tvar rfx []*regexp.Regexp\n\tfor _, tagf := range strings.Split(value, \",\") {\n\t\tfx, err := regexp.Compile(tagf)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parsing %s regexp: %v\", name, err)\n\t\t}\n\t\trfx = append(rfx, fx)\n\t}\n\tif wantKey == \"\" {\n\t\treturn func(s *profile.Sample) bool {\n\t\tmatchedrx:\n\t\t\tfor _, rx := range rfx {\n\t\t\t\tfor key, vals := range s.Label {\n\t\t\t\t\tfor _, val := range vals {\n\t\t\t\t\t\t// TODO: Match against val, not key:val in future\n\t\t\t\t\t\tif rx.MatchString(key + \":\" + val) {\n\t\t\t\t\t\t\tcontinue matchedrx\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}, nil\n\t}\n\treturn func(s *profile.Sample) bool {\n\t\tif vals, ok := s.Label[wantKey]; ok {\n\t\t\tfor _, rx := range rfx {\n\t\t\t\tif slices.ContainsFunc(vals, rx.MatchString) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}, nil\n}\n\n// parseTagFilterRange returns a function to checks if a value is\n// contained on the range described by a string. It can recognize\n// strings of the form:\n// \"32kb\" -- matches values == 32kb\n// \":64kb\" -- matches values <= 64kb\n// \"4mb:\" -- matches values >= 4mb\n// \"12kb:64mb\" -- matches values between 12kb and 64mb (both included).\nfunc parseTagFilterRange(filter string) func(int64, string) bool {\n\tranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2)\n\tif len(ranges) == 0 {\n\t\treturn nil // No ranges were identified\n\t}\n\tv, err := strconv.ParseInt(ranges[0][1], 10, 64)\n\tif err != nil {\n\t\tpanic(fmt.Errorf(\"failed to parse int %s: %v\", ranges[0][1], err))\n\t}\n\tscaledValue, unit := measurement.Scale(v, ranges[0][2], ranges[0][2])\n\tif len(ranges) == 1 {\n\t\tswitch match := ranges[0][0]; filter {\n\t\tcase match:\n\t\t\treturn func(v int64, u string) bool {\n\t\t\t\tsv, su := measurement.Scale(v, u, unit)\n\t\t\t\treturn su == unit && sv == scaledValue\n\t\t\t}\n\t\tcase match + \":\":\n\t\t\treturn func(v int64, u string) bool {\n\t\t\t\tsv, su := measurement.Scale(v, u, unit)\n\t\t\t\treturn su == unit && sv >= scaledValue\n\t\t\t}\n\t\tcase \":\" + match:\n\t\t\treturn func(v int64, u string) bool {\n\t\t\t\tsv, su := measurement.Scale(v, u, unit)\n\t\t\t\treturn su == unit && sv <= scaledValue\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif filter != ranges[0][0]+\":\"+ranges[1][0] {\n\t\treturn nil\n\t}\n\tif v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil {\n\t\tpanic(fmt.Errorf(\"failed to parse int %s: %v\", ranges[1][1], err))\n\t}\n\tscaledValue2, unit2 := measurement.Scale(v, ranges[1][2], unit)\n\tif unit != unit2 {\n\t\treturn nil\n\t}\n\treturn func(v int64, u string) bool {\n\t\tsv, su := measurement.Scale(v, u, unit)\n\t\treturn su == unit && sv >= scaledValue && sv <= scaledValue2\n\t}\n}\n\nfunc warnNoMatches(match bool, option string, ui plugin.UI) {\n\tif !match {\n\t\tui.PrintErr(option + \" expression matched no samples\")\n\t}\n}\n"
  },
  {
    "path": "internal/driver/driver_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/internal/symbolz\"\n\t\"github.com/google/pprof/profile\"\n)\n\nvar updateFlag = flag.Bool(\"update\", false, \"Update the golden files\")\n\nfunc TestParse(t *testing.T) {\n\t// Override weblist command to collect output in buffer\n\tpprofCommands[\"weblist\"].postProcess = nil\n\n\t// Our mockObjTool.Open will always return success, causing\n\t// driver.locateBinaries to \"find\" the binaries below in a non-existent\n\t// directory. As a workaround, point the search path to the fake\n\t// directory containing out fake binaries.\n\tsavePath := os.Getenv(\"PPROF_BINARY_PATH\")\n\tos.Setenv(\"PPROF_BINARY_PATH\", \"/path/to\")\n\tdefer os.Setenv(\"PPROF_BINARY_PATH\", savePath)\n\ttestcase := []struct {\n\t\tflags, source string\n\t}{\n\t\t{\"text,functions,flat\", \"cpu\"},\n\t\t{\"text,functions,noinlines,flat\", \"cpu\"},\n\t\t{\"text,filefunctions,noinlines,flat\", \"cpu\"},\n\t\t{\"text,addresses,noinlines,flat\", \"cpu\"},\n\t\t{\"tree,addresses,flat,nodecount=4\", \"cpusmall\"},\n\t\t{\"text,functions,flat,nodecount=5,call_tree\", \"unknown\"},\n\t\t{\"text,alloc_objects,flat\", \"heap_alloc\"},\n\t\t{\"text,files,flat\", \"heap\"},\n\t\t{\"text,files,flat,focus=[12]00,taghide=[X3]00\", \"heap\"},\n\t\t{\"text,inuse_objects,flat\", \"heap\"},\n\t\t{\"text,inuse_objects,flat,all_frames\", \"heap\"},\n\t\t{\"text,lines,cum,hide=line[X3]0\", \"cpu\"},\n\t\t{\"text,lines,cum,show=[12]00\", \"cpu\"},\n\t\t{\"text,lines,cum,hide=line[X3]0,focus=[12]00\", \"cpu\"},\n\t\t{\"topproto,lines,cum,hide=mangled[X3]0\", \"cpu\"},\n\t\t{\"topproto,lines\", \"cpu\"},\n\t\t{\"tree,lines,cum,focus=[24]00\", \"heap\"},\n\t\t{\"tree,relative_percentages,cum,focus=[24]00\", \"heap\"},\n\t\t{\"tree,lines,cum,show_from=line2\", \"cpu\"},\n\t\t{\"callgrind\", \"cpu\"},\n\t\t{\"callgrind,call_tree\", \"cpu\"},\n\t\t{\"callgrind\", \"heap\"},\n\t\t{\"dot,functions,flat\", \"cpu\"},\n\t\t{\"dot,functions,flat,call_tree\", \"cpu\"},\n\t\t{\"dot,lines,flat,focus=[12]00\", \"heap\"},\n\t\t{\"dot,unit=minimum\", \"heap_sizetags\"},\n\t\t{\"dot,addresses,flat,ignore=[X3]002,focus=[X1]000\", \"contention\"},\n\t\t{\"dot,files,cum\", \"contention\"},\n\t\t{\"comments,add_comment=some-comment\", \"cpu\"},\n\t\t{\"comments\", \"heap\"},\n\t\t{\"tags\", \"cpu\"},\n\t\t{\"tags,tagignore=tag[13],tagfocus=key[12]\", \"cpu\"},\n\t\t{\"tags\", \"heap\"},\n\t\t{\"tags,unit=bytes\", \"heap\"},\n\t\t{\"traces\", \"cpu\"},\n\t\t{\"traces,addresses\", \"cpu\"},\n\t\t{\"traces\", \"heap_tags\"},\n\t\t{\"dot,alloc_space,flat,focus=[234]00\", \"heap_alloc\"},\n\t\t{\"dot,alloc_space,flat,tagshow=[2]00\", \"heap_alloc\"},\n\t\t{\"dot,alloc_space,flat,hide=line.*1?23?\", \"heap_alloc\"},\n\t\t{\"dot,inuse_space,flat,tagfocus=1mb:2gb\", \"heap\"},\n\t\t{\"dot,inuse_space,flat,tagfocus=30kb:,tagignore=1mb:2mb\", \"heap\"},\n\t\t{\"disasm=line[13],addresses,flat\", \"cpu\"},\n\t\t{\"peek=line.*01\", \"cpu\"},\n\t\t{\"weblist=line(1000|3000)$,addresses,flat\", \"cpu\"},\n\t\t{\"tags,tagfocus=400kb:\", \"heap_request\"},\n\t\t{\"tags,tagfocus=+400kb:\", \"heap_request\"},\n\t\t{\"tags,relative_percentages,tagfocus=400kb:\", \"heap_request\"},\n\t\t{\"dot\", \"long_name_funcs\"},\n\t\t{\"text\", \"long_name_funcs\"},\n\t}\n\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\tfor _, tc := range testcase {\n\t\tt.Run(tc.flags+\":\"+tc.source, func(t *testing.T) {\n\t\t\t// Reset config before processing\n\t\t\tsetCurrentConfig(baseConfig)\n\n\t\t\ttestUI := &proftest.TestUI{T: t, AllowRx: \"Generating report in|Ignoring local file|expression matched no samples|Interpreted .* as range, not regexp\"}\n\n\t\t\tf := baseFlags()\n\t\t\tf.args = []string{tc.source}\n\n\t\t\tflags := strings.Split(tc.flags, \",\")\n\n\t\t\t// Encode profile into a protobuf and decode it again.\n\t\t\tprotoTempFile, err := os.CreateTemp(\"\", \"profile_proto\")\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"cannot create tempfile: %v\", err)\n\t\t\t}\n\t\t\tdefer os.Remove(protoTempFile.Name())\n\t\t\tdefer protoTempFile.Close()\n\t\t\tf.strings[\"output\"] = protoTempFile.Name()\n\n\t\t\tif flags[0] == \"topproto\" {\n\t\t\t\tf.bools[\"proto\"] = false\n\t\t\t\tf.bools[\"topproto\"] = true\n\t\t\t\tf.bools[\"addresses\"] = true\n\t\t\t}\n\n\t\t\t// First pprof invocation to save the profile into a profile.proto.\n\t\t\t// Pass in flag set hen setting defaults, because otherwise default\n\t\t\t// transport will try to add flags to the default flag set.\n\t\t\to1 := setDefaults(&plugin.Options{Flagset: f})\n\t\t\to1.Fetch = testFetcher{}\n\t\t\to1.Sym = testSymbolizer{}\n\t\t\to1.UI = testUI\n\t\t\tif err := PProf(o1); err != nil {\n\t\t\t\tt.Fatalf(\"%s %q:  %v\", tc.source, tc.flags, err)\n\t\t\t}\n\t\t\t// Reset config after the proto invocation\n\t\t\tsetCurrentConfig(baseConfig)\n\n\t\t\t// Read the profile from the encoded protobuf\n\t\t\toutputTempFile, err := os.CreateTemp(\"\", \"profile_output\")\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"cannot create tempfile: %v\", err)\n\t\t\t}\n\t\t\tdefer os.Remove(outputTempFile.Name())\n\t\t\tdefer outputTempFile.Close()\n\n\t\t\tf = baseFlags()\n\t\t\tf.strings[\"output\"] = outputTempFile.Name()\n\t\t\tf.args = []string{protoTempFile.Name()}\n\n\t\t\tdelete(f.bools, \"proto\")\n\t\t\taddFlags(&f, flags)\n\t\t\tsolution := solutionFilename(tc.source, &f)\n\t\t\t// Apply the flags for the second pprof run, and identify name of\n\t\t\t// the file containing expected results\n\t\t\tif flags[0] == \"topproto\" {\n\t\t\t\taddFlags(&f, flags)\n\t\t\t\tsolution = solutionFilename(tc.source, &f)\n\t\t\t\tdelete(f.bools, \"topproto\")\n\t\t\t\tf.bools[\"text\"] = true\n\t\t\t}\n\n\t\t\t// Second pprof invocation to read the profile from profile.proto\n\t\t\t// and generate a report.\n\t\t\t// Pass in flag set hen setting defaults, because otherwise default\n\t\t\t// transport will try to add flags to the default flag set.\n\t\t\to2 := setDefaults(&plugin.Options{Flagset: f})\n\t\t\to2.Sym = testSymbolizeDemangler{}\n\t\t\to2.Obj = new(mockObjTool)\n\t\t\to2.UI = testUI\n\n\t\t\tif err := PProf(o2); err != nil {\n\t\t\t\tt.Errorf(\"%s: %v\", tc.source, err)\n\t\t\t}\n\t\t\tb, err := os.ReadFile(outputTempFile.Name())\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Failed to read profile %s: %v\", outputTempFile.Name(), err)\n\t\t\t}\n\n\t\t\t// Read data file with expected solution\n\t\t\tsolution = \"testdata/\" + solution\n\t\t\tsbuf, err := os.ReadFile(solution)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"reading solution file %s: %v\", solution, err)\n\t\t\t}\n\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\tif flags[0] == \"dot\" {\n\t\t\t\t\t// The .dot test has the paths inside strings, so \\ must be escaped.\n\t\t\t\t\tsbuf = bytes.ReplaceAll(sbuf, []byte(\"testdata/\"), []byte(`testdata\\\\`))\n\t\t\t\t\tsbuf = bytes.ReplaceAll(sbuf, []byte(\"/path/to/\"), []byte(`\\\\path\\\\to\\\\`))\n\t\t\t\t} else {\n\t\t\t\t\tsbuf = bytes.ReplaceAll(sbuf, []byte(\"testdata/\"), []byte(`testdata\\`))\n\t\t\t\t\tsbuf = bytes.ReplaceAll(sbuf, []byte(\"/path/to/\"), []byte(`\\path\\to\\`))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif flags[0] == \"svg\" {\n\t\t\t\tb = removeScripts(b)\n\t\t\t\tsbuf = removeScripts(sbuf)\n\t\t\t}\n\n\t\t\tif string(b) != string(sbuf) {\n\t\t\t\tt.Errorf(\"diff %s %s\", solution, tc.source)\n\t\t\t\td, err := proftest.Diff(sbuf, b)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"diff %s %v\", solution, err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"%s\\n%s\\n\", solution, d)\n\t\t\t\tif *updateFlag {\n\t\t\t\t\terr := os.WriteFile(solution, b, 0644)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"failed to update the solution file %q: %v\", solution, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// removeScripts removes <script > .. </script> pairs from its input\nfunc removeScripts(in []byte) []byte {\n\tbeginMarker := []byte(\"<script\")\n\tendMarker := []byte(\"</script>\")\n\n\tif begin := bytes.Index(in, beginMarker); begin > 0 {\n\t\tif end := bytes.Index(in[begin:], endMarker); end > 0 {\n\t\t\tin = append(in[:begin], removeScripts(in[begin+end+len(endMarker):])...)\n\t\t}\n\t}\n\treturn in\n}\n\n// addFlags parses flag descriptions and adds them to the testFlags\nfunc addFlags(f *testFlags, flags []string) {\n\tfor _, flag := range flags {\n\t\tfields := strings.SplitN(flag, \"=\", 2)\n\t\tswitch len(fields) {\n\t\tcase 1:\n\t\t\tf.bools[fields[0]] = true\n\t\tcase 2:\n\t\t\tif i, err := strconv.Atoi(fields[1]); err == nil {\n\t\t\t\tf.ints[fields[0]] = i\n\t\t\t} else {\n\t\t\t\tf.strings[fields[0]] = fields[1]\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc testSourceURL(port int) string {\n\treturn fmt.Sprintf(\"http://%s/\", net.JoinHostPort(testSourceAddress, strconv.Itoa(port)))\n}\n\n// solutionFilename returns the name of the solution file for the test\nfunc solutionFilename(source string, f *testFlags) string {\n\tname := []string{\"pprof\", strings.TrimPrefix(source, testSourceURL(8000))}\n\tname = addString(name, f, []string{\"flat\", \"cum\"})\n\tname = addString(name, f, []string{\"functions\", \"filefunctions\", \"files\", \"lines\", \"addresses\"})\n\tname = addString(name, f, []string{\"noinlines\"})\n\tname = addString(name, f, []string{\"inuse_space\", \"inuse_objects\", \"alloc_space\", \"alloc_objects\"})\n\tname = addString(name, f, []string{\"relative_percentages\"})\n\tname = addString(name, f, []string{\"seconds\"})\n\tname = addString(name, f, []string{\"call_tree\"})\n\tname = addString(name, f, []string{\"text\", \"tree\", \"callgrind\", \"dot\", \"svg\", \"tags\", \"dot\", \"traces\", \"disasm\", \"peek\", \"weblist\", \"topproto\", \"comments\"})\n\tname = addString(name, f, []string{\"all_frames\"})\n\tif f.strings[\"focus\"] != \"\" || f.strings[\"tagfocus\"] != \"\" {\n\t\tname = append(name, \"focus\")\n\t}\n\tif f.strings[\"ignore\"] != \"\" || f.strings[\"tagignore\"] != \"\" {\n\t\tname = append(name, \"ignore\")\n\t}\n\tif f.strings[\"show_from\"] != \"\" {\n\t\tname = append(name, \"show_from\")\n\t}\n\tname = addString(name, f, []string{\"hide\", \"show\"})\n\tif f.strings[\"unit\"] != \"minimum\" {\n\t\tname = addString(name, f, []string{\"unit\"})\n\t}\n\treturn strings.Join(name, \".\")\n}\n\nfunc addString(name []string, f *testFlags, components []string) []string {\n\tfor _, c := range components {\n\t\tif f.bools[c] || f.strings[c] != \"\" || f.ints[c] != 0 {\n\t\t\treturn append(name, c)\n\t\t}\n\t}\n\treturn name\n}\n\n// testFlags implements the plugin.FlagSet interface.\ntype testFlags struct {\n\tbools       map[string]bool\n\tints        map[string]int\n\tfloats      map[string]float64\n\tstrings     map[string]string\n\targs        []string\n\tstringLists map[string][]string\n}\n\nfunc (testFlags) ExtraUsage() string { return \"\" }\n\nfunc (testFlags) AddExtraUsage(eu string) {}\n\nfunc (f testFlags) Bool(s string, d bool, c string) *bool {\n\tif b, ok := f.bools[s]; ok {\n\t\treturn &b\n\t}\n\treturn &d\n}\n\nfunc (f testFlags) Int(s string, d int, c string) *int {\n\tif i, ok := f.ints[s]; ok {\n\t\treturn &i\n\t}\n\treturn &d\n}\n\nfunc (f testFlags) Float64(s string, d float64, c string) *float64 {\n\tif g, ok := f.floats[s]; ok {\n\t\treturn &g\n\t}\n\treturn &d\n}\n\nfunc (f testFlags) String(s, d, c string) *string {\n\tif t, ok := f.strings[s]; ok {\n\t\treturn &t\n\t}\n\treturn &d\n}\n\nfunc (f testFlags) StringList(s, d, c string) *[]*string {\n\tif t, ok := f.stringLists[s]; ok {\n\t\t// convert slice of strings to slice of string pointers before returning.\n\t\ttp := make([]*string, len(t))\n\t\tfor i, v := range t {\n\t\t\ttp[i] = &v\n\t\t}\n\t\treturn &tp\n\t}\n\treturn &[]*string{}\n}\n\nfunc (f testFlags) Parse(func()) []string {\n\treturn f.args\n}\n\nfunc baseFlags() testFlags {\n\treturn testFlags{\n\t\tbools: map[string]bool{\n\t\t\t\"proto\":          true,\n\t\t\t\"trim\":           true,\n\t\t\t\"compact_labels\": true,\n\t\t},\n\t\tints: map[string]int{\n\t\t\t\"nodecount\": 20,\n\t\t},\n\t\tfloats: map[string]float64{\n\t\t\t\"nodefraction\": 0.05,\n\t\t\t\"edgefraction\": 0.01,\n\t\t\t\"divide_by\":    1.0,\n\t\t},\n\t\tstrings: map[string]string{\n\t\t\t\"unit\": \"minimum\",\n\t\t},\n\t}\n}\n\nconst testStart = 0x1000\nconst testOffset = 0x5000\n\ntype testFetcher struct{}\n\nfunc (testFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {\n\tvar p *profile.Profile\n\tswitch s {\n\tcase \"cpu\", \"unknown\":\n\t\tp = cpuProfile()\n\tcase \"cpusmall\":\n\t\tp = cpuProfileSmall()\n\tcase \"heap\":\n\t\tp = heapProfile()\n\tcase \"heap_alloc\":\n\t\tp = heapProfile()\n\t\tp.SampleType = []*profile.ValueType{\n\t\t\t{Type: \"alloc_objects\", Unit: \"count\"},\n\t\t\t{Type: \"alloc_space\", Unit: \"bytes\"},\n\t\t}\n\tcase \"heap_request\":\n\t\tp = heapProfile()\n\t\tfor _, s := range p.Sample {\n\t\t\ts.NumLabel[\"request\"] = s.NumLabel[\"bytes\"]\n\t\t}\n\tcase \"heap_sizetags\":\n\t\tp = heapProfile()\n\t\ttags := []int64{2, 4, 8, 16, 32, 64, 128, 256}\n\t\tfor _, s := range p.Sample {\n\t\t\tnumValues := append(s.NumLabel[\"bytes\"], tags...)\n\t\t\ts.NumLabel[\"bytes\"] = numValues\n\t\t}\n\tcase \"heap_tags\":\n\t\tp = heapProfile()\n\t\tfor i := 0; i < len(p.Sample); i += 2 {\n\t\t\ts := p.Sample[i]\n\t\t\tif s.Label == nil {\n\t\t\t\ts.Label = make(map[string][]string)\n\t\t\t}\n\t\t\ts.NumLabel[\"request\"] = s.NumLabel[\"bytes\"]\n\t\t\ts.Label[\"key1\"] = []string{\"tag\"}\n\t\t}\n\tcase \"contention\":\n\t\tp = contentionProfile()\n\tcase \"symbolz\":\n\t\tp = symzProfile()\n\tcase \"long_name_funcs\":\n\t\tp = longNameFuncsProfile()\n\tdefault:\n\t\treturn nil, \"\", fmt.Errorf(\"unexpected source: %s\", s)\n\t}\n\treturn p, testSourceURL(8000) + s, nil\n}\n\ntype testSymbolizer struct{}\n\nfunc (testSymbolizer) Symbolize(_ string, _ plugin.MappingSources, _ *profile.Profile) error {\n\treturn nil\n}\n\ntype testSymbolizeDemangler struct{}\n\nfunc (testSymbolizeDemangler) Symbolize(_ string, _ plugin.MappingSources, p *profile.Profile) error {\n\tfor _, fn := range p.Function {\n\t\tif fn.Name == \"\" || fn.SystemName == fn.Name {\n\t\t\tfn.Name = fakeDemangler(fn.SystemName)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc testFetchSymbols(source, post string) ([]byte, error) {\n\tvar buf bytes.Buffer\n\n\tswitch source {\n\tcase testSourceURL(8000) + \"symbolz\":\n\t\tfor _, address := range strings.Split(post, \"+\") {\n\t\t\ta, _ := strconv.ParseInt(address, 0, 64)\n\t\t\tfmt.Fprintf(&buf, \"%v\\t\", address)\n\t\t\tif a-testStart > testOffset {\n\t\t\t\tfmt.Fprintf(&buf, \"wrong_source_%v_\", address)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfmt.Fprintf(&buf, \"%#x\\n\", a-testStart)\n\t\t}\n\t\treturn buf.Bytes(), nil\n\tcase testSourceURL(8001) + \"symbolz\":\n\t\tfor _, address := range strings.Split(post, \"+\") {\n\t\t\ta, _ := strconv.ParseInt(address, 0, 64)\n\t\t\tfmt.Fprintf(&buf, \"%v\\t\", address)\n\t\t\tif a-testStart < testOffset {\n\t\t\t\tfmt.Fprintf(&buf, \"wrong_source_%v_\", address)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfmt.Fprintf(&buf, \"%#x\\n\", a-testStart-testOffset)\n\t\t}\n\t\treturn buf.Bytes(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected source: %s\", source)\n\t}\n}\n\ntype testSymbolzSymbolizer struct{}\n\nfunc (testSymbolzSymbolizer) Symbolize(variables string, sources plugin.MappingSources, p *profile.Profile) error {\n\treturn symbolz.Symbolize(p, false, sources, testFetchSymbols, nil)\n}\n\nfunc fakeDemangler(name string) string {\n\tswitch name {\n\tcase \"mangled1000\":\n\t\treturn \"line1000\"\n\tcase \"mangled2000\":\n\t\treturn \"line2000\"\n\tcase \"mangled2001\":\n\t\treturn \"line2001\"\n\tcase \"mangled3000\":\n\t\treturn \"line3000\"\n\tcase \"mangled3001\":\n\t\treturn \"line3001\"\n\tcase \"mangled3002\":\n\t\treturn \"line3002\"\n\tcase \"mangledNEW\":\n\t\treturn \"operator new\"\n\tcase \"mangledMALLOC\":\n\t\treturn \"malloc\"\n\tdefault:\n\t\treturn name\n\t}\n}\n\n// longNameFuncsProfile returns a profile with function names which should be\n// shortened in graph and flame views.\nfunc longNameFuncsProfile() *profile.Profile {\n\tvar longNameFuncsM = []*profile.Mapping{\n\t\t{\n\t\t\tID:              1,\n\t\t\tStart:           0x1000,\n\t\t\tLimit:           0x4000,\n\t\t\tFile:            \"/path/to/testbinary\",\n\t\t\tHasFunctions:    true,\n\t\t\tHasFilenames:    true,\n\t\t\tHasLineNumbers:  true,\n\t\t\tHasInlineFrames: true,\n\t\t},\n\t}\n\n\tvar longNameFuncsF = []*profile.Function{\n\t\t{ID: 1, Name: \"path/to/package1.object.function1\", SystemName: \"path/to/package1.object.function1\", Filename: \"path/to/package1.go\"},\n\t\t{ID: 2, Name: \"(anonymous namespace)::Bar::Foo\", SystemName: \"(anonymous namespace)::Bar::Foo\", Filename: \"a/long/path/to/package2.cc\"},\n\t\t{ID: 3, Name: \"java.bar.foo.FooBar.run(java.lang.Runnable)\", SystemName: \"java.bar.foo.FooBar.run(java.lang.Runnable)\", Filename: \"FooBar.java\"},\n\t}\n\n\tvar longNameFuncsL = []*profile.Location{\n\t\t{\n\t\t\tID:      1000,\n\t\t\tMapping: longNameFuncsM[0],\n\t\t\tAddress: 0x1000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: longNameFuncsF[0], Line: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      2000,\n\t\t\tMapping: longNameFuncsM[0],\n\t\t\tAddress: 0x2000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: longNameFuncsF[1], Line: 4},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3000,\n\t\t\tMapping: longNameFuncsM[0],\n\t\t\tAddress: 0x3000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: longNameFuncsF[2], Line: 9},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1], longNameFuncsL[2]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{longNameFuncsL[0], longNameFuncsL[1]},\n\t\t\t\tValue:    []int64{100, 100},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{longNameFuncsL[2]},\n\t\t\t\tValue:    []int64{10, 10},\n\t\t\t},\n\t\t},\n\t\tLocation: longNameFuncsL,\n\t\tFunction: longNameFuncsF,\n\t\tMapping:  longNameFuncsM,\n\t}\n}\n\nfunc cpuProfile() *profile.Profile {\n\tvar cpuM = []*profile.Mapping{\n\t\t{\n\t\t\tID:              1,\n\t\t\tStart:           0x1000,\n\t\t\tLimit:           0x4000,\n\t\t\tFile:            \"/path/to/testbinary\",\n\t\t\tHasFunctions:    true,\n\t\t\tHasFilenames:    true,\n\t\t\tHasLineNumbers:  true,\n\t\t\tHasInlineFrames: true,\n\t\t},\n\t}\n\n\tvar cpuF = []*profile.Function{\n\t\t{ID: 1, Name: \"mangled1000\", SystemName: \"mangled1000\", Filename: \"testdata/file1000.src\"},\n\t\t{ID: 2, Name: \"mangled2000\", SystemName: \"mangled2000\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 3, Name: \"mangled2001\", SystemName: \"mangled2001\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 4, Name: \"mangled3000\", SystemName: \"mangled3000\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 5, Name: \"mangled3001\", SystemName: \"mangled3001\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 6, Name: \"mangled3002\", SystemName: \"mangled3002\", Filename: \"testdata/file3000.src\"},\n\t}\n\n\tvar cpuL = []*profile.Location{\n\t\t{\n\t\t\tID:      1000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x1000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: cpuF[0], Line: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      2000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x2000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: cpuF[2], Line: 9},\n\t\t\t\t{Function: cpuF[1], Line: 4},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x3000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: cpuF[5], Line: 2},\n\t\t\t\t{Function: cpuF[4], Line: 5},\n\t\t\t\t{Function: cpuF[3], Line: 6},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3001,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x3001,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: cpuF[4], Line: 8},\n\t\t\t\t{Function: cpuF[3], Line: 9},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3002,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x3002,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: cpuF[5], Line: 5},\n\t\t\t\t{Function: cpuF[3], Line: 9},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[0], cpuL[3]},\n\t\t\t\tValue:    []int64{100, 100},\n\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[1], cpuL[4]},\n\t\t\t\tValue:    []int64{10, 10},\n\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[2]},\n\t\t\t\tValue:    []int64{10, 10},\n\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tLocation: cpuL,\n\t\tFunction: cpuF,\n\t\tMapping:  cpuM,\n\t}\n}\n\nfunc cpuProfileSmall() *profile.Profile {\n\tvar cpuM = []*profile.Mapping{\n\t\t{\n\t\t\tID:              1,\n\t\t\tStart:           0x1000,\n\t\t\tLimit:           0x4000,\n\t\t\tFile:            \"/path/to/testbinary\",\n\t\t\tHasFunctions:    true,\n\t\t\tHasFilenames:    true,\n\t\t\tHasLineNumbers:  true,\n\t\t\tHasInlineFrames: true,\n\t\t},\n\t}\n\n\tvar cpuL = []*profile.Location{\n\t\t{\n\t\t\tID:      1000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x1000,\n\t\t},\n\t\t{\n\t\t\tID:      2000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x2000,\n\t\t},\n\t\t{\n\t\t\tID:      3000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x3000,\n\t\t},\n\t\t{\n\t\t\tID:      4000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x4000,\n\t\t},\n\t\t{\n\t\t\tID:      5000,\n\t\t\tMapping: cpuM[0],\n\t\t\tAddress: 0x5000,\n\t\t},\n\t}\n\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[0], cpuL[1], cpuL[2]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[3], cpuL[1], cpuL[4]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[2]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{cpuL[4]},\n\t\t\t\tValue:    []int64{1000, 1000},\n\t\t\t},\n\t\t},\n\t\tLocation: cpuL,\n\t\tFunction: nil,\n\t\tMapping:  cpuM,\n\t}\n}\n\nfunc heapProfile() *profile.Profile {\n\tvar heapM = []*profile.Mapping{\n\t\t{\n\t\t\tID:              1,\n\t\t\tBuildID:         \"buildid\",\n\t\t\tStart:           0x1000,\n\t\t\tLimit:           0x4000,\n\t\t\tHasFunctions:    true,\n\t\t\tHasFilenames:    true,\n\t\t\tHasLineNumbers:  true,\n\t\t\tHasInlineFrames: true,\n\t\t},\n\t}\n\n\tvar heapF = []*profile.Function{\n\t\t{ID: 1, Name: \"pruneme\", SystemName: \"pruneme\", Filename: \"prune.h\"},\n\t\t{ID: 2, Name: \"mangled1000\", SystemName: \"mangled1000\", Filename: \"testdata/file1000.src\"},\n\t\t{ID: 3, Name: \"mangled2000\", SystemName: \"mangled2000\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 4, Name: \"mangled2001\", SystemName: \"mangled2001\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 5, Name: \"mangled3000\", SystemName: \"mangled3000\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 6, Name: \"mangled3001\", SystemName: \"mangled3001\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 7, Name: \"mangled3002\", SystemName: \"mangled3002\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 8, Name: \"mangledMALLOC\", SystemName: \"mangledMALLOC\", Filename: \"malloc.h\"},\n\t\t{ID: 9, Name: \"mangledNEW\", SystemName: \"mangledNEW\", Filename: \"new.h\"},\n\t}\n\n\tvar heapL = []*profile.Location{\n\t\t{\n\t\t\tID:      1000,\n\t\t\tMapping: heapM[0],\n\t\t\tAddress: 0x1000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: heapF[0], Line: 100},\n\t\t\t\t{Function: heapF[7], Line: 100},\n\t\t\t\t{Function: heapF[1], Line: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      2000,\n\t\t\tMapping: heapM[0],\n\t\t\tAddress: 0x2000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: heapF[8], Line: 100},\n\t\t\t\t{Function: heapF[3], Line: 2},\n\t\t\t\t{Function: heapF[2], Line: 3},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3000,\n\t\t\tMapping: heapM[0],\n\t\t\tAddress: 0x3000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: heapF[8], Line: 100},\n\t\t\t\t{Function: heapF[6], Line: 3},\n\t\t\t\t{Function: heapF[5], Line: 2},\n\t\t\t\t{Function: heapF[4], Line: 4},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3001,\n\t\t\tMapping: heapM[0],\n\t\t\tAddress: 0x3001,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: heapF[0], Line: 100},\n\t\t\t\t{Function: heapF[8], Line: 100},\n\t\t\t\t{Function: heapF[5], Line: 2},\n\t\t\t\t{Function: heapF[4], Line: 4},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3002,\n\t\t\tMapping: heapM[0],\n\t\t\tAddress: 0x3002,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: heapF[6], Line: 3},\n\t\t\t\t{Function: heapF[4], Line: 4},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn &profile.Profile{\n\t\tComments:   []string{\"comment\", \"#hidden comment\"},\n\t\tPeriodType: &profile.ValueType{Type: \"allocations\", Unit: \"bytes\"},\n\t\tPeriod:     524288,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"inuse_objects\", Unit: \"count\"},\n\t\t\t{Type: \"inuse_space\", Unit: \"bytes\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{heapL[0], heapL[1], heapL[2]},\n\t\t\t\tValue:    []int64{10, 1024000},\n\t\t\t\tNumLabel: map[string][]int64{\"bytes\": {102400}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{heapL[0], heapL[3]},\n\t\t\t\tValue:    []int64{20, 4096000},\n\t\t\t\tNumLabel: map[string][]int64{\"bytes\": {204800}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{heapL[1], heapL[4]},\n\t\t\t\tValue:    []int64{40, 65536000},\n\t\t\t\tNumLabel: map[string][]int64{\"bytes\": {1638400}},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{heapL[2]},\n\t\t\t\tValue:    []int64{80, 32768000},\n\t\t\t\tNumLabel: map[string][]int64{\"bytes\": {409600}},\n\t\t\t},\n\t\t},\n\t\tDropFrames: \".*operator new.*|malloc\",\n\t\tLocation:   heapL,\n\t\tFunction:   heapF,\n\t\tMapping:    heapM,\n\t}\n}\n\nfunc contentionProfile() *profile.Profile {\n\tvar contentionM = []*profile.Mapping{\n\t\t{\n\t\t\tID:              1,\n\t\t\tBuildID:         \"buildid-contention\",\n\t\t\tStart:           0x1000,\n\t\t\tLimit:           0x4000,\n\t\t\tHasFunctions:    true,\n\t\t\tHasFilenames:    true,\n\t\t\tHasLineNumbers:  true,\n\t\t\tHasInlineFrames: true,\n\t\t},\n\t}\n\n\tvar contentionF = []*profile.Function{\n\t\t{ID: 1, Name: \"mangled1000\", SystemName: \"mangled1000\", Filename: \"testdata/file1000.src\"},\n\t\t{ID: 2, Name: \"mangled2000\", SystemName: \"mangled2000\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 3, Name: \"mangled2001\", SystemName: \"mangled2001\", Filename: \"testdata/file2000.src\"},\n\t\t{ID: 4, Name: \"mangled3000\", SystemName: \"mangled3000\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 5, Name: \"mangled3001\", SystemName: \"mangled3001\", Filename: \"testdata/file3000.src\"},\n\t\t{ID: 6, Name: \"mangled3002\", SystemName: \"mangled3002\", Filename: \"testdata/file3000.src\"},\n\t}\n\n\tvar contentionL = []*profile.Location{\n\t\t{\n\t\t\tID:      1000,\n\t\t\tMapping: contentionM[0],\n\t\t\tAddress: 0x1000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: contentionF[0], Line: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      2000,\n\t\t\tMapping: contentionM[0],\n\t\t\tAddress: 0x2000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: contentionF[2], Line: 2},\n\t\t\t\t{Function: contentionF[1], Line: 3},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3000,\n\t\t\tMapping: contentionM[0],\n\t\t\tAddress: 0x3000,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: contentionF[5], Line: 2},\n\t\t\t\t{Function: contentionF[4], Line: 3},\n\t\t\t\t{Function: contentionF[3], Line: 5},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3001,\n\t\t\tMapping: contentionM[0],\n\t\t\tAddress: 0x3001,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: contentionF[4], Line: 3},\n\t\t\t\t{Function: contentionF[3], Line: 5},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3002,\n\t\t\tMapping: contentionM[0],\n\t\t\tAddress: 0x3002,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: contentionF[5], Line: 4},\n\t\t\t\t{Function: contentionF[3], Line: 3},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn &profile.Profile{\n\t\tPeriodType: &profile.ValueType{Type: \"contentions\", Unit: \"count\"},\n\t\tPeriod:     524288,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"contentions\", Unit: \"count\"},\n\t\t\t{Type: \"delay\", Unit: \"nanoseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{contentionL[0], contentionL[1], contentionL[2]},\n\t\t\t\tValue:    []int64{10, 10240000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{contentionL[0], contentionL[3]},\n\t\t\t\tValue:    []int64{20, 40960000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{contentionL[1], contentionL[4]},\n\t\t\t\tValue:    []int64{40, 65536000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{contentionL[2]},\n\t\t\t\tValue:    []int64{80, 32768000},\n\t\t\t},\n\t\t},\n\t\tLocation: contentionL,\n\t\tFunction: contentionF,\n\t\tMapping:  contentionM,\n\t\tComments: []string{\"Comment #1\", \"Comment #2\"},\n\t}\n}\n\nfunc symzProfile() *profile.Profile {\n\tvar symzM = []*profile.Mapping{\n\t\t{\n\t\t\tID:    1,\n\t\t\tStart: testStart,\n\t\t\tLimit: 0x4000,\n\t\t\tFile:  \"/path/to/testbinary\",\n\t\t},\n\t}\n\n\tvar symzL = []*profile.Location{\n\t\t{ID: 1, Mapping: symzM[0], Address: testStart},\n\t\t{ID: 2, Mapping: symzM[0], Address: testStart + 0x1000},\n\t\t{ID: 3, Mapping: symzM[0], Address: testStart + 0x2000},\n\t}\n\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{symzL[0], symzL[1], symzL[2]},\n\t\t\t\tValue:    []int64{1, 1},\n\t\t\t},\n\t\t},\n\t\tLocation: symzL,\n\t\tMapping:  symzM,\n\t}\n}\n\nfunc largeProfile(tb testing.TB) *profile.Profile {\n\ttb.Helper()\n\tinput := proftest.LargeProfile(tb)\n\tprof, err := profile.Parse(bytes.NewBuffer(input))\n\tif err != nil {\n\t\ttb.Fatal(err)\n\t}\n\treturn prof\n}\n\nvar autoCompleteTests = []struct {\n\tin  string\n\tout string\n}{\n\t{\"\", \"\"},\n\t{\"xyz\", \"xyz\"},                        // no match\n\t{\"dis\", \"disasm\"},                     // single match\n\t{\"t\", \"t\"},                            // many matches\n\t{\"top abc\", \"top abc\"},                // no function name match\n\t{\"top mangledM\", \"top mangledMALLOC\"}, // single function name match\n\t{\"top cmd mangledM\", \"top cmd mangledMALLOC\"},\n\t{\"top mangled\", \"top mangled\"},                      // many function name matches\n\t{\"cmd mangledM\", \"cmd mangledM\"},                    // invalid command\n\t{\"top mangledM cmd\", \"top mangledM cmd\"},            // cursor misplaced\n\t{\"top edMA\", \"top mangledMALLOC\"},                   // single infix function name match\n\t{\"top -mangledM\", \"top -mangledMALLOC\"},             // ignore sign handled\n\t{\"lin\", \"lines\"},                                    // single variable match\n\t{\"EdGeF\", \"edgefraction\"},                           // single capitalized match\n\t{\"help dis\", \"help disasm\"},                         // help command match\n\t{\"help relative_perc\", \"help relative_percentages\"}, // help variable match\n\t{\"help coMpa\", \"help compact_labels\"},               // help variable capitalized match\n}\n\nfunc TestAutoComplete(t *testing.T) {\n\tcomplete := newCompleter(functionNames(heapProfile()))\n\n\tfor _, test := range autoCompleteTests {\n\t\tif out := complete(test.in); out != test.out {\n\t\t\tt.Errorf(\"autoComplete(%s) = %s; want %s\", test.in, out, test.out)\n\t\t}\n\t}\n}\n\nfunc TestTagFilter(t *testing.T) {\n\tvar tagFilterTests = []struct {\n\t\tdesc, value string\n\t\ttags        map[string][]string\n\t\twant        bool\n\t}{\n\t\t{\n\t\t\t\"1 key with 1 matching value\",\n\t\t\t\"tag2\",\n\t\t\tmap[string][]string{\"value1\": {\"tag1\", \"tag2\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"1 key with no matching values\",\n\t\t\t\"tag3\",\n\t\t\tmap[string][]string{\"value1\": {\"tag1\", \"tag2\"}},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"two keys, each with value matching different one value in list\",\n\t\t\t\"tag1,tag3\",\n\t\t\tmap[string][]string{\"value1\": {\"tag1\", \"tag2\"}, \"value2\": {\"tag3\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\"two keys, all value matching different regex value in list\",\n\t\t\t\"t..[12],t..3\",\n\t\t\tmap[string][]string{\"value1\": {\"tag1\", \"tag2\"}, \"value2\": {\"tag3\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"one key, not all values in list matched\",\n\t\t\t\"tag2,tag3\",\n\t\t\tmap[string][]string{\"value1\": {\"tag1\", \"tag2\"}},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"key specified, list of tags where all tags in list matched\",\n\t\t\t\"key1=tag1,tag2\",\n\t\t\tmap[string][]string{\"key1\": {\"tag1\", \"tag2\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\"key specified, list of tag values where not all are matched\",\n\t\t\t\"key1=tag1,tag2\",\n\t\t\tmap[string][]string{\"key1\": {\"tag1\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"key included for regex matching, list of values where all values in list matched\",\n\t\t\t\"key1:tag1,tag2\",\n\t\t\tmap[string][]string{\"key1\": {\"tag1\", \"tag2\"}},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"key included for regex matching, list of values where not only second value matched\",\n\t\t\t\"key1:tag1,tag2\",\n\t\t\tmap[string][]string{\"key1\": {\"tag2\"}},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"key included for regex matching, list of values where not only first value matched\",\n\t\t\t\"key1:tag1,tag2\",\n\t\t\tmap[string][]string{\"key1\": {\"tag1\"}},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, test := range tagFilterTests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tfilter, err := compileTagFilter(test.desc, test.value, nil, &proftest.TestUI{T: t}, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"tagFilter %s:%v\", test.desc, err)\n\t\t\t}\n\t\t\ts := profile.Sample{\n\t\t\t\tLabel: test.tags,\n\t\t\t}\n\t\t\tif got := filter(&s); got != test.want {\n\t\t\t\tt.Errorf(\"tagFilter %s: got %v, want %v\", test.desc, got, test.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIdentifyNumLabelUnits(t *testing.T) {\n\tvar tagFilterTests = []struct {\n\t\tdesc               string\n\t\ttagVals            []map[string][]int64\n\t\ttagUnits           []map[string][]string\n\t\twantUnits          map[string]string\n\t\tallowedRx          string\n\t\twantIgnoreErrCount int\n\t}{\n\t\t{\n\t\t\t\"Multiple keys, no units for all keys\",\n\t\t\t[]map[string][]int64{{\"keyA\": {131072}, \"keyB\": {128}}},\n\t\t\t[]map[string][]string{{\"keyA\": {}, \"keyB\": {\"\"}}},\n\t\t\tmap[string]string{\"keyA\": \"keyA\", \"keyB\": \"keyB\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Multiple keys, different units for each key\",\n\t\t\t[]map[string][]int64{{\"keyA\": {131072}, \"keyB\": {128}}},\n\t\t\t[]map[string][]string{{\"keyA\": {\"bytes\"}, \"keyB\": {\"kilobytes\"}}},\n\t\t\tmap[string]string{\"keyA\": \"bytes\", \"keyB\": \"kilobytes\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Multiple keys with multiple values, different units for each key\",\n\t\t\t[]map[string][]int64{{\"keyC\": {131072, 1}, \"keyD\": {128, 252}}},\n\t\t\t[]map[string][]string{{\"keyC\": {\"bytes\", \"bytes\"}, \"keyD\": {\"kilobytes\", \"kilobytes\"}}},\n\t\t\tmap[string]string{\"keyC\": \"bytes\", \"keyD\": \"kilobytes\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Multiple keys with multiple values, some units missing\",\n\t\t\t[]map[string][]int64{{\"key1\": {131072, 1}, \"A\": {128, 252}, \"key3\": {128}, \"key4\": {1}}, {\"key3\": {128}, \"key4\": {1}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"\", \"bytes\"}, \"A\": {\"kilobytes\", \"\"}, \"key3\": {\"\"}, \"key4\": {\"hour\"}}, {\"key3\": {\"seconds\"}, \"key4\": {\"\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"A\": \"kilobytes\", \"key3\": \"seconds\", \"key4\": \"hour\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"One key with three units in same sample\",\n\t\t\t[]map[string][]int64{{\"key\": {8, 8, 16}}},\n\t\t\t[]map[string][]string{{\"key\": {\"bytes\", \"megabytes\", \"kilobytes\"}}},\n\t\t\tmap[string]string{\"key\": \"bytes\"},\n\t\t\t`(For tag key used unit bytes, also encountered unit\\(s\\) kilobytes, megabytes)`,\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t\"One key with four units in same sample\",\n\t\t\t[]map[string][]int64{{\"key\": {8, 8, 16, 32}}},\n\t\t\t[]map[string][]string{{\"key\": {\"bytes\", \"kilobytes\", \"a\", \"megabytes\"}}},\n\t\t\tmap[string]string{\"key\": \"bytes\"},\n\t\t\t`(For tag key used unit bytes, also encountered unit\\(s\\) a, kilobytes, megabytes)`,\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t\"One key with two units in same sample\",\n\t\t\t[]map[string][]int64{{\"key\": {8, 8}}},\n\t\t\t[]map[string][]string{{\"key\": {\"bytes\", \"seconds\"}}},\n\t\t\tmap[string]string{\"key\": \"bytes\"},\n\t\t\t`(For tag key used unit bytes, also encountered unit\\(s\\) seconds)`,\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t\"One key with different units in different samples\",\n\t\t\t[]map[string][]int64{{\"key1\": {8}}, {\"key1\": {8}}, {\"key1\": {8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\"}}, {\"key1\": {\"kilobytes\"}}, {\"key1\": {\"megabytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\t`(For tag key1 used unit bytes, also encountered unit\\(s\\) kilobytes, megabytes)`,\n\t\t\t1,\n\t\t},\n\t\t{\n\t\t\t\"Key alignment, unit not specified\",\n\t\t\t[]map[string][]int64{{\"alignment\": {8}}},\n\t\t\t[]map[string][]string{nil},\n\t\t\tmap[string]string{\"alignment\": \"bytes\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Key request, unit not specified\",\n\t\t\t[]map[string][]int64{{\"request\": {8}}, {\"request\": {8, 8}}},\n\t\t\t[]map[string][]string{nil, nil},\n\t\t\tmap[string]string{\"request\": \"bytes\"},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"Check units not over-written for keys with default units\",\n\t\t\t[]map[string][]int64{{\n\t\t\t\t\"alignment\": {8},\n\t\t\t\t\"request\":   {8},\n\t\t\t\t\"bytes\":     {8},\n\t\t\t}},\n\t\t\t[]map[string][]string{{\n\t\t\t\t\"alignment\": {\"seconds\"},\n\t\t\t\t\"request\":   {\"minutes\"},\n\t\t\t\t\"bytes\":     {\"hours\"},\n\t\t\t}},\n\t\t\tmap[string]string{\n\t\t\t\t\"alignment\": \"seconds\",\n\t\t\t\t\"request\":   \"minutes\",\n\t\t\t\t\"bytes\":     \"hours\",\n\t\t\t},\n\t\t\t\"\",\n\t\t\t0,\n\t\t},\n\t}\n\tfor _, test := range tagFilterTests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\tp := profile.Profile{Sample: make([]*profile.Sample, len(test.tagVals))}\n\t\t\tfor i, numLabel := range test.tagVals {\n\t\t\t\ts := profile.Sample{\n\t\t\t\t\tNumLabel: numLabel,\n\t\t\t\t\tNumUnit:  test.tagUnits[i],\n\t\t\t\t}\n\t\t\t\tp.Sample[i] = &s\n\t\t\t}\n\t\t\ttestUI := &proftest.TestUI{T: t, AllowRx: test.allowedRx}\n\t\t\tunits := identifyNumLabelUnits(&p, testUI)\n\t\t\tif !reflect.DeepEqual(test.wantUnits, units) {\n\t\t\t\tt.Errorf(\"got %v units, want %v\", units, test.wantUnits)\n\t\t\t}\n\t\t\tif got, want := testUI.NumAllowRxMatches, test.wantIgnoreErrCount; want != got {\n\t\t\t\tt.Errorf(\"got %d errors logged, want %d errors logged\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNumericTagFilter(t *testing.T) {\n\tvar tagFilterTests = []struct {\n\t\tdesc, value     string\n\t\ttags            map[string][]int64\n\t\tidentifiedUnits map[string]string\n\t\twant            bool\n\t}{\n\t\t{\n\t\t\t\"Match when unit conversion required\",\n\t\t\t\"128kb\",\n\t\t\tmap[string][]int64{\"key1\": {131072}, \"key2\": {128}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"key2\": \"kilobytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match only when values equal after unit conversion\",\n\t\t\t\"512kb\",\n\t\t\tmap[string][]int64{\"key1\": {512}, \"key2\": {128}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"key2\": \"kilobytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match when values and units initially equal\",\n\t\t\t\"10bytes\",\n\t\t\tmap[string][]int64{\"key1\": {10}, \"key2\": {128}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"key2\": \"kilobytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match range without lower bound, no unit conversion required\",\n\t\t\t\":10bytes\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match range without lower bound, unit conversion required\",\n\t\t\t\":10kb\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match range without upper bound, unit conversion required\",\n\t\t\t\"10b:\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"kilobytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match range without upper bound, no unit conversion required\",\n\t\t\t\"10b:\",\n\t\t\tmap[string][]int64{\"key1\": {12}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Don't match range without upper bound, no unit conversion required\",\n\t\t\t\"10b:\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Multiple keys with different units, don't match range without upper bound\",\n\t\t\t\"10kb:\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"key2\": \"kilobytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match range without upper bound, unit conversion required\",\n\t\t\t\"10b:\",\n\t\t\tmap[string][]int64{\"key1\": {8}},\n\t\t\tmap[string]string{\"key1\": \"kilobytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Don't match range without lower bound, no unit conversion required\",\n\t\t\t\":10b\",\n\t\t\tmap[string][]int64{\"key1\": {12}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key, key present, one of two values match\",\n\t\t\t\"bytes=5b\",\n\t\t\tmap[string][]int64{\"bytes\": {10, 5}},\n\t\t\tmap[string]string{\"bytes\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key, key present and value matches\",\n\t\t\t\"bytes=1024b\",\n\t\t\tmap[string][]int64{\"bytes\": {1024}},\n\t\t\tmap[string]string{\"bytes\": \"kilobytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key, matching key present and value matches, also non-matching key\",\n\t\t\t\"bytes=1024b\",\n\t\t\tmap[string][]int64{\"bytes\": {1024}, \"key2\": {5}},\n\t\t\tmap[string]string{\"bytes\": \"bytes\", \"key2\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key and range of values, value matches\",\n\t\t\t\"bytes=512b:1024b\",\n\t\t\tmap[string][]int64{\"bytes\": {780}},\n\t\t\tmap[string]string{\"bytes\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key and range of values, value too large\",\n\t\t\t\"key1=1kb:2kb\",\n\t\t\tmap[string][]int64{\"key1\": {4096}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key and range of values, value too small\",\n\t\t\t\"key1=1kb:2kb\",\n\t\t\tmap[string][]int64{\"key1\": {256}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key and value, unit conversion required\",\n\t\t\t\"bytes=1024b\",\n\t\t\tmap[string][]int64{\"bytes\": {1}},\n\t\t\tmap[string]string{\"bytes\": \"kilobytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match specific key and value, key does not appear\",\n\t\t\t\"key2=256bytes\",\n\t\t\tmap[string][]int64{\"key1\": {256}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match negative key and range of values, value matches\",\n\t\t\t\"bytes=-512b:-128b\",\n\t\t\tmap[string][]int64{\"bytes\": {-256}},\n\t\t\tmap[string]string{\"bytes\": \"bytes\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match negative key and range of values, value outside range\",\n\t\t\t\"bytes=-512b:-128b\",\n\t\t\tmap[string][]int64{\"bytes\": {-2048}},\n\t\t\tmap[string]string{\"bytes\": \"bytes\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match exact value, unitless tag\",\n\t\t\t\"pid=123\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Match range, unitless tag\",\n\t\t\t\"pid=123:123\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Don't match range, unitless tag\",\n\t\t\t\"pid=124:124\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match range without upper bound, unitless tag\",\n\t\t\t\"pid=100:\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Don't match range without upper bound, unitless tag\",\n\t\t\t\"pid=200:\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Match range without lower bound, unitless tag\",\n\t\t\t\"pid=:200\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Don't match range without lower bound, unitless tag\",\n\t\t\t\"pid=:100\",\n\t\t\tmap[string][]int64{\"pid\": {123}},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, test := range tagFilterTests {\n\t\tt.Run(test.desc, func(t *testing.T) {\n\t\t\twantErrMsg := strings.Join([]string{\"(\", test.desc, \":Interpreted '\", test.value[strings.Index(test.value, \"=\")+1:], \"' as range, not regexp\", \")\"}, \"\")\n\t\t\tfilter, err := compileTagFilter(test.desc, test.value, test.identifiedUnits, &proftest.TestUI{T: t,\n\t\t\t\tAllowRx: wantErrMsg}, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"%v\", err)\n\t\t\t}\n\t\t\ts := profile.Sample{\n\t\t\t\tNumLabel: test.tags,\n\t\t\t}\n\t\t\tif got := filter(&s); got != test.want {\n\t\t\t\tt.Fatalf(\"got %v, want %v\", got, test.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestOptionsHaveHelp tests that a help message is supplied for every\n// selectable option.\nfunc TestOptionsHaveHelp(t *testing.T) {\n\tfor _, f := range configFields {\n\t\t// Check all choices if this is a group, else check f.name.\n\t\tnames := f.choices\n\t\tif len(names) == 0 {\n\t\t\tnames = []string{f.name}\n\t\t}\n\t\tfor _, name := range names {\n\t\t\tif _, ok := configHelp[name]; !ok {\n\t\t\t\tt.Errorf(\"missing help message for %q\", name)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype testSymbolzMergeFetcher struct{}\n\nfunc (testSymbolzMergeFetcher) Fetch(s string, d, t time.Duration) (*profile.Profile, string, error) {\n\tvar p *profile.Profile\n\tswitch s {\n\tcase testSourceURL(8000) + \"symbolz\":\n\t\tp = symzProfile()\n\tcase testSourceURL(8001) + \"symbolz\":\n\t\tp = symzProfile()\n\t\tp.Mapping[0].Start += testOffset\n\t\tp.Mapping[0].Limit += testOffset\n\t\tfor i := range p.Location {\n\t\t\tp.Location[i].Address += testOffset\n\t\t}\n\tdefault:\n\t\treturn nil, \"\", fmt.Errorf(\"unexpected source: %s\", s)\n\t}\n\treturn p, s, nil\n}\n\nfunc TestSymbolzAfterMerge(t *testing.T) {\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\n\tf := baseFlags()\n\tf.args = []string{\n\t\ttestSourceURL(8000) + \"symbolz\",\n\t\ttestSourceURL(8001) + \"symbolz\",\n\t}\n\n\to := setDefaults(nil)\n\to.Flagset = f\n\to.Obj = new(mockObjTool)\n\tsrc, cmd, err := parseFlags(o)\n\tif err != nil {\n\t\tt.Fatalf(\"parseFlags: %v\", err)\n\t}\n\n\tif len(cmd) != 1 || cmd[0] != \"proto\" {\n\t\tt.Fatalf(\"parseFlags returned command %v, want [proto]\", cmd)\n\t}\n\n\to.Fetch = testSymbolzMergeFetcher{}\n\to.Sym = testSymbolzSymbolizer{}\n\tp, err := fetchProfiles(src, o)\n\tif err != nil {\n\t\tt.Fatalf(\"fetchProfiles: %v\", err)\n\t}\n\tif len(p.Location) != 3 {\n\t\tt.Errorf(\"Got %d locations after merge, want %d\", len(p.Location), 3)\n\t}\n\tfor i, l := range p.Location {\n\t\tif len(l.Line) != 1 {\n\t\t\tt.Errorf(\"Number of lines for symbolz %#x in iteration %d, got %d, want %d\", l.Address, i, len(l.Line), 1)\n\t\t\tcontinue\n\t\t}\n\t\taddress := l.Address - l.Mapping.Start\n\t\tif got, want := l.Line[0].Function.Name, fmt.Sprintf(\"%#x\", address); got != want {\n\t\t\tt.Errorf(\"symbolz %#x, got %s, want %s\", address, got, want)\n\t\t}\n\t}\n}\n\nfunc TestProfileCopier(t *testing.T) {\n\ttype testCase struct {\n\t\tname string\n\t\tprof *profile.Profile\n\t}\n\tfor _, c := range []testCase{\n\t\t{\"cpu\", cpuProfile()},\n\t\t{\"heap\", heapProfile()},\n\t\t{\"contention\", contentionProfile()},\n\t\t{\"symbolz\", symzProfile()},\n\t\t{\"long_name_funcs\", longNameFuncsProfile()},\n\t\t{\"large\", largeProfile(t)},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tcopier := makeProfileCopier(c.prof)\n\n\t\t\t// Muck with one copy to check that fresh copies are unaffected\n\t\t\ttmp := copier.newCopy()\n\t\t\ttmp.Sample = tmp.Sample[:0]\n\n\t\t\t// Get new copy and check it is same as the original.\n\t\t\twant := c.prof.String()\n\t\t\tgot := copier.newCopy().String()\n\t\t\tif got != want {\n\t\t\t\tt.Errorf(\"New copy is not same as original profile\")\n\t\t\t\tdiff, err := proftest.Diff([]byte(want), []byte(got))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Diff: %v\", err)\n\t\t\t\t}\n\t\t\t\tt.Logf(\"Diff:\\n%s\\n\", string(diff))\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockObjTool struct{}\n\nfunc (*mockObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\treturn &mockFile{file, \"abcdef\", 0}, nil\n}\n\nfunc (m *mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\tconst fn1 = \"line1000\"\n\tconst fn3 = \"line3000\"\n\tconst file1 = \"testdata/file1000.src\"\n\tconst file3 = \"testdata/file3000.src\"\n\tdata := []plugin.Inst{\n\t\t{Addr: 0x1000, Text: \"instruction one\", Function: fn1, File: file1, Line: 1},\n\t\t{Addr: 0x1001, Text: \"instruction two\", Function: fn1, File: file1, Line: 1},\n\t\t{Addr: 0x1002, Text: \"instruction three\", Function: fn1, File: file1, Line: 2},\n\t\t{Addr: 0x1003, Text: \"instruction four\", Function: fn1, File: file1, Line: 1},\n\t\t{Addr: 0x3000, Text: \"instruction one\", Function: fn3, File: file3},\n\t\t{Addr: 0x3001, Text: \"instruction two\", Function: fn3, File: file3},\n\t\t{Addr: 0x3002, Text: \"instruction three\", Function: fn3, File: file3},\n\t\t{Addr: 0x3003, Text: \"instruction four\", Function: fn3, File: file3},\n\t\t{Addr: 0x3004, Text: \"instruction five\", Function: fn3, File: file3},\n\t}\n\tvar result []plugin.Inst\n\tfor _, inst := range data {\n\t\tif inst.Addr >= start && inst.Addr <= end {\n\t\t\tresult = append(result, inst)\n\t\t}\n\t}\n\treturn result, nil\n}\n\ntype mockFile struct {\n\tname, buildID string\n\tbase          uint64\n}\n\n// Name returns the underlyinf file name, if available\nfunc (m *mockFile) Name() string {\n\treturn m.name\n}\n\n// ObjAddr returns the objdump address corresponding to a runtime address.\nfunc (m *mockFile) ObjAddr(addr uint64) (uint64, error) {\n\treturn addr - m.base, nil\n}\n\n// BuildID returns the GNU build ID of the file, or an empty string.\nfunc (m *mockFile) BuildID() string {\n\treturn m.buildID\n}\n\n// SourceLine reports the source line information for a given\n// address in the file. Due to inlining, the source line information\n// is in general a list of positions representing a call stack,\n// with the leaf function first.\nfunc (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\t// Return enough data to support the SourceLine() calls needed for\n\t// weblist on cpuProfile() contents.\n\tframe := func(fn, file string, num int) plugin.Frame {\n\t\t// Reuse the same num for line number and column number.\n\t\treturn plugin.Frame{Func: fn, File: file, Line: num, Column: num}\n\t}\n\tswitch addr {\n\tcase 0x1000:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled1000\", \"testdata/file1000.src\", 1),\n\t\t}, nil\n\tcase 0x1001:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled1000\", \"testdata/file1000.src\", 1),\n\t\t}, nil\n\tcase 0x1002:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled1000\", \"testdata/file1000.src\", 2),\n\t\t}, nil\n\tcase 0x1003:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled1000\", \"testdata/file1000.src\", 1),\n\t\t}, nil\n\tcase 0x2000:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled2001\", \"testdata/file2000.src\", 9),\n\t\t\tframe(\"mangled2000\", \"testdata/file2000.src\", 4),\n\t\t}, nil\n\tcase 0x3000:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled3002\", \"testdata/file3000.src\", 2),\n\t\t\tframe(\"mangled3001\", \"testdata/file3000.src\", 5),\n\t\t\tframe(\"mangled3000\", \"testdata/file3000.src\", 6),\n\t\t}, nil\n\tcase 0x3001:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled3001\", \"testdata/file3000.src\", 8),\n\t\t\tframe(\"mangled3000\", \"testdata/file3000.src\", 9),\n\t\t}, nil\n\tcase 0x3002:\n\t\treturn []plugin.Frame{\n\t\t\tframe(\"mangled3002\", \"testdata/file3000.src\", 5),\n\t\t\tframe(\"mangled3000\", \"testdata/file3000.src\", 9),\n\t\t}, nil\n\t}\n\n\treturn nil, nil\n}\n\n// Symbols returns a list of symbols in the object file.\n// If r is not nil, Symbols restricts the list to symbols\n// with names matching the regular expression.\n// If addr is not zero, Symbols restricts the list to symbols\n// containing that address.\nfunc (m *mockFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {\n\tswitch r.String() {\n\tcase \"line[13]\":\n\t\treturn []*plugin.Sym{\n\t\t\t{\n\t\t\t\tName: []string{\"line1000\"}, File: m.name,\n\t\t\t\tStart: 0x1000, End: 0x1003,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: []string{\"line3000\"}, File: m.name,\n\t\t\t\tStart: 0x3000, End: 0x3004,\n\t\t\t},\n\t\t}, nil\n\t}\n\treturn nil, fmt.Errorf(\"unimplemented\")\n}\n\n// Close closes the file, releasing associated resources.\nfunc (*mockFile) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "internal/driver/fetch.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// fetchProfiles fetches and symbolizes the profiles specified by s.\n// It will merge all the profiles it is able to retrieve, even if\n// there are some failures. It will return an error if it is unable to\n// fetch any profiles.\nfunc fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {\n\tsources := make([]profileSource, 0, len(s.Sources))\n\tfor _, src := range s.Sources {\n\t\tsources = append(sources, profileSource{\n\t\t\taddr:   src,\n\t\t\tsource: s,\n\t\t})\n\t}\n\n\tbases := make([]profileSource, 0, len(s.Base))\n\tfor _, src := range s.Base {\n\t\tbases = append(bases, profileSource{\n\t\t\taddr:   src,\n\t\t\tsource: s,\n\t\t})\n\t}\n\n\tp, pbase, m, mbase, save, err := grabSourcesAndBases(sources, bases, o.Fetch, o.Obj, o.UI, o.HTTPTransport)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif pbase != nil {\n\t\tif s.DiffBase {\n\t\t\tpbase.SetLabel(\"pprof::base\", []string{\"true\"})\n\t\t}\n\t\tif s.Normalize {\n\t\t\terr := p.Normalize(pbase)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tpbase.Scale(-1)\n\t\tp, m, err = combineProfiles([]*profile.Profile{p, pbase}, []plugin.MappingSources{m, mbase})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif s.AllFrames {\n\t\tp.DropFrames = \"\"\n\t\tp.KeepFrames = \"\"\n\t}\n\n\t// Symbolize the merged profile.\n\tif err := o.Sym.Symbolize(s.Symbolize, m, p); err != nil {\n\t\treturn nil, err\n\t}\n\tp.RemoveUninteresting()\n\tunsourceMappings(p)\n\n\tif s.Comment != \"\" {\n\t\tp.Comments = append(p.Comments, s.Comment)\n\t}\n\n\t// Save a copy of the merged profile if there is at least one remote source.\n\tif save {\n\t\tdir, err := setTmpDir(o.UI)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tprefix := \"pprof.\"\n\t\tif len(p.Mapping) > 0 && p.Mapping[0].File != \"\" {\n\t\t\tprefix += filepath.Base(p.Mapping[0].File) + \".\"\n\t\t}\n\t\tfor _, s := range p.SampleType {\n\t\t\tprefix += s.Type + \".\"\n\t\t}\n\n\t\ttempFile, err := newTempFile(dir, prefix, \".pb.gz\")\n\t\tif err == nil {\n\t\t\tif err = p.Write(tempFile); err == nil {\n\t\t\t\to.UI.PrintErr(\"Saved profile in \", tempFile.Name())\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\to.UI.PrintErr(\"Could not save profile: \", err)\n\t\t}\n\t}\n\n\tif err := p.CheckValid(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\nfunc grabSourcesAndBases(sources, bases []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, *profile.Profile, plugin.MappingSources, plugin.MappingSources, bool, error) {\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tvar psrc, pbase *profile.Profile\n\tvar msrc, mbase plugin.MappingSources\n\tvar savesrc, savebase bool\n\tvar errsrc, errbase error\n\tvar countsrc, countbase int\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tpsrc, msrc, savesrc, countsrc, errsrc = chunkedGrab(sources, fetch, obj, ui, tr)\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tpbase, mbase, savebase, countbase, errbase = chunkedGrab(bases, fetch, obj, ui, tr)\n\t}()\n\twg.Wait()\n\tsave := savesrc || savebase\n\n\tif errsrc != nil {\n\t\treturn nil, nil, nil, nil, false, fmt.Errorf(\"problem fetching source profiles: %v\", errsrc)\n\t}\n\tif errbase != nil {\n\t\treturn nil, nil, nil, nil, false, fmt.Errorf(\"problem fetching base profiles: %v,\", errbase)\n\t}\n\tif countsrc == 0 {\n\t\treturn nil, nil, nil, nil, false, fmt.Errorf(\"failed to fetch any source profiles\")\n\t}\n\tif countbase == 0 && len(bases) > 0 {\n\t\treturn nil, nil, nil, nil, false, fmt.Errorf(\"failed to fetch any base profiles\")\n\t}\n\tif want, got := len(sources), countsrc; want != got {\n\t\tui.PrintErr(fmt.Sprintf(\"Fetched %d source profiles out of %d\", got, want))\n\t}\n\tif want, got := len(bases), countbase; want != got {\n\t\tui.PrintErr(fmt.Sprintf(\"Fetched %d base profiles out of %d\", got, want))\n\t}\n\n\treturn psrc, pbase, msrc, mbase, save, nil\n}\n\n// chunkedGrab fetches the profiles described in source and merges them into\n// a single profile. It fetches a chunk of profiles concurrently, with a maximum\n// chunk size to limit its memory usage.\nfunc chunkedGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {\n\tconst chunkSize = 128\n\n\tvar p *profile.Profile\n\tvar msrc plugin.MappingSources\n\tvar save bool\n\tvar count int\n\n\tfor start := 0; start < len(sources); start += chunkSize {\n\t\tend := min(start+chunkSize, len(sources))\n\t\tchunkP, chunkMsrc, chunkSave, chunkCount, chunkErr := concurrentGrab(sources[start:end], fetch, obj, ui, tr)\n\t\tswitch {\n\t\tcase chunkErr != nil:\n\t\t\treturn nil, nil, false, 0, chunkErr\n\t\tcase chunkP == nil:\n\t\t\tcontinue\n\t\tcase p == nil:\n\t\t\tp, msrc, save, count = chunkP, chunkMsrc, chunkSave, chunkCount\n\t\tdefault:\n\t\t\tp, msrc, chunkErr = combineProfiles([]*profile.Profile{p, chunkP}, []plugin.MappingSources{msrc, chunkMsrc})\n\t\t\tif chunkErr != nil {\n\t\t\t\treturn nil, nil, false, 0, chunkErr\n\t\t\t}\n\t\t\tif chunkSave {\n\t\t\t\tsave = true\n\t\t\t}\n\t\t\tcount += chunkCount\n\t\t}\n\t}\n\n\treturn p, msrc, save, count, nil\n}\n\n// concurrentGrab fetches multiple profiles concurrently\nfunc concurrentGrab(sources []profileSource, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (*profile.Profile, plugin.MappingSources, bool, int, error) {\n\twg := sync.WaitGroup{}\n\twg.Add(len(sources))\n\tfor i := range sources {\n\t\tgo func(s *profileSource) {\n\t\t\tdefer wg.Done()\n\t\t\ts.p, s.msrc, s.remote, s.err = grabProfile(s.source, s.addr, fetch, obj, ui, tr)\n\t\t}(&sources[i])\n\t}\n\twg.Wait()\n\n\tvar save bool\n\tprofiles := make([]*profile.Profile, 0, len(sources))\n\tmsrcs := make([]plugin.MappingSources, 0, len(sources))\n\tfor i := range sources {\n\t\ts := &sources[i]\n\t\tif err := s.err; err != nil {\n\t\t\tui.PrintErr(s.addr + \": \" + err.Error())\n\t\t\tcontinue\n\t\t}\n\t\tsave = save || s.remote\n\t\tprofiles = append(profiles, s.p)\n\t\tmsrcs = append(msrcs, s.msrc)\n\t\t*s = profileSource{}\n\t}\n\n\tif len(profiles) == 0 {\n\t\treturn nil, nil, false, 0, nil\n\t}\n\n\tp, msrc, err := combineProfiles(profiles, msrcs)\n\tif err != nil {\n\t\treturn nil, nil, false, 0, err\n\t}\n\treturn p, msrc, save, len(profiles), nil\n}\n\nfunc combineProfiles(profiles []*profile.Profile, msrcs []plugin.MappingSources) (*profile.Profile, plugin.MappingSources, error) {\n\t// Merge profiles.\n\t//\n\t// The merge call below only treats exactly matching sample type lists as\n\t// compatible and will fail otherwise. Make the profiles' sample types\n\t// compatible for the merge, see CompatibilizeSampleTypes() doc for details.\n\tif err := profile.CompatibilizeSampleTypes(profiles); err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif err := measurement.ScaleProfiles(profiles); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Avoid expensive work for the common case of a single profile/src.\n\tif len(profiles) == 1 && len(msrcs) == 1 {\n\t\treturn profiles[0], msrcs[0], nil\n\t}\n\n\tp, err := profile.Merge(profiles)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// Combine mapping sources.\n\tmsrc := make(plugin.MappingSources)\n\tfor _, ms := range msrcs {\n\t\tfor m, s := range ms {\n\t\t\tmsrc[m] = append(msrc[m], s...)\n\t\t}\n\t}\n\treturn p, msrc, nil\n}\n\ntype profileSource struct {\n\taddr   string\n\tsource *source\n\n\tp      *profile.Profile\n\tmsrc   plugin.MappingSources\n\tremote bool\n\terr    error\n}\n\nfunc homeEnv() string {\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\treturn \"USERPROFILE\"\n\tcase \"plan9\":\n\t\treturn \"home\"\n\tdefault:\n\t\treturn \"HOME\"\n\t}\n}\n\n// setTmpDir prepares the directory to use to save profiles retrieved\n// remotely. It is selected from PPROF_TMPDIR, defaults to $HOME/pprof, and, if\n// $HOME is not set, falls back to os.TempDir().\nfunc setTmpDir(ui plugin.UI) (string, error) {\n\tvar dirs []string\n\tif profileDir := os.Getenv(\"PPROF_TMPDIR\"); profileDir != \"\" {\n\t\tdirs = append(dirs, profileDir)\n\t}\n\tif homeDir := os.Getenv(homeEnv()); homeDir != \"\" {\n\t\tdirs = append(dirs, filepath.Join(homeDir, \"pprof\"))\n\t}\n\tdirs = append(dirs, os.TempDir())\n\tfor _, tmpDir := range dirs {\n\t\tif err := os.MkdirAll(tmpDir, 0755); err != nil {\n\t\t\tui.PrintErr(\"Could not use temp dir \", tmpDir, \": \", err.Error())\n\t\t\tcontinue\n\t\t}\n\t\treturn tmpDir, nil\n\t}\n\treturn \"\", fmt.Errorf(\"failed to identify temp dir\")\n}\n\nconst testSourceAddress = \"pproftest.local\"\n\n// grabProfile fetches a profile. Returns the profile, sources for the\n// profile mappings, a bool indicating if the profile was fetched\n// remotely, and an error.\nfunc grabProfile(s *source, source string, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {\n\tvar src string\n\tduration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second\n\tif fetcher != nil {\n\t\tp, src, err = fetcher.Fetch(source, duration, timeout)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif err != nil || p == nil {\n\t\t// Fetch the profile over HTTP or from a file.\n\t\tp, src, err = fetch(source, duration, timeout, ui, tr)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err = p.CheckValid(); err != nil {\n\t\treturn\n\t}\n\n\t// Update the binary locations from command line and paths.\n\tlocateBinaries(p, s, obj, ui)\n\n\t// Collect the source URL for all mappings.\n\tif src != \"\" {\n\t\tmsrc = collectMappingSources(p, src)\n\t\tremote = true\n\t\tif strings.HasPrefix(src, \"http://\"+testSourceAddress) {\n\t\t\t// Treat test inputs as local to avoid saving\n\t\t\t// testcase profiles during driver testing.\n\t\t\tremote = false\n\t\t}\n\t}\n\treturn\n}\n\n// collectMappingSources saves the mapping sources of a profile.\nfunc collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {\n\tms := plugin.MappingSources{}\n\tfor _, m := range p.Mapping {\n\t\tsrc := struct {\n\t\t\tSource string\n\t\t\tStart  uint64\n\t\t}{\n\t\t\tsource, m.Start,\n\t\t}\n\t\tkey := m.BuildID\n\t\tif key == \"\" {\n\t\t\tkey = m.File\n\t\t}\n\t\tif key == \"\" {\n\t\t\t// If there is no build id or source file, use the source as the\n\t\t\t// mapping file. This will enable remote symbolization for this\n\t\t\t// mapping, in particular for Go profiles on the legacy format.\n\t\t\t// The source is reset back to empty string by unsourceMapping\n\t\t\t// which is called after symbolization is finished.\n\t\t\tm.File = source\n\t\t\tkey = source\n\t\t}\n\t\tms[key] = append(ms[key], src)\n\t}\n\treturn ms\n}\n\n// unsourceMappings iterates over the mappings in a profile and replaces file\n// set to the remote source URL by collectMappingSources back to empty string.\nfunc unsourceMappings(p *profile.Profile) {\n\tfor _, m := range p.Mapping {\n\t\tif m.BuildID == \"\" && filepath.VolumeName(m.File) == \"\" {\n\t\t\tif u, err := url.Parse(m.File); err == nil && u.IsAbs() {\n\t\t\t\tm.File = \"\"\n\t\t\t}\n\t\t}\n\t}\n}\n\n// locateBinaries searches for binary files listed in the profile and, if found,\n// updates the profile accordingly.\nfunc locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {\n\t// Construct search path to examine\n\tsearchPath := os.Getenv(\"PPROF_BINARY_PATH\")\n\tif searchPath == \"\" {\n\t\t// Use $HOME/pprof/binaries as default directory for local symbolization binaries\n\t\tsearchPath = filepath.Join(os.Getenv(homeEnv()), \"pprof\", \"binaries\")\n\t}\nmapping:\n\tfor _, m := range p.Mapping {\n\t\tvar noVolumeFile string\n\t\tvar baseName string\n\t\tvar dirName string\n\t\tif m.File != \"\" {\n\t\t\tnoVolumeFile = strings.TrimPrefix(m.File, filepath.VolumeName(m.File))\n\t\t\tbaseName = filepath.Base(m.File)\n\t\t\tdirName = filepath.Dir(noVolumeFile)\n\t\t}\n\n\t\tfor _, path := range filepath.SplitList(searchPath) {\n\t\t\tvar fileNames []string\n\t\t\tif m.BuildID != \"\" {\n\t\t\t\tfileNames = []string{filepath.Join(path, m.BuildID, baseName)}\n\t\t\t\tif matches, err := filepath.Glob(filepath.Join(path, m.BuildID, \"*\")); err == nil {\n\t\t\t\t\tfileNames = append(fileNames, matches...)\n\t\t\t\t}\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, noVolumeFile, m.BuildID)) // perf path format\n\t\t\t\t// Llvm buildid protocol: the first two characters of the build id\n\t\t\t\t// are used as directory, and the remaining part is in the filename.\n\t\t\t\t// e.g. `/ab/cdef0123456.debug`\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, m.BuildID[:2], m.BuildID[2:]+\".debug\"))\n\t\t\t}\n\t\t\tif m.File != \"\" {\n\t\t\t\t// Try both the basename and the full path, to support the same directory\n\t\t\t\t// structure as the perf symfs option.\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, baseName))\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, noVolumeFile))\n\t\t\t\t// Other locations: use the same search paths as GDB, according to\n\t\t\t\t// https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, noVolumeFile+\".debug\"))\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, dirName, \".debug\", baseName+\".debug\"))\n\t\t\t\tfileNames = append(fileNames, filepath.Join(path, \"usr\", \"lib\", \"debug\", dirName, baseName+\".debug\"))\n\t\t\t}\n\t\t\tfor _, name := range fileNames {\n\t\t\t\tif f, err := obj.Open(name, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol); err == nil {\n\t\t\t\t\tdefer f.Close()\n\t\t\t\t\tfileBuildID := f.BuildID()\n\t\t\t\t\tif m.BuildID != \"\" && m.BuildID != fileBuildID {\n\t\t\t\t\t\tui.PrintErr(\"Ignoring local file \" + name + \": build-id mismatch (\" + m.BuildID + \" != \" + fileBuildID + \")\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Explicitly do not update KernelRelocationSymbol --\n\t\t\t\t\t\t// the new local file name is most likely missing it.\n\t\t\t\t\t\tm.File = name\n\t\t\t\t\t\tcontinue mapping\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(p.Mapping) == 0 {\n\t\t// If there are no mappings, add a fake mapping to attempt symbolization.\n\t\t// This is useful for some profiles generated by the golang runtime, which\n\t\t// do not include any mappings. Symbolization with a fake mapping will only\n\t\t// be successful against a non-PIE binary.\n\t\tm := &profile.Mapping{ID: 1}\n\t\tp.Mapping = []*profile.Mapping{m}\n\t\tfor _, l := range p.Location {\n\t\t\tl.Mapping = m\n\t\t}\n\t}\n\t// If configured, apply executable filename override and (maybe, see below)\n\t// build ID override from source. Assume the executable is the first mapping.\n\tif execName, buildID := s.ExecName, s.BuildID; execName != \"\" || buildID != \"\" {\n\t\tm := p.Mapping[0]\n\t\tif execName != \"\" {\n\t\t\t// Explicitly do not update KernelRelocationSymbol --\n\t\t\t// the source override is most likely missing it.\n\t\t\tm.File = execName\n\t\t}\n\t\t// Only apply the build ID override if the build ID in the main mapping is\n\t\t// missing. Overwriting the build ID in case it's present is very likely a\n\t\t// wrong thing to do so we refuse to do that.\n\t\tif buildID != \"\" && m.BuildID == \"\" {\n\t\t\tm.BuildID = buildID\n\t\t}\n\t}\n}\n\n// fetch fetches a profile from source, within the timeout specified,\n// producing messages through the ui. It returns the profile and the\n// url of the actual source of the profile for remote profiles.\nfunc fetch(source string, duration, timeout time.Duration, ui plugin.UI, tr http.RoundTripper) (p *profile.Profile, src string, err error) {\n\tvar f io.ReadCloser\n\n\t// First determine whether the source is a file, if not, it will be treated as a URL.\n\tif _, err = os.Stat(source); err == nil {\n\t\tif isPerfFile(source) {\n\t\t\tf, err = convertPerfData(source, ui)\n\t\t} else {\n\t\t\tf, err = os.Open(source)\n\t\t}\n\t} else {\n\t\tsourceURL, timeout := adjustURL(source, duration, timeout)\n\t\tif sourceURL != \"\" {\n\t\t\tui.Print(\"Fetching profile over HTTP from \" + sourceURL)\n\t\t\tif duration > 0 {\n\t\t\t\tui.Print(fmt.Sprintf(\"Please wait... (%v)\", duration))\n\t\t\t}\n\t\t\tf, err = fetchURL(sourceURL, timeout, tr)\n\t\t\tsrc = sourceURL\n\t\t}\n\t}\n\tif err == nil {\n\t\tdefer f.Close()\n\t\tp, err = profile.Parse(f)\n\t}\n\treturn\n}\n\n// fetchURL fetches a profile from a URL using HTTP.\nfunc fetchURL(source string, timeout time.Duration, tr http.RoundTripper) (io.ReadCloser, error) {\n\tclient := &http.Client{\n\t\tTransport: tr,\n\t\tTimeout:   timeout + 5*time.Second,\n\t}\n\tresp, err := client.Get(source)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"http fetch: %v\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\tdefer resp.Body.Close()\n\t\treturn nil, statusCodeError(resp)\n\t}\n\n\treturn resp.Body, nil\n}\n\nfunc statusCodeError(resp *http.Response) error {\n\tif resp.Header.Get(\"X-Go-Pprof\") != \"\" && strings.Contains(resp.Header.Get(\"Content-Type\"), \"text/plain\") {\n\t\t// error is from pprof endpoint\n\t\tif body, err := io.ReadAll(resp.Body); err == nil {\n\t\t\treturn fmt.Errorf(\"server response: %s - %s\", resp.Status, body)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"server response: %s\", resp.Status)\n}\n\n// isPerfFile checks if a file is in perf.data format. It also returns false\n// if it encounters an error during the check.\nfunc isPerfFile(path string) bool {\n\tsourceFile, openErr := os.Open(path)\n\tif openErr != nil {\n\t\treturn false\n\t}\n\tdefer sourceFile.Close()\n\n\t// If the file is the output of a perf record command, it should begin\n\t// with the string PERFILE2.\n\tperfHeader := []byte(\"PERFILE2\")\n\tactualHeader := make([]byte, len(perfHeader))\n\tif _, readErr := sourceFile.Read(actualHeader); readErr != nil {\n\t\treturn false\n\t}\n\treturn bytes.Equal(actualHeader, perfHeader)\n}\n\n// convertPerfData converts the file at path which should be in perf.data format\n// using the perf_to_profile tool and returns the file containing the\n// profile.proto formatted data.\nfunc convertPerfData(perfPath string, ui plugin.UI) (*os.File, error) {\n\tui.Print(fmt.Sprintf(\n\t\t\"Converting %s to a profile.proto... (May take a few minutes)\",\n\t\tperfPath))\n\tprofile, err := newTempFile(os.TempDir(), \"pprof_\", \".pb.gz\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdeferDeleteTempFile(profile.Name())\n\tcmd := exec.Command(\"perf_to_profile\", \"-i\", perfPath, \"-o\", profile.Name(), \"-f\")\n\tcmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\tprofile.Close()\n\t\treturn nil, fmt.Errorf(\"failed to convert perf.data file. Try github.com/google/perf_data_converter: %v\", err)\n\t}\n\treturn profile, nil\n}\n\n// adjustURL validates if a profile source is a URL and returns an\n// cleaned up URL and the timeout to use for retrieval over HTTP.\n// If the source cannot be recognized as a URL it returns an empty string.\nfunc adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {\n\tu, err := url.Parse(source)\n\tif err != nil || (u.Host == \"\" && u.Scheme != \"\" && u.Scheme != \"file\") {\n\t\t// Try adding http:// to catch sources of the form hostname:port/path.\n\t\t// url.Parse treats \"hostname\" as the scheme.\n\t\tu, err = url.Parse(\"http://\" + source)\n\t}\n\tif err != nil || u.Host == \"\" {\n\t\treturn \"\", 0\n\t}\n\n\t// Apply duration/timeout overrides to URL.\n\tvalues := u.Query()\n\tif duration > 0 {\n\t\tvalues.Set(\"seconds\", fmt.Sprint(int(duration.Seconds())))\n\t} else {\n\t\tif urlSeconds := values.Get(\"seconds\"); urlSeconds != \"\" {\n\t\t\tif us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {\n\t\t\t\tduration = time.Duration(us) * time.Second\n\t\t\t}\n\t\t}\n\t}\n\tif timeout <= 0 {\n\t\tif duration > 0 {\n\t\t\ttimeout = duration + duration/2\n\t\t} else {\n\t\t\ttimeout = 60 * time.Second\n\t\t}\n\t}\n\tu.RawQuery = values.Encode()\n\treturn u.String(), timeout\n}\n"
  },
  {
    "path": "internal/driver/fetch_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/internal/symbolizer\"\n\t\"github.com/google/pprof/internal/transport\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestSymbolizationPath(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"test assumes Unix paths\")\n\t}\n\n\t// Save environment variables to restore after test\n\tsaveHome := os.Getenv(homeEnv())\n\tsavePath := os.Getenv(\"PPROF_BINARY_PATH\")\n\n\ttempdir, err := os.MkdirTemp(\"\", \"home\")\n\tif err != nil {\n\t\tt.Fatal(\"creating temp dir: \", err)\n\t}\n\tdefer os.RemoveAll(tempdir)\n\tos.MkdirAll(filepath.Join(tempdir, \"pprof\", \"binaries\", \"abcde10001\"), 0700)\n\tos.Create(filepath.Join(tempdir, \"pprof\", \"binaries\", \"abcde10001\", \"binary\"))\n\n\tos.MkdirAll(filepath.Join(tempdir, \"pprof\", \"binaries\", \"fg\"), 0700)\n\tos.Create(filepath.Join(tempdir, \"pprof\", \"binaries\", \"fg\", \"hij10001.debug\"))\n\n\tobj := testObj{tempdir}\n\tos.Setenv(homeEnv(), tempdir)\n\tfor _, tc := range []struct {\n\t\tenv, file, buildID, want string\n\t\tmsgCount                 int\n\t}{\n\t\t{\"\", \"/usr/bin/binary\", \"\", \"/usr/bin/binary\", 0},\n\t\t{\"\", \"/usr/bin/binary\", \"fedcb10000\", \"/usr/bin/binary\", 0},\n\t\t{\"/usr\", \"/bin/binary\", \"\", \"/usr/bin/binary\", 0},\n\t\t{\"\", \"/prod/path/binary\", \"abcde10001\", filepath.Join(tempdir, \"pprof/binaries/abcde10001/binary\"), 0},\n\t\t{\"/alternate/architecture\", \"/usr/bin/binary\", \"\", \"/alternate/architecture/binary\", 0},\n\t\t{\"/alternate/architecture\", \"/usr/bin/binary\", \"abcde10001\", \"/alternate/architecture/binary\", 0},\n\t\t{\"\", \"\", \"fghij10001\", filepath.Join(tempdir, \"pprof/binaries/fg/hij10001.debug\"), 0},\n\t\t{\"/nowhere:/alternate/architecture\", \"/usr/bin/binary\", \"fedcb10000\", \"/usr/bin/binary\", 1},\n\t\t{\"/nowhere:/alternate/architecture\", \"/usr/bin/binary\", \"abcde10002\", \"/usr/bin/binary\", 1},\n\t} {\n\t\tos.Setenv(\"PPROF_BINARY_PATH\", tc.env)\n\t\tp := &profile.Profile{\n\t\t\tMapping: []*profile.Mapping{\n\t\t\t\t{\n\t\t\t\t\tFile:    tc.file,\n\t\t\t\t\tBuildID: tc.buildID,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\ts := &source{}\n\t\tlocateBinaries(p, s, obj, &proftest.TestUI{T: t, Ignore: tc.msgCount})\n\t\tif file := p.Mapping[0].File; file != tc.want {\n\t\t\tt.Errorf(\"%s:%s:%s, want %s, got %s\", tc.env, tc.file, tc.buildID, tc.want, file)\n\t\t}\n\t}\n\tos.Setenv(homeEnv(), saveHome)\n\tos.Setenv(\"PPROF_BINARY_PATH\", savePath)\n}\n\nfunc TestCollectMappingSources(t *testing.T) {\n\tconst startAddress uint64 = 0x40000\n\tconst url = \"http://example.com\"\n\tfor _, tc := range []struct {\n\t\tfile, buildID string\n\t\twant          plugin.MappingSources\n\t}{\n\t\t{\"/usr/bin/binary\", \"buildId\", mappingSources(\"buildId\", url, startAddress)},\n\t\t{\"/usr/bin/binary\", \"\", mappingSources(\"/usr/bin/binary\", url, startAddress)},\n\t\t{\"\", \"\", mappingSources(url, url, startAddress)},\n\t} {\n\t\tp := &profile.Profile{\n\t\t\tMapping: []*profile.Mapping{\n\t\t\t\t{\n\t\t\t\t\tFile:    tc.file,\n\t\t\t\t\tBuildID: tc.buildID,\n\t\t\t\t\tStart:   startAddress,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tgot := collectMappingSources(p, url)\n\t\tif !reflect.DeepEqual(got, tc.want) {\n\t\t\tt.Errorf(\"%s:%s, want %v, got %v\", tc.file, tc.buildID, tc.want, got)\n\t\t}\n\t}\n}\n\nfunc TestUnsourceMappings(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tos, file, buildID, want string\n\t}{\n\t\t{\"any\", \"/usr/bin/binary\", \"buildId\", \"/usr/bin/binary\"},\n\t\t{\"any\", \"http://example.com\", \"\", \"\"},\n\t\t{\"windows\", `C:\\example.exe`, \"\", `C:\\example.exe`},\n\t\t{\"windows\", `c:/example.exe`, \"\", `c:/example.exe`},\n\t} {\n\t\tt.Run(tc.file+\"-\"+tc.os, func(t *testing.T) {\n\t\t\tif tc.os != \"any\" && tc.os != runtime.GOOS {\n\t\t\t\tt.Skipf(\"%s only test\", tc.os)\n\t\t\t}\n\n\t\t\tp := &profile.Profile{\n\t\t\t\tMapping: []*profile.Mapping{\n\t\t\t\t\t{\n\t\t\t\t\t\tFile:    tc.file,\n\t\t\t\t\t\tBuildID: tc.buildID,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tunsourceMappings(p)\n\t\t\tif got := p.Mapping[0].File; got != tc.want {\n\t\t\t\tt.Errorf(\"%s:%s, want %s, got %s\", tc.file, tc.buildID, tc.want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype testObj struct {\n\thome string\n}\n\nfunc (o testObj) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\tswitch file {\n\tcase \"/alternate/architecture/binary\":\n\t\treturn testFile{file, \"abcde10001\"}, nil\n\tcase \"/usr/bin/binary\":\n\t\treturn testFile{file, \"fedcb10000\"}, nil\n\tcase filepath.Join(o.home, \"pprof/binaries/abcde10001/binary\"):\n\t\treturn testFile{file, \"abcde10001\"}, nil\n\tcase filepath.Join(o.home, \"pprof/binaries/fg/hij10001.debug\"):\n\t\treturn testFile{file, \"fghij10001\"}, nil\n\t}\n\treturn nil, fmt.Errorf(\"not found: %s\", file)\n}\nfunc (testObj) Demangler(_ string) func(names []string) (map[string]string, error) {\n\treturn func(names []string) (map[string]string, error) { return nil, nil }\n}\nfunc (testObj) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\treturn nil, nil\n}\n\ntype testFile struct{ name, buildID string }\n\nfunc (f testFile) Name() string                                               { return f.name }\nfunc (testFile) ObjAddr(addr uint64) (uint64, error)                          { return addr, nil }\nfunc (f testFile) BuildID() string                                            { return f.buildID }\nfunc (testFile) SourceLine(addr uint64) ([]plugin.Frame, error)               { return nil, nil }\nfunc (testFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { return nil, nil }\nfunc (testFile) Close() error                                                 { return nil }\n\nfunc TestFetch(t *testing.T) {\n\tconst path = \"testdata/\"\n\ttype testcase struct {\n\t\tsource, execName string\n\t\twantErr          bool\n\t}\n\tts := []testcase{\n\t\t{path + \"go.crc32.cpu\", \"\", false},\n\t\t{path + \"go.nomappings.crash\", \"/bin/gotest.exe\", false},\n\t\t{\"http://localhost/profile?file=cppbench.cpu\", \"\", false},\n\t\t{\"./missing\", \"\", true},\n\t}\n\t// Test that paths with a colon character are recognized as file paths\n\t// if the file exists, rather than as a URL. We have to skip this test\n\t// on Windows since the colon char is not allowed in Windows paths.\n\tif runtime.GOOS != \"windows\" {\n\t\tsrc := filepath.Join(path, \"go.crc32.cpu\")\n\t\tdst := filepath.Join(t.TempDir(), \"go.crc32.cpu_2023-11-11_01:02:03\")\n\t\tdata, err := os.ReadFile(src)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read src file %s failed: %#v\", src, err)\n\t\t}\n\t\terr = os.WriteFile(dst, data, 0644)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"create dst file %s failed: %#v\", dst, err)\n\t\t}\n\t\tts = append(ts, testcase{dst, \"\", false})\n\t}\n\tfor _, tc := range ts {\n\t\tt.Run(tc.source, func(t *testing.T) {\n\t\t\tp, _, _, err := grabProfile(&source{ExecName: tc.execName}, tc.source, nil, testObj{}, &proftest.TestUI{T: t}, &httpTransport{})\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"got no error, want an error\")\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"got error %v, want no error\", err)\n\t\t\t}\n\t\t\tif len(p.Sample) == 0 {\n\t\t\t\tt.Error(\"got zero samples, want non-zero\")\n\t\t\t}\n\t\t\tif e := tc.execName; e != \"\" {\n\t\t\t\tswitch {\n\t\t\t\tcase len(p.Mapping) == 0 || p.Mapping[0] == nil:\n\t\t\t\t\tt.Errorf(\"got no mappings, want mapping[0].execName == %s\", e)\n\t\t\t\tcase p.Mapping[0].File != e:\n\t\t\t\t\tt.Errorf(\"got mapping[0].execName == %s, want %s\", p.Mapping[0].File, e)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFetchWithBase(t *testing.T) {\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\n\ttype WantSample struct {\n\t\tvalues []int64\n\t\tlabels map[string][]string\n\t}\n\n\tconst path = \"testdata/\"\n\ttype testcase struct {\n\t\tdesc              string\n\t\tsources           []string\n\t\tbases             []string\n\t\tdiffBases         []string\n\t\tnormalize         bool\n\t\twantSamples       []WantSample\n\t\twantParseErrorMsg string\n\t\twantFetchErrorMsg string\n\t}\n\n\ttestcases := []testcase{\n\t\t{\n\t\t\t\"not normalized base is same as source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"not normalized base is same as source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"not normalized single source, multiple base (all profiles same)\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\", path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\t[]WantSample{\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-2700, -608881724},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -23992},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-200, -179943},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -17778444},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -75976},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-300, -63568134},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"not normalized, different base and source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.small.contention\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\t[]WantSample{\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{1700, 608878600},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 23992},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{200, 179943},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 17778444},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 75976},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{300, 63568134},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"normalized base is same as source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"normalized single source, multiple base (all profiles same)\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\", path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"normalized different base and source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.small.contention\"},\n\t\t\tnil,\n\t\t\ttrue,\n\t\t\t[]WantSample{\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-229, -369},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{29, 0},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{57, 1},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{29, 80},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{29, 0},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{86, 288},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"not normalized diff base is same as source\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tfalse,\n\t\t\t[]WantSample{\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{2700, 608881724},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 23992},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{200, 179943},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 17778444},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{100, 75976},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{300, 63568134},\n\t\t\t\t\tlabels: map[string][]string{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-2700, -608881724},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -23992},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-200, -179943},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -17778444},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-100, -75976},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tvalues: []int64{-300, -63568134},\n\t\t\t\t\tlabels: map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"diff_base and base both specified\",\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.contention\"},\n\t\t\tfalse,\n\t\t\tnil,\n\t\t\t\"-base and -diff_base flags cannot both be specified\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"input profiles with different sample types (non empty intersection)\",\n\t\t\t[]string{path + \"cppbench.cpu\", path + \"cppbench.cpu_no_samples_type\"},\n\t\t\t[]string{path + \"cppbench.cpu\", path + \"cppbench.cpu_no_samples_type\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"input profiles with different sample types (empty intersection)\",\n\t\t\t[]string{path + \"cppbench.cpu\", path + \"cppbench.contention\"},\n\t\t\t[]string{path + \"cppbench.cpu\", path + \"cppbench.contention\"},\n\t\t\tnil,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t\t\"\",\n\t\t\t\"problem fetching source profiles: profiles have empty common sample type list\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tsetCurrentConfig(baseConfig)\n\t\t\tf := testFlags{\n\t\t\t\tstringLists: map[string][]string{\n\t\t\t\t\t\"base\":      tc.bases,\n\t\t\t\t\t\"diff_base\": tc.diffBases,\n\t\t\t\t},\n\t\t\t\tbools: map[string]bool{\n\t\t\t\t\t\"normalize\": tc.normalize,\n\t\t\t\t},\n\t\t\t}\n\t\t\tf.args = tc.sources\n\n\t\t\to := setDefaults(&plugin.Options{\n\t\t\t\tUI:            &proftest.TestUI{T: t, AllowRx: \"Local symbolization failed|Some binary filenames not available\"},\n\t\t\t\tFlagset:       f,\n\t\t\t\tHTTPTransport: transport.New(nil),\n\t\t\t})\n\t\t\tsrc, _, err := parseFlags(o)\n\n\t\t\tif tc.wantParseErrorMsg != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"got nil, want error %q\", tc.wantParseErrorMsg)\n\t\t\t\t}\n\n\t\t\t\tif gotErrMsg := err.Error(); gotErrMsg != tc.wantParseErrorMsg {\n\t\t\t\t\tt.Fatalf(\"got error %q, want error %q\", gotErrMsg, tc.wantParseErrorMsg)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"got error %q, want no error\", err)\n\t\t\t}\n\n\t\t\tp, err := fetchProfiles(src, o)\n\n\t\t\tif tc.wantFetchErrorMsg != \"\" {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"got nil, want error %q\", tc.wantFetchErrorMsg)\n\t\t\t\t}\n\n\t\t\t\tif gotErrMsg := err.Error(); gotErrMsg != tc.wantFetchErrorMsg {\n\t\t\t\t\tt.Fatalf(\"got error %q, want error %q\", gotErrMsg, tc.wantFetchErrorMsg)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"got error %q, want no error\", err)\n\t\t\t}\n\n\t\t\tif got, want := len(p.Sample), len(tc.wantSamples); got != want {\n\t\t\t\tt.Fatalf(\"got %d samples want %d\", got, want)\n\t\t\t}\n\n\t\t\tfor i, sample := range p.Sample {\n\t\t\t\tif !reflect.DeepEqual(tc.wantSamples[i].values, sample.Value) {\n\t\t\t\t\tt.Errorf(\"for sample %d got values %v, want %v\", i, sample.Value, tc.wantSamples[i])\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(tc.wantSamples[i].labels, sample.Label) {\n\t\t\t\t\tt.Errorf(\"for sample %d got labels %v, want %v\", i, sample.Label, tc.wantSamples[i].labels)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// mappingSources creates MappingSources map with a single item.\nfunc mappingSources(key, source string, start uint64) plugin.MappingSources {\n\treturn plugin.MappingSources{\n\t\tkey: []struct {\n\t\t\tSource string\n\t\t\tStart  uint64\n\t\t}{\n\t\t\t{Source: source, Start: start},\n\t\t},\n\t}\n}\n\ntype httpTransport struct{}\n\nfunc (tr *httpTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\tvalues := req.URL.Query()\n\tfile := values.Get(\"file\")\n\n\tif file == \"\" {\n\t\treturn nil, fmt.Errorf(\"want .../file?profile, got %s\", req.URL.String())\n\t}\n\n\tt := &http.Transport{}\n\tt.RegisterProtocol(\"file\", http.NewFileTransport(http.Dir(\"testdata/\")))\n\n\tc := &http.Client{Transport: t}\n\treturn c.Get(\"file:///\" + file)\n}\n\nfunc closedError() string {\n\tif runtime.GOOS == \"plan9\" {\n\t\treturn \"listen hungup\"\n\t}\n\treturn \"use of closed\"\n}\n\nfunc TestHTTPSInsecure(t *testing.T) {\n\tif runtime.GOOS == \"nacl\" || runtime.GOOS == \"js\" {\n\t\tt.Skip(\"test assumes tcp available\")\n\t}\n\tsaveHome := os.Getenv(homeEnv())\n\ttempdir, err := os.MkdirTemp(\"\", \"home\")\n\tif err != nil {\n\t\tt.Fatal(\"creating temp dir: \", err)\n\t}\n\tdefer os.RemoveAll(tempdir)\n\n\t// pprof writes to $HOME/pprof by default which is not necessarily\n\t// writeable (e.g. on a Debian build) so set $HOME to something we\n\t// know we can write to for the duration of the test.\n\tos.Setenv(homeEnv(), tempdir)\n\tdefer os.Setenv(homeEnv(), saveHome)\n\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\n\ttlsCert, _, _ := selfSignedCert(t, \"\")\n\ttlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}\n\n\tl, err := tls.Listen(\"tcp\", \"localhost:0\", tlsConfig)\n\tif err != nil {\n\t\tt.Fatalf(\"net.Listen: got error %v, want no error\", err)\n\t}\n\n\tdonec := make(chan error, 1)\n\tgo func(donec chan<- error) {\n\t\tdonec <- http.Serve(l, nil)\n\t}(donec)\n\tdefer func() {\n\t\tif got, want := <-donec, closedError(); !strings.Contains(got.Error(), want) {\n\t\t\tt.Fatalf(\"Serve got error %v, want %q\", got, want)\n\t\t}\n\t}()\n\tdefer l.Close()\n\n\toutputTempFile, err := os.CreateTemp(\"\", \"profile_output\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempfile: %v\", err)\n\t}\n\tdefer os.Remove(outputTempFile.Name())\n\tdefer outputTempFile.Close()\n\n\taddress := \"https+insecure://\" + l.Addr().String() + \"/debug/pprof/goroutine\"\n\ts := &source{\n\t\tSources:   []string{address},\n\t\tTimeout:   10,\n\t\tSymbolize: \"remote\",\n\t}\n\to := &plugin.Options{\n\t\tObj:           &binutils.Binutils{},\n\t\tUI:            &proftest.TestUI{T: t, AllowRx: \"Saved profile in\"},\n\t\tHTTPTransport: transport.New(nil),\n\t}\n\to.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI}\n\tp, err := fetchProfiles(s, o)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(p.SampleType) == 0 {\n\t\tt.Fatalf(\"fetchProfiles(%s) got empty profile: len(p.SampleType)==0\", address)\n\t}\n\tif len(p.Function) == 0 {\n\t\tt.Fatalf(\"fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0\", address)\n\t}\n\tif err := checkProfileHasFunction(p, \"TestHTTPSInsecure\"); err != nil {\n\t\tt.Fatalf(\"fetchProfiles(%s) %v\", address, err)\n\t}\n}\n\nfunc TestHTTPSWithServerCertFetch(t *testing.T) {\n\tif runtime.GOOS == \"nacl\" || runtime.GOOS == \"js\" {\n\t\tt.Skip(\"test assumes tcp available\")\n\t}\n\tsaveHome := os.Getenv(homeEnv())\n\ttempdir, err := os.MkdirTemp(\"\", \"home\")\n\tif err != nil {\n\t\tt.Fatal(\"creating temp dir: \", err)\n\t}\n\tdefer os.RemoveAll(tempdir)\n\n\t// pprof writes to $HOME/pprof by default which is not necessarily\n\t// writeable (e.g. on a Debian build) so set $HOME to something we\n\t// know we can write to for the duration of the test.\n\tos.Setenv(homeEnv(), tempdir)\n\tdefer os.Setenv(homeEnv(), saveHome)\n\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\n\tcert, certBytes, keyBytes := selfSignedCert(t, \"localhost\")\n\tcas := x509.NewCertPool()\n\tcas.AppendCertsFromPEM(certBytes)\n\n\ttlsConfig := &tls.Config{\n\t\tRootCAs:      cas,\n\t\tCertificates: []tls.Certificate{cert},\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    cas,\n\t}\n\n\tl, err := tls.Listen(\"tcp\", \"localhost:0\", tlsConfig)\n\tif err != nil {\n\t\tt.Fatalf(\"net.Listen: got error %v, want no error\", err)\n\t}\n\n\tdonec := make(chan error, 1)\n\tgo func(donec chan<- error) {\n\t\tdonec <- http.Serve(l, nil)\n\t}(donec)\n\tdefer func() {\n\t\tif got, want := <-donec, closedError(); !strings.Contains(got.Error(), want) {\n\t\t\tt.Fatalf(\"Serve got error %v, want %q\", got, want)\n\t\t}\n\t}()\n\tdefer l.Close()\n\n\toutputTempFile, err := os.CreateTemp(\"\", \"profile_output\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create tempfile: %v\", err)\n\t}\n\tdefer os.Remove(outputTempFile.Name())\n\tdefer outputTempFile.Close()\n\n\t// Get port from the address, so request to the server can be made using\n\t// the host name specified in certificates.\n\t_, portStr, err := net.SplitHostPort(l.Addr().String())\n\tif err != nil {\n\t\tt.Fatalf(\"cannot get port from URL: %v\", err)\n\t}\n\taddress := \"https://\" + \"localhost:\" + portStr + \"/debug/pprof/goroutine\"\n\ts := &source{\n\t\tSources:   []string{address},\n\t\tTimeout:   10,\n\t\tSymbolize: \"remote\",\n\t}\n\n\tcertTempFile, err := os.CreateTemp(\"\", \"cert_output\")\n\tif err != nil {\n\t\tt.Errorf(\"cannot create cert tempfile: %v\", err)\n\t}\n\tdefer os.Remove(certTempFile.Name())\n\tdefer certTempFile.Close()\n\tcertTempFile.Write(certBytes)\n\n\tkeyTempFile, err := os.CreateTemp(\"\", \"key_output\")\n\tif err != nil {\n\t\tt.Errorf(\"cannot create key tempfile: %v\", err)\n\t}\n\tdefer os.Remove(keyTempFile.Name())\n\tdefer keyTempFile.Close()\n\tkeyTempFile.Write(keyBytes)\n\n\tf := &testFlags{\n\t\tstrings: map[string]string{\n\t\t\t\"tls_cert\": certTempFile.Name(),\n\t\t\t\"tls_key\":  keyTempFile.Name(),\n\t\t\t\"tls_ca\":   certTempFile.Name(),\n\t\t},\n\t}\n\to := &plugin.Options{\n\t\tObj:           &binutils.Binutils{},\n\t\tUI:            &proftest.TestUI{T: t, AllowRx: \"Saved profile in\"},\n\t\tFlagset:       f,\n\t\tHTTPTransport: transport.New(f),\n\t}\n\n\to.Sym = &symbolizer.Symbolizer{Obj: o.Obj, UI: o.UI, Transport: o.HTTPTransport}\n\tp, err := fetchProfiles(s, o)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(p.SampleType) == 0 {\n\t\tt.Fatalf(\"fetchProfiles(%s) got empty profile: len(p.SampleType)==0\", address)\n\t}\n\tif len(p.Function) == 0 {\n\t\tt.Fatalf(\"fetchProfiles(%s) got non-symbolized profile: len(p.Function)==0\", address)\n\t}\n\tif err := checkProfileHasFunction(p, \"TestHTTPSWithServerCertFetch\"); err != nil {\n\t\tt.Fatalf(\"fetchProfiles(%s) %v\", address, err)\n\t}\n}\n\nfunc checkProfileHasFunction(p *profile.Profile, fname string) error {\n\tfor _, f := range p.Function {\n\t\tif strings.Contains(f.Name, fname) {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"got %s, want function %q\", p.String(), fname)\n}\n\n// selfSignedCert generates a self-signed certificate, and returns the\n// generated certificate, and byte arrays containing the certificate and\n// key associated with the certificate.\nfunc selfSignedCert(t *testing.T, host string) (tls.Certificate, []byte, []byte) {\n\tprivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to generate private key: %v\", err)\n\t}\n\tb, err := x509.MarshalECPrivateKey(privKey)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to marshal private key: %v\", err)\n\t}\n\tbk := pem.EncodeToMemory(&pem.Block{Type: \"EC PRIVATE KEY\", Bytes: b})\n\n\ttmpl := x509.Certificate{\n\t\tSerialNumber: big.NewInt(1),\n\t\tNotBefore:    time.Now(),\n\t\tNotAfter:     time.Now().Add(10 * time.Minute),\n\t\tIsCA:         true,\n\t\tDNSNames:     []string{host},\n\t}\n\n\tb, err = x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, privKey.Public(), privKey)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create cert: %v\", err)\n\t}\n\tbc := pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: b})\n\n\tcert, err := tls.X509KeyPair(bc, bk)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create TLS key pair: %v\", err)\n\t}\n\treturn cert, bc, bk\n}\n"
  },
  {
    "path": "internal/driver/flags.go",
    "content": "//  Copyright 2018 Google Inc. All Rights Reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\npackage driver\n\nimport (\n\t\"flag\"\n\t\"strings\"\n)\n\n// GoFlags implements the plugin.FlagSet interface.\ntype GoFlags struct {\n\tUsageMsgs []string\n}\n\n// Bool implements the plugin.FlagSet interface.\nfunc (*GoFlags) Bool(o string, d bool, c string) *bool {\n\treturn flag.Bool(o, d, c)\n}\n\n// Int implements the plugin.FlagSet interface.\nfunc (*GoFlags) Int(o string, d int, c string) *int {\n\treturn flag.Int(o, d, c)\n}\n\n// Float64 implements the plugin.FlagSet interface.\nfunc (*GoFlags) Float64(o string, d float64, c string) *float64 {\n\treturn flag.Float64(o, d, c)\n}\n\n// String implements the plugin.FlagSet interface.\nfunc (*GoFlags) String(o, d, c string) *string {\n\treturn flag.String(o, d, c)\n}\n\n// StringList implements the plugin.FlagSet interface.\nfunc (*GoFlags) StringList(o, d, c string) *[]*string {\n\treturn &[]*string{flag.String(o, d, c)}\n}\n\n// ExtraUsage implements the plugin.FlagSet interface.\nfunc (f *GoFlags) ExtraUsage() string {\n\treturn strings.Join(f.UsageMsgs, \"\\n\")\n}\n\n// AddExtraUsage implements the plugin.FlagSet interface.\nfunc (f *GoFlags) AddExtraUsage(eu string) {\n\tf.UsageMsgs = append(f.UsageMsgs, eu)\n}\n\n// Parse implements the plugin.FlagSet interface.\nfunc (*GoFlags) Parse(usage func()) []string {\n\tflag.Usage = usage\n\tflag.Parse()\n\targs := flag.Args()\n\tif len(args) == 0 {\n\t\tusage()\n\t}\n\treturn args\n}\n"
  },
  {
    "path": "internal/driver/html/common.css",
    "content": "* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n}\nhtml, body {\n  height: 100%;\n}\nbody {\n  font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n  font-size: 13px;\n  line-height: 1.4;\n  display: flex;\n  flex-direction: column;\n}\na {\n  color: #2a66d9;\n}\n.header {\n  display: flex;\n  align-items: center;\n  height: 44px;\n  min-height: 44px;\n  background-color: #eee;\n  color: #212121;\n  padding: 0 1rem;\n}\n.header > div {\n  margin: 0 0.125em;\n}\n.header .title h1 {\n  font-size: 1.75em;\n  margin-right: 1rem;\n  margin-bottom: 4px;\n}\n.header .title a {\n  color: #212121;\n  text-decoration: none;\n}\n.header .title a:hover {\n  text-decoration: underline;\n}\n.header .description {\n  width: 100%;\n  text-align: right;\n  white-space: nowrap;\n}\n@media screen and (max-width: 799px) {\n  .header input {\n    display: none;\n  }\n}\n#detailsbox {\n  display: none;\n  position: fixed;\n  top: 40px;\n  right: 20px;\n  background-color: #ffffff;\n  box-shadow: 0 1px 5px rgba(0,0,0,.3);\n  line-height: 24px;\n  padding: 1em;\n  text-align: left;\n}\n.header input {\n  background: white url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' style='pointer-events:none;display:block;width:100%25;height:100%25;fill:%23757575'%3E%3Cpath d='M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61.0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E\") no-repeat 4px center/20px 20px;\n  border: 1px solid #d1d2d3;\n  border-radius: 2px 0 0 2px;\n  padding: 0.25em;\n  padding-left: 28px;\n  margin-left: 1em;\n  font-family: 'Roboto', 'Noto', sans-serif;\n  font-size: 1em;\n  line-height: 24px;\n  color: #212121;\n}\n.downArrow {\n  border-top: .36em solid #ccc;\n  border-left: .36em solid transparent;\n  border-right: .36em solid transparent;\n  margin-bottom: .05em;\n  margin-left: .5em;\n  transition: border-top-color 200ms;\n}\n.menu-item {\n  height: 100%;\n  text-transform: uppercase;\n  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n  position: relative;\n}\n.menu-item .menu-name:hover {\n  opacity: 0.75;\n}\n.menu-item .menu-name:hover .downArrow {\n  border-top-color: #666;\n}\n.menu-name {\n  height: 100%;\n  padding: 0 0.5em;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n.menu-name a {\n  text-decoration: none;\n  color: #212121;\n}\n.submenu {\n  display: none;\n  margin-top: -4px;\n  min-width: 10em;\n  position: absolute;\n  left: 0px;\n  background-color: white;\n  box-shadow: 0 1px 5px rgba(0,0,0,.3);\n  font-size: 100%;\n  text-transform: none;\n  white-space: nowrap;\n}\n.menu-item, .submenu {\n  user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  -webkit-user-select: none;\n}\n.submenu hr {\n  border: 0;\n  border-top: 2px solid #eee;\n}\n.submenu a {\n  display: block;\n  padding: .5em 1em;\n  text-decoration: none;\n}\n.submenu a:hover, .submenu a.active {\n  color: white;\n  background-color: #6b82d6;\n}\n.submenu a.disabled {\n  color: gray;\n  pointer-events: none;\n}\n.menu-check-mark {\n  position: absolute;\n  left: 2px;\n}\n.menu-delete-btn {\n  position: absolute;\n  right: 2px;\n}\n\n.help {\n  padding-left: 1em;\n}\n\n{{/* Used to disable events when a modal dialog is displayed */}}\n#dialog-overlay {\n  display: none;\n  position: fixed;\n  left: 0px;\n  top: 0px;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(1,1,1,0.1);\n}\n\n.dialog {\n  {{/* Displayed centered horizontally near the top */}}\n  display: none;\n  position: fixed;\n  margin: 0px;\n  top: 60px;\n  left: 50%;\n  transform: translateX(-50%);\n  font-size: 125%;\n  background-color: #ffffff;\n  box-shadow: 0 1px 5px rgba(0,0,0,.3);\n}\n.dialog-header {\n  font-size: 120%;\n  border-bottom: 1px solid #CCCCCC;\n  width: 100%;\n  text-align: center;\n  background: #EEEEEE;\n  user-select: none;\n}\n.dialog-footer {\n  border-top: 1px solid #CCCCCC;\n  width: 100%;\n  text-align: right;\n  padding: 10px;\n}\n.dialog-error {\n  margin: 10px;\n  color: red;\n}\n.dialog input {\n  margin: 10px;\n  font-size: inherit;\n}\n.dialog button {\n  margin-left: 10px;\n  font-size: inherit;\n}\n#save-dialog, #delete-dialog {\n  width: 50%;\n  max-width: 20em;\n}\n#delete-prompt {\n  padding: 10px;\n}\n\n#content {\n  overflow-y: scroll;\n  padding: 1em;\n}\n#top {\n  overflow-y: scroll;\n}\n#graph {\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n#graph svg {\n  width: 100%;\n  height: 100%;\n  padding: 10px;\n}\n#content.source .filename {\n  margin-top: 0;\n  margin-bottom: 1em;\n  font-size: 120%;\n}\n#content.source pre {\n  margin-bottom: 3em;\n}\ntable {\n  border-spacing: 0px;\n  width: 100%;\n  padding-bottom: 1em;\n  white-space: nowrap;\n}\ntable thead {\n  font-family: 'Roboto Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n}\ntable tr th {\n  position: sticky;\n  top: 0;\n  background-color: #ddd;\n  text-align: right;\n  padding: .3em .5em;\n}\ntable tr td {\n  padding: .3em .5em;\n  text-align: right;\n}\n#top table tr th:nth-child(6),\n#top table tr th:nth-child(7),\n#top table tr td:nth-child(6),\n#top table tr td:nth-child(7) {\n  text-align: left;\n}\n#top table tr td:nth-child(6) {\n  width: 100%;\n  text-overflow: ellipsis;\n  overflow: hidden;\n  white-space: nowrap;\n}\n#flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {\n  cursor: ns-resize;\n}\n.hilite {\n  background-color: #ebf5fb;\n  font-weight: bold;\n}\n/* stacking order */\n.boxtext         { z-index: 2; }  /* flame graph box text */\n#current-details { z-index: 2; }  /* flame graph current box info */\n#detailsbox      { z-index: 3; }  /* profile details */\n.submenu         { z-index: 4; }\n.dialog          { z-index: 5; }\n"
  },
  {
    "path": "internal/driver/html/common.js",
    "content": "// Make svg pannable and zoomable.\n// Call clickHandler(t) if a click event is caught by the pan event handlers.\nfunction initPanAndZoom(svg, clickHandler) {\n  'use strict';\n\n  // Current mouse/touch handling mode\n  const IDLE = 0;\n  const MOUSEPAN = 1;\n  const TOUCHPAN = 2;\n  const TOUCHZOOM = 3;\n  let mode = IDLE;\n\n  // State needed to implement zooming.\n  let currentScale = 1.0;\n  const initWidth = svg.viewBox.baseVal.width;\n  const initHeight = svg.viewBox.baseVal.height;\n\n  // State needed to implement panning.\n  let panLastX = 0;      // Last event X coordinate\n  let panLastY = 0;      // Last event Y coordinate\n  let moved = false;     // Have we seen significant movement\n  let touchid = null;    // Current touch identifier\n\n  // State needed for pinch zooming\n  let touchid2 = null;     // Second id for pinch zooming\n  let initGap = 1.0;       // Starting gap between two touches\n  let initScale = 1.0;     // currentScale when pinch zoom started\n  let centerPoint = null;  // Center point for scaling\n\n  // Convert event coordinates to svg coordinates.\n  function toSvg(x, y) {\n    const p = svg.createSVGPoint();\n    p.x = x;\n    p.y = y;\n    let m = svg.getCTM();\n    if (m == null) m = svg.getScreenCTM(); // Firefox workaround.\n    return p.matrixTransform(m.inverse());\n  }\n\n  // Change the scaling for the svg to s, keeping the point denoted\n  // by u (in svg coordinates]) fixed at the same screen location.\n  function rescale(s, u) {\n    // Limit to a good range.\n    if (s < 0.2) s = 0.2;\n    if (s > 10.0) s = 10.0;\n\n    currentScale = s;\n\n    // svg.viewBox defines the visible portion of the user coordinate\n    // system.  So to magnify by s, divide the visible portion by s,\n    // which will then be stretched to fit the viewport.\n    const vb = svg.viewBox;\n    const w1 = vb.baseVal.width;\n    const w2 = initWidth / s;\n    const h1 = vb.baseVal.height;\n    const h2 = initHeight / s;\n    vb.baseVal.width = w2;\n    vb.baseVal.height = h2;\n\n    // We also want to adjust vb.baseVal.x so that u.x remains at same\n    // screen X coordinate.  In other words, want to change it from x1 to x2\n    // so that:\n    //     (u.x - x1) / w1 = (u.x - x2) / w2\n    // Simplifying that, we get\n    //     (u.x - x1) * (w2 / w1) = u.x - x2\n    //     x2 = u.x - (u.x - x1) * (w2 / w1)\n    vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1);\n    vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1);\n  }\n\n  function handleWheel(e) {\n    if (e.deltaY == 0) return;\n    // Change scale factor by 1.1 or 1/1.1\n    rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),\n            toSvg(e.offsetX, e.offsetY));\n  }\n\n  function setMode(m) {\n    mode = m;\n    touchid = null;\n    touchid2 = null;\n  }\n\n  function panStart(x, y) {\n    moved = false;\n    panLastX = x;\n    panLastY = y;\n  }\n\n  function panMove(x, y) {\n    let dx = x - panLastX;\n    let dy = y - panLastY;\n    if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return; // Ignore tiny moves\n\n    moved = true;\n    panLastX = x;\n    panLastY = y;\n\n    // Firefox workaround: get dimensions from parentNode.\n    const swidth = svg.clientWidth || svg.parentNode.clientWidth;\n    const sheight = svg.clientHeight || svg.parentNode.clientHeight;\n\n    // Convert deltas from screen space to svg space.\n    dx *= (svg.viewBox.baseVal.width / swidth);\n    dy *= (svg.viewBox.baseVal.height / sheight);\n\n    svg.viewBox.baseVal.x -= dx;\n    svg.viewBox.baseVal.y -= dy;\n  }\n\n  function handleScanStart(e) {\n    if (e.button != 0) return; // Do not catch right-clicks etc.\n    setMode(MOUSEPAN);\n    panStart(e.clientX, e.clientY);\n    e.preventDefault();\n    svg.addEventListener('mousemove', handleScanMove);\n  }\n\n  function handleScanMove(e) {\n    if (e.buttons == 0) {\n      // Missed an end event, perhaps because mouse moved outside window.\n      setMode(IDLE);\n      svg.removeEventListener('mousemove', handleScanMove);\n      return;\n    }\n    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);\n  }\n\n  function handleScanEnd(e) {\n    if (mode == MOUSEPAN) panMove(e.clientX, e.clientY);\n    setMode(IDLE);\n    svg.removeEventListener('mousemove', handleScanMove);\n    if (!moved) clickHandler(e.target);\n  }\n\n  // Find touch object with specified identifier.\n  function findTouch(tlist, id) {\n    for (const t of tlist) {\n      if (t.identifier == id) return t;\n    }\n    return null;\n  }\n\n  // Return distance between two touch points\n  function touchGap(t1, t2) {\n    const dx = t1.clientX - t2.clientX;\n    const dy = t1.clientY - t2.clientY;\n    return Math.hypot(dx, dy);\n  }\n\n  function handleTouchStart(e) {\n    if (mode == IDLE && e.changedTouches.length == 1) {\n      // Start touch based panning\n      const t = e.changedTouches[0];\n      setMode(TOUCHPAN);\n      touchid = t.identifier;\n      panStart(t.clientX, t.clientY);\n      e.preventDefault();\n    } else if (mode == TOUCHPAN && e.touches.length == 2) {\n      // Start pinch zooming\n      setMode(TOUCHZOOM);\n      const t1 = e.touches[0];\n      const t2 = e.touches[1];\n      touchid = t1.identifier;\n      touchid2 = t2.identifier;\n      initScale = currentScale;\n      initGap = touchGap(t1, t2);\n      centerPoint = toSvg((t1.clientX + t2.clientX) / 2,\n                          (t1.clientY + t2.clientY) / 2);\n      e.preventDefault();\n    }\n  }\n\n  function handleTouchMove(e) {\n    if (mode == TOUCHPAN) {\n      const t = findTouch(e.changedTouches, touchid);\n      if (t == null) return;\n      if (e.touches.length != 1) {\n        setMode(IDLE);\n        return;\n      }\n      panMove(t.clientX, t.clientY);\n      e.preventDefault();\n    } else if (mode == TOUCHZOOM) {\n      // Get two touches; new gap; rescale to ratio.\n      const t1 = findTouch(e.touches, touchid);\n      const t2 = findTouch(e.touches, touchid2);\n      if (t1 == null || t2 == null) return;\n      const gap = touchGap(t1, t2);\n      rescale(initScale * gap / initGap, centerPoint);\n      e.preventDefault();\n    }\n  }\n\n  function handleTouchEnd(e) {\n    if (mode == TOUCHPAN) {\n      const t = findTouch(e.changedTouches, touchid);\n      if (t == null) return;\n      panMove(t.clientX, t.clientY);\n      setMode(IDLE);\n      e.preventDefault();\n      if (!moved) clickHandler(t.target);\n    } else if (mode == TOUCHZOOM) {\n      setMode(IDLE);\n      e.preventDefault();\n    }\n  }\n\n  svg.addEventListener('mousedown', handleScanStart);\n  svg.addEventListener('mouseup', handleScanEnd);\n  svg.addEventListener('touchstart', handleTouchStart);\n  svg.addEventListener('touchmove', handleTouchMove);\n  svg.addEventListener('touchend', handleTouchEnd);\n  svg.addEventListener('wheel', handleWheel, true);\n}\n\nfunction initMenus() {\n  'use strict';\n\n  let activeMenu = null;\n  let activeMenuHdr = null;\n\n  function cancelActiveMenu() {\n    if (activeMenu == null) return;\n    activeMenu.style.display = 'none';\n    activeMenu = null;\n    activeMenuHdr = null;\n  }\n\n  // Set click handlers on every menu header.\n  for (const menu of document.getElementsByClassName('submenu')) {\n    const hdr = menu.parentElement;\n    if (hdr == null) return;\n    if (hdr.classList.contains('disabled')) return;\n    function showMenu(e) {\n      // menu is a child of hdr, so this event can fire for clicks\n      // inside menu. Ignore such clicks.\n      if (e.target.parentElement != hdr) return;\n      activeMenu = menu;\n      activeMenuHdr = hdr;\n      menu.style.display = 'block';\n    }\n    hdr.addEventListener('mousedown', showMenu);\n    hdr.addEventListener('touchstart', showMenu);\n  }\n\n  // If there is an active menu and a down event outside, retract the menu.\n  for (const t of ['mousedown', 'touchstart']) {\n    document.addEventListener(t, (e) => {\n      // Note: to avoid unnecessary flicker, if the down event is inside\n      // the active menu header, do not retract the menu.\n      if (activeMenuHdr != e.target.closest('.menu-item')) {\n        cancelActiveMenu();\n      }\n    }, { passive: true, capture: true });\n  }\n\n  // If there is an active menu and an up event inside, retract the menu.\n  document.addEventListener('mouseup', (e) => {\n    if (activeMenu == e.target.closest('.submenu')) {\n      cancelActiveMenu();\n    }\n  }, { passive: true, capture: true });\n}\n\nfunction sendURL(method, url, done) {\n  fetch(url.toString(), {method: method})\n      .then((response) => { done(response.ok); })\n      .catch((error) => { done(false); });\n}\n\n// Initialize handlers for saving/loading configurations.\nfunction initConfigManager() {\n  'use strict';\n\n  // Initialize various elements.\n  function elem(id) {\n    const result = document.getElementById(id);\n    if (!result) console.warn('element ' + id + ' not found');\n    return result;\n  }\n  const overlay = elem('dialog-overlay');\n  const saveDialog = elem('save-dialog');\n  const saveInput = elem('save-name');\n  const saveError = elem('save-error');\n  const delDialog = elem('delete-dialog');\n  const delPrompt = elem('delete-prompt');\n  const delError = elem('delete-error');\n\n  let currentDialog = null;\n  let currentDeleteTarget = null;\n\n  function showDialog(dialog) {\n    if (currentDialog != null) {\n      overlay.style.display = 'none';\n      currentDialog.style.display = 'none';\n    }\n    currentDialog = dialog;\n    if (dialog != null) {\n      overlay.style.display = 'block';\n      dialog.style.display = 'block';\n    }\n  }\n\n  function cancelDialog(e) {\n    showDialog(null);\n  }\n\n  // Show dialog for saving the current config.\n  function showSaveDialog(e) {\n    saveError.innerText = '';\n    showDialog(saveDialog);\n    saveInput.focus();\n  }\n\n  // Commit save config.\n  function commitSave(e) {\n    const name = saveInput.value;\n    const url = new URL(document.URL);\n    // Set path relative to existing path.\n    url.pathname = new URL('./saveconfig', document.URL).pathname;\n    url.searchParams.set('config', name);\n    saveError.innerText = '';\n    sendURL('POST', url, (ok) => {\n      if (!ok) {\n        saveError.innerText = 'Save failed';\n      } else {\n        showDialog(null);\n        location.reload();  // Reload to show updated config menu\n      }\n    });\n  }\n\n  function handleSaveInputKey(e) {\n    if (e.key === 'Enter') commitSave(e);\n  }\n\n  function deleteConfig(e, elem) {\n    e.preventDefault();\n    const config = elem.dataset.config;\n    delPrompt.innerText = 'Delete ' + config + '?';\n    currentDeleteTarget = elem;\n    showDialog(delDialog);\n  }\n\n  function commitDelete(e, elem) {\n    if (!currentDeleteTarget) return;\n    const config = currentDeleteTarget.dataset.config;\n    const url = new URL('./deleteconfig', document.URL);\n    url.searchParams.set('config', config);\n    delError.innerText = '';\n    sendURL('DELETE', url, (ok) => {\n      if (!ok) {\n        delError.innerText = 'Delete failed';\n        return;\n      }\n      showDialog(null);\n      // Remove menu entry for this config.\n      if (currentDeleteTarget && currentDeleteTarget.parentElement) {\n        currentDeleteTarget.parentElement.remove();\n      }\n    });\n  }\n\n  // Bind event on elem to fn.\n  function bind(event, elem, fn) {\n    if (elem == null) return;\n    elem.addEventListener(event, fn);\n    if (event == 'click') {\n      // Also enable via touch.\n      elem.addEventListener('touchstart', fn);\n    }\n  }\n\n  bind('click', elem('save-config'), showSaveDialog);\n  bind('click', elem('save-cancel'), cancelDialog);\n  bind('click', elem('save-confirm'), commitSave);\n  bind('keydown', saveInput, handleSaveInputKey);\n\n  bind('click', elem('delete-cancel'), cancelDialog);\n  bind('click', elem('delete-confirm'), commitDelete);\n\n  // Activate deletion button for all config entries in menu.\n  for (const del of Array.from(document.getElementsByClassName('menu-delete-btn'))) {\n    bind('click', del, (e) => {\n      deleteConfig(e, del);\n    });\n  }\n}\n\n// options if present can contain:\n//   hiliter: function(Number, Boolean): Boolean\n//     Overridable mechanism for highlighting/unhighlighting specified node.\n//   current: function() Map[Number,Boolean]\n//     Overridable mechanism for fetching set of currently selected nodes.\nfunction viewer(baseUrl, nodes, options) {\n  'use strict';\n\n  // Elements\n  const search = document.getElementById('search');\n  const graph0 = document.getElementById('graph0');\n  const svg = (graph0 == null ? null : graph0.parentElement);\n  const toptable = document.getElementById('toptable');\n\n  let regexpActive = false;\n  let selected = new Map();\n  let origFill = new Map();\n  let searchAlarm = null;\n  let buttonsEnabled = true;\n\n  // Return current selection.\n  function getSelection() {\n    if (selected.size > 0) {\n      return selected;\n    } else if (options && options.current) {\n      return options.current();\n    }\n    return new Map();\n  }\n\n  function handleDetails(e) {\n    e.preventDefault();\n    const detailsText = document.getElementById('detailsbox');\n    if (detailsText != null) {\n      if (detailsText.style.display === 'block') {\n        detailsText.style.display = 'none';\n      } else {\n        detailsText.style.display = 'block';\n      }\n    }\n  }\n\n  function handleKey(e) {\n    if (e.keyCode != 13) return;\n    setHrefParams(window.location, function (params) {\n      params.set('f', search.value);\n    });\n    e.preventDefault();\n  }\n\n  function handleSearch() {\n    // Delay expensive processing so a flurry of key strokes is handled once.\n    if (searchAlarm != null) {\n      clearTimeout(searchAlarm);\n    }\n    searchAlarm = setTimeout(selectMatching, 300);\n\n    regexpActive = true;\n    updateButtons();\n  }\n\n  function selectMatching() {\n    searchAlarm = null;\n    let re = null;\n    if (search.value != '') {\n      try {\n        re = new RegExp(search.value);\n      } catch (e) {\n        // TODO: Display error state in search box\n        return;\n      }\n    }\n\n    function match(text) {\n      return re != null && re.test(text);\n    }\n\n    // drop currently selected items that do not match re.\n    selected.forEach(function(v, n) {\n      if (!match(nodes[n])) {\n        unselect(n);\n      }\n    })\n\n    // add matching items that are not currently selected.\n    if (nodes) {\n      for (let n = 0; n < nodes.length; n++) {\n        if (!selected.has(n) && match(nodes[n])) {\n          select(n);\n        }\n      }\n    }\n\n    updateButtons();\n  }\n\n  function toggleSvgSelect(elem) {\n    // Walk up to immediate child of graph0\n    while (elem != null && elem.parentElement != graph0) {\n      elem = elem.parentElement;\n    }\n    if (!elem) return;\n\n    // Disable regexp mode.\n    regexpActive = false;\n\n    const n = nodeId(elem);\n    if (n < 0) return;\n    if (selected.has(n)) {\n      unselect(n);\n    } else {\n      select(n);\n    }\n    updateButtons();\n  }\n\n  function unselect(n) {\n    if (setNodeHighlight(n, false)) selected.delete(n);\n  }\n\n  function select(n, elem) {\n    if (setNodeHighlight(n, true)) selected.set(n, true);\n  }\n\n  function nodeId(elem) {\n    const id = elem.id;\n    if (!id) return -1;\n    if (!id.startsWith('node')) return -1;\n    const n = parseInt(id.slice(4), 10);\n    if (isNaN(n)) return -1;\n    if (n < 0 || n >= nodes.length) return -1;\n    return n;\n  }\n\n  // Change highlighting of node (returns true if node was found).\n  function setNodeHighlight(n, set) {\n    if (options && options.hiliter) return options.hiliter(n, set);\n\n    const elem = document.getElementById('node' + n);\n    if (!elem) return false;\n\n    // Handle table row highlighting.\n    if (elem.nodeName == 'TR') {\n      elem.classList.toggle('hilite', set);\n      return true;\n    }\n\n    // Handle svg element highlighting.\n    const p = findPolygon(elem);\n    if (p != null) {\n      if (set) {\n        origFill.set(p, p.style.fill);\n        p.style.fill = '#ccccff';\n      } else if (origFill.has(p)) {\n        p.style.fill = origFill.get(p);\n      }\n    }\n\n    return true;\n  }\n\n  function findPolygon(elem) {\n    if (elem.localName == 'polygon') return elem;\n    for (const c of elem.children) {\n      const p = findPolygon(c);\n      if (p != null) return p;\n    }\n    return null;\n  }\n\n  function setSampleIndexLink(si) {\n    const elem = document.getElementById('sampletype-' + si);\n    if (elem != null) {\n      setHrefParams(elem, function (params) {\n        params.set(\"si\", si);\n      });\n    }\n  }\n\n  // Update id's href to reflect current selection whenever it is\n  // liable to be followed.\n  function makeSearchLinkDynamic(id) {\n    const elem = document.getElementById(id);\n    if (elem == null) return;\n\n    // Most links copy current selection into the 'f' parameter,\n    // but Refine menu links are different.\n    let param = 'f';\n    if (id == 'ignore') param = 'i';\n    if (id == 'hide') param = 'h';\n    if (id == 'show') param = 's';\n    if (id == 'show-from') param = 'sf';\n\n    // We update on mouseenter so middle-click/right-click work properly.\n    elem.addEventListener('mouseenter', updater);\n    elem.addEventListener('touchstart', updater);\n\n    function updater() {\n      // The selection can be in one of two modes: regexp-based or\n      // list-based.  Construct regular expression depending on mode.\n      let re = regexpActive\n          ? search.value\n          : Array.from(getSelection().keys()).map(key => pprofQuoteMeta(nodes[key])).join('|');\n\n      setHrefParams(elem, function (params) {\n        if (re != '') {\n          // For focus/show/show-from, forget old parameter. For others, add to re.\n          if (param != 'f' && param != 's' && param != 'sf' && params.has(param)) {\n            const old = params.get(param);\n            if (old != '') {\n              re += '|' + old;\n            }\n          }\n          params.set(param, re);\n        } else {\n          params.delete(param);\n        }\n      });\n    }\n  }\n\n  function setHrefParams(elem, paramSetter) {\n    let url = new URL(elem.href);\n    url.hash = '';\n\n    // Copy params from this page's URL.\n    const params = url.searchParams;\n    for (const p of new URLSearchParams(window.location.search)) {\n      params.set(p[0], p[1]);\n    }\n\n    // Give the params to the setter to modify.\n    paramSetter(params);\n\n    elem.href = url.toString();\n  }\n\n  function handleTopClick(e) {\n    // Walk back until we find TR and then get the Name column (index 5)\n    let elem = e.target;\n    while (elem != null && elem.nodeName != 'TR') {\n      elem = elem.parentElement;\n    }\n    if (elem == null || elem.children.length < 6) return;\n\n    e.preventDefault();\n    const tr = elem;\n    const td = elem.children[5];\n    if (td.nodeName != 'TD') return;\n    const name = td.innerText;\n    const index = nodes.indexOf(name);\n    if (index < 0) return;\n\n    // Disable regexp mode.\n    regexpActive = false;\n\n    if (selected.has(index)) {\n      unselect(index, elem);\n    } else {\n      select(index, elem);\n    }\n    updateButtons();\n  }\n\n  function updateButtons() {\n    const enable = (search.value != '' || getSelection().size != 0);\n    if (buttonsEnabled == enable) return;\n    buttonsEnabled = enable;\n    for (const id of ['focus', 'ignore', 'hide', 'show', 'show-from']) {\n      const link = document.getElementById(id);\n      if (link != null) {\n        link.classList.toggle('disabled', !enable);\n      }\n    }\n  }\n\n  // Initialize button states\n  updateButtons();\n\n  // Setup event handlers\n  initMenus();\n  if (svg != null) {\n    initPanAndZoom(svg, toggleSvgSelect);\n  }\n  if (toptable != null) {\n    toptable.addEventListener('mousedown', handleTopClick);\n    toptable.addEventListener('touchstart', handleTopClick);\n  }\n\n  const ids = ['topbtn', 'graphbtn',\n               'flamegraph',\n               'peek', 'list',\n               'disasm', 'focus', 'ignore', 'hide', 'show', 'show-from'];\n  ids.forEach(makeSearchLinkDynamic);\n\n  const sampleIDs = [{{range .SampleTypes}}'{{.}}', {{end}}];\n  sampleIDs.forEach(setSampleIndexLink);\n\n  // Bind action to button with specified id.\n  function addAction(id, action) {\n    const btn = document.getElementById(id);\n    if (btn != null) {\n      btn.addEventListener('click', action);\n      btn.addEventListener('touchstart', action);\n    }\n  }\n\n  addAction('details', handleDetails);\n  initConfigManager();\n\n  search.addEventListener('input', handleSearch);\n  search.addEventListener('keydown', handleKey);\n\n  // Give initial focus to main container so it can be scrolled using keys.\n  const main = document.getElementById('bodycontainer');\n  if (main) {\n    main.focus();\n  }\n}\n\n// convert a string to a regexp that matches exactly that string.\nfunction pprofQuoteMeta(str) {\n  return '^' + str.replace(/([\\\\\\.?+*\\[\\](){}|^$])/g, '\\\\$1') + '$';\n}\n"
  },
  {
    "path": "internal/driver/html/graph.css",
    "content": "#graph {\n    cursor: grab;\n}\n\n#graph:active {\n    cursor: grabbing;\n}\n"
  },
  {
    "path": "internal/driver/html/graph.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{.Title}}</title>\n  {{template \"css\" .}}\n  {{template \"graph_css\" .}}\n</head>\n<body>\n  {{template \"header\" .}}\n  <div id=\"graph\">\n    {{.HTMLBody}}\n  </div>\n  {{template \"script\" .}}\n  <script>viewer(new URL(window.location.href), {{.Nodes}});</script>\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/html/header.html",
    "content": "<div class=\"header\">\n  <div class=\"title\">\n    <h1><a href=\"./\">pprof</a></h1>\n  </div>\n\n  <div id=\"view\" class=\"menu-item\">\n    <div class=\"menu-name\">\n      View\n      <i class=\"downArrow\"></i>\n    </div>\n    <div class=\"submenu\">\n      <a title=\"{{.Help.top}}\"  href=\"./top\" id=\"topbtn\">Top</a>\n      <a title=\"{{.Help.graph}}\" href=\"./graph\" id=\"graphbtn\">Graph</a>\n      <a title=\"{{.Help.flamegraph}}\" href=\"./flamegraph\" id=\"flamegraph\">Flame Graph</a>\n      <a title=\"{{.Help.peek}}\" href=\"./peek\" id=\"peek\">Peek</a>\n      <a title=\"{{.Help.list}}\" href=\"./source\" id=\"list\">Source</a>\n      <a title=\"{{.Help.disasm}}\" href=\"./disasm\" id=\"disasm\">Disassemble</a>\n    </div>\n  </div>\n\n  {{$sampleLen := len .SampleTypes}}\n  {{if gt $sampleLen 1}}\n  <div id=\"sample\" class=\"menu-item\">\n    <div class=\"menu-name\">\n      Sample\n      <i class=\"downArrow\"></i>\n    </div>\n    <div class=\"submenu\">\n      {{range .SampleTypes}}\n      <a href=\"?si={{.}}\" id=\"sampletype-{{.}}\">{{.}}</a>\n      {{end}}\n    </div>\n  </div>\n  {{end}}\n\n  <div id=\"refine\" class=\"menu-item\">\n    <div class=\"menu-name\">\n      Refine\n      <i class=\"downArrow\"></i>\n    </div>\n    <div class=\"submenu\">\n      <a title=\"{{.Help.focus}}\" href=\"?\" id=\"focus\">Focus</a>\n      <a title=\"{{.Help.ignore}}\" href=\"?\" id=\"ignore\">Ignore</a>\n      <a title=\"{{.Help.hide}}\" href=\"?\" id=\"hide\">Hide</a>\n      <a title=\"{{.Help.show}}\" href=\"?\" id=\"show\">Show</a>\n      <a title=\"{{.Help.show_from}}\" href=\"?\" id=\"show-from\">Show from</a>\n      <hr>\n      <a title=\"{{.Help.reset}}\" href=\"?\">Reset</a>\n    </div>\n  </div>\n\n  <div id=\"config\" class=\"menu-item\">\n    <div class=\"menu-name\">\n      Config\n      <i class=\"downArrow\"></i>\n    </div>\n    <div class=\"submenu\">\n      <a title=\"{{.Help.save_config}}\" id=\"save-config\">Save as ...</a>\n      <hr>\n      {{range .Configs}}\n      <a href=\"{{.URL}}\">\n        {{if .Current}}<span class=\"menu-check-mark\">✓</span>{{end}}\n        {{.Name}}\n        {{if .UserConfig}}<span class=\"menu-delete-btn\" data-config={{.Name}}>🗙</span>{{end}}\n      </a>\n      {{end}}\n    </div>\n  </div>\n\n  <div id=\"download\" class=\"menu-item\">\n    <div class=\"menu-name\">\n      <a href=\"./download\">Download</a>\n    </div>\n  </div>\n\n  <div>\n    <input id=\"search\" type=\"text\" placeholder=\"Search regexp\" autocomplete=\"off\" autocapitalize=\"none\" size=40>\n  </div>\n\n  <div class=\"description\">\n    <a title=\"{{.Help.details}}\" href=\"#\" id=\"details\">{{.Title}}</a>\n    <div id=\"detailsbox\">\n      {{range .Legend}}<div>{{.}}</div>{{end}}\n    </div>\n  </div>\n\n  {{if .DocURL}}\n     <div class=\"menu-item\">\n       <div class=\"help menu-name\"><a title=\"Profile documentation\" href=\"{{.DocURL}}\" target=\"_blank\">Help&nbsp;⤇</a></div>\n     </div>\n  {{end}}\n</div>\n\n<div id=\"dialog-overlay\"></div>\n\n<div class=\"dialog\" id=\"save-dialog\">\n  <div class=\"dialog-header\">Save options as</div>\n  <datalist id=\"config-list\">\n    {{range .Configs}}{{if .UserConfig}}<option value=\"{{.Name}}\" />{{end}}{{end}}\n  </datalist>\n  <input id=\"save-name\" type=\"text\" list=\"config-list\" placeholder=\"New config\" />\n  <div class=\"dialog-footer\">\n    <span class=\"dialog-error\" id=\"save-error\"></span>\n    <button id=\"save-cancel\">Cancel</button>\n    <button id=\"save-confirm\">Save</button>\n  </div>\n</div>\n\n<div class=\"dialog\" id=\"delete-dialog\">\n  <div class=\"dialog-header\" id=\"delete-dialog-title\">Delete config</div>\n  <div id=\"delete-prompt\"></div>\n  <div class=\"dialog-footer\">\n    <span class=\"dialog-error\" id=\"delete-error\"></span>\n    <button id=\"delete-cancel\">Cancel</button>\n    <button id=\"delete-confirm\">Delete</button>\n  </div>\n</div>\n\n<div id=\"errors\">{{range .Errors}}<div>{{.}}</div>{{end}}</div>\n"
  },
  {
    "path": "internal/driver/html/plaintext.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{.Title}}</title>\n  {{template \"css\" .}}\n</head>\n<body>\n  {{template \"header\" .}}\n  <div id=\"content\">\n    <pre>\n      {{.TextBody}}\n    </pre>\n  </div>\n  {{template \"script\" .}}\n  <script>viewer(new URL(window.location.href), null);</script>\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/html/source.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{.Title}}</title>\n  {{if not .Standalone}}{{template \"css\" .}}{{end}}\n  {{template \"weblistcss\" .}}\n  {{template \"weblistjs\" .}}\n</head>\n<body>{{\"\\n\" -}}\n  {{/* emit different header in standalone mode */ -}}\n  {{if .Standalone}}{{\"\\n\" -}}\n    <div class=\"legend\">{{\"\" -}}\n      {{range $i, $e := .Legend -}}\n        {{if $i}}<br>{{\"\\n\"}}{{end}}{{. -}}\n      {{end}}<br>Total: {{.Listing.Total -}}\n    </div>{{\"\" -}}\n  {{else -}}\n    {{template \"header\" .}}\n    <div id=\"content\" class=\"source\">{{\"\" -}}\n  {{end -}}\n\n  {{range .Listing.Files -}}\n    {{range .Funcs -}}\n      <h2>{{.Name}}</h2>{{\"\" -}}\n      <p class=\"filename\">{{.File}}</p>{{\"\\n\" -}}\n      <pre onClick=\"pprof_toggle_asm(event)\">{{\"\\n\" -}}\n        {{printf \"  Total:  %10s %10s (flat, cum) %s\" .Flat .Cumulative .Percent -}}\n        {{range .Lines -}}{{\"\\n\" -}}\n          {{/* source line */ -}}\n          <span class=line>{{printf \" %6d\" .Line}}</span>{{\" \" -}}\n          <span class={{.HTMLClass}}>\n            {{- printf \"  %10s %10s %8s  %s \" .Flat .Cumulative \"\" .SrcLine -}}\n          </span>{{\"\" -}}\n\n          {{if .Instructions -}}\n            {{/* instructions for this source line */ -}}\n            <span class=asm>{{\"\" -}}\n            {{range .Instructions -}}\n              {{/* separate when we hit a new basic block */ -}}\n              {{if .NewBlock -}}{{printf \" %8s %28s\\n\" \"\" \"⋮\"}}{{end -}}\n\n              {{/* inlined calls leading to this instruction */ -}}\n              {{range .InlinedCalls -}}\n                {{printf \" %8s %10s %10s %8s  \" \"\" \"\" \"\" \"\" -}}\n                <span class=inlinesrc>{{.SrcLine}}</span>{{\" \" -}}\n                <span class=unimportant>{{.FileBase}}:{{.Line}}</span>{{\"\\n\" -}}\n              {{end -}}\n\n              {{if not .Synthetic -}}\n                {{/* disassembled instruction */ -}}\n                {{printf \" %8s %10s %10s %8x: %s \" \"\" .Flat .Cumulative .Address .Disasm -}}\n                <span class=unimportant>{{.FileLine}}</span>{{\"\\n\" -}}\n              {{end -}}\n            {{end -}}\n            </span>{{\"\" -}}\n          {{end -}}\n          {{/* end of line */ -}}\n        {{end}}{{\"\\n\" -}}\n      </pre>{{\"\\n\" -}}\n      {{/* end of function */ -}}\n    {{end -}}\n    {{/* end of file */ -}}\n  {{end -}}\n\n  {{if not .Standalone}}{{\"\\n  \" -}}\n    </div>{{\"\\n\" -}}\n    {{template \"script\" .}}{{\"\\n\" -}}\n    <script>viewer(new URL(window.location.href), null);</script>{{\"\" -}}\n  {{end}}\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/html/stacks.css",
    "content": "body {\n  overflow: hidden; /* Want scrollbar not here, but in #stack-holder */\n}\n/* Scrollable container for flame graph */\n#stack-holder {\n  width: 100%;\n  flex-grow: 1;\n  overflow-y: auto;\n  background: #eee; /* Light grey gives better contrast with boxes */\n  position: relative; /* Allows absolute positioning of child boxes */\n}\n/* Flame graph */\n#stack-chart {\n  width: 100%;\n  position: relative; /* Allows absolute positioning of child boxes */\n}\n/* Holder for current frame details. */\n#current-details {\n  position: relative;\n  background: #eee; /* Light grey gives better contrast with boxes */\n  font-size: 12pt;\n  padding: 0 4px;\n  width: 100%;\n}\n/* Shows details of frame that is under the mouse */\n#current-details-left {\n  float: left;\n  max-width: 60%;\n  white-space: nowrap;\n  overflow-x: hidden;\n}\n#current-details-right {\n  float: right;\n  max-width: 40%;\n  white-space: nowrap;\n  overflow-x: hidden;\n}\n/* Background of a single flame-graph frame */\n.boxbg {\n  border-width: 0px;\n  position: absolute;\n  overflow: hidden;\n  box-sizing: border-box;\n  background: #d8d8d8;\n}\n.positive { position: absolute; background: #caa; }\n.negative { position: absolute; background: #aca; }\n/* Not-inlined frames are visually separated from their caller. */\n.not-inlined {\n  border-top: 1px solid black;\n}\n/* Function name */\n.boxtext {\n  position: absolute;\n  width: 100%;\n  padding-left: 2px;\n  line-height: 18px;\n  cursor: default;\n  font-family: \"Google Sans\", Arial, sans-serif;\n  font-size: 12pt;\n  z-index: 2;\n}\n/* Box highlighting via shadows to avoid size changes */\n.hilite { box-shadow: 0px 0px 0px 2px #000; z-index: 1; }\n.hilite2 { box-shadow: 0px 0px 0px 2px #000; z-index: 1; }\n/* Gap left between callers and callees */\n.separator {\n  position: absolute;\n  text-align: center;\n  font-size: 12pt;\n  font-weight: bold;\n}\n/* Right-click menu */\n#action-menu {\n  max-width: 15em;\n}\n/* Right-click menu title */\n#action-title {\n  display: block;\n  padding: 0.5em 1em;\n  background: #888;\n  text-overflow: ellipsis;\n  overflow: hidden;\n}\n/* Internal canvas used to measure text size when picking fonts */\n#textsizer {\n  position: absolute;\n  bottom: -100px;\n}\n"
  },
  {
    "path": "internal/driver/html/stacks.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{.Title}}</title>\n  {{template \"css\" .}}\n  {{template \"stacks_css\"}}\n</head>\n<body>\n  {{template \"header\" .}}\n  <div id=\"current-details\">\n    <div id=\"current-details-left\"></div>\n    <div id=\"current-details-right\"> </div>\n  </div>\n  <div id=\"stack-holder\">\n    <div id=\"stack-chart\"></div>\n  </div>\n  <div id=\"action-menu\" class=\"submenu\">\n    <span id=\"action-title\"></span>\n    <hr>\n    <a title=\"{{.Help.list}}\" id=\"action-source\" href=\"./source\">Show source code</a>\n    <a title=\"{{.Help.list}}\" id=\"action-source-tab\" href=\"./source\" target=\"_blank\">Show source in new tab</a>\n    <hr>\n    <a title=\"{{.Help.focus}}\" id=\"action-focus\" href=\"?\">Focus</a>\n    <a title=\"{{.Help.ignore}}\" id=\"action-ignore\" href=\"?\">Ignore</a>\n    <a title=\"{{.Help.hide}}\" id=\"action-hide\" href=\"?\">Hide</a>\n    <a title=\"{{.Help.show_from}}\" id=\"action-showfrom\" href=\"?\">Show from</a>\n  </div>\n  {{template \"script\" .}}\n  {{template \"stacks_js\"}}\n  <script>\n    pprofUnitDefs = {{.UnitDefs}};\n    stackViewer({{.Stacks}}, {{.Nodes}});\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/html/stacks.js",
    "content": "// stackViewer displays a flame-graph like view (extended to show callers).\n//   stacks - report.StackSet\n//   nodes  - List of names for each source in report.StackSet\nfunction stackViewer(stacks, nodes) {\n  'use strict';\n\n  // Constants used in rendering.\n  const ROW = 20;\n  const PADDING = 2;\n  const MIN_WIDTH = 4;\n  const MIN_TEXT_WIDTH = 16;\n  const TEXT_MARGIN = 2;\n  const FONT_SIZE = 12;\n  const MIN_FONT_SIZE = 8;\n\n  // Fields\n  let pivots = [];          // Indices of currently selected data.Sources entries.\n  let matches = new Set();  // Indices of sources that match search\n  let elems = new Map();    // Mapping from source index to display elements\n  let displayList = [];     // List of boxes to display.\n  let actionMenuOn = false; // Is action menu visible?\n  let actionTarget = null;  // Box on which action menu is operating.\n  let diff = false;         // Are we displaying a diff?\n  let shown = 0;            // How many profile values are being displayed?\n\n  for (const stack of stacks.Stacks) {\n    if (stack.Value < 0) {\n      diff = true;\n      break;\n    }\n  }\n\n  // Setup to allow measuring text width.\n  const textSizer = document.createElement('canvas');\n  textSizer.id = 'textsizer';\n  const textContext = textSizer.getContext('2d');\n\n  // Get DOM elements.\n  const chart = find('stack-chart');\n  const search = find('search');\n  const actions = find('action-menu');\n  const actionTitle = find('action-title');\n  const leftDetailBox = find('current-details-left');\n  const rightDetailBox = find('current-details-right');\n\n  window.addEventListener('resize', render);\n  window.addEventListener('popstate', render);\n  search.addEventListener('keydown', handleSearchKey);\n\n  // Withdraw action menu when clicking outside, or when item selected.\n  document.addEventListener('mousedown', (e) => {\n    if (!actions.contains(e.target)) {\n      hideActionMenu();\n    }\n  });\n  actions.addEventListener('click', hideActionMenu);\n\n  // Initialize menus and other general UI elements.\n  viewer(new URL(window.location.href), nodes, {\n    hiliter: (n, on) => { return hilite(n, on); },\n    current: () => {\n      let r = new Map();\n      if (pivots.length == 1 && pivots[0] == 0) {\n        // Not pivoting\n      } else {\n        for (let p of pivots) {\n          r.set(p, true);\n        }\n      }\n      return r;\n    }});\n\n  render();\n  clearDetails();\n\n  // Helper functions follow:\n\n  // hilite changes the highlighting of elements corresponding to specified src.\n  function hilite(src, on) {\n    if (on) {\n      matches.add(src);\n    } else {\n      matches.delete(src);\n    }\n    toggleClass(src, 'hilite', on);\n    return true;\n  }\n\n  // Display action menu (triggered by right-click on a frame)\n  function showActionMenu(e, box) {\n    if (box.src == 0) return; // No action menu for root\n    e.preventDefault(); // Disable browser context menu\n    const src = stacks.Sources[box.src];\n    actionTitle.innerText = src.Display[src.Display.length-1];\n    const menu = actions;\n    menu.style.display = 'block';\n    // Compute position so menu stays visible and near the mouse.\n    const x = Math.min(e.clientX - 10, document.body.clientWidth - menu.clientWidth);\n    const y = Math.min(e.clientY - 10, document.body.clientHeight - menu.clientHeight);\n    menu.style.left = x + 'px';\n    menu.style.top = y + 'px';\n    // Set menu links to operate on clicked box.\n    setHrefParam('action-source', 'f', box.src);\n    setHrefParam('action-source-tab', 'f', box.src);\n    setHrefParam('action-focus', 'f', box.src);\n    setHrefParam('action-ignore', 'i', box.src);\n    setHrefParam('action-hide', 'h', box.src);\n    setHrefParam('action-showfrom', 'sf', box.src);\n    toggleClass(box.src, 'hilite2', true);\n    actionTarget = box;\n    actionMenuOn = true;\n  }\n\n  function hideActionMenu() {\n    actions.style.display = 'none';\n    actionMenuOn = false;\n    if (actionTarget != null) {\n      toggleClass(actionTarget.src, 'hilite2', false);\n    }\n  }\n\n  // setHrefParam updates the specified parameter in the  href of an <a>\n  // element to make it operate on the specified src.\n  function setHrefParam(id, param, src) {\n    const elem = document.getElementById(id);\n    if (!elem) return;\n\n    let url = new URL(elem.href);\n    url.hash = '';\n\n    // Copy params from this page's URL.\n    const params = url.searchParams;\n    for (const p of new URLSearchParams(window.location.search)) {\n      params.set(p[0], p[1]);\n    }\n\n    // Update params to include src.\n    // When `pprof` is invoked with `-lines`, FullName will be suffixed with `:<line>`,\n    // which we need to remove.\n    let v = pprofQuoteMeta(stacks.Sources[src].FullName.replace(/:[0-9]+$/, ''));\n    if (param != 'f' && param != 'sf') { // old f,sf values are overwritten\n      // Add new source to current parameter value.\n      const old = params.get(param);\n      if (old && old != '') {\n        v += '|' + old;\n      }\n    }\n    params.set(param, v);\n\n    elem.href = url.toString();\n  }\n\n  // Capture Enter key in the search box to make it pivot instead of focus.\n  function handleSearchKey(e) {\n    if (e.key != 'Enter') return;\n    e.stopImmediatePropagation();  // Disable normal enter key handling\n    const val = search.value;\n    try {\n      new RegExp(search.value);\n    } catch (error) {\n      return;  // TODO: Display error state in search box\n    }\n    switchPivots(val);\n  }\n\n  function switchPivots(regexp) {\n    // Switch URL without hitting the server.\n    const url = new URL(document.URL);\n    if (regexp === '' || regexp === '^$') {\n      url.searchParams.delete('p');  // Not pivoting\n    } else {\n      url.searchParams.set('p', regexp);\n    }\n    history.pushState('', '', url.toString()); // Makes back-button work\n    matches = new Set();\n    search.value = '';\n    render();\n  }\n\n  function handleEnter(box, div) {\n    if (actionMenuOn) return;\n    const src = stacks.Sources[box.src];\n    div.title = details(box) + ' │ ' + src.FullName + (src.Inlined ? \"\\n(inlined)\" : \"\");\n    leftDetailBox.innerText = src.FullName + (src.Inlined ? \" (inlined)\" : \"\");\n    let timing = summary(box.sumpos, box.sumneg);\n    if (box.self != 0) {\n      timing = \"self \" + unitText(box.self) + \" │ \" + timing;\n    }\n    rightDetailBox.innerText = timing;\n    // Highlight all boxes that have the same source as box.\n    toggleClass(box.src, 'hilite2', true);\n  }\n\n  function handleLeave(box) {\n    if (actionMenuOn) return;\n    clearDetails();\n    toggleClass(box.src, 'hilite2', false);\n  }\n\n  function clearDetails() {\n    leftDetailBox.innerText = '';\n    rightDetailBox.innerText = percentText(shown);\n  }\n\n  // Return list of sources that match the regexp given by the 'p' URL parameter.\n  function urlPivots() {\n    const pivots = [];\n    const params = (new URL(document.URL)).searchParams;\n    const val = params.get('p');\n    if (val !== null && val != '') {\n      try {\n        const re = new RegExp(val);\n        for (let i = 0; i < stacks.Sources.length; i++) {\n          const src = stacks.Sources[i];\n          if (re.test(src.UniqueName) || re.test(src.FileName)) {\n            pivots.push(i);\n          }\n        }\n      } catch (error) {}\n    }\n    if (pivots.length == 0) {\n      pivots.push(0);\n    }\n    return pivots;\n  }\n\n  // render re-generates the stack display.\n  function render() {\n    pivots = urlPivots();\n\n    // Get places where pivots occur.\n    let places = [];\n    for (let pivot of pivots) {\n      const src = stacks.Sources[pivot];\n      for (let p of src.Places) {\n        places.push(p);\n      }\n    }\n\n    const width = chart.clientWidth;\n    elems.clear();\n    actionTarget = null;\n    const [pos, neg] = totalValue(places);\n    const total = pos + neg;\n    const xscale = (width-2*PADDING) / total; // Converts from profile value to X pixels\n    const x = PADDING;\n    const y = 0;\n\n    // Show summary for pivots if we are actually pivoting.\n    const showPivotSummary = !(pivots.length == 1 && pivots[0] == 0);\n\n    shown = pos + neg;\n    displayList.length = 0;\n    renderStacks(0, xscale, x, y, places, +1);  // Callees\n    renderStacks(0, xscale, x, y-ROW, places, -1);  // Callers (ROW left for separator)\n    display(xscale, pos, neg, displayList, showPivotSummary);\n  }\n\n  // renderStacks creates boxes with top-left at x,y with children drawn as\n  // nested stacks (below or above based on the sign of direction).\n  // Returns the largest y coordinate filled.\n  function renderStacks(depth, xscale, x, y, places, direction) {\n    // Example: suppose we are drawing the following stacks:\n    //   a->b->c\n    //   a->b->d\n    //   a->e->f\n    // After rendering a, we will call renderStacks, with places pointing to\n    // the preceding stacks.\n    //\n    // We first group all places with the same leading entry. In this example\n    // we get [b->c, b->d] and [e->f]. We render the two groups side-by-side.\n    const groups = partitionPlaces(places);\n    for (const g of groups) {\n      renderGroup(depth, xscale, x, y, g, direction);\n      x += groupWidth(xscale, g);\n    }\n  }\n\n  // Some of the types used below:\n  //\n  // // Group represents a displayed (sub)tree.\n  // interface Group {\n  //   name: string;     // Full name of source\n  //   src: number;      // Index in stacks.Sources\n  //   self: number;     // Contribution as leaf (may be < 0 for diffs)\n  //   sumpos: number;   // Sum of |self| of positive nodes in tree (>= 0)\n  //   sumneg: number;   // Sum of |self| of negative nodes in tree (>= 0)\n  //   places: Place[];  // Stack slots that contributed to this group\n  // }\n  //\n  // // Box is a rendered item.\n  // interface Box {\n  //   x: number;          // X coordinate of top-left\n  //   y: number;          // Y coordinate of top-left\n  //   width: number;      // Width of box to display\n  //   src: number;        // Index in stacks.Sources\n  //   sumpos: number;     // From corresponding Group\n  //   sumneg: number;     // From corresponding Group\n  //   self: number;       // From corresponding Group\n  // };\n\n  function groupWidth(xscale, g) {\n    return xscale * (g.sumpos + g.sumneg);\n  }\n\n  function renderGroup(depth, xscale, x, y, g, direction) {\n    // Skip if not wide enough.\n    const width = groupWidth(xscale, g);\n    if (width < MIN_WIDTH) return;\n\n    // Draw the box for g.src (except for selected element in upwards direction\n    // since that duplicates the box we added in downwards direction).\n    if (depth != 0 || direction > 0) {\n      const box = {\n        x:      x,\n        y:      y,\n        width:  width,\n        src:    g.src,\n        sumpos: g.sumpos,\n        sumneg: g.sumneg,\n        self:   g.self,\n      };\n      displayList.push(box);\n      if (direction > 0) {\n        // Leave gap on left hand side to indicate self contribution.\n        x += xscale*Math.abs(g.self);\n      }\n    }\n    y += direction * ROW;\n\n    // Find child or parent stacks.\n    const next = [];\n    for (const place of g.places) {\n      const stack = stacks.Stacks[place.Stack];\n      const nextSlot = place.Pos + direction;\n      if (nextSlot >= 0 && nextSlot < stack.Sources.length) {\n        next.push({Stack: place.Stack, Pos: nextSlot});\n      }\n    }\n    renderStacks(depth+1, xscale, x, y, next, direction);\n  }\n\n  // partitionPlaces partitions a set of places into groups where each group\n  // contains places with the same source. If a stack occurs multiple times\n  // in places, only the outer-most occurrence is kept.\n  function partitionPlaces(places) {\n    // Find outer-most slot per stack (used later to elide duplicate stacks).\n    const stackMap = new Map();  // Map from stack index to outer-most slot#\n    for (const place of places) {\n      const prevSlot = stackMap.get(place.Stack);\n      if (prevSlot && prevSlot <= place.Pos) {\n        // We already have a higher slot in this stack.\n      } else {\n        stackMap.set(place.Stack, place.Pos);\n      }\n    }\n\n    // Now partition the stacks.\n    const groups = [];           // Array of Group {name, src, sum, self, places}\n    const groupMap = new Map();  // Map from Source to Group\n    for (const place of places) {\n      if (stackMap.get(place.Stack) != place.Pos) {\n        continue;\n      }\n\n      const stack = stacks.Stacks[place.Stack];\n      const src = stack.Sources[place.Pos];\n      let group = groupMap.get(src);\n      if (!group) {\n        const name = stacks.Sources[src].FullName;\n        group = {name: name, src: src, sumpos: 0, sumneg: 0, self: 0, places: []};\n        groupMap.set(src, group);\n        groups.push(group);\n      }\n      if (stack.Value < 0) {\n        group.sumneg += -stack.Value;\n      } else {\n        group.sumpos += stack.Value;\n      }\n      group.self += (place.Pos == stack.Sources.length-1) ? stack.Value : 0;\n      group.places.push(place);\n    }\n\n    // Order by decreasing cost (makes it easier to spot heavy functions).\n    // Though alphabetical ordering is a potential alternative that will make\n    // profile comparisons easier.\n    groups.sort(function(a, b) {\n      return (b.sumpos + b.sumneg) - (a.sumpos + a.sumneg);\n    });\n\n    return groups;\n  }\n\n  function display(xscale, posTotal, negTotal, list, showPivotSummary) {\n    // Sort boxes so that text selection follows a predictable order.\n    list.sort(function(a, b) {\n      if (a.y != b.y) return a.y - b.y;\n      return a.x - b.x;\n    });\n\n    // Adjust Y coordinates so that zero is at top.\n    let adjust = (list.length > 0) ? list[0].y : 0;\n\n    const divs = [];\n    for (const box of list) {\n      box.y -= adjust;\n      divs.push(drawBox(xscale, box));\n    }\n    if (showPivotSummary) {\n      divs.push(drawSep(-adjust, posTotal, negTotal));\n    }\n\n    const h = (list.length > 0 ?  list[list.length-1].y : 0) + 4*ROW;\n    chart.style.height = h+'px';\n    chart.replaceChildren(...divs);\n  }\n\n  function drawBox(xscale, box) {\n    const srcIndex = box.src;\n    const src = stacks.Sources[srcIndex];\n\n    function makeRect(cl, x, y, w, h) {\n      const r = document.createElement('div');\n      r.style.left = x+'px';\n      r.style.top = y+'px';\n      r.style.width = w+'px';\n      r.style.height = h+'px';\n      r.classList.add(cl);\n      return r;\n    }\n\n    // Background\n    const w = box.width - 1; // Leave 1px gap\n    const r = makeRect('boxbg', box.x, box.y, w, ROW);\n    if (!diff) r.style.background = makeColor(src.Color);\n    addElem(srcIndex, r);\n    if (!src.Inlined) {\n      r.classList.add('not-inlined');\n    }\n\n    // Positive/negative indicator for diff mode.\n    if (diff) {\n      const delta = box.sumpos - box.sumneg;\n      const partWidth = xscale * Math.abs(delta);\n      if (partWidth >= MIN_WIDTH) {\n        r.appendChild(makeRect((delta < 0 ? 'negative' : 'positive'),\n                               0, 0, partWidth, ROW-1));\n      }\n    }\n\n    // Label\n    if (box.width >= MIN_TEXT_WIDTH) {\n      const t = document.createElement('div');\n      t.classList.add('boxtext');\n      fitText(t, box.width-2*TEXT_MARGIN, src.Display);\n      r.appendChild(t);\n    }\n\n    onClick(r, () => { switchPivots(pprofQuoteMeta(src.UniqueName)); });\n    r.addEventListener('mouseenter', () => { handleEnter(box, r); });\n    r.addEventListener('mouseleave', () => { handleLeave(box); });\n    r.addEventListener('contextmenu', (e) => { showActionMenu(e, box); });\n    return r;\n  }\n\n  // Handle clicks, but only if the mouse did not move during the click.\n  function onClick(target, handler) {\n    // Disable click if mouse moves more than threshold pixels since mousedown.\n    const threshold = 3;\n    let [x, y] = [-1, -1];\n    target.addEventListener('mousedown', (e) => {\n      [x, y] = [e.clientX, e.clientY];\n    });\n    target.addEventListener('click', (e) => {\n      if (Math.abs(e.clientX - x) <= threshold &&\n          Math.abs(e.clientY - y) <= threshold) {\n        handler();\n      }\n    });\n  }\n\n  function drawSep(y, posTotal, negTotal) {\n    const m = document.createElement('div');\n    m.innerText = summary(posTotal, negTotal);\n    m.style.top = (y-ROW) + 'px';\n    m.style.left = PADDING + 'px';\n    m.style.width = (chart.clientWidth - PADDING*2) + 'px';\n    m.classList.add('separator');\n    return m;\n  }\n\n  // addElem registers an element that belongs to the specified src.\n  function addElem(src, elem) {\n    let list = elems.get(src);\n    if (!list) {\n      list = [];\n      elems.set(src, list);\n    }\n    list.push(elem);\n    elem.classList.toggle('hilite', matches.has(src));\n  }\n\n  // Adds or removes cl from classList of all elements for the specified source.\n  function toggleClass(src, cl, value) {\n    const list = elems.get(src);\n    if (list) {\n      for (const elem of list) {\n        elem.classList.toggle(cl, value);\n      }\n    }\n  }\n\n  // fitText sets text and font-size clipped to the specified width w.\n  function fitText(t, avail, textList) {\n    // Find first entry in textList that fits.\n    let width = avail;\n    textContext.font = FONT_SIZE + 'pt Arial';\n    for (let i = 0; i < textList.length; i++) {\n      let text = textList[i];\n      width = textContext.measureText(text).width;\n      if (width <= avail) {\n        t.innerText = text;\n        return;\n      }\n    }\n\n    // Try to fit by dropping font size.\n    let text = textList[textList.length-1];\n    const fs = Math.max(MIN_FONT_SIZE, FONT_SIZE * (avail / width));\n    t.style.fontSize = fs + 'pt';\n    t.innerText = text;\n  }\n\n  // totalValue returns the positive and negative sums of the Values of stacks\n  // listed in places.\n  function totalValue(places) {\n    const seen = new Set();\n    let pos = 0;\n    let neg = 0;\n    for (const place of places) {\n      if (seen.has(place.Stack)) continue; // Do not double-count stacks\n      seen.add(place.Stack);\n      const stack = stacks.Stacks[place.Stack];\n      if (stack.Value < 0) {\n        neg += -stack.Value;\n      } else {\n        pos += stack.Value;\n      }\n    }\n    return [pos, neg];\n  }\n\n  function summary(pos, neg) {\n    // Examples:\n    //    6s (10%)\n    //    12s (20%) 🠆 18s (30%)\n    return diff ? diffText(neg, pos) : percentText(pos);\n  }\n\n  function details(box) {\n    // Examples:\n    //    6s (10%)\n    //    6s (10%) │ self 3s (5%)\n    //    6s (10%) │ 12s (20%) 🠆 18s (30%)\n    let result = percentText(box.sumpos - box.sumneg);\n    if (box.self != 0) {\n      result += \" │ self \" + unitText(box.self);\n    }\n    if (diff && box.sumpos > 0 && box.sumneg > 0) {\n      result += \" │ \" + diffText(box.sumneg, box.sumpos);\n    }\n    return result;\n  }\n\n  // diffText returns text that displays from and to alongside their percentages.\n  // E.g., 9s (45%) 🠆 10s (50%)\n  function diffText(from, to) {\n    return percentText(from) + \" 🠆 \" + percentText(to);\n  }\n\n  // percentText returns text that displays v in appropriate units alongside its\n  // percentage.\n  function percentText(v) {\n    function percent(v, total) {\n      return Number(((100.0 * v) / total).toFixed(1)) + '%';\n    }\n    return unitText(v) + \" (\" + percent(v, stacks.Total) + \")\";\n  }\n\n  // unitText returns a formatted string to display for value.\n  function unitText(value) {\n    return pprofUnitText(value*stacks.Scale, stacks.Unit);\n  }\n\n  function find(name) {\n    const elem = document.getElementById(name);\n    if (!elem) {\n      throw 'element not found: ' + name\n    }\n    return elem;\n  }\n\n  function makeColor(index) {\n    // Rotate hue around a circle. Multiple by phi to spread things\n    // out better. Use 50% saturation to make subdued colors, and\n    // 80% lightness to have good contrast with black foreground text.\n    const PHI = 1.618033988;\n    const hue = (index+1) * PHI * 2 * Math.PI; // +1 to avoid 0\n    const hsl = `hsl(${hue}rad 50% 80%)`;\n    return hsl;\n  }\n}\n\n// pprofUnitText returns a formatted string to display for value in the specified unit.\nfunction pprofUnitText(value, unit) {\n  const sign = (value < 0) ? \"-\" : \"\";\n  let v = Math.abs(value);\n  // Rescale to appropriate display unit.\n  let list = null;\n  for (const def of pprofUnitDefs) {\n    if (def.DefaultUnit.CanonicalName == unit) {\n      list = def.Units;\n      v *= def.DefaultUnit.Factor;\n      break;\n    }\n  }\n  if (list) {\n    // Stop just before entry that is too large.\n    for (let i = 0; i < list.length; i++) {\n      if (i == list.length-1 || list[i+1].Factor > v) {\n        v /= list[i].Factor;\n        unit = list[i].CanonicalName;\n        break;\n      }\n    }\n  }\n  return sign + Number(v.toFixed(2)) + unit;\n}\n"
  },
  {
    "path": "internal/driver/html/top.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{.Title}}</title>\n  {{template \"css\" .}}\n  <style type=\"text/css\">\n  </style>\n</head>\n<body>\n  {{template \"header\" .}}\n  <div id=\"top\">\n    <table id=\"toptable\">\n      <thead>\n        <tr>\n          <th id=\"flathdr1\">Flat</th>\n          <th id=\"flathdr2\">Flat%</th>\n          <th>Sum%</th>\n          <th id=\"cumhdr1\">Cum</th>\n          <th id=\"cumhdr2\">Cum%</th>\n          <th id=\"namehdr\">Name</th>\n          <th>Inlined?</th>\n        </tr>\n      </thead>\n      <tbody id=\"rows\"></tbody>\n    </table>\n  </div>\n  {{template \"script\" .}}\n  <script>\n    function makeTopTable(total, entries) {\n      const rows = document.getElementById('rows');\n      if (rows == null) return;\n\n      // Store initial index in each entry so we have stable node ids for selection.\n      for (let i = 0; i < entries.length; i++) {\n        entries[i].Id = 'node' + i;\n      }\n\n      // Which column are we currently sorted by and in what order?\n      let currentColumn = '';\n      let descending = false;\n      sortBy('Flat');\n\n      function sortBy(column) {\n        // Update sort criteria\n        if (column == currentColumn) {\n          descending = !descending; // Reverse order\n        } else {\n          currentColumn = column;\n          descending = (column != 'Name');\n        }\n\n        // Sort according to current criteria.\n        function cmp(a, b) {\n          const av = a[currentColumn];\n          const bv = b[currentColumn];\n          if (av < bv) return -1;\n          if (av > bv) return +1;\n          return 0;\n        }\n        entries.sort(cmp);\n        if (descending) entries.reverse();\n\n        function addCell(tr, val) {\n          const td = document.createElement('td');\n          td.textContent = val;\n          tr.appendChild(td);\n        }\n\n        function percent(v) {\n          return (v * 100.0 / total).toFixed(2) + '%';\n        }\n\n        // Generate rows\n        const fragment = document.createDocumentFragment();\n        let sum = 0;\n        for (const row of entries) {\n          const tr = document.createElement('tr');\n          tr.id = row.Id;\n          sum += row.Flat;\n          addCell(tr, row.FlatFormat);\n          addCell(tr, percent(row.Flat));\n          addCell(tr, percent(sum));\n          addCell(tr, row.CumFormat);\n          addCell(tr, percent(row.Cum));\n          addCell(tr, row.Name);\n          addCell(tr, row.InlineLabel);\n          fragment.appendChild(tr);\n        }\n\n        rows.textContent = ''; // Remove old rows\n        rows.appendChild(fragment);\n      }\n\n      // Make different column headers trigger sorting.\n      function bindSort(id, column) {\n        const hdr = document.getElementById(id);\n        if (hdr == null) return;\n        const fn = function() { sortBy(column) };\n        hdr.addEventListener('click', fn);\n        hdr.addEventListener('touch', fn);\n      }\n      bindSort('flathdr1', 'Flat');\n      bindSort('flathdr2', 'Flat');\n      bindSort('cumhdr1', 'Cum');\n      bindSort('cumhdr2', 'Cum');\n      bindSort('namehdr', 'Name');\n    }\n\n    viewer(new URL(window.location.href), {{.Nodes}});\n    makeTopTable({{.Total}}, {{.Top}});\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/interactive.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/report\"\n\t\"github.com/google/pprof/profile\"\n)\n\nvar commentStart = \"//:\" // Sentinel for comments on options\nvar tailDigitsRE = regexp.MustCompile(\"[0-9]+$\")\n\n// interactive starts a shell to read pprof commands.\nfunc interactive(p *profile.Profile, o *plugin.Options) error {\n\t// Enter command processing loop.\n\to.UI.SetAutoComplete(newCompleter(functionNames(p)))\n\tconfigure(\"compact_labels\", \"true\")\n\tconfigHelp[\"sample_index\"] += fmt.Sprintf(\"Or use sample_index=name, with name in %v.\\n\", sampleTypes(p))\n\n\t// Do not wait for the visualizer to complete, to allow multiple\n\t// graphs to be visualized simultaneously.\n\tinteractiveMode = true\n\tshortcuts := profileShortcuts(p)\n\n\tcopier := makeProfileCopier(p)\n\tgreetings(p, o.UI)\n\tfor {\n\t\tinput, err := o.UI.ReadLine(\"(pprof) \")\n\t\tif err != nil {\n\t\t\tif err != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif input == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\tfor _, input := range shortcuts.expand(input) {\n\t\t\t// Process assignments of the form variable=value\n\t\t\tif s := strings.SplitN(input, \"=\", 2); len(s) > 0 {\n\t\t\t\tname := strings.TrimSpace(s[0])\n\t\t\t\tvar value string\n\t\t\t\tif len(s) == 2 {\n\t\t\t\t\tvalue = s[1]\n\t\t\t\t\tif comment := strings.LastIndex(value, commentStart); comment != -1 {\n\t\t\t\t\t\tvalue = value[:comment]\n\t\t\t\t\t}\n\t\t\t\t\tvalue = strings.TrimSpace(value)\n\t\t\t\t}\n\t\t\t\tif isConfigurable(name) {\n\t\t\t\t\t// All non-bool options require inputs\n\t\t\t\t\tif len(s) == 1 && !isBoolConfig(name) {\n\t\t\t\t\t\to.UI.PrintErr(fmt.Errorf(\"please specify a value, e.g. %s=<val>\", name))\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif name == \"sample_index\" {\n\t\t\t\t\t\t// Error check sample_index=xxx to ensure xxx is a valid sample type.\n\t\t\t\t\t\tindex, err := p.SampleIndexByName(value)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\to.UI.PrintErr(err)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif index < 0 || index >= len(p.SampleType) {\n\t\t\t\t\t\t\to.UI.PrintErr(fmt.Errorf(\"invalid sample_index %q\", value))\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalue = p.SampleType[index].Type\n\t\t\t\t\t}\n\t\t\t\t\tif err := configure(name, value); err != nil {\n\t\t\t\t\t\to.UI.PrintErr(err)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttokens := strings.Fields(input)\n\t\t\tif len(tokens) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch tokens[0] {\n\t\t\tcase \"o\", \"options\":\n\t\t\t\tprintCurrentOptions(p, o.UI)\n\t\t\t\tcontinue\n\t\t\tcase \"exit\", \"quit\", \"q\":\n\t\t\t\treturn nil\n\t\t\tcase \"help\":\n\t\t\t\tcommandHelp(strings.Join(tokens[1:], \" \"), o.UI)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\targs, cfg, err := parseCommandLine(tokens)\n\t\t\tif err == nil {\n\t\t\t\terr = generateReportWrapper(copier.newCopy(), args, cfg, o)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\to.UI.PrintErr(err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar generateReportWrapper = generateReport // For testing purposes.\n\n// greetings prints a brief welcome and some overall profile\n// information before accepting interactive commands.\nfunc greetings(p *profile.Profile, ui plugin.UI) {\n\tnumLabelUnits := identifyNumLabelUnits(p, ui)\n\tropt, err := reportOptions(p, numLabelUnits, currentConfig())\n\tif err == nil {\n\t\trpt := report.New(p, ropt)\n\t\tui.Print(strings.Join(report.ProfileLabels(rpt), \"\\n\"))\n\t\tif rpt.Total() == 0 && len(p.SampleType) > 1 {\n\t\t\tui.Print(`No samples were found with the default sample value type.`)\n\t\t\tui.Print(`Try \"sample_index\" command to analyze different sample values.`, \"\\n\")\n\t\t}\n\t}\n\tui.Print(`Entering interactive mode (type \"help\" for commands, \"o\" for options)`)\n}\n\n// shortcuts represents composite commands that expand into a sequence\n// of other commands.\ntype shortcuts map[string][]string\n\nfunc (a shortcuts) expand(input string) []string {\n\tinput = strings.TrimSpace(input)\n\tif a != nil {\n\t\tif r, ok := a[input]; ok {\n\t\t\treturn r\n\t\t}\n\t}\n\treturn []string{input}\n}\n\nvar pprofShortcuts = shortcuts{\n\t\":\": []string{\"focus=\", \"ignore=\", \"hide=\", \"tagfocus=\", \"tagignore=\"},\n}\n\n// profileShortcuts creates macros for convenience and backward compatibility.\nfunc profileShortcuts(p *profile.Profile) shortcuts {\n\ts := pprofShortcuts\n\t// Add shortcuts for sample types\n\tfor _, st := range p.SampleType {\n\t\tcommand := fmt.Sprintf(\"sample_index=%s\", st.Type)\n\t\ts[st.Type] = []string{command}\n\t\ts[\"total_\"+st.Type] = []string{\"mean=0\", command}\n\t\ts[\"mean_\"+st.Type] = []string{\"mean=1\", command}\n\t}\n\treturn s\n}\n\nfunc sampleTypes(p *profile.Profile) []string {\n\ttypes := make([]string, len(p.SampleType))\n\tfor i, t := range p.SampleType {\n\t\ttypes[i] = t.Type\n\t}\n\treturn types\n}\n\nfunc printCurrentOptions(p *profile.Profile, ui plugin.UI) {\n\tvar args []string\n\tcurrent := currentConfig()\n\tfor _, f := range configFields {\n\t\tn := f.name\n\t\tv := current.get(f)\n\t\tcomment := \"\"\n\t\tswitch {\n\t\tcase len(f.choices) > 0:\n\t\t\tvalues := append([]string{}, f.choices...)\n\t\t\tsort.Strings(values)\n\t\t\tcomment = \"[\" + strings.Join(values, \" | \") + \"]\"\n\t\tcase n == \"sample_index\":\n\t\t\tst := sampleTypes(p)\n\t\t\tif v == \"\" {\n\t\t\t\t// Apply default (last sample index).\n\t\t\t\tv = st[len(st)-1]\n\t\t\t}\n\t\t\t// Add comments for all sample types in profile.\n\t\t\tcomment = \"[\" + strings.Join(st, \" | \") + \"]\"\n\t\tcase n == \"source_path\":\n\t\t\tcontinue\n\t\tcase n == \"nodecount\" && v == \"-1\":\n\t\t\tcomment = \"default\"\n\t\tcase v == \"\":\n\t\t\t// Add quotes for empty values.\n\t\t\tv = `\"\"`\n\t\t}\n\t\tif n == \"granularity\" && v == \"\" {\n\t\t\tv = \"(default)\"\n\t\t}\n\t\tif comment != \"\" {\n\t\t\tcomment = commentStart + \" \" + comment\n\t\t}\n\t\targs = append(args, fmt.Sprintf(\"  %-25s = %-20s %s\", n, v, comment))\n\t}\n\tsort.Strings(args)\n\tui.Print(strings.Join(args, \"\\n\"))\n}\n\n// parseCommandLine parses a command and returns the pprof command to\n// execute and the configuration to use for the report.\nfunc parseCommandLine(input []string) ([]string, config, error) {\n\tcmd, args := input[:1], input[1:]\n\tname := cmd[0]\n\n\tc := pprofCommands[name]\n\tif c == nil {\n\t\t// Attempt splitting digits on abbreviated commands (eg top10)\n\t\tif d := tailDigitsRE.FindString(name); d != \"\" && d != name {\n\t\t\tname = name[:len(name)-len(d)]\n\t\t\tcmd[0], args = name, append([]string{d}, args...)\n\t\t\tc = pprofCommands[name]\n\t\t}\n\t}\n\tif c == nil {\n\t\tif _, ok := configHelp[name]; ok {\n\t\t\tvalue := \"<val>\"\n\t\t\tif len(args) > 0 {\n\t\t\t\tvalue = args[0]\n\t\t\t}\n\t\t\treturn nil, config{}, fmt.Errorf(\"did you mean: %s=%s\", name, value)\n\t\t}\n\t\treturn nil, config{}, fmt.Errorf(\"unrecognized command: %q\", name)\n\t}\n\n\tif c.hasParam {\n\t\tif len(args) == 0 {\n\t\t\treturn nil, config{}, fmt.Errorf(\"command %s requires an argument\", name)\n\t\t}\n\t\tcmd = append(cmd, args[0])\n\t\targs = args[1:]\n\t}\n\n\t// Copy config since options set in the command line should not persist.\n\tvcopy := currentConfig()\n\n\tvar focus, ignore string\n\tfor i := 0; i < len(args); i++ {\n\t\tt := args[i]\n\t\tif n, err := strconv.ParseInt(t, 10, 32); err == nil {\n\t\t\tvcopy.NodeCount = int(n)\n\t\t\tcontinue\n\t\t}\n\t\tswitch t[0] {\n\t\tcase '>':\n\t\t\toutputFile := t[1:]\n\t\t\tif outputFile == \"\" {\n\t\t\t\ti++\n\t\t\t\tif i >= len(args) {\n\t\t\t\t\treturn nil, config{}, fmt.Errorf(\"unexpected end of line after >\")\n\t\t\t\t}\n\t\t\t\toutputFile = args[i]\n\t\t\t}\n\t\t\tvcopy.Output = outputFile\n\t\tcase '-':\n\t\t\tif t == \"--cum\" || t == \"-cum\" {\n\t\t\t\tvcopy.Sort = \"cum\"\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tignore = catRegex(ignore, t[1:])\n\t\tdefault:\n\t\t\tfocus = catRegex(focus, t)\n\t\t}\n\t}\n\n\tif name == \"tags\" {\n\t\tif focus != \"\" {\n\t\t\tvcopy.TagFocus = focus\n\t\t}\n\t\tif ignore != \"\" {\n\t\t\tvcopy.TagIgnore = ignore\n\t\t}\n\t} else {\n\t\tif focus != \"\" {\n\t\t\tvcopy.Focus = focus\n\t\t}\n\t\tif ignore != \"\" {\n\t\t\tvcopy.Ignore = ignore\n\t\t}\n\t}\n\tif vcopy.NodeCount == -1 && (name == \"text\" || name == \"top\") {\n\t\tvcopy.NodeCount = 10\n\t}\n\n\treturn cmd, vcopy, nil\n}\n\nfunc catRegex(a, b string) string {\n\tif a != \"\" && b != \"\" {\n\t\treturn a + \"|\" + b\n\t}\n\treturn a + b\n}\n\n// commandHelp displays help and usage information for all Commands\n// and Variables or a specific Command or Variable.\nfunc commandHelp(args string, ui plugin.UI) {\n\tif args == \"\" {\n\t\thelp := usage(false)\n\t\thelp = help + `\n  :   Clear focus/ignore/hide/tagfocus/tagignore\n\n  type \"help <cmd|option>\" for more information\n`\n\n\t\tui.Print(help)\n\t\treturn\n\t}\n\n\tif c := pprofCommands[args]; c != nil {\n\t\tui.Print(c.help(args))\n\t\treturn\n\t}\n\n\tif help, ok := configHelp[args]; ok {\n\t\tui.Print(help + \"\\n\")\n\t\treturn\n\t}\n\n\tui.PrintErr(\"Unknown command: \" + args)\n}\n\n// newCompleter creates an autocompletion function for a set of commands.\nfunc newCompleter(fns []string) func(string) string {\n\treturn func(line string) string {\n\t\tswitch tokens := strings.Fields(line); len(tokens) {\n\t\tcase 0:\n\t\t\t// Nothing to complete\n\t\tcase 1:\n\t\t\t// Single token -- complete command name\n\t\t\tif match := matchVariableOrCommand(tokens[0]); match != \"\" {\n\t\t\t\treturn match\n\t\t\t}\n\t\tcase 2:\n\t\t\tif tokens[0] == \"help\" {\n\t\t\t\tif match := matchVariableOrCommand(tokens[1]); match != \"\" {\n\t\t\t\t\treturn tokens[0] + \" \" + match\n\t\t\t\t}\n\t\t\t\treturn line\n\t\t\t}\n\t\t\tfallthrough\n\t\tdefault:\n\t\t\t// Multiple tokens -- complete using functions, except for tags\n\t\t\tif cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != \"tags\" {\n\t\t\t\tlastTokenIdx := len(tokens) - 1\n\t\t\t\tlastToken := tokens[lastTokenIdx]\n\t\t\t\tif strings.HasPrefix(lastToken, \"-\") {\n\t\t\t\t\tlastToken = \"-\" + functionCompleter(lastToken[1:], fns)\n\t\t\t\t} else {\n\t\t\t\t\tlastToken = functionCompleter(lastToken, fns)\n\t\t\t\t}\n\t\t\t\treturn strings.Join(append(tokens[:lastTokenIdx], lastToken), \" \")\n\t\t\t}\n\t\t}\n\t\treturn line\n\t}\n}\n\n// matchVariableOrCommand attempts to match a string token to the prefix of a Command.\nfunc matchVariableOrCommand(token string) string {\n\ttoken = strings.ToLower(token)\n\tvar matches []string\n\tfor cmd := range pprofCommands {\n\t\tif strings.HasPrefix(cmd, token) {\n\t\t\tmatches = append(matches, cmd)\n\t\t}\n\t}\n\tmatches = append(matches, completeConfig(token)...)\n\tif len(matches) == 1 {\n\t\treturn matches[0]\n\t}\n\treturn \"\"\n}\n\n// functionCompleter replaces provided substring with a function\n// name retrieved from a profile if a single match exists. Otherwise,\n// it returns unchanged substring. It defaults to no-op if the profile\n// is not specified.\nfunc functionCompleter(substring string, fns []string) string {\n\tfound := \"\"\n\tfor _, fName := range fns {\n\t\tif strings.Contains(fName, substring) {\n\t\t\tif found != \"\" {\n\t\t\t\treturn substring\n\t\t\t}\n\t\t\tfound = fName\n\t\t}\n\t}\n\tif found != \"\" {\n\t\treturn found\n\t}\n\treturn substring\n}\n\nfunc functionNames(p *profile.Profile) []string {\n\tvar fns []string\n\tfor _, fn := range p.Function {\n\t\tfns = append(fns, fn.Name)\n\t}\n\treturn fns\n}\n"
  },
  {
    "path": "internal/driver/interactive_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/internal/report\"\n\t\"github.com/google/pprof/internal/transport\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestShell(t *testing.T) {\n\tp := &profile.Profile{}\n\tgenerateReportWrapper = checkValue\n\tdefer func() { generateReportWrapper = generateReport }()\n\n\t// Use test commands and variables to exercise interactive processing\n\tvar savedCommands commands\n\tsavedCommands, pprofCommands = pprofCommands, testCommands\n\tdefer func() { pprofCommands = savedCommands }()\n\n\tsavedConfig := currentConfig()\n\tdefer setCurrentConfig(savedConfig)\n\n\tshortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1)\n\tshortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2)\n\n\tvar testcases = []struct {\n\t\tname              string\n\t\tinput             []string\n\t\tshortcuts         shortcuts\n\t\tallowRx           string\n\t\tnumAllowRxMatches int\n\t\tpropagateError    bool\n\t}{\n\t\t{\"Random interleave of independent scripts 1\", interleave(script, 0), pprofShortcuts, \"\", 0, false},\n\t\t{\"Random interleave of independent scripts 2\", interleave(script, 1), pprofShortcuts, \"\", 0, false},\n\t\t{\"Random interleave of independent scripts with shortcuts 1\", scScript1, shortcuts1, \"\", 0, false},\n\t\t{\"Random interleave of independent scripts with shortcuts 2\", scScript2, shortcuts2, \"\", 0, false},\n\t\t{\"Group with invalid value\", []string{\"sort=this\"}, pprofShortcuts, `invalid \"sort\" value`, 1, false},\n\t\t{\"No special value provided for the option\", []string{\"sample_index\"}, pprofShortcuts, `please specify a value, e.g. sample_index=<val>`, 1, false},\n\t\t{\"No string value provided for the option\", []string{\"focus\"}, pprofShortcuts, `please specify a value, e.g. focus=<val>`, 1, false},\n\t\t{\"No float value provided for the option\", []string{\"divide_by\"}, pprofShortcuts, `please specify a value, e.g. divide_by=<val>`, 1, false},\n\t\t{\"Helpful input format reminder\", []string{\"sample_index 0\"}, pprofShortcuts, `did you mean: sample_index=0`, 1, false},\n\t\t{\"Verify propagation of IO errors\", []string{\"**error**\"}, pprofShortcuts, \"\", 0, true},\n\t}\n\n\to := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsetCurrentConfig(savedConfig)\n\t\t\tpprofShortcuts = tc.shortcuts\n\t\t\tui := &proftest.TestUI{\n\t\t\t\tT:       t,\n\t\t\t\tInput:   tc.input,\n\t\t\t\tAllowRx: tc.allowRx,\n\t\t\t}\n\t\t\to.UI = ui\n\n\t\t\terr := interactive(p, o)\n\t\t\tif (tc.propagateError && err == nil) || (!tc.propagateError && err != nil) {\n\t\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t\t}\n\n\t\t\t// Confirm error message written out once.\n\t\t\tif tc.numAllowRxMatches != ui.NumAllowRxMatches {\n\t\t\t\tt.Errorf(\"want error message to be printed %d time(s), got %d\",\n\t\t\t\t\ttc.numAllowRxMatches, ui.NumAllowRxMatches)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar testCommands = commands{\n\t\"check\": &command{report.Raw, nil, nil, true, \"\", \"\"},\n}\n\n// script contains sequences of commands to be executed for testing. Commands\n// are split by semicolon and interleaved randomly, so they must be\n// independent from each other.\nvar script = []string{\n\t\"call_tree=true;call_tree=false;check call_tree=false;call_tree=yes;check call_tree=true\",\n\t\"mean=1;check mean=true;mean=n;check mean=false\",\n\t\"nodecount=-1;nodecount=-2;check nodecount=-2;nodecount=999999;check nodecount=999999\",\n\t\"nodefraction=-1;nodefraction=-2.5;check nodefraction=-2.5;nodefraction=0.0001;check nodefraction=0.0001\",\n\t\"focus=one;focus=two;check focus=two\",\n\t\"flat=true;check sort=flat;cum=1;check sort=cum\",\n}\n\nfunc makeShortcuts(input []string, seed int64) (shortcuts, []string) {\n\trand := rand.New(rand.NewSource(seed))\n\n\ts := shortcuts{}\n\tvar output, chunk []string\n\tfor _, l := range input {\n\t\tchunk = append(chunk, l)\n\t\tswitch rand.Intn(3) {\n\t\tcase 0:\n\t\t\t// Create a macro for commands in 'chunk'.\n\t\t\tmacro := fmt.Sprintf(\"alias%d\", len(s))\n\t\t\ts[macro] = chunk\n\t\t\toutput = append(output, macro)\n\t\t\tchunk = nil\n\t\tcase 1:\n\t\t\t// Append commands in 'chunk' by themselves.\n\t\t\toutput = append(output, chunk...)\n\t\t\tchunk = nil\n\t\tcase 2:\n\t\t\t// Accumulate commands into 'chunk'\n\t\t}\n\t}\n\toutput = append(output, chunk...)\n\treturn s, output\n}\n\nfunc checkValue(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {\n\tif len(cmd) != 2 {\n\t\treturn fmt.Errorf(\"expected len(cmd)==2, got %v\", cmd)\n\t}\n\n\tinput := cmd[1]\n\targs := strings.SplitN(input, \"=\", 2)\n\tif len(args) == 0 {\n\t\treturn fmt.Errorf(\"unexpected empty input\")\n\t}\n\tname, value := args[0], \"\"\n\tif len(args) == 2 {\n\t\tvalue = args[1]\n\t}\n\n\tf, ok := configFieldMap[name]\n\tif !ok {\n\t\treturn fmt.Errorf(\"Could not find variable named %s\", name)\n\t}\n\n\tif got := cfg.get(f); got != value {\n\t\treturn fmt.Errorf(\"Variable %s, want %s, got %s\", name, value, got)\n\t}\n\treturn nil\n}\n\nfunc interleave(input []string, seed int64) []string {\n\tvar inputs [][]string\n\tfor _, s := range input {\n\t\tinputs = append(inputs, strings.Split(s, \";\"))\n\t}\n\trand := rand.New(rand.NewSource(seed))\n\tvar output []string\n\tfor len(inputs) > 0 {\n\t\tnext := rand.Intn(len(inputs))\n\t\toutput = append(output, inputs[next][0])\n\t\tif tail := inputs[next][1:]; len(tail) > 0 {\n\t\t\tinputs[next] = tail\n\t\t} else {\n\t\t\tinputs = append(inputs[:next], inputs[next+1:]...)\n\t\t}\n\t}\n\treturn output\n}\n\nfunc TestInteractiveCommands(t *testing.T) {\n\ttype interactiveTestcase struct {\n\t\tinput string\n\t\twant  map[string]string\n\t}\n\n\ttestcases := []interactiveTestcase{\n\t\t{\n\t\t\t\"top 10 --cum focus1 -ignore focus2\",\n\t\t\tmap[string]string{\n\t\t\t\t\"nodecount\": \"10\",\n\t\t\t\t\"sort\":      \"cum\",\n\t\t\t\t\"focus\":     \"focus1|focus2\",\n\t\t\t\t\"ignore\":    \"ignore\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"top10 --cum focus1 -ignore focus2\",\n\t\t\tmap[string]string{\n\t\t\t\t\"nodecount\": \"10\",\n\t\t\t\t\"sort\":      \"cum\",\n\t\t\t\t\"focus\":     \"focus1|focus2\",\n\t\t\t\t\"ignore\":    \"ignore\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"dot\",\n\t\t\tmap[string]string{\n\t\t\t\t\"nodecount\": \"80\",\n\t\t\t\t\"sort\":      \"flat\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"tags   -ignore1 -ignore2 focus1 >out\",\n\t\t\tmap[string]string{\n\t\t\t\t\"nodecount\": \"80\",\n\t\t\t\t\"sort\":      \"flat\",\n\t\t\t\t\"output\":    \"out\",\n\t\t\t\t\"tagfocus\":  \"focus1\",\n\t\t\t\t\"tagignore\": \"ignore1|ignore2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"weblist  find -test\",\n\t\t\tmap[string]string{\n\t\t\t\t\"granularity\": \"addresses\",\n\t\t\t\t\"noinlines\":   \"false\",\n\t\t\t\t\"nodecount\":   \"0\",\n\t\t\t\t\"sort\":        \"flat\",\n\t\t\t\t\"ignore\":      \"test\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"callgrind   fun -ignore  >out\",\n\t\t\tmap[string]string{\n\t\t\t\t\"granularity\": \"addresses\",\n\t\t\t\t\"nodecount\":   \"0\",\n\t\t\t\t\"sort\":        \"flat\",\n\t\t\t\t\"output\":      \"out\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"999\",\n\t\t\tnil, // Error\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tcmd, cfg, err := parseCommandLine(strings.Fields(tc.input))\n\t\tif tc.want == nil && err != nil {\n\t\t\t// Error expected\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed on %q: %v\", tc.input, err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get report output format\n\t\tc := pprofCommands[cmd[0]]\n\t\tif c == nil {\n\t\t\tt.Fatalf(\"unexpected nil command\")\n\t\t}\n\t\tcfg = applyCommandOverrides(cmd[0], c.format, cfg)\n\n\t\tfor n, want := range tc.want {\n\t\t\tif got := cfg.get(configFieldMap[n]); got != want {\n\t\t\t\tt.Errorf(\"failed on %q, cmd=%q, %s got %s, want %s\", tc.input, cmd, n, got, want)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/driver/options.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/symbolizer\"\n\t\"github.com/google/pprof/internal/transport\"\n)\n\n// setDefaults returns a new plugin.Options with zero fields sets to\n// sensible defaults.\nfunc setDefaults(o *plugin.Options) *plugin.Options {\n\td := &plugin.Options{}\n\tif o != nil {\n\t\t*d = *o\n\t}\n\tif d.Writer == nil {\n\t\td.Writer = oswriter{}\n\t}\n\tif d.Flagset == nil {\n\t\td.Flagset = &GoFlags{}\n\t}\n\tif d.Obj == nil {\n\t\td.Obj = &binutils.Binutils{}\n\t}\n\tif d.UI == nil {\n\t\td.UI = &stdUI{r: bufio.NewReader(os.Stdin)}\n\t}\n\tif d.HTTPTransport == nil {\n\t\td.HTTPTransport = transport.New(d.Flagset)\n\t}\n\tif d.Sym == nil {\n\t\td.Sym = &symbolizer.Symbolizer{Obj: d.Obj, UI: d.UI, Transport: d.HTTPTransport}\n\t}\n\treturn d\n}\n\ntype stdUI struct {\n\tr *bufio.Reader\n}\n\nfunc (ui *stdUI) ReadLine(prompt string) (string, error) {\n\tos.Stdout.WriteString(prompt)\n\treturn ui.r.ReadString('\\n')\n}\n\nfunc (ui *stdUI) Print(args ...interface{}) {\n\tui.fprint(os.Stderr, args)\n}\n\nfunc (ui *stdUI) PrintErr(args ...interface{}) {\n\tui.fprint(os.Stderr, args)\n}\n\nfunc (ui *stdUI) IsTerminal() bool {\n\treturn false\n}\n\nfunc (ui *stdUI) WantBrowser() bool {\n\treturn true\n}\n\nfunc (ui *stdUI) SetAutoComplete(func(string) string) {\n}\n\nfunc (ui *stdUI) fprint(f *os.File, args []interface{}) {\n\ttext := fmt.Sprint(args...)\n\tif !strings.HasSuffix(text, \"\\n\") {\n\t\ttext += \"\\n\"\n\t}\n\tf.WriteString(text)\n}\n\n// oswriter implements the Writer interface using a regular file.\ntype oswriter struct{}\n\nfunc (oswriter) Open(name string) (io.WriteCloser, error) {\n\tf, err := os.Create(name)\n\treturn f, err\n}\n"
  },
  {
    "path": "internal/driver/settings.go",
    "content": "package driver\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// settings holds pprof settings.\ntype settings struct {\n\t// Configs holds a list of named UI configurations.\n\tConfigs []namedConfig `json:\"configs\"`\n}\n\n// namedConfig associates a name with a config.\ntype namedConfig struct {\n\tName string `json:\"name\"`\n\tconfig\n}\n\n// settingsFileName returns the name of the file where settings should be saved.\nfunc settingsFileName() (string, error) {\n\t// Return \"pprof/settings.json\" under os.UserConfigDir().\n\tdir, err := os.UserConfigDir()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Join(dir, \"pprof\", \"settings.json\"), nil\n}\n\n// readSettings reads settings from fname.\nfunc readSettings(fname string) (*settings, error) {\n\tdata, err := os.ReadFile(fname)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn &settings{}, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"could not read settings: %w\", err)\n\t}\n\tsettings := &settings{}\n\tif err := json.Unmarshal(data, settings); err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse settings: %w\", err)\n\t}\n\tfor i := range settings.Configs {\n\t\tsettings.Configs[i].resetTransient()\n\t}\n\treturn settings, nil\n}\n\n// writeSettings saves settings to fname.\nfunc writeSettings(fname string, settings *settings) error {\n\tdata, err := json.MarshalIndent(settings, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not encode settings: %w\", err)\n\t}\n\n\t// create the settings directory if it does not exist\n\t// XDG specifies permissions 0700 when creating settings dirs:\n\t// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n\tif err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {\n\t\treturn fmt.Errorf(\"failed to create settings directory: %w\", err)\n\t}\n\n\tif err := os.WriteFile(fname, data, 0644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write settings: %w\", err)\n\t}\n\treturn nil\n}\n\n// configMenuEntry holds information for a single config menu entry.\ntype configMenuEntry struct {\n\tName       string\n\tURL        string\n\tCurrent    bool // Is this the currently selected config?\n\tUserConfig bool // Is this a user-provided config?\n}\n\n// configMenu returns a list of items to add to a menu in the web UI.\nfunc configMenu(fname string, u url.URL) []configMenuEntry {\n\t// Start with system configs.\n\tconfigs := []namedConfig{{Name: \"Default\", config: defaultConfig()}}\n\tif settings, err := readSettings(fname); err == nil {\n\t\t// Add user configs.\n\t\tconfigs = append(configs, settings.Configs...)\n\t}\n\n\t// Convert to menu entries.\n\tresult := make([]configMenuEntry, len(configs))\n\tlastMatch := -1\n\tfor i, cfg := range configs {\n\t\tdst, changed := cfg.makeURL(u)\n\t\tif !changed {\n\t\t\tlastMatch = i\n\t\t}\n\t\t// Use a relative URL to work in presence of stripping/redirects in webui.go.\n\t\trel := &url.URL{RawQuery: dst.RawQuery, ForceQuery: true}\n\t\tresult[i] = configMenuEntry{\n\t\t\tName:       cfg.Name,\n\t\t\tURL:        rel.String(),\n\t\t\tUserConfig: (i != 0),\n\t\t}\n\t}\n\t// Mark the last matching config as current\n\tif lastMatch >= 0 {\n\t\tresult[lastMatch].Current = true\n\t}\n\treturn result\n}\n\n// editSettings edits settings by applying fn to them.\nfunc editSettings(fname string, fn func(s *settings) error) error {\n\tsettings, err := readSettings(fname)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := fn(settings); err != nil {\n\t\treturn err\n\t}\n\treturn writeSettings(fname, settings)\n}\n\n// setConfig saves the config specified in request to fname.\nfunc setConfig(fname string, request url.URL) error {\n\tq := request.Query()\n\tname := q.Get(\"config\")\n\tif name == \"\" {\n\t\treturn fmt.Errorf(\"invalid config name\")\n\t}\n\tcfg := currentConfig()\n\tif err := cfg.applyURL(q); err != nil {\n\t\treturn err\n\t}\n\treturn editSettings(fname, func(s *settings) error {\n\t\tfor i, c := range s.Configs {\n\t\t\tif c.Name == name {\n\t\t\t\ts.Configs[i].config = cfg\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\ts.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})\n\t\treturn nil\n\t})\n}\n\n// removeConfig removes config from fname.\nfunc removeConfig(fname, config string) error {\n\treturn editSettings(fname, func(s *settings) error {\n\t\tfor i, c := range s.Configs {\n\t\t\tif c.Name == config {\n\t\t\t\ts.Configs = append(s.Configs[:i], s.Configs[i+1:]...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"config %s not found\", config)\n\t})\n}\n"
  },
  {
    "path": "internal/driver/settings_test.go",
    "content": "package driver\n\nimport (\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"testing\"\n)\n\n// settingsDirAndFile returns a directory in which settings should be stored\n// and the name of the settings file. The caller must delete the directory when\n// done.\nfunc settingsDirAndFile(t *testing.T) (string, string) {\n\ttmpDir, err := os.MkdirTemp(\"\", \"pprof_settings_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"error creating temporary directory: %v\", err)\n\t}\n\treturn tmpDir, filepath.Join(tmpDir, \"settings.json\")\n}\n\nfunc TestSettings(t *testing.T) {\n\ttmpDir, fname := settingsDirAndFile(t)\n\tdefer os.RemoveAll(tmpDir)\n\ts, err := readSettings(fname)\n\tif err != nil {\n\t\tt.Fatalf(\"error reading empty settings: %v\", err)\n\t}\n\tif len(s.Configs) != 0 {\n\t\tt.Fatalf(\"expected empty settings; got %v\", s)\n\t}\n\ts.Configs = append(s.Configs, namedConfig{\n\t\tName: \"Foo\",\n\t\tconfig: config{\n\t\t\tFocus: \"focus\",\n\t\t\t// Ensure that transient fields are not saved/restored.\n\t\t\tOutput:     \"output\",\n\t\t\tSourcePath: \"source\",\n\t\t\tTrimPath:   \"trim\",\n\t\t\tDivideBy:   -2,\n\t\t},\n\t})\n\tif err := writeSettings(fname, s); err != nil {\n\t\tt.Fatal(err)\n\t}\n\ts2, err := readSettings(fname)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Change the transient fields to their expected values.\n\ts.Configs[0].resetTransient()\n\tif !reflect.DeepEqual(s, s2) {\n\t\tt.Fatalf(\"ReadSettings = %v; expected %v\", s2, s)\n\t}\n}\n\nfunc TestParseConfig(t *testing.T) {\n\t// Use all the fields to check they are saved/restored from URL.\n\tcfg := config{\n\t\tOutput:              \"\",\n\t\tDropNegative:        true,\n\t\tCallTree:            true,\n\t\tRelativePercentages: true,\n\t\tUnit:                \"auto\",\n\t\tCompactLabels:       true,\n\t\tSourcePath:          \"\",\n\t\tTrimPath:            \"\",\n\t\tNodeCount:           10,\n\t\tNodeFraction:        0.1,\n\t\tEdgeFraction:        0.2,\n\t\tTrim:                true,\n\t\tFocus:               \"focus\",\n\t\tIgnore:              \"ignore\",\n\t\tPruneFrom:           \"prune_from\",\n\t\tHide:                \"hide\",\n\t\tShow:                \"show\",\n\t\tShowFrom:            \"show_from\",\n\t\tTagFocus:            \"tagfocus\",\n\t\tTagIgnore:           \"tagignore\",\n\t\tTagShow:             \"tagshow\",\n\t\tTagHide:             \"taghide\",\n\t\tDivideBy:            1,\n\t\tMean:                true,\n\t\tNormalize:           true,\n\t\tSort:                \"cum\",\n\t\tGranularity:         \"functions\",\n\t\tNoInlines:           true,\n\t\tShowColumns:         true,\n\t}\n\turl, changed := cfg.makeURL(url.URL{})\n\tif !changed {\n\t\tt.Error(\"applyConfig returned changed=false after applying non-empty config\")\n\t}\n\tcfg2 := defaultConfig()\n\tif err := cfg2.applyURL(url.Query()); err != nil {\n\t\tt.Fatalf(\"fromURL failed: %v\", err)\n\t}\n\tif !reflect.DeepEqual(cfg, cfg2) {\n\t\tt.Fatalf(\"parsed config = %+v; expected match with %+v\", cfg2, cfg)\n\t}\n\tif url2, changed := cfg.makeURL(url); changed {\n\t\tt.Errorf(\"ApplyConfig returned changed=true after applying same config (%q instead of expected %q\", url2.String(), url.String())\n\t}\n}\n\n// TestDefaultConfig verifies that default config values are omitted from URL.\nfunc TestDefaultConfig(t *testing.T) {\n\tcfg := defaultConfig()\n\turl, changed := cfg.makeURL(url.URL{})\n\tif changed {\n\t\tt.Error(\"applyConfig returned changed=true after applying default config\")\n\t}\n\tif url.String() != \"\" {\n\t\tt.Errorf(\"applyConfig returned %q; expecting %q\", url.String(), \"\")\n\t}\n}\n\nfunc TestConfigMenu(t *testing.T) {\n\t// Save some test settings.\n\ttmpDir, fname := settingsDirAndFile(t)\n\tdefer os.RemoveAll(tmpDir)\n\ta, b := defaultConfig(), defaultConfig()\n\ta.Focus, b.Focus = \"foo\", \"bar\"\n\ts := &settings{\n\t\tConfigs: []namedConfig{\n\t\t\t{Name: \"A\", config: a},\n\t\t\t{Name: \"B\", config: b},\n\t\t},\n\t}\n\tif err := writeSettings(fname, s); err != nil {\n\t\tt.Fatal(\"error writing settings\", err)\n\t}\n\n\tpageURL, _ := url.Parse(\"/top?f=foo\")\n\tmenu := configMenu(fname, *pageURL)\n\twant := []configMenuEntry{\n\t\t{Name: \"Default\", URL: \"?\", Current: false, UserConfig: false},\n\t\t{Name: \"A\", URL: \"?f=foo\", Current: true, UserConfig: true},\n\t\t{Name: \"B\", URL: \"?f=bar\", Current: false, UserConfig: true},\n\t}\n\tif !reflect.DeepEqual(menu, want) {\n\t\tt.Errorf(\"ConfigMenu returned %v; want %v\", menu, want)\n\t}\n}\n\nfunc TestEditConfig(t *testing.T) {\n\ttmpDir, fname := settingsDirAndFile(t)\n\tdefer os.RemoveAll(tmpDir)\n\n\ttype testConfig struct {\n\t\tname  string\n\t\tfocus string\n\t\thide  string\n\t}\n\ttype testCase struct {\n\t\tremove  bool\n\t\trequest string\n\t\texpect  []testConfig\n\t}\n\tfor _, c := range []testCase{\n\t\t// Create setting c1\n\t\t{false, \"/?config=c1&f=foo\", []testConfig{\n\t\t\t{\"c1\", \"foo\", \"\"},\n\t\t}},\n\t\t// Create setting c2\n\t\t{false, \"/?config=c2&h=bar\", []testConfig{\n\t\t\t{\"c1\", \"foo\", \"\"},\n\t\t\t{\"c2\", \"\", \"bar\"},\n\t\t}},\n\t\t// Overwrite c1\n\t\t{false, \"/?config=c1&f=baz\", []testConfig{\n\t\t\t{\"c1\", \"baz\", \"\"},\n\t\t\t{\"c2\", \"\", \"bar\"},\n\t\t}},\n\t\t// Delete c2\n\t\t{true, \"c2\", []testConfig{\n\t\t\t{\"c1\", \"baz\", \"\"},\n\t\t}},\n\t} {\n\t\tif c.remove {\n\t\t\tif err := removeConfig(fname, c.request); err != nil {\n\t\t\t\tt.Errorf(\"error removing config %s: %v\", c.request, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else {\n\t\t\treq, err := url.Parse(c.request)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error parsing request %q: %v\", c.request, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err := setConfig(fname, *req); err != nil {\n\t\t\t\tt.Errorf(\"error saving request %q: %v\", c.request, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Check resulting settings.\n\t\ts, err := readSettings(fname)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"error reading settings after applying %q: %v\", c.request, err)\n\t\t\tcontinue\n\t\t}\n\t\t// Convert to a list that can be compared to c.expect\n\t\tgot := make([]testConfig, len(s.Configs))\n\t\tfor i, c := range s.Configs {\n\t\t\tgot[i] = testConfig{c.Name, c.Focus, c.Hide}\n\t\t}\n\t\tif !reflect.DeepEqual(got, c.expect) {\n\t\t\tt.Errorf(\"Settings after applying %q = %v; want %v\", c.request, got, c.expect)\n\t\t}\n\t}\n}\n\nfunc TestAssign(t *testing.T) {\n\tbaseConfig := currentConfig()\n\tdefer setCurrentConfig(baseConfig)\n\n\t// Test assigning to a simple field.\n\tif err := configure(\"nodecount\", \"20\"); err != nil {\n\t\tt.Errorf(\"error setting nodecount: %v\", err)\n\t}\n\tif n := currentConfig().NodeCount; n != 20 {\n\t\tt.Errorf(\"incorrect nodecount; expecting 20, got %d\", n)\n\t}\n\n\t// Test assignment to a group field.\n\tif err := configure(\"granularity\", \"files\"); err != nil {\n\t\tt.Errorf(\"error setting granularity: %v\", err)\n\t}\n\tif g := currentConfig().Granularity; g != \"files\" {\n\t\tt.Errorf(\"incorrect granularity; expecting %v, got %v\", \"files\", g)\n\t}\n\n\t// Test assignment to one choice of a group field.\n\tif err := configure(\"lines\", \"t\"); err != nil {\n\t\tt.Errorf(\"error setting lines: %v\", err)\n\t}\n\tif g := currentConfig().Granularity; g != \"lines\" {\n\t\tt.Errorf(\"incorrect granularity; expecting %v, got %v\", \"lines\", g)\n\t}\n\n\t// Test assignment to invalid choice,\n\tif err := configure(\"granularity\", \"cheese\"); err == nil {\n\t\tt.Errorf(\"allowed assignment of invalid granularity\")\n\t}\n}\n"
  },
  {
    "path": "internal/driver/stacks.go",
    "content": "// Copyright 2022 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"encoding/json\"\n\t\"html/template\"\n\t\"net/http\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n)\n\n// stackView generates the flamegraph view.\nfunc (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) {\n\t// Get all data in a report.\n\trpt, errList := ui.makeReport(w, req, []string{\"svg\"}, func(cfg *config) {\n\t\tcfg.CallTree = true\n\t\tcfg.Trim = false\n\t\tif cfg.Granularity == \"\" {\n\t\t\tcfg.Granularity = \"filefunctions\"\n\t\t}\n\t})\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\n\t// Make stack data and generate corresponding JSON.\n\tstacks := rpt.Stacks()\n\tb, err := json.Marshal(stacks)\n\tif err != nil {\n\t\thttp.Error(w, \"error serializing stacks for flame graph\",\n\t\t\thttp.StatusInternalServerError)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n\n\tnodes := make([]string, len(stacks.Sources))\n\tfor i, src := range stacks.Sources {\n\t\tnodes[i] = src.FullName\n\t}\n\tnodes[0] = \"\" // root is not a real node\n\n\tui.render(w, req, \"stacks\", rpt, errList, stacks.Legend(), webArgs{\n\t\tStacks:   template.JS(b),\n\t\tNodes:    nodes,\n\t\tUnitDefs: measurement.UnitTypes,\n\t})\n}\n"
  },
  {
    "path": "internal/driver/svg.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/third_party/svgpan\"\n)\n\nvar (\n\tviewBox  = regexp.MustCompile(`<svg\\s*width=\"[^\"]+\"\\s*height=\"[^\"]+\"\\s*viewBox=\"[^\"]+\"`)\n\tgraphID  = regexp.MustCompile(`<g id=\"graph\\d\"`)\n\tsvgClose = regexp.MustCompile(`</svg>`)\n)\n\n// massageSVG enhances the SVG output from DOT to provide better\n// panning inside a web browser. It uses the svgpan library, which is\n// embedded into the svgpan.JSSource variable.\nfunc massageSVG(svg string) string {\n\t// Work around for dot bug which misses quoting some ampersands,\n\t// resulting on unparsable SVG.\n\tsvg = strings.ReplaceAll(svg, \"&;\", \"&amp;;\")\n\n\t// Dot's SVG output is\n\t//\n\t//    <svg width=\"___\" height=\"___\"\n\t//     viewBox=\"___\" xmlns=...>\n\t//    <g id=\"graph0\" transform=\"...\">\n\t//    ...\n\t//    </g>\n\t//    </svg>\n\t//\n\t// Change it to\n\t//\n\t//    <svg width=\"100%\" height=\"100%\"\n\t//     xmlns=...>\n\n\t//    <script type=\"text/ecmascript\"><![CDATA[` ..$(svgpan.JSSource)... `]]></script>`\n\t//    <g id=\"viewport\" transform=\"translate(0,0)\">\n\t//    <g id=\"graph0\" transform=\"...\">\n\t//    ...\n\t//    </g>\n\t//    </g>\n\t//    </svg>\n\n\tif loc := viewBox.FindStringIndex(svg); loc != nil {\n\t\tsvg = svg[:loc[0]] +\n\t\t\t`<svg width=\"100%\" height=\"100%\"` +\n\t\t\tsvg[loc[1]:]\n\t}\n\n\tif loc := graphID.FindStringIndex(svg); loc != nil {\n\t\tsvg = svg[:loc[0]] +\n\t\t\t`<script type=\"text/ecmascript\"><![CDATA[` + svgpan.JSSource + `]]></script>` +\n\t\t\t`<g id=\"viewport\" transform=\"scale(0.5,0.5) translate(0,0)\">` +\n\t\t\tsvg[loc[0]:]\n\t}\n\n\tif loc := svgClose.FindStringIndex(svg); loc != nil {\n\t\tsvg = svg[:loc[0]] +\n\t\t\t`</g>` +\n\t\t\tsvg[loc[0]:]\n\t}\n\n\treturn svg\n}\n"
  },
  {
    "path": "internal/driver/tagroot.go",
    "content": "package driver\n\nimport (\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// addLabelNodes adds pseudo stack frames \"label:value\" to each Sample with\n// labels matching the supplied keys.\n//\n// rootKeys adds frames at the root of the callgraph (first key becomes new root).\n// leafKeys adds frames at the leaf of the callgraph (last key becomes new leaf).\n//\n// Returns whether there were matches found for the label keys.\nfunc addLabelNodes(p *profile.Profile, rootKeys, leafKeys []string, outputUnit string) (rootm, leafm bool) {\n\t// Find where to insert the new locations and functions at the end of\n\t// their ID spaces.\n\tvar maxLocID uint64\n\tvar maxFunctionID uint64\n\tfor _, loc := range p.Location {\n\t\tif loc.ID > maxLocID {\n\t\t\tmaxLocID = loc.ID\n\t\t}\n\t}\n\tfor _, f := range p.Function {\n\t\tif f.ID > maxFunctionID {\n\t\t\tmaxFunctionID = f.ID\n\t\t}\n\t}\n\tnextLocID := maxLocID + 1\n\tnextFuncID := maxFunctionID + 1\n\n\t// Intern the new locations and functions we are generating.\n\ttype locKey struct {\n\t\tfunctionName, fileName string\n\t}\n\tlocs := map[locKey]*profile.Location{}\n\n\tinternLoc := func(locKey locKey) *profile.Location {\n\t\tloc, found := locs[locKey]\n\t\tif found {\n\t\t\treturn loc\n\t\t}\n\n\t\tfunction := &profile.Function{\n\t\t\tID:       nextFuncID,\n\t\t\tName:     locKey.functionName,\n\t\t\tFilename: locKey.fileName,\n\t\t}\n\t\tnextFuncID++\n\t\tp.Function = append(p.Function, function)\n\n\t\tloc = &profile.Location{\n\t\t\tID: nextLocID,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{\n\t\t\t\t\tFunction: function,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tnextLocID++\n\t\tp.Location = append(p.Location, loc)\n\t\tlocs[locKey] = loc\n\t\treturn loc\n\t}\n\n\tmakeLabelLocs := func(s *profile.Sample, keys []string) ([]*profile.Location, bool) {\n\t\tvar locs []*profile.Location\n\t\tvar match bool\n\t\tfor i := range keys {\n\t\t\t// Loop backwards, ensuring the first tag is closest to the root,\n\t\t\t// and the last tag is closest to the leaves.\n\t\t\tk := keys[len(keys)-1-i]\n\t\t\tvalues := formatLabelValues(s, k, outputUnit)\n\t\t\tif len(values) > 0 {\n\t\t\t\tmatch = true\n\t\t\t}\n\t\t\tlocKey := locKey{\n\t\t\t\tfunctionName: strings.Join(values, \",\"),\n\t\t\t\tfileName:     k,\n\t\t\t}\n\t\t\tloc := internLoc(locKey)\n\t\t\tlocs = append(locs, loc)\n\t\t}\n\t\treturn locs, match\n\t}\n\n\tfor _, s := range p.Sample {\n\t\trootsToAdd, sampleMatchedRoot := makeLabelLocs(s, rootKeys)\n\t\tif sampleMatchedRoot {\n\t\t\trootm = true\n\t\t}\n\t\tleavesToAdd, sampleMatchedLeaf := makeLabelLocs(s, leafKeys)\n\t\tif sampleMatchedLeaf {\n\t\t\tleafm = true\n\t\t}\n\n\t\tif len(leavesToAdd)+len(rootsToAdd) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar newLocs []*profile.Location\n\t\tnewLocs = append(newLocs, leavesToAdd...)\n\t\tnewLocs = append(newLocs, s.Location...)\n\t\tnewLocs = append(newLocs, rootsToAdd...)\n\t\ts.Location = newLocs\n\t}\n\treturn\n}\n\n// formatLabelValues returns all the string and numeric labels in Sample, with\n// the numeric labels formatted according to outputUnit.\nfunc formatLabelValues(s *profile.Sample, k string, outputUnit string) []string {\n\tvar values []string\n\tvalues = append(values, s.Label[k]...)\n\tnumLabels := s.NumLabel[k]\n\tnumUnits := s.NumUnit[k]\n\tif len(numLabels) != len(numUnits) && len(numUnits) != 0 {\n\t\treturn values\n\t}\n\tfor i, numLabel := range numLabels {\n\t\tvar value string\n\t\tif len(numUnits) != 0 {\n\t\t\tvalue = measurement.ScaledLabel(numLabel, numUnits[i], outputUnit)\n\t\t} else {\n\t\t\tvalue = measurement.ScaledLabel(numLabel, \"\", \"\")\n\t\t}\n\t\tvalues = append(values, value)\n\t}\n\treturn values\n}\n"
  },
  {
    "path": "internal/driver/tagroot_test.go",
    "content": "package driver\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/profile\"\n)\n\nconst mainBinary = \"/bin/main\"\n\nvar cpuF = []*profile.Function{\n\t{ID: 1, Name: \"main\", SystemName: \"main\", Filename: \"main.c\"},\n\t{ID: 2, Name: \"foo\", SystemName: \"foo\", Filename: \"foo.c\"},\n\t{ID: 3, Name: \"foo_caller\", SystemName: \"foo_caller\", Filename: \"foo.c\"},\n\t{ID: 4, Name: \"bar\", SystemName: \"bar\", Filename: \"bar.c\"},\n}\n\nvar cpuM = []*profile.Mapping{\n\t{\n\t\tID:              1,\n\t\tStart:           0x10000,\n\t\tLimit:           0x40000,\n\t\tFile:            mainBinary,\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              2,\n\t\tStart:           0x1000,\n\t\tLimit:           0x4000,\n\t\tFile:            \"/lib/lib.so\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n}\n\nvar cpuL = []*profile.Location{\n\t{\n\t\tID:      1000,\n\t\tMapping: cpuM[1],\n\t\tAddress: 0x1000,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[0], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      2000,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x2000,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[1], Line: 2},\n\t\t\t{Function: cpuF[2], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      3000,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3000,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[1], Line: 2},\n\t\t\t{Function: cpuF[2], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      3001,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3001,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[2], Line: 2},\n\t\t},\n\t},\n\t{\n\t\tID:      3002,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3002,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[2], Line: 3},\n\t\t},\n\t},\n\t{\n\t\tID:      3003,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3003,\n\t\tLine: []profile.Line{\n\t\t\t{Function: cpuF[3], Line: 1},\n\t\t},\n\t},\n}\n\nvar testProfile1 = &profile.Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*profile.ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[0]},\n\t\t\tValue:    []int64{1000, 1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[1], cpuL[0]},\n\t\t\tValue:    []int64{100, 100},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[2], cpuL[0]},\n\t\t\tValue:    []int64{10, 10},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"allocations\": {1024},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"allocations\": {\"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[3], cpuL[0]},\n\t\t\tValue:    []int64{10000, 10000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"allocations\": {1024, 2048},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"allocations\": {\"bytes\", \"b\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[4], cpuL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\", \"tag5\"},\n\t\t\t},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"allocations\": {1024, 1},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"allocations\": {\"byte\", \"kilobyte\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{cpuL[5], cpuL[0]},\n\t\t\tValue:    []int64{200, 200},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"allocations\": {1024},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nfunc TestAddLabelNodesMatchBooleans(t *testing.T) {\n\ttype addLabelNodesTestcase struct {\n\t\tname             string\n\t\ttagroot, tagleaf []string\n\t\toutputUnit       string\n\t\trootm, leafm     bool\n\t\t// wantSampleFuncs contains expected stack functions and sample value after\n\t\t// adding nodes, in the same order as in the profile. The format is as\n\t\t// returned by stackCollapse function, which is \"callee caller: <num>\".\n\t\twantSampleFuncs []string\n\t}\n\tfor _, tc := range []addLabelNodesTestcase{\n\t\t{\n\t\t\tname: \"Without tagroot or tagleaf, add no extra nodes, and should not match\",\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Keys that aren't found add empty nodes, and should not match\",\n\t\t\ttagroot: []string{\"key404\"},\n\t\t\ttagleaf: []string{\"key404\"},\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"(key404);main(main.c);(key404) 1000\",\n\t\t\t\t\"(key404);main(main.c);foo(foo.c);foo_caller(foo.c);(key404) 100\",\n\t\t\t\t\"(key404);main(main.c);foo(foo.c);foo_caller(foo.c);(key404) 10\",\n\t\t\t\t\"(key404);main(main.c);foo_caller(foo.c);(key404) 10000\",\n\t\t\t\t\"(key404);main(main.c);foo_caller(foo.c);(key404) 1\",\n\t\t\t\t\"(key404);main(main.c);bar(bar.c);(key404) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagroot adds nodes for key1 and reports a match\",\n\t\t\ttagroot: []string{\"key1\"},\n\t\t\trootm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"tag1(key1);main(main.c) 1000\",\n\t\t\t\t\"tag2(key1);main(main.c);foo(foo.c);foo_caller(foo.c) 100\",\n\t\t\t\t\"tag3(key1);main(main.c);foo(foo.c);foo_caller(foo.c) 10\",\n\t\t\t\t\"tag4(key1);main(main.c);foo_caller(foo.c) 10000\",\n\t\t\t\t\"tag4(key1);main(main.c);foo_caller(foo.c) 1\",\n\t\t\t\t\"(key1);main(main.c);bar(bar.c) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagroot adds nodes for key2 and reports a match\",\n\t\t\ttagroot: []string{\"key2\"},\n\t\t\trootm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"tag1(key2);main(main.c) 1000\",\n\t\t\t\t\"(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 100\",\n\t\t\t\t\"tag2(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 10\",\n\t\t\t\t\"tag1(key2);main(main.c);foo_caller(foo.c) 10000\",\n\t\t\t\t\"tag1,tag5(key2);main(main.c);foo_caller(foo.c) 1\",\n\t\t\t\t\"(key2);main(main.c);bar(bar.c) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagleaf adds nodes for key1 and reports a match\",\n\t\t\ttagleaf: []string{\"key1\"},\n\t\t\tleafm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);tag1(key1) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key1) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);tag3(key1) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);tag4(key1) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);tag4(key1) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);(key1) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagleaf adds nodes for key3 and reports a match\",\n\t\t\ttagleaf: []string{\"key3\"},\n\t\t\tleafm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);(key3) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key3) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);(key3) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);(key3) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);(key3) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);(key3) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagroot adds nodes for key1,key2 in order and reports a match\",\n\t\t\ttagroot: []string{\"key1\", \"key2\"},\n\t\t\trootm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"tag1(key1);tag1(key2);main(main.c) 1000\",\n\t\t\t\t\"tag2(key1);(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 100\",\n\t\t\t\t\"tag3(key1);tag2(key2);main(main.c);foo(foo.c);foo_caller(foo.c) 10\",\n\t\t\t\t\"tag4(key1);tag1(key2);main(main.c);foo_caller(foo.c) 10000\",\n\t\t\t\t\"tag4(key1);tag1,tag5(key2);main(main.c);foo_caller(foo.c) 1\",\n\t\t\t\t\"(key1);(key2);main(main.c);bar(bar.c) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"tagleaf adds nodes for key1,key2 in order and reports a match\",\n\t\t\ttagleaf: []string{\"key1\", \"key2\"},\n\t\t\tleafm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);tag1(key1);tag1(key2) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);tag2(key1);(key2) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);tag3(key1);tag2(key2) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);tag4(key1);tag1(key2) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);tag4(key1);tag1,tag5(key2) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);(key1);(key2) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Numeric units are added with units with tagleaf\",\n\t\t\ttagleaf: []string{\"allocations\"},\n\t\t\tleafm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);(allocations) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1024B,2048B(allocations) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1024B,1024B(allocations) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);1024(allocations) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Numeric units are added with units with tagroot\",\n\t\t\ttagroot: []string{\"allocations\"},\n\t\t\trootm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"(allocations);main(main.c) 1000\",\n\t\t\t\t\"(allocations);main(main.c);foo(foo.c);foo_caller(foo.c) 100\",\n\t\t\t\t\"1024(allocations);main(main.c);foo(foo.c);foo_caller(foo.c) 10\",\n\t\t\t\t\"1024B,2048B(allocations);main(main.c);foo_caller(foo.c) 10000\",\n\t\t\t\t\"1024B,1024B(allocations);main(main.c);foo_caller(foo.c) 1\",\n\t\t\t\t\"1024(allocations);main(main.c);bar(bar.c) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"Numeric labels are formatted according to outputUnit\",\n\t\t\toutputUnit: \"kB\",\n\t\t\ttagleaf:    []string{\"allocations\"},\n\t\t\tleafm:      true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);(allocations) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1kB,2kB(allocations) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1kB,1kB(allocations) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);1024(allocations) 200\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Numeric units with no units are handled properly by tagleaf\",\n\t\t\ttagleaf: []string{\"allocations\"},\n\t\t\tleafm:   true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"main(main.c);(allocations) 1000\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);(allocations) 100\",\n\t\t\t\t\"main(main.c);foo(foo.c);foo_caller(foo.c);1024(allocations) 10\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1024B,2048B(allocations) 10000\",\n\t\t\t\t\"main(main.c);foo_caller(foo.c);1024B,1024B(allocations) 1\",\n\t\t\t\t\"main(main.c);bar(bar.c);1024(allocations) 200\",\n\t\t\t},\n\t\t},\n\t} {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tp := testProfile1.Copy()\n\t\t\trootm, leafm := addLabelNodes(p, tc.tagroot, tc.tagleaf, tc.outputUnit)\n\t\t\tif rootm != tc.rootm {\n\t\t\t\tt.Errorf(\"Got rootm=%v, want=%v\", rootm, tc.rootm)\n\t\t\t}\n\t\t\tif leafm != tc.leafm {\n\t\t\t\tt.Errorf(\"Got leafm=%v, want=%v\", leafm, tc.leafm)\n\t\t\t}\n\t\t\tif got, want := strings.Join(stackCollapse(p), \"\\n\")+\"\\n\", strings.Join(tc.wantSampleFuncs, \"\\n\")+\"\\n\"; got != want {\n\t\t\t\tdiff, err := proftest.Diff([]byte(want), []byte(got))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to get diff: %v\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Profile samples got diff(want->got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// stackCollapse returns a slice of strings where each string represents one\n// profile sample in Brendan Gregg's \"Folded Stacks\" format:\n// \"<root_fn>(filename);<fun2>(filename);<leaf_fn>(filename) <value>\". This\n// allows the expected values for test cases to be specified in human-readable\n// strings.\nfunc stackCollapse(p *profile.Profile) []string {\n\tvar ret []string\n\tfor _, s := range p.Sample {\n\t\tvar funcs []string\n\t\tfor i := range s.Location {\n\t\t\tloc := s.Location[len(s.Location)-1-i]\n\t\t\tfor _, line := range loc.Line {\n\t\t\t\tfuncs = append(funcs, fmt.Sprintf(\"%s(%s)\", line.Function.Name, line.Function.Filename))\n\t\t\t}\n\t\t}\n\t\tret = append(ret, fmt.Sprintf(\"%s %d\", strings.Join(funcs, \";\"), s.Value[0]))\n\t}\n\treturn ret\n}\n"
  },
  {
    "path": "internal/driver/tempfile.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n)\n\n// newTempFile returns a new output file in dir with the provided prefix and suffix.\nfunc newTempFile(dir, prefix, suffix string) (*os.File, error) {\n\tfor index := 1; index < 10000; index++ {\n\t\tswitch f, err := os.OpenFile(filepath.Join(dir, fmt.Sprintf(\"%s%03d%s\", prefix, index, suffix)), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666); {\n\t\tcase err == nil:\n\t\t\treturn f, nil\n\t\tcase !os.IsExist(err):\n\t\t\treturn nil, err\n\t\t}\n\t}\n\t// Give up\n\treturn nil, fmt.Errorf(\"could not create file of the form %s%03d%s\", prefix, 1, suffix)\n}\n\nvar tempFiles []string\nvar tempFilesMu = sync.Mutex{}\n\n// deferDeleteTempFile marks a file to be deleted by next call to Cleanup()\nfunc deferDeleteTempFile(path string) {\n\ttempFilesMu.Lock()\n\ttempFiles = append(tempFiles, path)\n\ttempFilesMu.Unlock()\n}\n\n// cleanupTempFiles removes any temporary files selected for deferred cleaning.\nfunc cleanupTempFiles() error {\n\ttempFilesMu.Lock()\n\tdefer tempFilesMu.Unlock()\n\tvar lastErr error\n\tfor _, f := range tempFiles {\n\t\tif err := os.Remove(f); err != nil {\n\t\t\tlastErr = err\n\t\t}\n\t}\n\ttempFiles = nil\n\treturn lastErr\n}\n"
  },
  {
    "path": "internal/driver/tempfile_test.go",
    "content": "package driver\n\nimport (\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc TestNewTempFile(t *testing.T) {\n\tconst n = 100\n\t// Line up ready to execute goroutines with a read-write lock.\n\tvar mu sync.RWMutex\n\tmu.Lock()\n\tvar wg sync.WaitGroup\n\terrc := make(chan error, n)\n\tfor i := 0; i < n; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tmu.RLock()\n\t\t\tdefer mu.RUnlock()\n\t\t\tdefer wg.Done()\n\t\t\tf, err := newTempFile(os.TempDir(), \"profile\", \".tmp\")\n\t\t\terrc <- err\n\t\t\tdeferDeleteTempFile(f.Name())\n\t\t\tf.Close()\n\t\t}()\n\t}\n\t// Start the file creation race.\n\tmu.Unlock()\n\t// Wait for the goroutines to finish.\n\twg.Wait()\n\n\tfor i := 0; i < n; i++ {\n\t\tif err := <-errc; err != nil {\n\t\t\tt.Fatalf(\"newTempFile(): got %v, want no error\", err)\n\t\t}\n\t}\n\tif len(tempFiles) != n {\n\t\tt.Errorf(\"len(tempFiles): got %d, want %d\", len(tempFiles), n)\n\t}\n\tnames := map[string]bool{}\n\tfor _, name := range tempFiles {\n\t\tif names[name] {\n\t\t\tt.Errorf(\"got temp file %s created multiple times\", name)\n\t\t\tbreak\n\t\t}\n\t\tnames[name] = true\n\t}\n\tif err := cleanupTempFiles(); err != nil {\n\t\tt.Errorf(\"cleanupTempFiles(): got error %v, want no error\", err)\n\t}\n\tif len(tempFiles) != 0 {\n\t\tt.Errorf(\"len(tempFiles) after the cleanup: got %d, want 0\", len(tempFiles))\n\t}\n}\n"
  },
  {
    "path": "internal/driver/testdata/cppbench.contention",
    "content": "--- contentionz 1 ---\ncycles/second = 3201000000\nsampling period = 100\nms since reset = 16502830\ndiscarded samples = 0\n  19490304       27 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n       768        1 @ 0xbccc97 0xa42dc7 0xa456e4 0x7fcdc2ff214e\n      5760        2 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x4e969d 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n    569088        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87f08 0xb8814c 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n      2432        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x7aa74c 0x7ab844 0x7ab914 0x79e9e9 0x79e326 0x4d299e 0x4d4b7b 0x4b7be8 0x4b7ff1 0x4d2dae 0x79e80a\n   2034816        3 @ 0xbccc97 0xb82f0f 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n--- Memory map: ---\n  00400000-00fcb000: cppbench_server_main\n  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so\n  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so\n  7fcdc272f000-7fcdc28dd000: /libc-2.15.so\n  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so\n  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so\n  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so\n  7fcdc3208000-7fcdc320a000: /libdl-2.15.so\n  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so\n  7fcdc3645000-7fcdc3669000: /ld-2.15.so\n  7fff86bff000-7fff86c00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "internal/driver/testdata/cppbench.small.contention",
    "content": "--- contentionz 1 ---\ncycles/second = 3201000000\nsampling period = 100\nms since reset = 16502830\ndiscarded samples = 0\n       100     10 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n--- Memory map: ---\n  00400000-00fcb000: cppbench_server_main\n  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so\n  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so\n  7fcdc272f000-7fcdc28dd000: /libc-2.15.so\n  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so\n  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so\n  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so\n  7fcdc3208000-7fcdc320a000: /libdl-2.15.so\n  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so\n  7fcdc3645000-7fcdc3669000: /ld-2.15.so\n  7fff86bff000-7fff86c00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "internal/driver/testdata/file1000.src",
    "content": "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline0\nline1\nline2\nline3\nline4\nline5\n\t\t\n\n"
  },
  {
    "path": "internal/driver/testdata/file2000.src",
    "content": "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline0\nline1\nline2\nline3\nline4\nline5\n\t\t\n\n"
  },
  {
    "path": "internal/driver/testdata/file3000.src",
    "content": "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline0\nline1\nline2\nline3\nline4\nline5\n\t\t\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.contention.cum.files.dot",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph 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\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"file3000.src\\n32.77ms (21.92%)\\nof 149.50ms (100%)\" id=\"node1\" fontsize=20 shape=box tooltip=\"testdata/file3000.src (149.50ms)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN2 [label=\"file1000.src\\n51.20ms (34.25%)\" id=\"node2\" fontsize=23 shape=box tooltip=\"testdata/file1000.src (51.20ms)\" color=\"#b23100\" fillcolor=\"#eddbd5\"]\nN3 [label=\"file2000.src\\n65.54ms (43.84%)\\nof 75.78ms (50.68%)\" id=\"node3\" fontsize=24 shape=box tooltip=\"testdata/file2000.src (75.78ms)\" color=\"#b22000\" fillcolor=\"#edd9d5\"]\nN1 -> 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)\"]\nN1 -> 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)\"]\nN3 -> 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)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid-contention\" [shape=box fontsize=16 label=\"Build ID: buildid-contention\\lComment #1\\lComment #2\\lType: delay\\lActive filters:\\l   focus=[X1]000\\l   ignore=[X3]002\\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"0000000000001000\\nline1000\\nfile1000.src:1\\n40.96ms (27.40%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)\" color=\"#b23900\" fillcolor=\"#edddd5\"]\nN2 [label=\"0000000000003001\\nline3000\\nfile3000.src:5\\n0 of 40.96ms (27.40%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)\" color=\"#b23900\" fillcolor=\"#edddd5\"]\nN3 [label=\"0000000000003001\\nline3001\\nfile3000.src:3\\n0 of 40.96ms (27.40%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)\" color=\"#b23900\" fillcolor=\"#edddd5\"]\nN2 -> 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)\"]\nN3 -> 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)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.addresses.traces",
    "content": "File: testbinary\nType: cpu\nDuration: 10s, Total samples = 1.12s (11.20%)\n-----------+-------------------------------------------------------\n      key1:  tag1\n      key2:  tag1\n        1s   0000000000001000 line1000 testdata/file1000.src:1\n             0000000000002000 line2001 testdata/file2000.src:9 (inline)\n             0000000000002000 line2000 testdata/file2000.src:4\n             0000000000003000 line3002 testdata/file3000.src:2 (inline)\n             0000000000003000 line3001 testdata/file3000.src:5 (inline)\n             0000000000003000 line3000 testdata/file3000.src:6\n-----------+-------------------------------------------------------\n      key1:  tag2\n      key3:  tag2\n     100ms   0000000000001000 line1000 testdata/file1000.src:1\n             0000000000003001 line3001 testdata/file3000.src:8 (inline)\n             0000000000003001 line3000 testdata/file3000.src:9\n-----------+-------------------------------------------------------\n      key1:  tag3\n      key2:  tag2\n      10ms   0000000000002000 line2001 testdata/file2000.src:9 (inline)\n             0000000000002000 line2000 testdata/file2000.src:4\n             0000000000003002 line3002 testdata/file3000.src:5 (inline)\n             0000000000003002 line3000 testdata/file3000.src:9\n-----------+-------------------------------------------------------\n      key1:  tag4\n      key2:  tag1\n      10ms   0000000000003000 line3002 testdata/file3000.src:2 (inline)\n             0000000000003000 line3001 testdata/file3000.src:5 (inline)\n             0000000000003000 line3000 testdata/file3000.src:6\n-----------+-------------------------------------------------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.call_tree.callgrind",
    "content": "positions: instr line\nevents: cpu(ms)\n\nob=(1) /path/to/testbinary\nfl=(1) testdata/file1000.src\nfn=(1) line1000\n0x1000 1 1000\n* 1 100\n\nob=(1)\nfl=(2) testdata/file2000.src\nfn=(2) line2001\n+4096 9 10\n\nob=(1)\nfl=(3) testdata/file3000.src\nfn=(3) line3002\n+4096 2 10\ncfl=(2)\ncfn=(4) line2000 [1/2]\ncalls=0 * 4\n* * 1000\n\nob=(1)\nfl=(2)\nfn=(5) line2000\n-4096 4 0\ncfl=(2)\ncfn=(6) line2001 [2/2]\ncalls=0 -4096 9\n* * 1000\n* 4 0\ncfl=(2)\ncfn=(7) line2001 [1/2]\ncalls=0 * 9\n* * 10\n\nob=(1)\nfl=(2)\nfn=(2)\n* 9 0\ncfl=(1)\ncfn=(8) line1000 [1/2]\ncalls=0 -4096 1\n* * 1000\n\nob=(1)\nfl=(3)\nfn=(9) line3000\n+4096 6 0\ncfl=(3)\ncfn=(10) line3001 [1/2]\ncalls=0 +4096 5\n* * 1010\n\nob=(1)\nfl=(3)\nfn=(11) line3001\n* 5 0\ncfl=(3)\ncfn=(12) line3002 [1/2]\ncalls=0 * 2\n* * 1010\n\nob=(1)\nfl=(3)\nfn=(9)\n+1 9 0\ncfl=(3)\ncfn=(13) line3001 [2/2]\ncalls=0 +1 8\n* * 100\n\nob=(1)\nfl=(3)\nfn=(11)\n* 8 0\ncfl=(1)\ncfn=(14) line1000 [2/2]\ncalls=0 -8193 1\n* * 100\n\nob=(1)\nfl=(3)\nfn=(9)\n+1 9 0\ncfl=(3)\ncfn=(15) line3002 [2/2]\ncalls=0 +1 5\n* * 10\n\nob=(1)\nfl=(3)\nfn=(3)\n* 5 0\ncfl=(2)\ncfn=(16) line2000 [2/2]\ncalls=0 -4098 4\n* * 10\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.callgrind",
    "content": "positions: instr line\nevents: cpu(ms)\n\nob=(1) /path/to/testbinary\nfl=(1) testdata/file1000.src\nfn=(1) line1000\n0x1000 1 1100\n\nob=(1)\nfl=(2) testdata/file2000.src\nfn=(2) line2001\n+4096 9 10\ncfl=(1)\ncfn=(1)\ncalls=0 * 1\n* * 1000\n\nob=(1)\nfl=(3) testdata/file3000.src\nfn=(3) line3002\n+4096 2 10\ncfl=(2)\ncfn=(4) line2000\ncalls=0 * 4\n* * 1000\n\nob=(1)\nfl=(2)\nfn=(4)\n-4096 4 0\ncfl=(2)\ncfn=(2)\ncalls=0 -4096 9\n* * 1010\n\nob=(1)\nfl=(3)\nfn=(5) line3000\n+4096 6 0\ncfl=(3)\ncfn=(6) line3001\ncalls=0 +4096 5\n* * 1010\n\nob=(1)\nfl=(3)\nfn=(6)\n* 5 0\ncfl=(3)\ncfn=(3)\ncalls=0 * 2\n* * 1010\n\nob=(1)\nfl=(3)\nfn=(5)\n+1 9 0\ncfl=(3)\ncfn=(6)\ncalls=0 +1 8\n* * 100\n\nob=(1)\nfl=(3)\nfn=(6)\n* 8 0\ncfl=(1)\ncfn=(1)\ncalls=0 -8193 1\n* * 100\n\nob=(1)\nfl=(3)\nfn=(5)\n+1 9 0\ncfl=(3)\ncfn=(3)\ncalls=0 +1 5\n* * 10\n\nob=(1)\nfl=(3)\nfn=(3)\n* 5 0\ncfl=(2)\ncfn=(4)\ncalls=0 -4098 4\n* * 10\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.comments",
    "content": "some-comment\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.cum.lines.text.focus.hide",
    "content": "Active filters:\n   focus=[12]00\n   hide=line[X3]0\nShowing nodes accounting for 1.11s, 99.11% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src:1\n         0     0% 98.21%      1.01s 90.18%  line2000 testdata/file2000.src:4\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src:9 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.cum.lines.text.hide",
    "content": "Active filters:\n   hide=line[X3]0\nShowing nodes accounting for 1.11s, 99.11% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src:1\n         0     0% 98.21%      1.01s 90.18%  line2000 testdata/file2000.src:4\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src:9 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.cum.lines.text.show",
    "content": "Active filters:\n   show=[12]00\nShowing nodes accounting for 1.11s, 99.11% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src:1\n         0     0% 98.21%      1.01s 90.18%  line2000 testdata/file2000.src:4\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src:9 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide",
    "content": "Active filters:\n   hide=mangled[X3]0\nShowing nodes accounting for 1s, 100% of 1s total\n      flat  flat%   sum%        cum   cum%\n        1s   100%   100%         1s   100%  mangled1000 testdata/file1000.src:1\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.cum.lines.tree.show_from",
    "content": "Active filters:\n   show_from=line2\nShowing nodes accounting for 1.01s, 90.18% of 1.12s total\n----------------------------------------------------------+-------------\n      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \n----------------------------------------------------------+-------------\n         0     0%     0%      1.01s 90.18%                | line2000 testdata/file2000.src:4\n                                             1.01s   100% |   line2001 testdata/file2000.src:9 (inline)\n----------------------------------------------------------+-------------\n                                             1.01s   100% |   line2000 testdata/file2000.src:4 (inline)\n     0.01s  0.89%  0.89%      1.01s 90.18%                | line2001 testdata/file2000.src:9\n                                                1s 99.01% |   line1000 testdata/file1000.src:1\n----------------------------------------------------------+-------------\n                                                1s   100% |   line2001 testdata/file2000.src:9\n        1s 89.29% 90.18%         1s 89.29%                | line1000 testdata/file1000.src:1\n----------------------------------------------------------+-------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.addresses.disasm",
    "content": "Total: 1.12s\nROUTINE ======================== line1000\n     1.10s      1.10s (flat, cum) 98.21% of Total\n     1.10s      1.10s       1000: instruction one                         ;line1000 file1000.src:1\n         .          .       1001: instruction two\n         .          .       1002: instruction three                       ;line1000 file1000.src:2\n         .          .       1003: instruction four                        ;line1000 file1000.src:1\nROUTINE ======================== line3000\n      10ms      1.12s (flat, cum)   100% of Total\n      10ms      1.01s       3000: instruction one                         ;line3000 file3000.src:6\n         .      100ms       3001: instruction two                         ;line3000 file3000.src:9\n         .       10ms       3002: instruction three\n         .          .       3003: instruction four                        ;line3000 file3000.src\n         .          .       3004: instruction five\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.addresses.noinlines.text",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\nDropped 1 node (cum <= 0.06s)\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  0000000000001000 line1000 testdata/file1000.src:1\n     0.01s  0.89% 99.11%      1.01s 90.18%  0000000000002000 line2000 testdata/file2000.src:4\n     0.01s  0.89%   100%      1.01s 90.18%  0000000000003000 line3000 testdata/file3000.src:6\n         0     0%   100%      0.10s  8.93%  0000000000003001 line3000 testdata/file3000.src:9\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.addresses.weblist",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>testbinary cpu</title>\n  \n  <style type=\"text/css\">\nbody #content{\nfont-family: sans-serif;\n}\nh1 {\n  font-size: 1.5em;\n}\n.legend {\n  font-size: 1.25em;\n}\n.line, .nop, .unimportant {\n  color: #aaaaaa;\n}\n.inlinesrc {\n  color: #000066;\n}\n.livesrc {\ncursor: pointer;\n}\n.livesrc:hover {\nbackground-color: #eeeeee;\n}\n.asm {\ncolor: #008800;\ndisplay: none;\n}\n</style>\n  <script type=\"text/javascript\">\nfunction pprof_toggle_asm(e) {\n  var target;\n  if (!e) e = window.event;\n  if (e.target) target = e.target;\n  else if (e.srcElement) target = e.srcElement;\n\n  if (target) {\n    var asm = target.nextSibling;\n    if (asm && asm.className == \"asm\") {\n      asm.style.display = (asm.style.display == \"block\" ? \"\" : \"block\");\n      e.preventDefault();\n      return false;\n    }\n  }\n}\n</script>\n</head>\n<body>\n\n<div class=\"legend\">File: testbinary<br>\nType: cpu<br>\nDuration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h2>line1000</h2><p class=\"filename\">testdata/file1000.src</p>\n<pre onClick=\"pprof_toggle_asm(event)\">\n  Total:       1.10s      1.10s (flat, cum) 98.21%\n<span class=line>      1</span> <span class=livesrc>       1.10s      1.10s           line1 </span><span class=asm>               1.10s      1.10s     1000:     instruction one                                                              <span class=unimportant>file1000.src:1</span>\n                   .          .     1001:     instruction two                                                              <span class=unimportant>file1000.src:1</span>\n                                     ⋮\n                   .          .     1003:     instruction four                                                             <span class=unimportant>file1000.src:1</span>\n</span>\n<span class=line>      2</span> <span class=livesrc>           .          .           line2 </span><span class=asm>                   .          .     1002:     instruction three                                                            <span class=unimportant>file1000.src:2</span>\n</span>\n<span class=line>      3</span> <span class=nop>           .          .           line3 </span>\n<span class=line>      4</span> <span class=nop>           .          .           line4 </span>\n<span class=line>      5</span> <span class=nop>           .          .           line5 </span>\n<span class=line>      6</span> <span class=nop>           .          .           line6 </span>\n<span class=line>      7</span> <span class=nop>           .          .           line7 </span>\n</pre>\n<h2>line3000</h2><p class=\"filename\">testdata/file3000.src</p>\n<pre onClick=\"pprof_toggle_asm(event)\">\n  Total:        10ms      1.12s (flat, cum)   100%\n<span class=line>      1</span> <span class=nop>           .          .           line1 </span>\n<span class=line>      2</span> <span class=nop>           .          .           line2 </span>\n<span class=line>      3</span> <span class=nop>           .          .           line3 </span>\n<span class=line>      4</span> <span class=nop>           .          .           line4 </span>\n<span class=line>      5</span> <span class=nop>           .          .           line5 </span>\n<span class=line>      6</span> <span class=livesrc>        10ms      1.01s           line6 </span><span class=asm>                                          <span class=inlinesrc>    line5                                                                       </span> <span class=unimportant>file3000.src:5</span>\n                                          <span class=inlinesrc>        line2                                                                   </span> <span class=unimportant>file3000.src:2</span>\n                10ms      1.01s     3000:             instruction one                                                      <span class=unimportant>file3000.src:2</span>\n</span>\n<span class=line>      7</span> <span class=nop>           .          .           line7 </span>\n<span class=line>      8</span> <span class=nop>           .          .           line8 </span>\n<span class=line>      9</span> <span class=livesrc>           .      110ms           line9 </span><span class=asm>                                          <span class=inlinesrc>    line8                                                                       </span> <span class=unimportant>file3000.src:8</span>\n                   .      100ms     3001:         instruction two                                                          <span class=unimportant>file3000.src:8</span>\n                                          <span class=inlinesrc>    line5                                                                       </span> <span class=unimportant>file3000.src:5</span>\n                   .       10ms     3002:         instruction three                                                        <span class=unimportant>file3000.src:5</span>\n                   .          .     3003:         instruction four                                                         <span class=unimportant></span>\n                   .          .     3004:         instruction five                                                         <span class=unimportant></span>\n</span>\n<span class=line>     10</span> <span class=nop>           .          .           line0 </span>\n<span class=line>     11</span> <span class=nop>           .          .           line1 </span>\n<span class=line>     12</span> <span class=nop>           .          .           line2 </span>\n<span class=line>     13</span> <span class=nop>           .          .           line3 </span>\n<span class=line>     14</span> <span class=nop>           .          .           line4 </span>\n</pre>\n\n</body>\n</html>\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.filefunctions.noinlines.text",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2000 testdata/file2000.src\n     0.01s  0.89%   100%      1.12s   100%  line3000 testdata/file3000.src\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.functions.call_tree.dot",
    "content": "digraph \"testbinary\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph 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.11s, 99.11% of 1.12s total\\lDropped 3 nodes (cum <= 0.06s)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\" tooltip=\"testbinary\"] }\nN1 [label=\"line1000\\n1s (89.29%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"line1000 (1s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN1_0 [label = \"key1:tag1\\nkey2:tag1\" id=\"N1_0\" fontsize=8 shape=box3d tooltip=\"1s\"]\nN1 -> N1_0 [label=\" 1s\" weight=100 tooltip=\"1s\" labeltooltip=\"1s\"]\nN2 [label=\"line3000\\n0 of 1.12s (100%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (1.12s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"line3001\\n0 of 1.11s (99.11%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"line3001 (1.11s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN4 [label=\"line1000\\n0.10s (8.93%)\" id=\"node4\" fontsize=14 shape=box tooltip=\"line1000 (0.10s)\" color=\"#b28b62\" fillcolor=\"#ede8e2\"]\nN4_0 [label = \"key1:tag2\\nkey3:tag2\" id=\"N4_0\" fontsize=8 shape=box3d tooltip=\"0.10s\"]\nN4 -> N4_0 [label=\" 0.10s\" weight=100 tooltip=\"0.10s\" labeltooltip=\"0.10s\"]\nN5 [label=\"line3002\\n0.01s (0.89%)\\nof 1.01s (90.18%)\" id=\"node5\" fontsize=10 shape=box tooltip=\"line3002 (1.01s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN6 [label=\"line2000\\n0 of 1s (89.29%)\" id=\"node6\" fontsize=8 shape=box tooltip=\"line2000 (1s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN7 [label=\"line2001\\n0 of 1s (89.29%)\" id=\"node7\" fontsize=8 shape=box tooltip=\"line2001 (1s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN2 -> N3 [label=\" 1.11s\\n (inline)\" weight=100 penwidth=5 color=\"#b20000\" tooltip=\"line3000 -> line3001 (1.11s)\" labeltooltip=\"line3000 -> line3001 (1.11s)\"]\nN3 -> N5 [label=\" 1.01s\\n (inline)\" weight=91 penwidth=5 color=\"#b20500\" tooltip=\"line3001 -> line3002 (1.01s)\" labeltooltip=\"line3001 -> line3002 (1.01s)\"]\nN6 -> N7 [label=\" 1s\\n (inline)\" weight=90 penwidth=5 color=\"#b20500\" tooltip=\"line2000 -> line2001 (1s)\" labeltooltip=\"line2000 -> line2001 (1s)\"]\nN7 -> N1 [label=\" 1s\" weight=90 penwidth=5 color=\"#b20500\" tooltip=\"line2001 -> line1000 (1s)\" labeltooltip=\"line2001 -> line1000 (1s)\"]\nN5 -> N6 [label=\" 1s\" weight=90 penwidth=5 color=\"#b20500\" tooltip=\"line3002 -> line2000 (1s)\" labeltooltip=\"line3002 -> line2000 (1s)\"]\nN3 -> N4 [label=\" 0.10s\" weight=9 color=\"#b28b62\" tooltip=\"line3001 -> line1000 (0.10s)\" labeltooltip=\"line3001 -> line1000 (0.10s)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.functions.dot",
    "content": "digraph \"testbinary\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph 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\\lSee https://git.io/JfYMW for how to read the graph\\l\" tooltip=\"testbinary\"] }\nN1 [label=\"line1000\\n1.10s (98.21%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"line1000 (1.10s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN1_0 [label = \"key1:tag1\\nkey2:tag1\" id=\"N1_0\" fontsize=8 shape=box3d tooltip=\"1s\"]\nN1 -> N1_0 [label=\" 1s\" weight=100 tooltip=\"1s\" labeltooltip=\"1s\"]\nN1_1 [label = \"key1:tag2\\nkey3:tag2\" id=\"N1_1\" fontsize=8 shape=box3d tooltip=\"0.10s\"]\nN1 -> N1_1 [label=\" 0.10s\" weight=100 tooltip=\"0.10s\" labeltooltip=\"0.10s\"]\nN2 [label=\"line3000\\n0 of 1.12s (100%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (1.12s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"line3001\\n0 of 1.11s (99.11%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"line3001 (1.11s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN4 [label=\"line3002\\n0.01s (0.89%)\\nof 1.02s (91.07%)\" id=\"node4\" fontsize=10 shape=box tooltip=\"line3002 (1.02s)\" color=\"#b20400\" fillcolor=\"#edd6d5\"]\nN5 [label=\"line2001\\n0.01s (0.89%)\\nof 1.01s (90.18%)\" id=\"node5\" fontsize=10 shape=box tooltip=\"line2001 (1.01s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN6 [label=\"line2000\\n0 of 1.01s (90.18%)\" id=\"node6\" fontsize=8 shape=box tooltip=\"line2000 (1.01s)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN2 -> N3 [label=\" 1.11s\\n (inline)\" weight=100 penwidth=5 color=\"#b20000\" tooltip=\"line3000 -> line3001 (1.11s)\" labeltooltip=\"line3000 -> line3001 (1.11s)\"]\nN6 -> N5 [label=\" 1.01s\\n (inline)\" weight=91 penwidth=5 color=\"#b20500\" tooltip=\"line2000 -> line2001 (1.01s)\" labeltooltip=\"line2000 -> line2001 (1.01s)\"]\nN3 -> N4 [label=\" 1.01s\\n (inline)\" weight=91 penwidth=5 color=\"#b20500\" tooltip=\"line3001 -> line3002 (1.01s)\" labeltooltip=\"line3001 -> line3002 (1.01s)\"]\nN4 -> N6 [label=\" 1.01s\" weight=91 penwidth=5 color=\"#b20500\" tooltip=\"line3002 -> line2000 (1.01s)\" labeltooltip=\"line3002 -> line2000 (1.01s)\"]\nN5 -> N1 [label=\" 1s\" weight=90 penwidth=5 color=\"#b20500\" tooltip=\"line2001 -> line1000 (1s)\" labeltooltip=\"line2001 -> line1000 (1s)\"]\nN3 -> N1 [label=\" 0.10s\" weight=9 color=\"#b28b62\" tooltip=\"line3001 -> line1000 (0.10s)\" labeltooltip=\"line3001 -> line1000 (0.10s)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.functions.noinlines.text",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2000\n     0.01s  0.89%   100%      1.12s   100%  line3000\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.flat.functions.text",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 (inline)\n     0.01s  0.89%   100%      1.02s 91.07%  line3002 (inline)\n         0     0%   100%      1.01s 90.18%  line2000\n         0     0%   100%      1.12s   100%  line3000\n         0     0%   100%      1.11s 99.11%  line3001 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.lines.topproto",
    "content": "Showing nodes accounting for 1s, 100% of 1s total\n      flat  flat%   sum%        cum   cum%\n        1s   100%   100%         1s   100%  mangled1000 testdata/file1000.src:1\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.peek",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\n----------------------------------------------------------+-------------\n      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \n----------------------------------------------------------+-------------\n                                             1.01s   100% |   line2000 (inline)\n     0.01s  0.89%  0.89%      1.01s 90.18%                | line2001\n                                                1s 99.01% |   line1000\n----------------------------------------------------------+-------------\n                                             1.11s   100% |   line3000 (inline)\n         0     0%  0.89%      1.11s 99.11%                | line3001\n                                             1.01s 90.99% |   line3002 (inline)\n                                             0.10s  9.01% |   line1000\n----------------------------------------------------------+-------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.tags",
    "content": " key1: Total 1.12s of 1.12s (  100%)\n          1s (89.29%): tag1\n       100ms ( 8.93%): tag2\n        10ms ( 0.89%): tag3\n        10ms ( 0.89%): tag4\n\n key2: Total 1.02s of 1.12s (91.07%)\n       1.01s (90.18%): tag1\n        10ms ( 0.89%): tag2\n\n key3: Total 100ms of 1.12s ( 8.93%)\n       100ms ( 8.93%): tag2\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.tags.focus.ignore",
    "content": " key1: Total 100ms of 1.12s ( 8.93%)\n       100ms ( 8.93%): tag2\n\n key3: Total 100ms of 1.12s ( 8.93%)\n       100ms ( 8.93%): tag2\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpu.traces",
    "content": "File: testbinary\nType: cpu\nDuration: 10s, Total samples = 1.12s (11.20%)\n-----------+-------------------------------------------------------\n      key1:  tag1\n      key2:  tag1\n        1s   line1000\n             line2001 (inline)\n             line2000\n             line3002 (inline)\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n      key1:  tag2\n      key3:  tag2\n     100ms   line1000\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n      key1:  tag3\n      key2:  tag2\n      10ms   line2001 (inline)\n             line2000\n             line3002 (inline)\n             line3000\n-----------+-------------------------------------------------------\n      key1:  tag4\n      key2:  tag1\n      10ms   line3002 (inline)\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.cpusmall.flat.addresses.tree",
    "content": "Showing nodes accounting for 4s, 100% of 4s total\nShowing top 4 nodes out of 5\n----------------------------------------------------------+-------------\n      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \n----------------------------------------------------------+-------------\n                                                1s   100% |   0000000000003000 [testbinary]\n        1s 25.00% 25.00%         1s 25.00%                | 0000000000001000 [testbinary]\n----------------------------------------------------------+-------------\n        1s 25.00% 50.00%         2s 50.00%                | 0000000000003000 [testbinary]\n                                                1s 50.00% |   0000000000001000 [testbinary]\n----------------------------------------------------------+-------------\n                                                1s   100% |   0000000000005000 [testbinary]\n        1s 25.00% 75.00%         1s 25.00%                | 0000000000004000 [testbinary]\n----------------------------------------------------------+-------------\n        1s 25.00%   100%         2s 50.00%                | 0000000000005000 [testbinary]\n                                                1s 50.00% |   0000000000004000 [testbinary]\n----------------------------------------------------------+-------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.callgrind",
    "content": "positions: instr line\nevents: inuse_space(MB)\n\nob=\nfl=(1) testdata/file2000.src\nfn=(1) line2001\n0x2000 2 62\ncfl=(2) testdata/file1000.src\ncfn=(2) line1000\ncalls=0 0x1000 1\n* * 0\n\nob=\nfl=(3) testdata/file3000.src\nfn=(3) line3002\n+4096 3 31\ncfl=(1)\ncfn=(4) line2000\ncalls=0 * 3\n* * 0\n\nob=\nfl=(2)\nfn=(2)\n-8192 1 4\n\nob=\nfl=(1)\nfn=(4)\n+4096 3 0\ncfl=(1)\ncfn=(1)\ncalls=0 +4096 2\n* * 63\n\nob=\nfl=(3)\nfn=(5) line3000\n+4096 4 0\ncfl=(3)\ncfn=(6) line3001\ncalls=0 +4096 2\n* * 32\n\nob=\nfl=(3)\nfn=(6)\n* 2 0\ncfl=(3)\ncfn=(3)\ncalls=0 * 3\n* * 32\n\nob=\nfl=(3)\nfn=(5)\n+1 4 0\ncfl=(3)\ncfn=(6)\ncalls=0 +1 2\n* * 3\n\nob=\nfl=(3)\nfn=(6)\n* 2 0\ncfl=(2)\ncfn=(2)\ncalls=0 -8193 1\n* * 3\n\nob=\nfl=(3)\nfn=(5)\n+1 4 0\ncfl=(3)\ncfn=(3)\ncalls=0 +1 3\n* * 62\n\nob=\nfl=(3)\nfn=(3)\n* 3 0\ncfl=(1)\ncfn=(4)\ncalls=0 -4098 3\n* * 62\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.comments",
    "content": "comment\n#hidden comment\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.cum.lines.tree.focus",
    "content": "Active filters:\n   focus=[24]00\nShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\nDropped 2 nodes (cum <= 4.93MB)\n----------------------------------------------------------+-------------\n      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line3002 testdata/file3000.src:3\n         0     0%     0%    63.48MB 64.36%                | line2000 testdata/file2000.src:3\n                                           63.48MB   100% |   line2001 testdata/file2000.src:2 (inline)\n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line2000 testdata/file2000.src:3 (inline)\n   62.50MB 63.37% 63.37%    63.48MB 64.36%                | line2001 testdata/file2000.src:2\n----------------------------------------------------------+-------------\n         0     0% 63.37%    63.48MB 64.36%                | line3000 testdata/file3000.src:4\n                                           63.48MB   100% |   line3002 testdata/file3000.src:3 (inline)\n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line3000 testdata/file3000.src:4 (inline)\n         0     0% 63.37%    63.48MB 64.36%                | line3002 testdata/file3000.src:3\n                                           63.48MB   100% |   line2000 testdata/file2000.src:3\n----------------------------------------------------------+-------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus",
    "content": "Active filters:\n   focus=[24]00\nShowing nodes accounting for 62.50MB, 98.46% of 63.48MB total\nDropped 2 nodes (cum <= 3.17MB)\n----------------------------------------------------------+-------------\n      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line3002\n         0     0%     0%    63.48MB   100%                | line2000\n                                           63.48MB   100% |   line2001 (inline)\n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line2000 (inline)\n   62.50MB 98.46% 98.46%    63.48MB   100%                | line2001\n----------------------------------------------------------+-------------\n         0     0% 98.46%    63.48MB   100%                | line3000\n                                           63.48MB   100% |   line3002 (inline)\n----------------------------------------------------------+-------------\n                                           63.48MB   100% |   line3000 (inline)\n         0     0% 98.46%    63.48MB   100%                | line3002\n                                           63.48MB   100% |   line2000\n----------------------------------------------------------+-------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.files.seconds.text",
    "content": "Showing nodes accounting for 0, 0% of 0 total\n      flat  flat%   sum%        cum   cum%\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.files.text",
    "content": "Showing nodes accounting for 93.75MB, 95.05% of 98.63MB total\nDropped 1 node (cum <= 4.93MB)\n      flat  flat%   sum%        cum   cum%\n   62.50MB 63.37% 63.37%    63.48MB 64.36%  testdata/file2000.src\n   31.25MB 31.68% 95.05%    98.63MB   100%  testdata/file3000.src\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.files.text.focus",
    "content": "Active filters:\n   focus=[12]00\n   taghide=[X3]00\nShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\n      flat  flat%   sum%        cum   cum%\n   62.50MB 63.37% 63.37%    63.48MB 64.36%  testdata/file2000.src\n    4.88MB  4.95% 68.32%     4.88MB  4.95%  testdata/file1000.src\n         0     0% 68.32%    67.38MB 68.32%  testdata/file3000.src\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.inuse_objects.text",
    "content": "Showing nodes accounting for 150, 100% of 150 total\n      flat  flat%   sum%        cum   cum%\n        80 53.33% 53.33%        130 86.67%  line3002 (inline)\n        40 26.67% 80.00%         50 33.33%  line2001 (inline)\n        30 20.00%   100%         30 20.00%  line1000\n         0     0%   100%         50 33.33%  line2000\n         0     0%   100%        150   100%  line3000\n         0     0%   100%        110 73.33%  line3001 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.inuse_objects.text.all_frames",
    "content": "Showing nodes accounting for 150, 100% of 150 total\n      flat  flat%   sum%        cum   cum%\n       120 80.00% 80.00%        150   100%  operator new (inline)\n        30 20.00%   100%         30 20.00%  pruneme (inline)\n         0     0%   100%         30 20.00%  line1000\n         0     0%   100%         50 33.33%  line2000\n         0     0%   100%         50 33.33%  line2001 (inline)\n         0     0%   100%        150   100%  line3000\n         0     0%   100%        110 73.33%  line3001 (inline)\n         0     0%   100%        130 86.67%  line3002 (inline)\n         0     0%   100%         30 20.00%  malloc (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: inuse_space\\lActive filters:\\l   tagfocus=1mb:2gb\\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line2001\\n62.50MB (63.37%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"line2001 (62.50MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nNN1_0 [label = \"1.56MB\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"62.50MB\"]\nN1 -> NN1_0 [label=\" 62.50MB\" weight=100 tooltip=\"62.50MB\" labeltooltip=\"62.50MB\"]\nN2 [label=\"line3000\\n0 of 62.50MB (63.37%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (62.50MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN3 [label=\"line2000\\n0 of 62.50MB (63.37%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"line2000 (62.50MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN4 [label=\"line3002\\n0 of 62.50MB (63.37%)\" id=\"node4\" fontsize=8 shape=box tooltip=\"line3002 (62.50MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN3 -> N1 [label=\" 62.50MB\\n (inline)\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line2000 -> line2001 (62.50MB)\" labeltooltip=\"line2000 -> line2001 (62.50MB)\"]\nN2 -> N4 [label=\" 62.50MB\\n (inline)\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line3000 -> line3002 (62.50MB)\" labeltooltip=\"line3000 -> line3002 (62.50MB)\"]\nN4 -> N3 [label=\" 62.50MB\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line3002 -> line2000 (62.50MB)\" labeltooltip=\"line3002 -> line2000 (62.50MB)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: inuse_space\\lActive filters:\\l   tagfocus=30kb:\\l   tagignore=1mb:2mb\\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\\lDropped 2 nodes (cum <= 4.93MB)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3002\\n31.25MB (31.68%)\\nof 32.23MB (32.67%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"line3002 (32.23MB)\" color=\"#b23200\" fillcolor=\"#eddcd5\"]\nNN1_0 [label = \"400kB\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"31.25MB\"]\nN1 -> NN1_0 [label=\" 31.25MB\" weight=100 tooltip=\"31.25MB\" labeltooltip=\"31.25MB\"]\nN2 [label=\"line3000\\n0 of 36.13MB (36.63%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nN3 [label=\"line3001\\n0 of 36.13MB (36.63%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"line3001 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nN4 [label=\"line1000\\n4.88MB (4.95%)\" id=\"node4\" fontsize=15 shape=box tooltip=\"line1000 (4.88MB)\" color=\"#b2a086\" fillcolor=\"#edeae7\"]\nNN4_0 [label = \"200kB\" id=\"NN4_0\" fontsize=8 shape=box3d tooltip=\"3.91MB\"]\nN4 -> NN4_0 [label=\" 3.91MB\" weight=100 tooltip=\"3.91MB\" labeltooltip=\"3.91MB\"]\nN2 -> N3 [label=\" 36.13MB\\n (inline)\" weight=37 penwidth=2 color=\"#b22e00\" tooltip=\"line3000 -> line3001 (36.13MB)\" labeltooltip=\"line3000 -> line3001 (36.13MB)\"]\nN3 -> N1 [label=\" 32.23MB\\n (inline)\" weight=33 penwidth=2 color=\"#b23200\" tooltip=\"line3001 -> line3002 (32.23MB)\" labeltooltip=\"line3001 -> line3002 (32.23MB)\"]\nN3 -> N4 [label=\" 3.91MB\" weight=4 color=\"#b2a58f\" tooltip=\"line3001 -> line1000 (3.91MB)\" labeltooltip=\"line3001 -> line1000 (3.91MB)\"]\nN1 -> N4 [label=\" 0.98MB\" color=\"#b2b0a9\" tooltip=\"line3002 ... line1000 (0.98MB)\" labeltooltip=\"line3002 ... line1000 (0.98MB)\" style=\"dotted\" minlen=2]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.flat.lines.dot.focus",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: inuse_space\\lActive filters:\\l   focus=[12]00\\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3000\\nfile3000.src:4\\n0 of 67.38MB (68.32%)\" id=\"node1\" fontsize=8 shape=box tooltip=\"line3000 testdata/file3000.src:4 (67.38MB)\" color=\"#b21300\" fillcolor=\"#edd7d5\"]\nN2 [label=\"line2001\\nfile2000.src:2\\n62.50MB (63.37%)\\nof 63.48MB (64.36%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"line2001 testdata/file2000.src:2 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nNN2_0 [label = \"1.56MB\" id=\"NN2_0\" fontsize=8 shape=box3d tooltip=\"62.50MB\"]\nN2 -> NN2_0 [label=\" 62.50MB\" weight=100 tooltip=\"62.50MB\" labeltooltip=\"62.50MB\"]\nN3 [label=\"line1000\\nfile1000.src:1\\n4.88MB (4.95%)\" id=\"node3\" fontsize=13 shape=box tooltip=\"line1000 testdata/file1000.src:1 (4.88MB)\" color=\"#b2a086\" fillcolor=\"#edeae7\"]\nNN3_0 [label = \"200kB\" id=\"NN3_0\" fontsize=8 shape=box3d tooltip=\"3.91MB\"]\nN3 -> NN3_0 [label=\" 3.91MB\" weight=100 tooltip=\"3.91MB\" labeltooltip=\"3.91MB\"]\nN4 [label=\"line3002\\nfile3000.src:3\\n0 of 63.48MB (64.36%)\" id=\"node4\" fontsize=8 shape=box tooltip=\"line3002 testdata/file3000.src:3 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN5 [label=\"line3001\\nfile3000.src:2\\n0 of 4.88MB (4.95%)\" id=\"node5\" fontsize=8 shape=box tooltip=\"line3001 testdata/file3000.src:2 (4.88MB)\" color=\"#b2a086\" fillcolor=\"#edeae7\"]\nN6 [label=\"line2000\\nfile2000.src:3\\n0 of 63.48MB (64.36%)\" id=\"node6\" fontsize=8 shape=box tooltip=\"line2000 testdata/file2000.src:3 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN6 -> 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)\"]\nN4 -> 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)\"]\nN1 -> 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)\"]\nN1 -> 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)\"]\nN5 -> 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)\"]\nN2 -> 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]\nN5 -> 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)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.tags",
    "content": " bytes: Total 98.63MB of 98.63MB (  100%)\n        62.50MB (63.37%): 1.56MB\n        31.25MB (31.68%): 400kB\n         3.91MB ( 3.96%): 200kB\n         1000kB ( 0.99%): 100kB\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap.tags.unit",
    "content": " bytes: Total 103424000B of 103424000B (  100%)\n        65536000B (63.37%): 1638400B\n        32768000B (31.68%): 409600B\n         4096000B ( 3.96%): 204800B\n         1024000B ( 0.99%): 102400B\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text",
    "content": "Showing nodes accounting for 150, 100% of 150 total\n      flat  flat%   sum%        cum   cum%\n        80 53.33% 53.33%        130 86.67%  line3002 (inline)\n        40 26.67% 80.00%         50 33.33%  line2001 (inline)\n        30 20.00%   100%         30 20.00%  line1000\n         0     0%   100%         50 33.33%  line2000\n         0     0%   100%        150   100%  line3000\n         0     0%   100%        110 73.33%  line3001 (inline)\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: alloc_space\\lActive filters:\\l   tagshow=[2]00\\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\\lDropped 1 node (cum <= 4.93MB)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3002\\n31.25MB (31.68%)\\nof 94.73MB (96.04%)\" id=\"node1\" fontsize=20 shape=box tooltip=\"line3002 (94.73MB)\" color=\"#b20200\" fillcolor=\"#edd5d5\"]\nN2 [label=\"line3000\\n0 of 98.63MB (100%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (98.63MB)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"line2001\\n62.50MB (63.37%)\\nof 63.48MB (64.36%)\" id=\"node3\" fontsize=24 shape=box tooltip=\"line2001 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN4 [label=\"line2000\\n0 of 63.48MB (64.36%)\" id=\"node4\" fontsize=8 shape=box tooltip=\"line2000 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN5 [label=\"line3001\\n0 of 36.13MB (36.63%)\" id=\"node5\" fontsize=8 shape=box tooltip=\"line3001 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nN4 -> N3 [label=\" 63.48MB\\n (inline)\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line2000 -> line2001 (63.48MB)\" labeltooltip=\"line2000 -> line2001 (63.48MB)\"]\nN1 -> N4 [label=\" 63.48MB\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line3002 -> line2000 (63.48MB)\" labeltooltip=\"line3002 -> line2000 (63.48MB)\"]\nN2 -> N1 [label=\" 62.50MB\\n (inline)\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line3000 -> line3002 (62.50MB)\" labeltooltip=\"line3000 -> line3002 (62.50MB)\"]\nN2 -> N5 [label=\" 36.13MB\\n (inline)\" weight=37 penwidth=2 color=\"#b22e00\" tooltip=\"line3000 -> line3001 (36.13MB)\" labeltooltip=\"line3000 -> line3001 (36.13MB)\"]\nN5 -> N1 [label=\" 32.23MB\\n (inline)\" weight=33 penwidth=2 color=\"#b23200\" tooltip=\"line3001 -> line3002 (32.23MB)\" labeltooltip=\"line3001 -> line3002 (32.23MB)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: alloc_space\\lActive filters:\\l   focus=[234]00\\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\\lDropped 1 node (cum <= 4.93MB)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3002\\n31.25MB (31.68%)\\nof 94.73MB (96.04%)\" id=\"node1\" fontsize=20 shape=box tooltip=\"line3002 (94.73MB)\" color=\"#b20200\" fillcolor=\"#edd5d5\"]\nNN1_0 [label = \"400kB\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"31.25MB\"]\nN1 -> NN1_0 [label=\" 31.25MB\" weight=100 tooltip=\"31.25MB\" labeltooltip=\"31.25MB\"]\nN2 [label=\"line3000\\n0 of 98.63MB (100%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (98.63MB)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"line2001\\n62.50MB (63.37%)\\nof 63.48MB (64.36%)\" id=\"node3\" fontsize=24 shape=box tooltip=\"line2001 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nNN3_0 [label = \"1.56MB\" id=\"NN3_0\" fontsize=8 shape=box3d tooltip=\"62.50MB\"]\nN3 -> NN3_0 [label=\" 62.50MB\" weight=100 tooltip=\"62.50MB\" labeltooltip=\"62.50MB\"]\nN4 [label=\"line2000\\n0 of 63.48MB (64.36%)\" id=\"node4\" fontsize=8 shape=box tooltip=\"line2000 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN5 [label=\"line3001\\n0 of 36.13MB (36.63%)\" id=\"node5\" fontsize=8 shape=box tooltip=\"line3001 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nN4 -> N3 [label=\" 63.48MB\\n (inline)\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line2000 -> line2001 (63.48MB)\" labeltooltip=\"line2000 -> line2001 (63.48MB)\"]\nN1 -> N4 [label=\" 63.48MB\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line3002 -> line2000 (63.48MB)\" labeltooltip=\"line3002 -> line2000 (63.48MB)\" minlen=2]\nN2 -> N1 [label=\" 62.50MB\\n (inline)\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line3000 -> line3002 (62.50MB)\" labeltooltip=\"line3000 -> line3002 (62.50MB)\"]\nN2 -> N5 [label=\" 36.13MB\\n (inline)\" weight=37 penwidth=2 color=\"#b22e00\" tooltip=\"line3000 -> line3001 (36.13MB)\" labeltooltip=\"line3000 -> line3001 (36.13MB)\"]\nN5 -> N1 [label=\" 32.23MB\\n (inline)\" weight=33 penwidth=2 color=\"#b23200\" tooltip=\"line3001 -> line3002 (32.23MB)\" labeltooltip=\"line3001 -> line3002 (32.23MB)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: alloc_space\\lActive filters:\\l   hide=line.*1?23?\\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\\lDropped 1 node (cum <= 4.93MB)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3000\\n62.50MB (63.37%)\\nof 98.63MB (100%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"line3000 (98.63MB)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nNN1_0 [label = \"1.56MB\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"62.50MB\"]\nN1 -> NN1_0 [label=\" 62.50MB\" weight=100 tooltip=\"62.50MB\" labeltooltip=\"62.50MB\"]\nN2 [label=\"line3001\\n31.25MB (31.68%)\\nof 36.13MB (36.63%)\" id=\"node2\" fontsize=20 shape=box tooltip=\"line3001 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nNN2_0 [label = \"400kB\" id=\"NN2_0\" fontsize=8 shape=box3d tooltip=\"31.25MB\"]\nN2 -> NN2_0 [label=\" 31.25MB\" weight=100 tooltip=\"31.25MB\" labeltooltip=\"31.25MB\"]\nN1 -> N2 [label=\" 36.13MB\\n (inline)\" weight=37 penwidth=2 color=\"#b22e00\" tooltip=\"line3000 -> line3001 (36.13MB)\" labeltooltip=\"line3000 -> line3001 (36.13MB)\" minlen=2]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_request.relative_percentages.tags.focus",
    "content": " bytes: Total 93.75MB of 93.75MB (  100%)\n        62.50MB (66.67%): 1.56MB\n        31.25MB (33.33%): 400kB\n\n request: Total 93.75MB of 93.75MB (  100%)\n          62.50MB (66.67%): 1.56MB\n          31.25MB (33.33%): 400kB\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_request.tags.focus",
    "content": " bytes: Total 93.75MB of 98.63MB (95.05%)\n        62.50MB (63.37%): 1.56MB\n        31.25MB (31.68%): 400kB\n\n request: Total 93.75MB of 98.63MB (95.05%)\n          62.50MB (63.37%): 1.56MB\n          31.25MB (31.68%): 400kB\n\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_sizetags.dot",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"Build ID: buildid\" [shape=box fontsize=16 label=\"Build ID: buildid\\lcomment\\lType: inuse_space\\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\\lDropped 1 node (cum <= 4.93MB)\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"line3002\\n31.25MB (31.68%)\\nof 94.73MB (96.04%)\" id=\"node1\" fontsize=20 shape=box tooltip=\"line3002 (94.73MB)\" color=\"#b20200\" fillcolor=\"#edd5d5\"]\nNN1_0 [label = \"16B..64B\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"93.75MB\"]\nN1 -> NN1_0 [label=\" 93.75MB\" weight=100 tooltip=\"93.75MB\" labeltooltip=\"93.75MB\"]\nNN1_1 [label = \"2B..8B\" id=\"NN1_1\" fontsize=8 shape=box3d tooltip=\"93.75MB\"]\nN1 -> NN1_1 [label=\" 93.75MB\" weight=100 tooltip=\"93.75MB\" labeltooltip=\"93.75MB\"]\nNN1_2 [label = \"256B..1.56MB\" id=\"NN1_2\" fontsize=8 shape=box3d tooltip=\"62.50MB\"]\nN1 -> NN1_2 [label=\" 62.50MB\" weight=100 tooltip=\"62.50MB\" labeltooltip=\"62.50MB\"]\nNN1_3 [label = \"128B\" id=\"NN1_3\" fontsize=8 shape=box3d tooltip=\"31.25MB\"]\nN1 -> NN1_3 [label=\" 31.25MB\" weight=100 tooltip=\"31.25MB\" labeltooltip=\"31.25MB\"]\nN2 [label=\"line3000\\n0 of 98.63MB (100%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"line3000 (98.63MB)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"line2001\\n62.50MB (63.37%)\\nof 63.48MB (64.36%)\" id=\"node3\" fontsize=24 shape=box tooltip=\"line2001 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nNN3_0 [label = \"16B..64B\" id=\"NN3_0\" fontsize=8 shape=box3d tooltip=\"190.43MB\"]\nN3 -> NN3_0 [label=\" 190.43MB\" weight=100 tooltip=\"190.43MB\" labeltooltip=\"190.43MB\" style=\"dotted\"]\nNN3_1 [label = \"2B..8B\" id=\"NN3_1\" fontsize=8 shape=box3d tooltip=\"190.43MB\"]\nN3 -> NN3_1 [label=\" 190.43MB\" weight=100 tooltip=\"190.43MB\" labeltooltip=\"190.43MB\" style=\"dotted\"]\nNN3_2 [label = \"256B..1.56MB\" id=\"NN3_2\" fontsize=8 shape=box3d tooltip=\"125.98MB\"]\nN3 -> NN3_2 [label=\" 125.98MB\" weight=100 tooltip=\"125.98MB\" labeltooltip=\"125.98MB\" style=\"dotted\"]\nNN3_3 [label = \"128B\" id=\"NN3_3\" fontsize=8 shape=box3d tooltip=\"63.48MB\"]\nN3 -> NN3_3 [label=\" 63.48MB\" weight=100 tooltip=\"63.48MB\" labeltooltip=\"63.48MB\" style=\"dotted\"]\nN4 [label=\"line2000\\n0 of 63.48MB (64.36%)\" id=\"node4\" fontsize=8 shape=box tooltip=\"line2000 (63.48MB)\" color=\"#b21600\" fillcolor=\"#edd8d5\"]\nN5 [label=\"line3001\\n0 of 36.13MB (36.63%)\" id=\"node5\" fontsize=8 shape=box tooltip=\"line3001 (36.13MB)\" color=\"#b22e00\" fillcolor=\"#eddbd5\"]\nN4 -> N3 [label=\" 63.48MB\\n (inline)\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line2000 -> line2001 (63.48MB)\" labeltooltip=\"line2000 -> line2001 (63.48MB)\"]\nN1 -> N4 [label=\" 63.48MB\" weight=65 penwidth=4 color=\"#b21600\" tooltip=\"line3002 -> line2000 (63.48MB)\" labeltooltip=\"line3002 -> line2000 (63.48MB)\" minlen=2]\nN2 -> N1 [label=\" 62.50MB\\n (inline)\" weight=64 penwidth=4 color=\"#b21600\" tooltip=\"line3000 -> line3002 (62.50MB)\" labeltooltip=\"line3000 -> line3002 (62.50MB)\"]\nN2 -> N5 [label=\" 36.13MB\\n (inline)\" weight=37 penwidth=2 color=\"#b22e00\" tooltip=\"line3000 -> line3001 (36.13MB)\" labeltooltip=\"line3000 -> line3001 (36.13MB)\"]\nN5 -> N1 [label=\" 32.23MB\\n (inline)\" weight=33 penwidth=2 color=\"#b23200\" tooltip=\"line3001 -> line3002 (32.23MB)\" labeltooltip=\"line3001 -> line3002 (32.23MB)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.heap_tags.traces",
    "content": "Build ID: buildid\ncomment\nType: inuse_space\n-----------+-------------------------------------------------------\n      key1:  tag\n     bytes:  100kB\n   request:  100kB\n    1000kB   line1000\n             line2001 (inline)\n             line2000\n             line3002 (inline)\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n     bytes:  200kB\n    3.91MB   line1000\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n      key1:  tag\n     bytes:  1.56MB\n   request:  1.56MB\n   62.50MB   line2001 (inline)\n             line2000\n             line3002 (inline)\n             line3000\n-----------+-------------------------------------------------------\n     bytes:  400kB\n   31.25MB   line3002 (inline)\n             line3001 (inline)\n             line3000\n-----------+-------------------------------------------------------\n"
  },
  {
    "path": "internal/driver/testdata/pprof.long_name_funcs.dot",
    "content": "digraph \"testbinary\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"File: testbinary\" [shape=box fontsize=16 label=\"File: testbinary\\lType: cpu\\lDuration: 10s, Total samples = 1.11s (11.10%)\\lShowing nodes accounting for 1.11s, 100% of 1.11s total\\l\\lSee https://git.io/JfYMW for how to read the graph\\l\" tooltip=\"testbinary\"] }\nN1 [label=\"package1\\nobject\\nfunction1\\n1.10s (99.10%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"path/to/package1.object.function1 (1.10s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN2 [label=\"FooBar\\nrun\\n0.01s (0.9%)\\nof 1.01s (90.99%)\" id=\"node2\" fontsize=10 shape=box tooltip=\"java.bar.foo.FooBar.run(java.lang.Runnable) (1.01s)\" color=\"#b20400\" fillcolor=\"#edd6d5\"]\nN3 [label=\"Bar\\nFoo\\n0 of 1.10s (99.10%)\" id=\"node3\" fontsize=8 shape=box tooltip=\"(anonymous namespace)::Bar::Foo (1.10s)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 -> N1 [label=\" 1.10s\" weight=100 penwidth=5 color=\"#b20000\" tooltip=\"(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)\" labeltooltip=\"(anonymous namespace)::Bar::Foo -> path/to/package1.object.function1 (1.10s)\"]\nN2 -> N3 [label=\" 1s\" weight=91 penwidth=5 color=\"#b20500\" tooltip=\"java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)\" labeltooltip=\"java.bar.foo.FooBar.run(java.lang.Runnable) -> (anonymous namespace)::Bar::Foo (1s)\"]\n}\n"
  },
  {
    "path": "internal/driver/testdata/pprof.long_name_funcs.text",
    "content": "Showing nodes accounting for 1.11s, 100% of 1.11s total\n      flat  flat%   sum%        cum   cum%\n     1.10s 99.10% 99.10%      1.10s 99.10%  path/to/package1.object.function1\n     0.01s   0.9%   100%      1.01s 90.99%  java.bar.foo.FooBar.run(java.lang.Runnable)\n         0     0%   100%      1.10s 99.10%  (anonymous namespace)::Bar::Foo\n"
  },
  {
    "path": "internal/driver/testdata/pprof.unknown.flat.functions.call_tree.text",
    "content": "Showing nodes accounting for 1.12s, 100% of 1.12s total\nShowing top 5 nodes out of 6\n      flat  flat%   sum%        cum   cum%\n     1.10s 98.21% 98.21%      1.10s 98.21%  line1000\n     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 (inline)\n     0.01s  0.89%   100%      1.02s 91.07%  line3002 (inline)\n         0     0%   100%      1.01s 90.18%  line2000\n         0     0%   100%      1.12s   100%  line3000\n"
  },
  {
    "path": "internal/driver/webhtml.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/google/pprof/internal/report\"\n)\n\nvar (\n\thtmlTemplates    *template.Template // Lazily loaded templates\n\thtmlTemplateInit sync.Once\n)\n\n// getHTMLTemplates returns the set of HTML templates used by pprof,\n// initializing them if necessary.\nfunc getHTMLTemplates() *template.Template {\n\thtmlTemplateInit.Do(func() {\n\t\thtmlTemplates = template.New(\"templategroup\")\n\t\taddTemplates(htmlTemplates)\n\t\treport.AddSourceTemplates(htmlTemplates)\n\t})\n\treturn htmlTemplates\n}\n\n//go:embed html\nvar embeddedFiles embed.FS\n\n// addTemplates adds a set of template definitions to templates.\nfunc addTemplates(templates *template.Template) {\n\t// Load specified file.\n\tloadFile := func(fname string) string {\n\t\tdata, err := embeddedFiles.ReadFile(fname)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"internal/driver: embedded file %q not found\\n\",\n\t\t\t\tfname)\n\t\t\tos.Exit(1)\n\t\t}\n\t\treturn string(data)\n\t}\n\tloadCSS := func(fname string) string {\n\t\treturn `<style type=\"text/css\">` + \"\\n\" + loadFile(fname) + `</style>` + \"\\n\"\n\t}\n\tloadJS := func(fname string) string {\n\t\treturn `<script>` + \"\\n\" + loadFile(fname) + `</script>` + \"\\n\"\n\t}\n\n\t// Define a named template with specified contents.\n\tdef := func(name, contents string) {\n\t\tsub := template.New(name)\n\t\ttemplate.Must(sub.Parse(contents))\n\t\ttemplate.Must(templates.AddParseTree(name, sub.Tree))\n\t}\n\n\t// Embedded files.\n\tdef(\"css\", loadCSS(\"html/common.css\"))\n\tdef(\"header\", loadFile(\"html/header.html\"))\n\tdef(\"graph\", loadFile(\"html/graph.html\"))\n\tdef(\"graph_css\", loadCSS(\"html/graph.css\"))\n\tdef(\"script\", loadJS(\"html/common.js\"))\n\tdef(\"top\", loadFile(\"html/top.html\"))\n\tdef(\"sourcelisting\", loadFile(\"html/source.html\"))\n\tdef(\"plaintext\", loadFile(\"html/plaintext.html\"))\n\t// TODO: Rename \"stacks\" to \"flamegraph\" to seal moving off d3 flamegraph.\n\tdef(\"stacks\", loadFile(\"html/stacks.html\"))\n\tdef(\"stacks_css\", loadCSS(\"html/stacks.css\"))\n\tdef(\"stacks_js\", loadJS(\"html/stacks.js\"))\n}\n"
  },
  {
    "path": "internal/driver/webui.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"maps\"\n\t\"net\"\n\t\"net/http\"\n\tgourl \"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/graph\"\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/report\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// webInterface holds the state needed for serving a browser based interface.\ntype webInterface struct {\n\tprof         *profile.Profile\n\tcopier       profileCopier\n\toptions      *plugin.Options\n\thelp         map[string]string\n\tsettingsFile string\n}\n\nfunc makeWebInterface(p *profile.Profile, copier profileCopier, opt *plugin.Options) (*webInterface, error) {\n\tsettingsFile, err := settingsFileName()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &webInterface{\n\t\tprof:         p,\n\t\tcopier:       copier,\n\t\toptions:      opt,\n\t\thelp:         make(map[string]string),\n\t\tsettingsFile: settingsFile,\n\t}, nil\n}\n\n// maxEntries is the maximum number of entries to print for text interfaces.\nconst maxEntries = 50\n\n// errorCatcher is a UI that captures errors for reporting to the browser.\ntype errorCatcher struct {\n\tplugin.UI\n\terrors []string\n}\n\nfunc (ec *errorCatcher) PrintErr(args ...interface{}) {\n\tec.errors = append(ec.errors, strings.TrimSuffix(fmt.Sprintln(args...), \"\\n\"))\n\tec.UI.PrintErr(args...)\n}\n\n// webArgs contains arguments passed to templates in webhtml.go.\ntype webArgs struct {\n\tTitle       string\n\tErrors      []string\n\tTotal       int64\n\tSampleTypes []string\n\tLegend      []string\n\tDocURL      string\n\tStandalone  bool // True for command-line generation of HTML\n\tHelp        map[string]string\n\tNodes       []string\n\tHTMLBody    template.HTML\n\tTextBody    string\n\tTop         []report.TextItem\n\tListing     report.WebListData\n\tFlameGraph  template.JS\n\tStacks      template.JS\n\tConfigs     []configMenuEntry\n\tUnitDefs    []measurement.UnitType\n}\n\nfunc serveWebInterface(hostport string, p *profile.Profile, o *plugin.Options, disableBrowser bool) error {\n\thost, port, err := getHostAndPort(hostport)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinteractiveMode = true\n\tcopier := makeProfileCopier(p)\n\tui, err := makeWebInterface(p, copier, o)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor n, c := range pprofCommands {\n\t\tui.help[n] = c.description\n\t}\n\tmaps.Copy(ui.help, configHelp)\n\tui.help[\"details\"] = \"Show information about the profile and this view\"\n\tui.help[\"graph\"] = \"Display profile as a directed graph\"\n\tui.help[\"flamegraph\"] = \"Display profile as a flame graph\"\n\tui.help[\"reset\"] = \"Show the entire profile\"\n\tui.help[\"save_config\"] = \"Save current settings\"\n\n\tserver := o.HTTPServer\n\tif server == nil {\n\t\tserver = defaultWebServer\n\t}\n\targs := &plugin.HTTPServerArgs{\n\t\tHostport: net.JoinHostPort(host, strconv.Itoa(port)),\n\t\tHost:     host,\n\t\tPort:     port,\n\t\tHandlers: map[string]http.Handler{\n\t\t\t\"/\":             redirectWithQuery(\"flamegraph\", http.StatusMovedPermanently),\n\t\t\t\"/graph\":        http.HandlerFunc(ui.dot),\n\t\t\t\"/top\":          http.HandlerFunc(ui.top),\n\t\t\t\"/disasm\":       http.HandlerFunc(ui.disasm),\n\t\t\t\"/source\":       http.HandlerFunc(ui.source),\n\t\t\t\"/peek\":         http.HandlerFunc(ui.peek),\n\t\t\t\"/flamegraph\":   http.HandlerFunc(ui.stackView),\n\t\t\t\"/saveconfig\":   http.HandlerFunc(ui.saveConfig),\n\t\t\t\"/deleteconfig\": http.HandlerFunc(ui.deleteConfig),\n\t\t\t\"/download\": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tw.Header().Set(\"Content-Type\", \"application/vnd.google.protobuf+gzip\")\n\t\t\t\tw.Header().Set(\"Content-Disposition\", \"attachment;filename=profile.pb.gz\")\n\t\t\t\tp.Write(w)\n\t\t\t}),\n\t\t\t// Keep legacy URLs working.\n\t\t\t\"/flamegraph2\":   redirectWithQuery(\"flamegraph\", http.StatusMovedPermanently),\n\t\t\t\"/flamegraphold\": redirectWithQuery(\"flamegraph\", http.StatusMovedPermanently),\n\t\t},\n\t}\n\n\turl := \"http://\" + args.Hostport\n\n\to.UI.Print(\"Serving web UI on \", url)\n\n\tif o.UI.WantBrowser() && !disableBrowser {\n\t\tgo openBrowser(url, o)\n\t}\n\treturn server(args)\n}\n\nfunc getHostAndPort(hostport string) (string, int, error) {\n\thost, portStr, err := net.SplitHostPort(hostport)\n\tif err != nil {\n\t\treturn \"\", 0, fmt.Errorf(\"could not split http address: %v\", err)\n\t}\n\tif host == \"\" {\n\t\thost = \"localhost\"\n\t}\n\tvar port int\n\tif portStr == \"\" {\n\t\tln, err := net.Listen(\"tcp\", net.JoinHostPort(host, \"0\"))\n\t\tif err != nil {\n\t\t\treturn \"\", 0, fmt.Errorf(\"could not generate random port: %v\", err)\n\t\t}\n\t\tport = ln.Addr().(*net.TCPAddr).Port\n\t\terr = ln.Close()\n\t\tif err != nil {\n\t\t\treturn \"\", 0, fmt.Errorf(\"could not generate random port: %v\", err)\n\t\t}\n\t} else {\n\t\tport, err = strconv.Atoi(portStr)\n\t\tif err != nil {\n\t\t\treturn \"\", 0, fmt.Errorf(\"invalid port number: %v\", err)\n\t\t}\n\t}\n\treturn host, port, nil\n}\nfunc defaultWebServer(args *plugin.HTTPServerArgs) error {\n\tln, err := net.Listen(\"tcp\", args.Hostport)\n\tif err != nil {\n\t\treturn err\n\t}\n\tisLocal := isLocalhost(args.Host)\n\thandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tif isLocal {\n\t\t\t// Only allow local clients\n\t\t\thost, _, err := net.SplitHostPort(req.RemoteAddr)\n\t\t\tif err != nil || !isLocalhost(host) {\n\t\t\t\thttp.Error(w, \"permission denied\", http.StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\th := args.Handlers[req.URL.Path]\n\t\tif h == nil {\n\t\t\t// Fall back to default behavior\n\t\t\th = http.DefaultServeMux\n\t\t}\n\t\th.ServeHTTP(w, req)\n\t})\n\n\t// We serve the ui at /ui/ and redirect there from the root. This is done\n\t// to surface any problems with serving the ui at a non-root early. See:\n\t//\n\t// https://github.com/google/pprof/pull/348\n\tmux := http.NewServeMux()\n\tmux.Handle(\"/ui/\", http.StripPrefix(\"/ui\", handler))\n\tmux.Handle(\"/\", redirectWithQuery(\"/ui\", http.StatusTemporaryRedirect))\n\ts := &http.Server{Handler: mux}\n\treturn s.Serve(ln)\n}\n\n// redirectWithQuery responds with a given redirect code, preserving query\n// parameters in the redirect URL. It does not convert relative paths to\n// absolute paths like http.Redirect does, so that HTTPServerArgs.Handlers can\n// generate relative redirects that work with the external prefixing.\nfunc redirectWithQuery(path string, code int) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tpathWithQuery := &gourl.URL{Path: path, RawQuery: r.URL.RawQuery}\n\t\tw.Header().Set(\"Location\", pathWithQuery.String())\n\t\tw.WriteHeader(code)\n\t}\n}\n\nfunc isLocalhost(host string) bool {\n\treturn slices.Contains([]string{\"localhost\", \"127.0.0.1\", \"[::1]\", \"::1\"}, host)\n}\n\nfunc openBrowser(url string, o *plugin.Options) {\n\t// Construct URL.\n\tbaseURL, _ := gourl.Parse(url)\n\tcurrent := currentConfig()\n\tu, _ := current.makeURL(*baseURL)\n\n\t// Give server a little time to get ready.\n\ttime.Sleep(time.Millisecond * 500)\n\n\tfor _, b := range browsers() {\n\t\targs := strings.Split(b, \" \")\n\t\tif len(args) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tviewer := exec.Command(args[0], append(args[1:], u.String())...)\n\t\tviewer.Stderr = os.Stderr\n\t\tif err := viewer.Start(); err == nil {\n\t\t\treturn\n\t\t}\n\t}\n\t// No visualizer succeeded, so just print URL.\n\to.UI.PrintErr(u.String())\n}\n\n// makeReport generates a report for the specified command.\n// If configEditor is not null, it is used to edit the config used for the report.\nfunc (ui *webInterface) makeReport(w http.ResponseWriter, req *http.Request,\n\tcmd []string, configEditor func(*config)) (*report.Report, []string) {\n\tcfg := currentConfig()\n\tif err := cfg.applyURL(req.URL.Query()); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn nil, nil\n\t}\n\tif configEditor != nil {\n\t\tconfigEditor(&cfg)\n\t}\n\tcatcher := &errorCatcher{UI: ui.options.UI}\n\toptions := *ui.options\n\toptions.UI = catcher\n\t_, rpt, err := generateRawReport(ui.copier.newCopy(), cmd, cfg, &options)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn nil, nil\n\t}\n\treturn rpt, catcher.errors\n}\n\n// renderHTML generates html using the named template based on the contents of data.\nfunc renderHTML(dst io.Writer, tmpl string, rpt *report.Report, errList, legend []string, data webArgs) error {\n\tfile := getFromLegend(legend, \"File: \", \"unknown\")\n\tprofile := getFromLegend(legend, \"Type: \", \"unknown\")\n\tdata.Title = file + \" \" + profile\n\tdata.Errors = errList\n\tdata.Total = rpt.Total()\n\tdata.DocURL = rpt.DocURL()\n\tdata.Legend = legend\n\treturn getHTMLTemplates().ExecuteTemplate(dst, tmpl, data)\n}\n\n// render responds with html generated by passing data to the named template.\nfunc (ui *webInterface) render(w http.ResponseWriter, req *http.Request, tmpl string,\n\trpt *report.Report, errList, legend []string, data webArgs) {\n\tdata.SampleTypes = sampleTypes(ui.prof)\n\tdata.Help = ui.help\n\tdata.Configs = configMenu(ui.settingsFile, *req.URL)\n\thtml := &bytes.Buffer{}\n\tif err := renderHTML(html, tmpl, rpt, errList, legend, data); err != nil {\n\t\thttp.Error(w, \"internal template error\", http.StatusInternalServerError)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"text/html\")\n\tw.Write(html.Bytes())\n}\n\n// dot generates a web page containing an svg diagram.\nfunc (ui *webInterface) dot(w http.ResponseWriter, req *http.Request) {\n\trpt, errList := ui.makeReport(w, req, []string{\"svg\"}, nil)\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\n\t// Generate dot graph.\n\tg, config := report.GetDOT(rpt)\n\tlegend := config.Labels\n\tconfig.Labels = nil\n\tdot := &bytes.Buffer{}\n\tgraph.ComposeDot(dot, g, &graph.DotAttributes{}, config)\n\n\t// Convert to svg.\n\tsvg, err := dotToSvg(dot.Bytes())\n\tif err != nil {\n\t\thttp.Error(w, \"Could not execute dot; may need to install graphviz.\",\n\t\t\thttp.StatusNotImplemented)\n\t\tui.options.UI.PrintErr(\"Failed to execute dot. Is Graphviz installed?\\n\", err)\n\t\treturn\n\t}\n\n\t// Get all node names into an array.\n\tnodes := []string{\"\"} // dot starts with node numbered 1\n\tfor _, n := range g.Nodes {\n\t\tnodes = append(nodes, n.Info.Name)\n\t}\n\n\tui.render(w, req, \"graph\", rpt, errList, legend, webArgs{\n\t\tHTMLBody: template.HTML(string(svg)),\n\t\tNodes:    nodes,\n\t})\n}\n\nfunc dotToSvg(dot []byte) ([]byte, error) {\n\tcmd := exec.Command(\"dot\", \"-Tsvg\")\n\tout := &bytes.Buffer{}\n\tcmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr\n\tif err := cmd.Run(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Fix dot bug related to unquoted ampersands.\n\tsvg := bytes.ReplaceAll(out.Bytes(), []byte(\"&;\"), []byte(\"&amp;;\"))\n\n\t// Cleanup for embedding by dropping stuff before the <svg> start.\n\tif pos := bytes.Index(svg, []byte(\"<svg\")); pos >= 0 {\n\t\tsvg = svg[pos:]\n\t}\n\treturn svg, nil\n}\n\nfunc (ui *webInterface) top(w http.ResponseWriter, req *http.Request) {\n\trpt, errList := ui.makeReport(w, req, []string{\"top\"}, func(cfg *config) {\n\t\tcfg.NodeCount = 500\n\t})\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\ttop, legend := report.TextItems(rpt)\n\tvar nodes []string\n\tfor _, item := range top {\n\t\tnodes = append(nodes, item.Name)\n\t}\n\n\tui.render(w, req, \"top\", rpt, errList, legend, webArgs{\n\t\tTop:   top,\n\t\tNodes: nodes,\n\t})\n}\n\n// disasm generates a web page containing disassembly.\nfunc (ui *webInterface) disasm(w http.ResponseWriter, req *http.Request) {\n\targs := []string{\"disasm\", req.URL.Query().Get(\"f\")}\n\trpt, errList := ui.makeReport(w, req, args, nil)\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\n\tout := &bytes.Buffer{}\n\tif err := report.PrintAssembly(out, rpt, ui.options.Obj, maxEntries); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n\n\tlegend := report.ProfileLabels(rpt)\n\tui.render(w, req, \"plaintext\", rpt, errList, legend, webArgs{\n\t\tTextBody: out.String(),\n\t})\n\n}\n\n// source generates a web page containing source code annotated with profile\n// data.\nfunc (ui *webInterface) source(w http.ResponseWriter, req *http.Request) {\n\targs := []string{\"weblist\", req.URL.Query().Get(\"f\")}\n\trpt, errList := ui.makeReport(w, req, args, nil)\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\n\t// Generate source listing.\n\tlisting, err := report.MakeWebList(rpt, ui.options.Obj, maxEntries)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n\n\tlegend := report.ProfileLabels(rpt)\n\tui.render(w, req, \"sourcelisting\", rpt, errList, legend, webArgs{\n\t\tListing: listing,\n\t})\n}\n\n// peek generates a web page listing callers/callers.\nfunc (ui *webInterface) peek(w http.ResponseWriter, req *http.Request) {\n\targs := []string{\"peek\", req.URL.Query().Get(\"f\")}\n\trpt, errList := ui.makeReport(w, req, args, func(cfg *config) {\n\t\tcfg.Granularity = \"lines\"\n\t})\n\tif rpt == nil {\n\t\treturn // error already reported\n\t}\n\n\tout := &bytes.Buffer{}\n\tif err := report.Generate(out, rpt, ui.options.Obj); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n\n\tlegend := report.ProfileLabels(rpt)\n\tui.render(w, req, \"plaintext\", rpt, errList, legend, webArgs{\n\t\tTextBody: out.String(),\n\t})\n}\n\n// saveConfig saves URL configuration.\nfunc (ui *webInterface) saveConfig(w http.ResponseWriter, req *http.Request) {\n\tif err := setConfig(ui.settingsFile, *req.URL); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n}\n\n// deleteConfig deletes a configuration.\nfunc (ui *webInterface) deleteConfig(w http.ResponseWriter, req *http.Request) {\n\tname := req.URL.Query().Get(\"config\")\n\tif err := removeConfig(ui.settingsFile, name); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\tui.options.UI.PrintErr(err)\n\t\treturn\n\t}\n}\n\n// getFromLegend returns the suffix of an entry in legend that starts\n// with param.  It returns def if no such entry is found.\nfunc getFromLegend(legend []string, param, def string) string {\n\tfor _, s := range legend {\n\t\tif strings.HasPrefix(s, param) {\n\t\t\treturn s[len(param):]\n\t\t}\n\t}\n\treturn def\n}\n"
  },
  {
    "path": "internal/driver/webui_test.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage driver\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc makeTestServer(t testing.TB, prof *profile.Profile) *httptest.Server {\n\tif runtime.GOOS == \"nacl\" || runtime.GOOS == \"js\" {\n\t\tt.Skip(\"test assumes tcp available\")\n\t}\n\n\t// Custom http server creator\n\tvar server *httptest.Server\n\tserverCreated := make(chan bool)\n\tcreator := func(a *plugin.HTTPServerArgs) error {\n\t\tserver = httptest.NewServer(http.HandlerFunc(\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif h := a.Handlers[r.URL.Path]; h != nil {\n\t\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\t}\n\t\t\t}))\n\t\tserverCreated <- true\n\t\treturn nil\n\t}\n\n\t// Start server and wait for it to be initialized\n\tgo serveWebInterface(\"unused:1234\", prof, &plugin.Options{\n\t\tObj:        fakeObjTool{},\n\t\tUI:         &proftest.TestUI{T: t},\n\t\tHTTPServer: creator,\n\t}, false)\n\t<-serverCreated\n\n\t// Close the server when the test is done.\n\tt.Cleanup(server.Close)\n\n\treturn server\n}\n\nfunc TestWebInterface(t *testing.T) {\n\tprof := makeFakeProfile()\n\tserver := makeTestServer(t, prof)\n\thaveDot := false\n\tif _, err := exec.LookPath(\"dot\"); err == nil {\n\t\thaveDot = true\n\t}\n\n\ttype testCase struct {\n\t\tpath    string\n\t\twant    []string\n\t\tneedDot bool\n\t}\n\ttestcases := []testCase{\n\t\t{\"/\", []string{\"F1\", \"F2\", \"F3\", \"testbin\", \"cpu\"}, true},\n\t\t{\"/top\", []string{`\"Name\":\"F2\",\"InlineLabel\":\"\",\"Flat\":200,\"Cum\":300,\"FlatFormat\":\"200ms\",\"CumFormat\":\"300ms\"}`}, false},\n\t\t{\"/source?f=\" + url.QueryEscape(\"F[12]\"), []string{\n\t\t\t\"F1\",\n\t\t\t\"F2\",\n\t\t\t`\\. +300ms .*f1:asm`,    // Cumulative count for F1\n\t\t\t\"200ms +300ms .*f2:asm\", // Flat + cumulative count for F2\n\t\t}, false},\n\t\t{\"/peek?f=\" + url.QueryEscape(\"F[12]\"),\n\t\t\t[]string{\"300ms.*F1\", \"200ms.*300ms.*F2\"}, false},\n\t\t{\"/disasm?f=\" + url.QueryEscape(\"F[12]\"),\n\t\t\t[]string{\"f1:asm\", \"f2:asm\"}, false},\n\t\t{\"/flamegraph\", []string{\n\t\t\t\"File: testbin\",\n\t\t\t// Check that interesting frames are included.\n\t\t\t`\\bF1\\b`,\n\t\t\t`\\bF2\\b`,\n\t\t\t// Check new view JS is included.\n\t\t\t`function stackViewer`,\n\t\t\t// Check new view CSS is included.\n\t\t\t\"#stack-chart {\",\n\t\t}, false},\n\t}\n\tfor _, c := range testcases {\n\t\tif c.needDot && !haveDot {\n\t\t\tt.Log(\"skipping\", c.path, \"since dot (graphviz) does not seem to be installed\")\n\t\t\tcontinue\n\t\t}\n\t\tres, err := http.Get(server.URL + c.path)\n\t\tif err != nil {\n\t\t\tt.Error(\"could not fetch\", c.path, err)\n\t\t\tcontinue\n\t\t}\n\t\tdata, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\tt.Error(\"could not read response\", c.path, err)\n\t\t\tcontinue\n\t\t}\n\t\tresult := string(data)\n\t\tfor _, w := range c.want {\n\t\t\tif match, _ := regexp.MatchString(w, result); !match {\n\t\t\t\tt.Errorf(\"response for %s does not match \"+\n\t\t\t\t\t\"expected pattern '%s'; \"+\n\t\t\t\t\t\"actual result:\\n%s\", c.path, w, result)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Also fetch all the test case URLs in parallel to test thread\n\t// safety when run under the race detector.\n\tvar wg sync.WaitGroup\n\tfor _, c := range testcases {\n\t\tif c.needDot && !haveDot {\n\t\t\tcontinue\n\t\t}\n\t\tpath := server.URL + c.path\n\t\tfor count := 0; count < 2; count++ {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tres, err := http.Get(path)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(\"could not fetch\", path, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif _, err = io.ReadAll(res.Body); err != nil {\n\t\t\t\t\tt.Error(\"could not read response\", path, err)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n\twg.Wait()\n}\n\n// Implement fake object file support.\n\nconst addrBase = 0x1000\nconst fakeSource = \"testdata/file1000.src\"\n\ntype fakeObj struct{}\n\nfunc (f fakeObj) Close() error                        { return nil }\nfunc (f fakeObj) Name() string                        { return \"testbin\" }\nfunc (f fakeObj) ObjAddr(addr uint64) (uint64, error) { return addr, nil }\nfunc (f fakeObj) BuildID() string                     { return \"\" }\nfunc (f fakeObj) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\treturn nil, fmt.Errorf(\"SourceLine unimplemented\")\n}\nfunc (f fakeObj) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {\n\treturn []*plugin.Sym{\n\t\t{\n\t\t\tName: []string{\"F1\"}, File: fakeSource,\n\t\t\tStart: addrBase, End: addrBase + 10,\n\t\t},\n\t\t{\n\t\t\tName: []string{\"F2\"}, File: fakeSource,\n\t\t\tStart: addrBase + 10, End: addrBase + 20,\n\t\t},\n\t\t{\n\t\t\tName: []string{\"F3\"}, File: fakeSource,\n\t\t\tStart: addrBase + 20, End: addrBase + 30,\n\t\t},\n\t}, nil\n}\n\ntype fakeObjTool struct{}\n\nfunc (obj fakeObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\treturn fakeObj{}, nil\n}\n\nfunc (obj fakeObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\treturn []plugin.Inst{\n\t\t{Addr: addrBase + 10, Text: \"f1:asm\", Function: \"F1\", Line: 3},\n\t\t{Addr: addrBase + 20, Text: \"f2:asm\", Function: \"F2\", Line: 11},\n\t\t{Addr: addrBase + 30, Text: \"d3:asm\", Function: \"F3\", Line: 22},\n\t}, nil\n}\n\nfunc makeFakeProfile() *profile.Profile {\n\t// Three functions: F1, F2, F3 with three lines, 11, 22, 33.\n\tfuncs := []*profile.Function{\n\t\t{ID: 1, Name: \"F1\", Filename: fakeSource, StartLine: 3},\n\t\t{ID: 2, Name: \"F2\", Filename: fakeSource, StartLine: 5},\n\t\t{ID: 3, Name: \"F3\", Filename: fakeSource, StartLine: 7},\n\t}\n\tlines := []profile.Line{\n\t\t{Function: funcs[0], Line: 11},\n\t\t{Function: funcs[1], Line: 22},\n\t\t{Function: funcs[2], Line: 33},\n\t}\n\tmapping := []*profile.Mapping{\n\t\t{\n\t\t\tID:             1,\n\t\t\tStart:          addrBase,\n\t\t\tLimit:          addrBase + 100,\n\t\t\tOffset:         0,\n\t\t\tFile:           \"testbin\",\n\t\t\tHasFunctions:   true,\n\t\t\tHasFilenames:   true,\n\t\t\tHasLineNumbers: true,\n\t\t},\n\t}\n\n\t// Three interesting addresses: base+{10,20,30}\n\tlocs := []*profile.Location{\n\t\t{ID: 1, Address: addrBase + 10, Line: lines[0:1], Mapping: mapping[0]},\n\t\t{ID: 2, Address: addrBase + 20, Line: lines[1:2], Mapping: mapping[0]},\n\t\t{ID: 3, Address: addrBase + 30, Line: lines[2:3], Mapping: mapping[0]},\n\t}\n\n\t// Two stack traces.\n\treturn &profile.Profile{\n\t\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tPeriod:        1,\n\t\tDurationNanos: 10e9,\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locs[2], locs[1], locs[0]},\n\t\t\t\tValue:    []int64{100},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locs[1], locs[0]},\n\t\t\t\tValue:    []int64{200},\n\t\t\t},\n\t\t},\n\t\tLocation: locs,\n\t\tFunction: funcs,\n\t\tMapping:  mapping,\n\t}\n}\n\nfunc TestGetHostAndPort(t *testing.T) {\n\tif runtime.GOOS == \"nacl\" || runtime.GOOS == \"js\" {\n\t\tt.Skip(\"test assumes tcp available\")\n\t}\n\n\ttype testCase struct {\n\t\thostport       string\n\t\twantHost       string\n\t\twantPort       int\n\t\twantRandomPort bool\n\t}\n\n\ttestCases := []testCase{\n\t\t{\":\", \"localhost\", 0, true},\n\t\t{\":4681\", \"localhost\", 4681, false},\n\t\t{\"localhost:4681\", \"localhost\", 4681, false},\n\t}\n\tfor _, tc := range testCases {\n\t\thost, port, err := getHostAndPort(tc.hostport)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"could not get host and port for %q: %v\", tc.hostport, err)\n\t\t}\n\t\tif got, want := host, tc.wantHost; got != want {\n\t\t\tt.Errorf(\"for %s, got host %s, want %s\", tc.hostport, got, want)\n\t\t\tcontinue\n\t\t}\n\t\tif !tc.wantRandomPort {\n\t\t\tif got, want := port, tc.wantPort; got != want {\n\t\t\t\tt.Errorf(\"for %s, got port %d, want %d\", tc.hostport, got, want)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestIsLocalHost(t *testing.T) {\n\tfor _, s := range []string{\"localhost:10000\", \"[::1]:10000\", \"127.0.0.1:10000\"} {\n\t\thost, _, err := net.SplitHostPort(s)\n\t\tif err != nil {\n\t\t\tt.Error(\"unexpected error when splitting\", s)\n\t\t\tcontinue\n\t\t}\n\t\tif !isLocalhost(host) {\n\t\t\tt.Errorf(\"host %s from %s not considered local\", host, s)\n\t\t}\n\t}\n}\n\nfunc BenchmarkTop(b *testing.B)   { benchmarkURL(b, \"/top\", false) }\nfunc BenchmarkFlame(b *testing.B) { benchmarkURL(b, \"/flamegraph\", false) }\nfunc BenchmarkDot(b *testing.B)   { benchmarkURL(b, \"/\", true) }\n\nfunc benchmarkURL(b *testing.B, path string, needDot bool) {\n\tif needDot {\n\t\tif _, err := exec.LookPath(\"dot\"); err != nil {\n\t\t\tb.Skip(\"dot not available\")\n\t\t}\n\t}\n\tprof := largeProfile(b)\n\tserver := makeTestServer(b, prof)\n\turl := server.URL + path\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tres, err := http.Get(url)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tdata, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tif i == 0 && testing.Verbose() {\n\t\t\tb.Logf(\"%-12s : %10d bytes\", path, len(data))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/elfexec/elfexec.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package elfexec provides utility routines to examine ELF binaries.\npackage elfexec\n\nimport (\n\t\"bufio\"\n\t\"debug/elf\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n)\n\nconst (\n\tmaxNoteSize        = 1 << 20 // in bytes\n\tnoteTypeGNUBuildID = 3\n)\n\n// elfNote is the payload of a Note Section in an ELF file.\ntype elfNote struct {\n\tName string // Contents of the \"name\" field, omitting the trailing zero byte.\n\tDesc []byte // Contents of the \"desc\" field.\n\tType uint32 // Contents of the \"type\" field.\n}\n\n// parseNotes returns the notes from a SHT_NOTE section or PT_NOTE segment.\nfunc parseNotes(reader io.Reader, alignment int, order binary.ByteOrder) ([]elfNote, error) {\n\tr := bufio.NewReader(reader)\n\n\t// padding returns the number of bytes required to pad the given size to an\n\t// alignment boundary.\n\tpadding := func(size int) int {\n\t\treturn ((size + (alignment - 1)) &^ (alignment - 1)) - size\n\t}\n\n\tvar notes []elfNote\n\tfor {\n\t\tnoteHeader := make([]byte, 12) // 3 4-byte words\n\t\tif _, err := io.ReadFull(r, noteHeader); err == io.EOF {\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnamesz := order.Uint32(noteHeader[0:4])\n\t\tdescsz := order.Uint32(noteHeader[4:8])\n\t\ttyp := order.Uint32(noteHeader[8:12])\n\n\t\tif uint64(namesz) > uint64(maxNoteSize) {\n\t\t\treturn nil, fmt.Errorf(\"note name too long (%d bytes)\", namesz)\n\t\t}\n\t\tvar name string\n\t\tif namesz > 0 {\n\t\t\t// Documentation differs as to whether namesz is meant to include the\n\t\t\t// trailing zero, but everyone agrees that name is null-terminated.\n\t\t\t// So we'll just determine the actual length after the fact.\n\t\t\tvar err error\n\t\t\tname, err = r.ReadString('\\x00')\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil, fmt.Errorf(\"missing note name (want %d bytes)\", namesz)\n\t\t\t} else if err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tnamesz = uint32(len(name))\n\t\t\tname = name[:len(name)-1]\n\t\t}\n\n\t\t// Drop padding bytes until the desc field.\n\t\tfor n := padding(len(noteHeader) + int(namesz)); n > 0; n-- {\n\t\t\tif _, err := r.ReadByte(); err == io.EOF {\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"missing %d bytes of padding after note name\", n)\n\t\t\t} else if err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tif uint64(descsz) > uint64(maxNoteSize) {\n\t\t\treturn nil, fmt.Errorf(\"note desc too long (%d bytes)\", descsz)\n\t\t}\n\t\tdesc := make([]byte, int(descsz))\n\t\tif _, err := io.ReadFull(r, desc); err == io.EOF {\n\t\t\treturn nil, fmt.Errorf(\"missing desc (want %d bytes)\", len(desc))\n\t\t} else if err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tnotes = append(notes, elfNote{Name: name, Desc: desc, Type: typ})\n\n\t\t// Drop padding bytes until the next note or the end of the section,\n\t\t// whichever comes first.\n\t\tfor n := padding(len(desc)); n > 0; n-- {\n\t\t\tif _, err := r.ReadByte(); err == io.EOF {\n\t\t\t\t// We hit the end of the section before an alignment boundary.\n\t\t\t\t// This can happen if this section is at the end of the file or the next\n\t\t\t\t// section has a smaller alignment requirement.\n\t\t\t\tbreak\n\t\t\t} else if err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn notes, nil\n}\n\n// GetBuildID returns the GNU build-ID for an ELF binary.\n//\n// If no build-ID was found but the binary was read without error, it returns\n// (nil, nil).\nfunc GetBuildID(f *elf.File) ([]byte, error) {\n\tfindBuildID := func(notes []elfNote) ([]byte, error) {\n\t\tvar buildID []byte\n\t\tfor _, note := range notes {\n\t\t\tif note.Name == \"GNU\" && note.Type == noteTypeGNUBuildID {\n\t\t\t\tif buildID == nil {\n\t\t\t\t\tbuildID = note.Desc\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, fmt.Errorf(\"multiple build ids found, don't know which to use\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn buildID, nil\n\t}\n\n\tfor _, p := range f.Progs {\n\t\tif p.Type != elf.PT_NOTE {\n\t\t\tcontinue\n\t\t}\n\t\tnotes, err := parseNotes(p.Open(), int(p.Align), f.ByteOrder)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif b, err := findBuildID(notes); b != nil || err != nil {\n\t\t\treturn b, err\n\t\t}\n\t}\n\tfor _, s := range f.Sections {\n\t\tif s.Type != elf.SHT_NOTE {\n\t\t\tcontinue\n\t\t}\n\t\tnotes, err := parseNotes(s.Open(), int(s.Addralign), f.ByteOrder)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif b, err := findBuildID(notes); b != nil || err != nil {\n\t\t\treturn b, err\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// kernelBase calculates the base for kernel mappings, which usually require\n// special handling. For kernel mappings, tools (like perf) use the address of\n// the kernel relocation symbol (_text or _stext) as the mmap start. Additionally,\n// for obfuscation, ChromeOS profiles have the kernel image remapped to the 0-th page.\nfunc kernelBase(loadSegment *elf.ProgHeader, stextOffset *uint64, start, limit, offset uint64) (uint64, bool) {\n\tconst (\n\t\t// PAGE_OFFSET for PowerPC64, see arch/powerpc/Kconfig in the kernel sources.\n\t\tpageOffsetPpc64 = 0xc000000000000000\n\t\tpageSize        = 4096\n\t)\n\n\tif loadSegment.Vaddr == start-offset {\n\t\treturn offset, true\n\t}\n\tif start == 0 && limit != 0 && stextOffset != nil {\n\t\t// ChromeOS remaps its kernel to 0. Nothing else should come\n\t\t// down this path. Empirical values:\n\t\t//       VADDR=0xffffffff80200000\n\t\t// stextOffset=0xffffffff80200198\n\t\treturn start - *stextOffset, true\n\t}\n\tif start >= 0x8000000000000000 && limit > start && (offset == 0 || offset == pageOffsetPpc64 || offset == start) {\n\t\t// Some kernels look like:\n\t\t//       VADDR=0xffffffff80200000\n\t\t// stextOffset=0xffffffff80200198\n\t\t//       Start=0xffffffff83200000\n\t\t//       Limit=0xffffffff84200000\n\t\t//      Offset=0 (0xc000000000000000 for PowerPC64) (== Start for ASLR kernel)\n\t\t// So the base should be:\n\t\tif stextOffset != nil && (start%pageSize) == (*stextOffset%pageSize) {\n\t\t\t// perf uses the address of _stext as start. Some tools may\n\t\t\t// adjust for this before calling GetBase, in which case the page\n\t\t\t// alignment should be different from that of stextOffset.\n\t\t\treturn start - *stextOffset, true\n\t\t}\n\n\t\treturn start - loadSegment.Vaddr, true\n\t}\n\tif start%pageSize != 0 && stextOffset != nil && *stextOffset%pageSize == start%pageSize {\n\t\t// ChromeOS remaps its kernel to 0 + start%pageSize. Nothing\n\t\t// else should come down this path. Empirical values:\n\t\t//       start=0x198 limit=0x2f9fffff offset=0\n\t\t//       VADDR=0xffffffff81000000\n\t\t// stextOffset=0xffffffff81000198\n\t\treturn start - *stextOffset, true\n\t}\n\treturn 0, false\n}\n\n// GetBase determines the base address to subtract from virtual\n// address to get symbol table address. For an executable, the base\n// is 0. Otherwise, it's a shared library, and the base is the\n// address where the mapping starts. The kernel needs special handling.\nfunc GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint64, start, limit, offset uint64) (uint64, error) {\n\n\tif start == 0 && offset == 0 && (limit == ^uint64(0) || limit == 0) {\n\t\t// Some tools may introduce a fake mapping that spans the entire\n\t\t// address space. Assume that the address has already been\n\t\t// adjusted, so no additional base adjustment is necessary.\n\t\treturn 0, nil\n\t}\n\n\tswitch fh.Type {\n\tcase elf.ET_EXEC:\n\t\tif loadSegment == nil {\n\t\t\t// Assume fixed-address executable and so no adjustment.\n\t\t\treturn 0, nil\n\t\t}\n\t\tif stextOffset == nil && start > 0 && start < 0x8000000000000000 {\n\t\t\t// A regular user-mode executable. Compute the base offset using same\n\t\t\t// arithmetic as in ET_DYN case below, see the explanation there.\n\t\t\t// Ideally, the condition would just be \"stextOffset == nil\" as that\n\t\t\t// represents the address of _stext symbol in the vmlinux image. Alas,\n\t\t\t// the caller may skip reading it from the binary (it's expensive to scan\n\t\t\t// all the symbols) and so it may be nil even for the kernel executable.\n\t\t\t// So additionally check that the start is within the user-mode half of\n\t\t\t// the 64-bit address space.\n\t\t\treturn start - offset + loadSegment.Off - loadSegment.Vaddr, nil\n\t\t}\n\t\t// Various kernel heuristics and cases are handled separately.\n\t\tif base, match := kernelBase(loadSegment, stextOffset, start, limit, offset); match {\n\t\t\treturn base, nil\n\t\t}\n\t\t// ChromeOS can remap its kernel to 0, and the caller might have not found\n\t\t// the _stext symbol. Split this case from kernelBase() above, since we don't\n\t\t// want to apply it to an ET_DYN user-mode executable.\n\t\tif start == 0 && limit != 0 && stextOffset == nil {\n\t\t\treturn start - loadSegment.Vaddr, nil\n\t\t}\n\n\t\treturn 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)\n\tcase elf.ET_REL:\n\t\tif offset != 0 {\n\t\t\treturn 0, fmt.Errorf(\"don't know how to handle mapping.Offset\")\n\t\t}\n\t\treturn start, nil\n\tcase elf.ET_DYN:\n\t\t// The process mapping information, start = start of virtual address range,\n\t\t// and offset = offset in the executable file of the start address, tells us\n\t\t// that a runtime virtual address x maps to a file offset\n\t\t// fx = x - start + offset.\n\t\tif loadSegment == nil {\n\t\t\treturn start - offset, nil\n\t\t}\n\t\t// Kernels compiled as PIE can be ET_DYN as well. Use heuristic, similar to\n\t\t// the ET_EXEC case above.\n\t\tif base, match := kernelBase(loadSegment, stextOffset, start, limit, offset); match {\n\t\t\treturn base, nil\n\t\t}\n\t\t// The program header, if not nil, indicates the offset in the file where\n\t\t// the executable segment is located (loadSegment.Off), and the base virtual\n\t\t// address where the first byte of the segment is loaded\n\t\t// (loadSegment.Vaddr). A file offset fx maps to a virtual (symbol) address\n\t\t// sx = fx - loadSegment.Off + loadSegment.Vaddr.\n\t\t//\n\t\t// Thus, a runtime virtual address x maps to a symbol address\n\t\t// sx = x - start + offset - loadSegment.Off + loadSegment.Vaddr.\n\t\treturn start - offset + loadSegment.Off - loadSegment.Vaddr, nil\n\t}\n\treturn 0, fmt.Errorf(\"don't know how to handle FileHeader.Type %v\", fh.Type)\n}\n\n// FindTextProgHeader finds the program segment header containing the .text\n// section or nil if the segment cannot be found.\nfunc FindTextProgHeader(f *elf.File) *elf.ProgHeader {\n\tfor _, s := range f.Sections {\n\t\tif s.Name == \".text\" {\n\t\t\t// Find the LOAD segment containing the .text section.\n\t\t\tfor _, p := range f.Progs {\n\t\t\t\tif p.Type == elf.PT_LOAD && p.Flags&elf.PF_X != 0 && s.Addr >= p.Vaddr && s.Addr < p.Vaddr+p.Memsz {\n\t\t\t\t\treturn &p.ProgHeader\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// ProgramHeadersForMapping returns the program segment headers that overlap\n// the runtime mapping with file offset mapOff and memory size mapSz. We skip\n// over segments zero file size because their file offset values are unreliable.\n// Even if overlapping, a segment is not selected if its aligned file offset is\n// greater than the mapping file offset, or if the mapping includes the last\n// page of the segment, but not the full segment and the mapping includes\n// additional pages after the segment end.\n// The function returns a slice of pointers to the headers in the input\n// slice, which are valid only while phdrs is not modified or discarded.\nfunc ProgramHeadersForMapping(phdrs []elf.ProgHeader, mapOff, mapSz uint64) []*elf.ProgHeader {\n\tconst (\n\t\t// pageSize defines the virtual memory page size used by the loader. This\n\t\t// value is dependent on the memory management unit of the CPU. The page\n\t\t// size is 4KB virtually on all the architectures that we care about, so we\n\t\t// define this metric as a constant. If we encounter architectures where\n\t\t// page size is not 4KB, we must try to guess the page size on the system\n\t\t// where the profile was collected, possibly using the architecture\n\t\t// specified in the ELF file header.\n\t\tpageSize = 4096\n\t)\n\tmapLimit := mapOff + mapSz\n\tvar headers []*elf.ProgHeader\n\tfor i := range phdrs {\n\t\tp := &phdrs[i]\n\t\t// Skip over segments with zero file size. Their file offsets can have\n\t\t// arbitrary values, see b/195427553.\n\t\tif p.Filesz == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tsegLimit := p.Off + p.Memsz\n\t\t// The segment must overlap the mapping.\n\t\tif p.Type == elf.PT_LOAD && mapOff < segLimit && p.Off < mapLimit {\n\t\t\t// If the mapping offset is strictly less than the segment offset aligned\n\t\t\t// to the segment p_align value then this mapping comes from a different\n\t\t\t// segment, fixes b/179920361.\n\t\t\talignedSegOffset := uint64(0)\n\t\t\tif p.Off > (p.Vaddr & (p.Align - 1)) {\n\t\t\t\talignedSegOffset = p.Off - (p.Vaddr & (p.Align - 1))\n\t\t\t}\n\t\t\tif mapOff < alignedSegOffset {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// If the mapping starts in the middle of the segment, it covers less than\n\t\t\t// one page of the segment, and it extends at least one page past the\n\t\t\t// segment, then this mapping comes from a different segment.\n\t\t\tif mapOff > p.Off && (segLimit < mapOff+pageSize) && (mapLimit >= segLimit+pageSize) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theaders = append(headers, p)\n\t\t}\n\t}\n\treturn headers\n}\n\n// HeaderForFileOffset attempts to identify a unique program header that\n// includes the given file offset. It returns an error if it cannot identify a\n// unique header.\nfunc HeaderForFileOffset(headers []*elf.ProgHeader, fileOffset uint64) (*elf.ProgHeader, error) {\n\tvar ph *elf.ProgHeader\n\tfor _, h := range headers {\n\t\tif fileOffset >= h.Off && fileOffset < h.Off+h.Memsz {\n\t\t\tif ph != nil {\n\t\t\t\t// Assuming no other bugs, this can only happen if we have two or\n\t\t\t\t// more small program segments that fit on the same page, and a\n\t\t\t\t// segment other than the last one includes uninitialized data, or\n\t\t\t\t// if the debug binary used for symbolization is stripped of some\n\t\t\t\t// sections, so segment file sizes are smaller than memory sizes.\n\t\t\t\treturn nil, fmt.Errorf(\"found second program header (%#v) that matches file offset %x, first program header is %#v. Is this a stripped binary, or does the first program segment contain uninitialized data?\", *h, fileOffset, *ph)\n\t\t\t}\n\t\t\tph = h\n\t\t}\n\t}\n\tif ph == nil {\n\t\treturn nil, fmt.Errorf(\"no program header matches file offset %x\", fileOffset)\n\t}\n\treturn ph, nil\n}\n"
  },
  {
    "path": "internal/elfexec/elfexec_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage elfexec\n\nimport (\n\t\"debug/elf\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestGetBase(t *testing.T) {\n\n\tfhExec := &elf.FileHeader{\n\t\tType: elf.ET_EXEC,\n\t}\n\tfhRel := &elf.FileHeader{\n\t\tType: elf.ET_REL,\n\t}\n\tfhDyn := &elf.FileHeader{\n\t\tType: elf.ET_DYN,\n\t}\n\tlsOffset := &elf.ProgHeader{\n\t\tVaddr: 0x400000,\n\t\tOff:   0x200000,\n\t}\n\tkernelHeader := &elf.ProgHeader{\n\t\tVaddr: 0xffffffff81000000,\n\t}\n\tkernelAslrHeader := &elf.ProgHeader{\n\t\tVaddr: 0xffffffff80200000,\n\t\tOff:   0x1000,\n\t}\n\t// Kernel PIE header with vaddr aligned to a 4k boundary\n\tkernelPieAlignedHeader := &elf.ProgHeader{\n\t\tVaddr: 0xffff800010010000,\n\t\tOff:   0x10000,\n\t}\n\t// Kernel PIE header with vaddr that doesn't fall on a 4k boundary\n\tkernelPieUnalignedHeader := &elf.ProgHeader{\n\t\tVaddr: 0xffffffc010080800,\n\t\tOff:   0x10800,\n\t}\n\tppc64KernelHeader := &elf.ProgHeader{\n\t\tVaddr: 0xc000000000000000,\n\t}\n\n\ttestcases := []struct {\n\t\tlabel                string\n\t\tfh                   *elf.FileHeader\n\t\tloadSegment          *elf.ProgHeader\n\t\tstextOffset          *uint64\n\t\tstart, limit, offset uint64\n\t\twant                 uint64\n\t\twanterr              bool\n\t}{\n\t\t{\"exec\", fhExec, nil, nil, 0x400000, 0, 0, 0, false},\n\t\t{\"exec offset\", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0x200000, false},\n\t\t{\"exec offset 2\", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},\n\t\t{\"exec nomap\", fhExec, nil, nil, 0, 0, 0, 0, false},\n\t\t{\"exec kernel\", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},\n\t\t{\"exec kernel\", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0x0, 0x0, false},\n\t\t{\"exec kernel ASLR\", fhExec, kernelHeader, uint64p(0xffffffff810002b8), 0xffffffff81000000, 0xffffffffa0000000, 0xffffffff81000000, 0x0, false},\n\t\t// TODO(aalexand): Figure out where this test case exactly comes from and\n\t\t// whether it's still relevant.\n\t\t{\"exec kernel ASLR 2\", fhExec, kernelAslrHeader, nil, 0xffffffff83e00000, 0xfffffffffc3fffff, 0x3c00000, 0x3c00000, false},\n\t\t{\"exec PPC64 kernel\", fhExec, ppc64KernelHeader, uint64p(0xc000000000000000), 0xc000000000000000, 0xd00000001a730000, 0x0, 0x0, false},\n\t\t{\"exec chromeos kernel\", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10197, 0, 0x7efffe68, false},\n\t\t{\"exec chromeos kernel 2\", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10198, 0, 0x7efffe68, false},\n\t\t{\"exec chromeos kernel 3\", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0x198, 0x100000, 0, 0x7f000000, false},\n\t\t{\"exec chromeos kernel 4\", fhExec, kernelHeader, uint64p(0xffffffff81200198), 0x198, 0x100000, 0, 0x7ee00000, false},\n\t\t{\"exec chromeos kernel unremapped\", fhExec, kernelHeader, uint64p(0xffffffff810001c8), 0xffffffff834001c8, 0xffffffffc0000000, 0xffffffff834001c8, 0x2400000, false},\n\t\t{\"dyn\", fhDyn, nil, nil, 0x200000, 0x300000, 0, 0x200000, false},\n\t\t{\"dyn map\", fhDyn, lsOffset, nil, 0x0, 0x300000, 0, 0xFFFFFFFFFFE00000, false},\n\t\t{\"dyn nomap\", fhDyn, nil, nil, 0x0, 0x0, 0, 0, false},\n\t\t{\"dyn map+offset\", fhDyn, lsOffset, nil, 0x900000, 0xa00000, 0x200000, 0x500000, false},\n\t\t{\"dyn kernel\", fhDyn, kernelPieAlignedHeader, uint64p(0xffff800010000000), 0xffff800010000000, 0xffff800012815c00, 0xffff800010000000, 0, false},\n\t\t{\"dyn chromeos aslr kernel\", fhDyn, kernelPieUnalignedHeader, uint64p(0xffffffc010080800), 0x800, 0xb7f800, 0, 0x3feff80000, false},\n\t\t{\"dyn chromeos aslr kernel unremapped\", fhDyn, kernelPieUnalignedHeader, uint64p(0xffffffc010080800), 0xffffffdb5d680800, 0xffffffdb5e200000, 0xffffffdb5d680800, 0x1b4d600000, false},\n\t\t{\"rel\", fhRel, nil, nil, 0x2000000, 0x3000000, 0, 0x2000000, false},\n\t\t{\"rel nomap\", fhRel, nil, nil, 0x0, ^uint64(0), 0, 0, false},\n\t\t{\"rel offset\", fhRel, nil, nil, 0x100000, 0x200000, 0x1, 0, true},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tbase, err := GetBase(tc.fh, tc.loadSegment, tc.stextOffset, tc.start, tc.limit, tc.offset)\n\t\tif err != nil {\n\t\t\tif !tc.wanterr {\n\t\t\t\tt.Errorf(\"%s: want no error, got %v\", tc.label, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif tc.wanterr {\n\t\t\tt.Errorf(\"%s: want error, got nil\", tc.label)\n\t\t\tcontinue\n\t\t}\n\t\tif base != tc.want {\n\t\t\tt.Errorf(\"%s: want 0x%x, got 0x%x\", tc.label, tc.want, base)\n\t\t}\n\t}\n}\n\nfunc uint64p(n uint64) *uint64 {\n\treturn &n\n}\n\nfunc TestFindProgHeaderForMapping(t *testing.T) {\n\tbuildList := func(headers []*elf.ProgHeader) (result string) {\n\t\tbuilder := strings.Builder{}\n\t\tif err := builder.WriteByte('['); err != nil {\n\t\t\tt.Error(\"Failed to append '[' to the builder\")\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := builder.WriteByte(']'); err != nil {\n\t\t\t\tt.Error(\"Failed to append ']' to the builder\")\n\t\t\t}\n\t\t\tresult = builder.String()\n\t\t}()\n\t\tif len(headers) == 0 {\n\t\t\tif _, err := builder.WriteString(\"nil\"); err != nil {\n\t\t\t\tt.Error(\"Failed to append 'nil' to the builder\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif _, err := builder.WriteString(fmt.Sprintf(\"%#v\", *headers[0])); err != nil {\n\t\t\tt.Error(\"Failed to append first header to the builder\")\n\t\t}\n\t\tfor i, h := range headers[1:] {\n\t\t\tif _, err := builder.WriteString(fmt.Sprintf(\", %#v\", *h)); err != nil {\n\t\t\t\tt.Errorf(\"Failed to append header %d to the builder\", i+1)\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\t// Various ELF program headers for unit tests.\n\ttinyHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t}\n\ttinyBadBSSHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x90, Memsz: 0x90, Align: 0x200000},\n\t}\n\tsmallHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_PHDR, Flags: elf.PF_R | elf.PF_X, Off: 0x40, Vaddr: 0x400040, Paddr: 0x400040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8},\n\t\t{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x400238, Paddr: 0x400238, Filesz: 0x1c, Memsz: 0x1c, Align: 1},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x400000, Paddr: 0x400000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t\t{Type: elf.PT_DYNAMIC, Flags: elf.PF_R | elf.PF_W, Off: 0xe28, Vaddr: 0x600e28, Paddr: 0x600e28, Filesz: 0x1d0, Memsz: 0x1d0, Align: 8},\n\t}\n\tsmallBadBSSHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x200000, Paddr: 0x200000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x700, Vaddr: 0x400700, Paddr: 0x400700, Filesz: 0x500, Memsz: 0x710, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t}\n\tmediumHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_PHDR, Flags: elf.PF_R, Off: 0x40, Vaddr: 0x40, Paddr: 0x40, Filesz: 0x2d8, Memsz: 0x2d8, Align: 8},\n\t\t{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x318, Vaddr: 0x318, Paddr: 0x318, Filesz: 0x28, Memsz: 0x28, Align: 1},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0x354ca0, Memsz: 0x354ca0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x400000, Vaddr: 600000, Paddr: 600000, Filesz: 0x0083b8, Memsz: 0x009000, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x600000, Vaddr: 0xa00000, Paddr: 0xa00000, Filesz: 0x007030, Memsz: 0x9cf090, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0x7cf090, Vaddr: 0x15cf090, Paddr: 0x15cf090, Filesz: 0x028460, Memsz: 0x028460, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x7f7500, Vaddr: 0x17f7500, Paddr: 0x17f7500, Filesz: 0x001f80, Memsz: 0x5bdd9a0, Align: 0x200000},\n\t\t{Type: elf.PT_TLS, Flags: elf.PF_R, Off: 0x407740, Vaddr: 0x607740, Paddr: 0x607740, Filesz: 0x98, Memsz: 0x350, Align: 0x40},\n\t\t{Type: elf.PT_DYNAMIC, Flags: elf.PF_R | elf.PF_W, Off: 0x407920, Vaddr: 0x607920, Paddr: 0x607920, Filesz: 0x230, Memsz: 0x230, Align: 8},\n\t}\n\tlargeHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_PHDR, Flags: elf.PF_R, Off: 0x40, Vaddr: 0x40, Paddr: 0x40, Filesz: 0x268, Memsz: 0x268, Align: 8},\n\t\t{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x2a8, Vaddr: 0x2a8, Paddr: 0x2a8, Filesz: 0x28, Memsz: 0x28, Align: 1},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0x2ec5d2c0, Memsz: 0x2ec5d2c0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x2ec5d2c0, Vaddr: 0x2ee5d2c0, Paddr: 0x2ee5d2c0, Filesz: 0x1361118, Memsz: 0x1361150, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x2ffbe440, Vaddr: 0x303be440, Paddr: 0x303be440, Filesz: 0x4637c0, Memsz: 0xc91610, Align: 0x200000},\n\t\t{Type: elf.PT_TLS, Flags: elf.PF_R, Off: 0x2ec5d2c0, Vaddr: 0x2ee5d2c0, Paddr: 0x2ee5d2c0, Filesz: 0x120, Memsz: 0x103f8, Align: 0x40},\n\t\t{Type: elf.PT_DYNAMIC, Flags: elf.PF_R | elf.PF_W, Off: 0x2ffbc9e0, Vaddr: 0x301bc9e0, Paddr: 0x301bc9e0, Filesz: 0x1f0, Memsz: 0x1f0, Align: 8},\n\t}\n\tlargeHeadersWithRoSegment := []elf.ProgHeader{\n\t\t{Type: elf.PT_PHDR, Flags: elf.PF_R, Off: 0x40, Vaddr: 0x40, Paddr: 0x40, Filesz: 0x348, Memsz: 0x348, Align: 8},\n\t\t{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x388, Vaddr: 0x388, Paddr: 0x388, Filesz: 0x28, Memsz: 0x28, Align: 1},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0x81c5628, Memsz: 0x81c5628, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x81c6000, Vaddr: 0x83c6000, Paddr: 0x83c6000, Filesz: 0x1af7fb0, Memsz: 0x1af7fb0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0x9cbdfc0, Vaddr: 0xa0bdfc0, Paddr: 0xa0bdfc0, Filesz: 0x16e68fc0, Memsz: 0x16e68fc0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x20c00000, Vaddr: 0x21200000, Paddr: 0x21200000, Filesz: 0x1c602f8, Memsz: 0x1c61000, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x22a00000, Vaddr: 0x23200000, Paddr: 0x23200000, Filesz: 0x120ed30, Memsz: 0x1d2c2e0, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0x23d2c2e0, Vaddr: 0x2512c2e0, Paddr: 0x2512c2e0, Filesz: 0x2cbc4dc, Memsz: 0x2cbc4dc, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x269e87c0, Vaddr: 0x27fe87c0, Paddr: 0x27fe87c0, Filesz: 0x6f43c8, Memsz: 0x736e00, Align: 0x200000},\n\t\t{Type: elf.PT_TLS, Flags: elf.PF_R, Off: 0x22840200, Vaddr: 0x22e40200, Paddr: 0x22e40200, Filesz: 0x298, Memsz: 0x5428, Align: 0x40},\n\t\t{Type: elf.PT_DYNAMIC, Flags: elf.PF_R | elf.PF_W, Off: 0x2285daa0, Vaddr: 0x22e5daa0, Paddr: 0x22e5daa0, Filesz: 0x250, Memsz: 0x250, Align: 8},\n\t}\n\tffmpegHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_PHDR, Flags: elf.PF_R, Off: 0x40, Vaddr: 0x200040, Paddr: 0x200040, Filesz: 0x1f8, Memsz: 0x1f8, Align: 8},\n\t\t{Type: elf.PT_INTERP, Flags: elf.PF_R, Off: 0x238, Vaddr: 0x200238, Paddr: 0x200238, Filesz: 0x28, Memsz: 0x28, Align: 1},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x200000, Paddr: 0x200000, Filesz: 0x48d8410, Memsz: 0x48d8410, Align: 0x200000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x48d8440, Vaddr: 0x4cd8440, Paddr: 0x4cd8440, Filesz: 0x18cbe0, Memsz: 0xd2fb70, Align: 0x200000},\n\t\t{Type: elf.PT_TLS, Flags: elf.PF_R, Off: 0x48d8440, Vaddr: 0x4cd8440, Paddr: 0x4cd8440, Filesz: 0xa8, Memsz: 0x468, Align: 0x40},\n\t\t{Type: elf.PT_DYNAMIC, Flags: elf.PF_R | elf.PF_W, Off: 0x4a63ad0, Vaddr: 0x4e63ad0, Paddr: 0x4e63ad0, Filesz: 0x200, Memsz: 0x200, Align: 8},\n\t}\n\tsentryHeaders := []elf.ProgHeader{\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_X + elf.PF_R, Off: 0x0, Vaddr: 0x7f0000000000, Paddr: 0x7f0000000000, Filesz: 0xbc64d5, Memsz: 0xbc64d5, Align: 0x1000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0xbc7000, Vaddr: 0x7f0000bc7000, Paddr: 0x7f0000bc7000, Filesz: 0xcd6b30, Memsz: 0xcd6b30, Align: 0x1000},\n\t\t{Type: elf.PT_LOAD, Flags: elf.PF_W + elf.PF_R, Off: 0x189e000, Vaddr: 0x7f000189e000, Paddr: 0x7f000189e000, Filesz: 0x58180, Memsz: 0x92d10, Align: 0x1000},\n\t}\n\n\tfor _, tc := range []struct {\n\t\tdesc        string\n\t\tphdrs       []elf.ProgHeader\n\t\tpgoff       uint64\n\t\tmemsz       uint64\n\t\twantHeaders []*elf.ProgHeader\n\t}{\n\t\t{\n\t\t\tdesc:        \"no prog headers\",\n\t\t\tphdrs:       nil,\n\t\t\tpgoff:       0,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: nil,\n\t\t},\n\t\t{\n\t\t\tdesc:  \"tiny file, 4KB at offset 0 matches both headers, b/178747588\",\n\t\t\tphdrs: tinyHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"tiny file, file offset 4KB matches no headers\",\n\t\t\tphdrs:       tinyHeaders,\n\t\t\tpgoff:       0x1000,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: nil,\n\t\t},\n\t\t{\n\t\t\tdesc:        \"tiny file with unaligned memsz matches executable segment\",\n\t\t\tphdrs:       tinyHeaders,\n\t\t\tpgoff:       0,\n\t\t\tmemsz:       0xc80,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"tiny file with unaligned offset matches data segment\",\n\t\t\tphdrs:       tinyHeaders,\n\t\t\tpgoff:       0xc80,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"tiny bad BSS file, 4KB at offset 0 matches all three headers\",\n\t\t\tphdrs: tinyBadBSSHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x90, Memsz: 0x90, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"small file, offset 0, memsz 4KB matches both segments\",\n\t\t\tphdrs: smallHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x400000, Paddr: 0x400000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"small file, offset 0, memsz 8KB matches both segments\",\n\t\t\tphdrs: smallHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x2000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x400000, Paddr: 0x400000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"small file, offset 4KB matches data segment\",\n\t\t\tphdrs:       smallHeaders,\n\t\t\tpgoff:       0x1000,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"small file, offset 8KB matches no segment\",\n\t\t\tphdrs:       smallHeaders,\n\t\t\tpgoff:       0x2000,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: nil,\n\t\t},\n\t\t{\n\t\t\tdesc:  \"small bad BSS file, offset 0, memsz 4KB matches all three segments\",\n\t\t\tphdrs: smallBadBSSHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x200000, Paddr: 0x200000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x700, Vaddr: 0x400700, Paddr: 0x400700, Filesz: 0x500, Memsz: 0x710, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"small bad BSS file, offset 0, memsz 8KB matches all three segments\",\n\t\t\tphdrs: smallBadBSSHeaders,\n\t\t\tpgoff: 0,\n\t\t\tmemsz: 0x2000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x200000, Paddr: 0x200000, Filesz: 0x6fc, Memsz: 0x6fc, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x700, Vaddr: 0x400700, Paddr: 0x400700, Filesz: 0x500, Memsz: 0x710, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"small bad BSS file, offset 4KB matches second data segment\",\n\t\t\tphdrs:       smallBadBSSHeaders,\n\t\t\tpgoff:       0x1000,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe10, Vaddr: 0x600e10, Paddr: 0x600e10, Filesz: 0x230, Memsz: 0x238, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"medium file large mapping that includes all address space matches executable segment, b/179920361\",\n\t\t\tphdrs:       mediumHeaders,\n\t\t\tpgoff:       0,\n\t\t\tmemsz:       0x73d5000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0x354ca0, Memsz: 0x354ca0, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"large file executable mapping matches executable segment\",\n\t\t\tphdrs:       largeHeaders,\n\t\t\tpgoff:       0,\n\t\t\tmemsz:       0x2ec5e000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0x2ec5d2c0, Memsz: 0x2ec5d2c0, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"large file first data mapping matches first data segment\",\n\t\t\tphdrs:       largeHeaders,\n\t\t\tpgoff:       0x2ec5d000,\n\t\t\tmemsz:       0x1362000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x2ec5d2c0, Vaddr: 0x2ee5d2c0, Paddr: 0x2ee5d2c0, Filesz: 0x1361118, Memsz: 0x1361150, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"large file, split second data mapping matches second data segment\",\n\t\t\tphdrs:       largeHeaders,\n\t\t\tpgoff:       0x2ffbe000,\n\t\t\tmemsz:       0xb11000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x2ffbe440, Vaddr: 0x303be440, Paddr: 0x303be440, Filesz: 0x4637c0, Memsz: 0xc91610, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"large file with RO segment executable mapping includes executable segment\",\n\t\t\tphdrs: largeHeadersWithRoSegment,\n\t\t\tpgoff: 0x9c00000,\n\t\t\tmemsz: 0x16f27000,\n\t\t\twantHeaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0x81c6000, Vaddr: 0x83c6000, Paddr: 0x83c6000, Filesz: 0x1af7fb0, Memsz: 0x1af7fb0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0x9cbdfc0, Vaddr: 0xa0bdfc0, Paddr: 0xa0bdfc0, Filesz: 0x16e68fc0, Memsz: 0x16e68fc0, Align: 0x200000},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"sentry headers, mapping for last page of executable segment matches executable segment\",\n\t\t\tphdrs:       sentryHeaders,\n\t\t\tpgoff:       0xbc6000,\n\t\t\tmemsz:       0x1000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_X + elf.PF_R, Off: 0x0, Vaddr: 0x7f0000000000, Paddr: 0x7f0000000000, Filesz: 0xbc64d5, Memsz: 0xbc64d5, Align: 0x1000}},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"ffmpeg headers, split mapping for executable segment matches executable segment, b/193176694\",\n\t\t\tphdrs:       ffmpegHeaders,\n\t\t\tpgoff:       0,\n\t\t\tmemsz:       0x48d8000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0x200000, Paddr: 0x200000, Filesz: 0x48d8410, Memsz: 0x48d8410, Align: 0x200000}},\n\t\t},\n\t\t{\n\t\t\tdesc: \"segments with no file bits (b/195427553), mapping for executable segment matches executable segment\",\n\t\t\tphdrs: []elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0x0, Vaddr: 0x0, Paddr: 0x0, Filesz: 0x115000, Memsz: 0x115000, Align: 0x1000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_X + elf.PF_R, Off: 0x115000, Vaddr: 0x115000, Paddr: 0x115000, Filesz: 0x361e15, Memsz: 0x361e15, Align: 0x1000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_W + elf.PF_R, Off: 0x0, Vaddr: 0x477000, Paddr: 0x477000, Filesz: 0x0, Memsz: 0x33c, Align: 0x1000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0x0, Vaddr: 0x478000, Paddr: 0x478000, Filesz: 0x0, Memsz: 0x47dc28, Align: 0x1000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R, Off: 0x477000, Vaddr: 0x8f6000, Paddr: 0x8f6000, Filesz: 0x140, Memsz: 0x140, Align: 0x1000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_W + elf.PF_R, Off: 0x478000, Vaddr: 0x8f7000, Paddr: 0x8f7000, Filesz: 0x38, Memsz: 0x38, Align: 0x1000},\n\t\t\t},\n\t\t\tpgoff:       0x115000,\n\t\t\tmemsz:       0x362000,\n\t\t\twantHeaders: []*elf.ProgHeader{{Type: elf.PT_LOAD, Flags: elf.PF_X + elf.PF_R, Off: 0x115000, Vaddr: 0x115000, Paddr: 0x115000, Filesz: 0x361e15, Memsz: 0x361e15, Align: 0x1000}},\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tgotHeaders := ProgramHeadersForMapping(tc.phdrs, tc.pgoff, tc.memsz)\n\t\t\tif !reflect.DeepEqual(gotHeaders, tc.wantHeaders) {\n\t\t\t\tt.Errorf(\"got program headers %q; want %q\", buildList(gotHeaders), buildList(tc.wantHeaders))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHeaderForFileOffset(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc       string\n\t\theaders    []*elf.ProgHeader\n\t\tfileOffset uint64\n\t\twantError  bool\n\t\twant       *elf.ProgHeader\n\t}{\n\t\t{\n\t\t\tdesc:      \"no headers, want error\",\n\t\t\theaders:   nil,\n\t\t\twantError: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in last segment, file offset selects first header\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xc79,\n\t\t\twant:       &elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in last segment, file offset selects second header\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xc80,\n\t\t\twant:       &elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in last segment, file offset selects third header\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xef0,\n\t\t\twant:       &elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in last segment, file offset in uninitialized section selects third header\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xf40,\n\t\t\twant:       &elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in last segment, file offset past any segment gives error\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x1f0, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xe70, Vaddr: 0x400e70, Paddr: 0x400e70, Filesz: 0x90, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xf70,\n\t\t\twantError:  true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in second segment, file offset in mapped section selects second header\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x100, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xd79,\n\t\t\twant:       &elf.ProgHeader{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000},\n\t\t},\n\t\t{\n\t\t\tdesc: \"three headers, BSS in second segment, file offset in unmapped section gives error\",\n\t\t\theaders: []*elf.ProgHeader{\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_X, Off: 0, Vaddr: 0, Paddr: 0, Filesz: 0xc80, Memsz: 0xc80, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xc80, Vaddr: 0x200c80, Paddr: 0x200c80, Filesz: 0x100, Memsz: 0x1f0, Align: 0x200000},\n\t\t\t\t{Type: elf.PT_LOAD, Flags: elf.PF_R | elf.PF_W, Off: 0xd80, Vaddr: 0x400d80, Paddr: 0x400d80, Filesz: 0x100, Memsz: 0x100, Align: 0x200000},\n\t\t\t},\n\t\t\tfileOffset: 0xd80,\n\t\t\twantError:  true,\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tgot, err := HeaderForFileOffset(tc.headers, tc.fileOffset)\n\t\t\tif (err != nil) != tc.wantError {\n\t\t\t\tt.Errorf(\"got error %v, want any error=%v\", err, tc.wantError)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tc.want) {\n\t\t\t\tt.Errorf(\"got program header %#v, want %#v\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/graph/dotgraph.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage graph\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n)\n\n// DotAttributes contains details about the graph itself, giving\n// insight into how its elements should be rendered.\ntype DotAttributes struct {\n\tNodes map[*Node]*DotNodeAttributes // A map allowing each Node to have its own visualization option\n}\n\n// DotNodeAttributes contains Node specific visualization options.\ntype DotNodeAttributes struct {\n\tShape       string                 // The optional shape of the node when rendered visually\n\tBold        bool                   // If the node should be bold or not\n\tPeripheries int                    // An optional number of borders to place around a node\n\tURL         string                 // An optional url link to add to a node\n\tFormatter   func(*NodeInfo) string // An optional formatter for the node's label\n}\n\n// DotConfig contains attributes about how a graph should be\n// constructed and how it should look.\ntype DotConfig struct {\n\tTitle     string   // The title of the DOT graph\n\tLegendURL string   // The URL to link to from the legend.\n\tLabels    []string // The labels for the DOT's legend\n\n\tFormatValue func(int64) string // A formatting function for values\n\tTotal       int64              // The total weight of the graph, used to compute percentages\n}\n\nconst maxNodelets = 4 // Number of nodelets for labels (both numeric and non)\n\n// ComposeDot creates and writes a in the DOT format to the writer, using\n// the configurations given.\nfunc ComposeDot(w io.Writer, g *Graph, a *DotAttributes, c *DotConfig) {\n\tbuilder := &builder{w, a, c}\n\n\t// Begin constructing DOT by adding a title and legend.\n\tbuilder.start()\n\tdefer builder.finish()\n\tbuilder.addLegend()\n\n\tif len(g.Nodes) == 0 {\n\t\treturn\n\t}\n\n\t// Preprocess graph to get id map and find max flat.\n\tnodeIDMap := make(map[*Node]int)\n\thasNodelets := make(map[*Node]bool)\n\n\tmaxFlat := float64(abs64(g.Nodes[0].FlatValue()))\n\tfor i, n := range g.Nodes {\n\t\tnodeIDMap[n] = i + 1\n\t\tif float64(abs64(n.FlatValue())) > maxFlat {\n\t\t\tmaxFlat = float64(abs64(n.FlatValue()))\n\t\t}\n\t}\n\n\tedges := EdgeMap{}\n\n\t// Add nodes and nodelets to DOT builder.\n\tfor _, n := range g.Nodes {\n\t\tbuilder.addNode(n, nodeIDMap[n], maxFlat)\n\t\thasNodelets[n] = builder.addNodelets(n, nodeIDMap[n])\n\n\t\t// Collect all edges. Use a fake node to support multiple incoming edges.\n\t\tfor _, e := range n.Out {\n\t\t\tedges[&Node{}] = e\n\t\t}\n\t}\n\n\t// Add edges to DOT builder. Sort edges by frequency as a hint to the graph layout engine.\n\tfor _, e := range edges.Sort() {\n\t\tbuilder.addEdge(e, nodeIDMap[e.Src], nodeIDMap[e.Dest], hasNodelets[e.Src])\n\t}\n}\n\n// builder wraps an io.Writer and understands how to compose DOT formatted elements.\ntype builder struct {\n\tio.Writer\n\tattributes *DotAttributes\n\tconfig     *DotConfig\n}\n\n// start generates a title and initial node in DOT format.\nfunc (b *builder) start() {\n\tgraphname := \"unnamed\"\n\tif b.config.Title != \"\" {\n\t\tgraphname = b.config.Title\n\t}\n\tfmt.Fprintln(b, `digraph \"`+graphname+`\" {`)\n\tfmt.Fprintln(b, `node [style=filled fillcolor=\"#f8f8f8\"]`)\n}\n\n// finish closes the opening curly bracket in the constructed DOT buffer.\nfunc (b *builder) finish() {\n\tfmt.Fprintln(b, \"}\")\n}\n\n// addLegend generates a legend in DOT format.\nfunc (b *builder) addLegend() {\n\tlabels := b.config.Labels\n\tif len(labels) == 0 {\n\t\treturn\n\t}\n\ttitle := labels[0]\n\tfmt.Fprintf(b, `subgraph cluster_L { \"%s\" [shape=box fontsize=16`, escapeForDot(title))\n\tfmt.Fprintf(b, ` label=\"%s\\l\"`, strings.Join(escapeAllForDot(labels), `\\l`))\n\tif b.config.LegendURL != \"\" {\n\t\tfmt.Fprintf(b, ` URL=\"%s\" target=\"_blank\"`, b.config.LegendURL)\n\t}\n\tif b.config.Title != \"\" {\n\t\tfmt.Fprintf(b, ` tooltip=\"%s\"`, b.config.Title)\n\t}\n\tfmt.Fprintf(b, \"] }\\n\")\n}\n\n// addNode generates a graph node in DOT format.\nfunc (b *builder) addNode(node *Node, nodeID int, maxFlat float64) {\n\tflat, cum := node.FlatValue(), node.CumValue()\n\tattrs := b.attributes.Nodes[node]\n\n\t// Populate label for node.\n\tvar label string\n\tif attrs != nil && attrs.Formatter != nil {\n\t\tlabel = attrs.Formatter(&node.Info)\n\t} else {\n\t\tlabel = multilinePrintableName(&node.Info)\n\t}\n\n\tflatValue := b.config.FormatValue(flat)\n\tif flat != 0 {\n\t\tlabel = label + fmt.Sprintf(`%s (%s)`,\n\t\t\tflatValue,\n\t\t\tstrings.TrimSpace(measurement.Percentage(flat, b.config.Total)))\n\t} else {\n\t\tlabel = label + \"0\"\n\t}\n\tcumValue := flatValue\n\tif cum != flat {\n\t\tif flat != 0 {\n\t\t\tlabel = label + `\\n`\n\t\t} else {\n\t\t\tlabel = label + \" \"\n\t\t}\n\t\tcumValue = b.config.FormatValue(cum)\n\t\tlabel = label + fmt.Sprintf(`of %s (%s)`,\n\t\t\tcumValue,\n\t\t\tstrings.TrimSpace(measurement.Percentage(cum, b.config.Total)))\n\t}\n\n\t// Scale font sizes from 8 to 24 based on percentage of flat frequency.\n\t// Use non linear growth to emphasize the size difference.\n\tbaseFontSize, maxFontGrowth := 8, 16.0\n\tfontSize := baseFontSize\n\tif maxFlat != 0 && flat != 0 && float64(abs64(flat)) <= maxFlat {\n\t\tfontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(abs64(flat))/maxFlat)))\n\t}\n\n\t// Determine node shape.\n\tshape := \"box\"\n\tif attrs != nil && attrs.Shape != \"\" {\n\t\tshape = attrs.Shape\n\t}\n\n\t// Create DOT attribute for node.\n\tattr := fmt.Sprintf(`label=\"%s\" id=\"node%d\" fontsize=%d shape=%s tooltip=\"%s (%s)\" color=\"%s\" fillcolor=\"%s\"`,\n\t\tlabel, nodeID, fontSize, shape, escapeForDot(node.Info.PrintableName()), cumValue,\n\t\tdotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), false),\n\t\tdotColor(float64(node.CumValue())/float64(abs64(b.config.Total)), true))\n\n\t// Add on extra attributes if provided.\n\tif attrs != nil {\n\t\t// Make bold if specified.\n\t\tif attrs.Bold {\n\t\t\tattr += ` style=\"bold,filled\"`\n\t\t}\n\n\t\t// Add peripheries if specified.\n\t\tif attrs.Peripheries != 0 {\n\t\t\tattr += fmt.Sprintf(` peripheries=%d`, attrs.Peripheries)\n\t\t}\n\n\t\t// Add URL if specified. target=\"_blank\" forces the link to open in a new tab.\n\t\tif attrs.URL != \"\" {\n\t\t\tattr += fmt.Sprintf(` URL=\"%s\" target=\"_blank\"`, attrs.URL)\n\t\t}\n\t}\n\n\tfmt.Fprintf(b, \"N%d [%s]\\n\", nodeID, attr)\n}\n\n// addNodelets generates the DOT boxes for the node tags if they exist.\nfunc (b *builder) addNodelets(node *Node, nodeID int) bool {\n\tvar nodelets string\n\n\t// Populate two Tag slices, one for LabelTags and one for NumericTags.\n\tvar ts []*Tag\n\tlnts := make(map[string][]*Tag)\n\tfor _, t := range node.LabelTags {\n\t\tts = append(ts, t)\n\t}\n\tfor l, tm := range node.NumericTags {\n\t\tfor _, t := range tm {\n\t\t\tlnts[l] = append(lnts[l], t)\n\t\t}\n\t}\n\n\t// For leaf nodes, print cumulative tags (includes weight from\n\t// children that have been deleted).\n\t// For internal nodes, print only flat tags.\n\tflatTags := len(node.Out) > 0\n\n\t// Select the top maxNodelets alphanumeric labels by weight.\n\tSortTags(ts, flatTags)\n\tif len(ts) > maxNodelets {\n\t\tts = ts[:maxNodelets]\n\t}\n\tfor i, t := range ts {\n\t\tw := t.CumValue()\n\t\tif flatTags {\n\t\t\tw = t.FlatValue()\n\t\t}\n\t\tif w == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tweight := b.config.FormatValue(w)\n\t\tnodelets += fmt.Sprintf(`N%d_%d [label = \"%s\" id=\"N%d_%d\" fontsize=8 shape=box3d tooltip=\"%s\"]`+\"\\n\", nodeID, i, t.Name, nodeID, i, weight)\n\t\tnodelets += fmt.Sprintf(`N%d -> N%d_%d [label=\" %s\" weight=100 tooltip=\"%s\" labeltooltip=\"%s\"]`+\"\\n\", nodeID, nodeID, i, weight, weight, weight)\n\t\tif nts := lnts[t.Name]; nts != nil {\n\t\t\tnodelets += b.numericNodelets(nts, maxNodelets, flatTags, fmt.Sprintf(`N%d_%d`, nodeID, i))\n\t\t}\n\t}\n\n\tif nts := lnts[\"\"]; nts != nil {\n\t\tnodelets += b.numericNodelets(nts, maxNodelets, flatTags, fmt.Sprintf(`N%d`, nodeID))\n\t}\n\n\tfmt.Fprint(b, nodelets)\n\treturn nodelets != \"\"\n}\n\nfunc (b *builder) numericNodelets(nts []*Tag, maxNumNodelets int, flatTags bool, source string) string {\n\tnodelets := \"\"\n\n\t// Collapse numeric labels into maxNumNodelets buckets, of the form:\n\t// 1MB..2MB, 3MB..5MB, ...\n\tfor j, t := range b.collapsedTags(nts, maxNumNodelets, flatTags) {\n\t\tw, attr := t.CumValue(), ` style=\"dotted\"`\n\t\tif flatTags || t.FlatValue() == t.CumValue() {\n\t\t\tw, attr = t.FlatValue(), \"\"\n\t\t}\n\t\tif w != 0 {\n\t\t\tweight := b.config.FormatValue(w)\n\t\t\tnodelets += fmt.Sprintf(`N%s_%d [label = \"%s\" id=\"N%s_%d\" fontsize=8 shape=box3d tooltip=\"%s\"]`+\"\\n\", source, j, t.Name, source, j, weight)\n\t\t\tnodelets += fmt.Sprintf(`%s -> N%s_%d [label=\" %s\" weight=100 tooltip=\"%s\" labeltooltip=\"%s\"%s]`+\"\\n\", source, source, j, weight, weight, weight, attr)\n\t\t}\n\t}\n\treturn nodelets\n}\n\n// addEdge generates a graph edge in DOT format.\nfunc (b *builder) addEdge(edge *Edge, from, to int, hasNodelets bool) {\n\tvar inline string\n\tif edge.Inline {\n\t\tinline = `\\n (inline)`\n\t}\n\tw := b.config.FormatValue(edge.WeightValue())\n\tattr := fmt.Sprintf(`label=\" %s%s\"`, w, inline)\n\tif b.config.Total != 0 {\n\t\t// Note: edge.weight > b.config.Total is possible for profile diffs.\n\t\tif weight := 1 + int(min64(abs64(edge.WeightValue()*100/b.config.Total), 100)); weight > 1 {\n\t\t\tattr = fmt.Sprintf(`%s weight=%d`, attr, weight)\n\t\t}\n\t\tif width := 1 + int(min64(abs64(edge.WeightValue()*5/b.config.Total), 5)); width > 1 {\n\t\t\tattr = fmt.Sprintf(`%s penwidth=%d`, attr, width)\n\t\t}\n\t\tattr = fmt.Sprintf(`%s color=\"%s\"`, attr,\n\t\t\tdotColor(float64(edge.WeightValue())/float64(abs64(b.config.Total)), false))\n\t}\n\tarrow := \"->\"\n\tif edge.Residual {\n\t\tarrow = \"...\"\n\t}\n\ttooltip := fmt.Sprintf(`\"%s %s %s (%s)\"`,\n\t\tescapeForDot(edge.Src.Info.PrintableName()), arrow,\n\t\tescapeForDot(edge.Dest.Info.PrintableName()), w)\n\tattr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`, attr, tooltip, tooltip)\n\n\tif edge.Residual {\n\t\tattr = attr + ` style=\"dotted\"`\n\t}\n\n\tif hasNodelets {\n\t\t// Separate children further if source has tags.\n\t\tattr = attr + \" minlen=2\"\n\t}\n\n\tfmt.Fprintf(b, \"N%d -> N%d [%s]\\n\", from, to, attr)\n}\n\n// dotColor returns a color for the given score (between -1.0 and\n// 1.0), with -1.0 colored green, 0.0 colored grey, and 1.0 colored\n// red. If isBackground is true, then a light (low-saturation)\n// color is returned (suitable for use as a background color);\n// otherwise, a darker color is returned (suitable for use as a\n// foreground color).\nfunc dotColor(score float64, isBackground bool) string {\n\t// A float between 0.0 and 1.0, indicating the extent to which\n\t// colors should be shifted away from grey (to make positive and\n\t// negative values easier to distinguish, and to make more use of\n\t// the color range.)\n\tconst shift = 0.7\n\n\t// Saturation and value (in hsv colorspace) for background colors.\n\tconst bgSaturation = 0.1\n\tconst bgValue = 0.93\n\n\t// Saturation and value (in hsv colorspace) for foreground colors.\n\tconst fgSaturation = 1.0\n\tconst fgValue = 0.7\n\n\t// Choose saturation and value based on isBackground.\n\tvar saturation float64\n\tvar value float64\n\tif isBackground {\n\t\tsaturation = bgSaturation\n\t\tvalue = bgValue\n\t} else {\n\t\tsaturation = fgSaturation\n\t\tvalue = fgValue\n\t}\n\n\t// Limit the score values to the range [-1.0, 1.0].\n\tscore = math.Max(-1.0, math.Min(1.0, score))\n\n\t// Reduce saturation near score=0 (so it is colored grey, rather than yellow).\n\tif math.Abs(score) < 0.2 {\n\t\tsaturation *= math.Abs(score) / 0.2\n\t}\n\n\t// Apply 'shift' to move scores away from 0.0 (grey).\n\tif score > 0.0 {\n\t\tscore = math.Pow(score, (1.0 - shift))\n\t}\n\tif score < 0.0 {\n\t\tscore = -math.Pow(-score, (1.0 - shift))\n\t}\n\n\tvar r, g, b float64 // red, green, blue\n\tif score < 0.0 {\n\t\tg = value\n\t\tr = value * (1 + saturation*score)\n\t} else {\n\t\tr = value\n\t\tg = value * (1 - saturation*score)\n\t}\n\tb = value * (1 - saturation)\n\treturn fmt.Sprintf(\"#%02x%02x%02x\", uint8(r*255.0), uint8(g*255.0), uint8(b*255.0))\n}\n\nfunc multilinePrintableName(info *NodeInfo) string {\n\tinfoCopy := *info\n\tinfoCopy.Name = escapeForDot(ShortenFunctionName(infoCopy.Name))\n\tinfoCopy.Name = strings.ReplaceAll(infoCopy.Name, \"::\", `\\n`)\n\t// Go type parameters are reported as \"[...]\" by Go pprof profiles.\n\t// Keep this ellipsis rather than replacing with newlines below.\n\tinfoCopy.Name = strings.ReplaceAll(infoCopy.Name, \"[...]\", \"[…]\")\n\tinfoCopy.Name = strings.ReplaceAll(infoCopy.Name, \".\", `\\n`)\n\tif infoCopy.File != \"\" {\n\t\tinfoCopy.File = filepath.Base(infoCopy.File)\n\t}\n\treturn strings.Join(infoCopy.NameComponents(), `\\n`) + `\\n`\n}\n\n// collapsedTags trims and sorts a slice of tags.\nfunc (b *builder) collapsedTags(ts []*Tag, count int, flatTags bool) []*Tag {\n\tts = SortTags(ts, flatTags)\n\tif len(ts) <= count {\n\t\treturn ts\n\t}\n\n\ttagGroups := make([][]*Tag, count)\n\tfor i, t := range (ts)[:count] {\n\t\ttagGroups[i] = []*Tag{t}\n\t}\n\tfor _, t := range (ts)[count:] {\n\t\tg, d := 0, tagDistance(t, tagGroups[0][0])\n\t\tfor i := 1; i < count; i++ {\n\t\t\tif nd := tagDistance(t, tagGroups[i][0]); nd < d {\n\t\t\t\tg, d = i, nd\n\t\t\t}\n\t\t}\n\t\ttagGroups[g] = append(tagGroups[g], t)\n\t}\n\n\tvar nts []*Tag\n\tfor _, g := range tagGroups {\n\t\tl, w, c := b.tagGroupLabel(g)\n\t\tnts = append(nts, &Tag{\n\t\t\tName: l,\n\t\t\tFlat: w,\n\t\t\tCum:  c,\n\t\t})\n\t}\n\treturn SortTags(nts, flatTags)\n}\n\nfunc tagDistance(t, u *Tag) float64 {\n\tv, _ := measurement.Scale(u.Value, u.Unit, t.Unit)\n\tif v < float64(t.Value) {\n\t\treturn float64(t.Value) - v\n\t}\n\treturn v - float64(t.Value)\n}\n\nfunc (b *builder) tagGroupLabel(g []*Tag) (label string, flat, cum int64) {\n\tif len(g) == 1 {\n\t\tt := g[0]\n\t\treturn measurement.Label(t.Value, t.Unit), t.FlatValue(), t.CumValue()\n\t}\n\tmin := g[0]\n\tmax := g[0]\n\tdf, f := min.FlatDiv, min.Flat\n\tdc, c := min.CumDiv, min.Cum\n\tfor _, t := range g[1:] {\n\t\tif v, _ := measurement.Scale(t.Value, t.Unit, min.Unit); int64(v) < min.Value {\n\t\t\tmin = t\n\t\t}\n\t\tif v, _ := measurement.Scale(t.Value, t.Unit, max.Unit); int64(v) > max.Value {\n\t\t\tmax = t\n\t\t}\n\t\tf += t.Flat\n\t\tdf += t.FlatDiv\n\t\tc += t.Cum\n\t\tdc += t.CumDiv\n\t}\n\tif df != 0 {\n\t\tf = f / df\n\t}\n\tif dc != 0 {\n\t\tc = c / dc\n\t}\n\n\t// Tags are not scaled with the selected output unit because tags are often\n\t// much smaller than other values which appear, so the range of tag sizes\n\t// sometimes would appear to be \"0..0\" when scaled to the selected output unit.\n\treturn measurement.Label(min.Value, min.Unit) + \"..\" + measurement.Label(max.Value, max.Unit), f, c\n}\n\nfunc min64(a, b int64) int64 {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// escapeAllForDot applies escapeForDot to all strings in the given slice.\nfunc escapeAllForDot(in []string) []string {\n\tvar out = make([]string, len(in))\n\tfor i := range in {\n\t\tout[i] = escapeForDot(in[i])\n\t}\n\treturn out\n}\n\n// escapeForDot escapes double quotes and backslashes, and replaces Graphviz's\n// \"center\" character (\\n) with a left-justified character.\n// See https://graphviz.org/docs/attr-types/escString/ for more info.\nfunc escapeForDot(str string) string {\n\treturn strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(str, `\\`, `\\\\`), `\"`, `\\\"`), \"\\n\", `\\l`)\n}\n"
  },
  {
    "path": "internal/graph/dotgraph_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage graph\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n)\n\nvar updateFlag = flag.Bool(\"update\", false, \"Update the golden files\")\n\nfunc TestComposeWithStandardGraph(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose1.dot\")\n}\n\nfunc TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\n\t// Set NodeAttributes for Node 1.\n\ta.Nodes[g.Nodes[0]] = &DotNodeAttributes{\n\t\tShape:       \"folder\",\n\t\tBold:        true,\n\t\tPeripheries: 2,\n\t\tURL:         \"www.google.com\",\n\t\tFormatter: func(ni *NodeInfo) string {\n\t\t\treturn strings.ToUpper(ni.Name)\n\t\t},\n\t}\n\n\t// Set Flat value to zero on Node 2.\n\tg.Nodes[1].Flat = 0\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose2.dot\")\n}\n\nfunc TestComposeWithTagsAndResidualEdge(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\n\t// Add tags to Node 1.\n\tg.Nodes[0].LabelTags[\"a\"] = &Tag{\n\t\tName: \"tag1\",\n\t\tCum:  10,\n\t\tFlat: 10,\n\t}\n\tg.Nodes[0].NumericTags[\"\"] = TagMap{\n\t\t\"b\": &Tag{\n\t\t\tName: \"tag2\",\n\t\t\tCum:  20,\n\t\t\tFlat: 20,\n\t\t\tUnit: \"ms\",\n\t\t},\n\t}\n\n\t// Set edge to be Residual.\n\tg.Nodes[0].Out[g.Nodes[1]].Residual = true\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose3.dot\")\n}\n\nfunc TestComposeWithNestedTags(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\n\t// Add tags to Node 1.\n\tg.Nodes[0].LabelTags[\"tag1\"] = &Tag{\n\t\tName: \"tag1\",\n\t\tCum:  10,\n\t\tFlat: 10,\n\t}\n\tg.Nodes[0].NumericTags[\"tag1\"] = TagMap{\n\t\t\"tag2\": &Tag{\n\t\t\tName: \"tag2\",\n\t\t\tCum:  20,\n\t\t\tFlat: 20,\n\t\t\tUnit: \"ms\",\n\t\t},\n\t}\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose5.dot\")\n}\n\nfunc TestComposeWithEmptyGraph(t *testing.T) {\n\tg := &Graph{}\n\ta, c := baseAttrsAndConfig()\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose4.dot\")\n}\n\nfunc TestComposeWithStandardGraphAndURL(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\tc.LegendURL = \"http://example.com\"\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose6.dot\")\n}\n\nfunc TestComposeWithNamesThatNeedEscaping(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\tg.Nodes[0].Info = NodeInfo{Name: `var\"src\"`}\n\tg.Nodes[1].Info = NodeInfo{Name: `var\"#dest#\"`}\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose7.dot\")\n}\n\nfunc TestComposeWithCommentsWithNewlines(t *testing.T) {\n\tg := baseGraph()\n\ta, c := baseAttrsAndConfig()\n\t// comments that could be added with the -add_comment command line tool\n\t// the first label is used as the dot \"node name\"; the others are escaped as labels\n\tc.Labels = []string{\"comment line 1\\ncomment line 2 \\\"unterminated double quote\", `second comment \"double quote\"`}\n\n\tvar buf bytes.Buffer\n\tComposeDot(&buf, g, a, c)\n\n\tcompareGraphs(t, buf.Bytes(), \"compose9.dot\")\n}\n\nfunc baseGraph() *Graph {\n\tsrc := &Node{\n\t\tInfo:        NodeInfo{Name: \"src\"},\n\t\tFlat:        10,\n\t\tCum:         25,\n\t\tIn:          make(EdgeMap),\n\t\tOut:         make(EdgeMap),\n\t\tLabelTags:   make(TagMap),\n\t\tNumericTags: make(map[string]TagMap),\n\t}\n\tdest := &Node{\n\t\tInfo:        NodeInfo{Name: \"dest\"},\n\t\tFlat:        15,\n\t\tCum:         25,\n\t\tIn:          make(EdgeMap),\n\t\tOut:         make(EdgeMap),\n\t\tLabelTags:   make(TagMap),\n\t\tNumericTags: make(map[string]TagMap),\n\t}\n\tedge := &Edge{\n\t\tSrc:    src,\n\t\tDest:   dest,\n\t\tWeight: 10,\n\t}\n\tsrc.Out[dest] = edge\n\tsrc.In[src] = edge\n\treturn &Graph{\n\t\tNodes: Nodes{\n\t\t\tsrc,\n\t\t\tdest,\n\t\t},\n\t}\n}\n\nfunc baseAttrsAndConfig() (*DotAttributes, *DotConfig) {\n\ta := &DotAttributes{\n\t\tNodes: make(map[*Node]*DotNodeAttributes),\n\t}\n\tc := &DotConfig{\n\t\tTitle:  \"testtitle\",\n\t\tLabels: []string{\"label1\", \"label2\", `label3: \"foo\"`},\n\t\tTotal:  100,\n\t\tFormatValue: func(v int64) string {\n\t\t\treturn strconv.FormatInt(v, 10)\n\t\t},\n\t}\n\treturn a, c\n}\n\nfunc compareGraphs(t *testing.T, got []byte, wantFile string) {\n\twantFile = filepath.Join(\"testdata\", wantFile)\n\twant, err := os.ReadFile(wantFile)\n\tif err != nil {\n\t\tt.Fatalf(\"error reading test file %s: %v\", wantFile, err)\n\t}\n\n\tif string(got) != string(want) {\n\t\td, err := proftest.Diff(got, want)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error finding diff: %v\", err)\n\t\t}\n\t\tt.Errorf(\"Compose incorrectly wrote %s\", string(d))\n\t\tif *updateFlag {\n\t\t\terr := os.WriteFile(wantFile, got, 0644)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"failed to update the golden file %q: %v\", wantFile, err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNodeletCountCapping(t *testing.T) {\n\tlabelTags := make(TagMap)\n\tfor i := 0; i < 10; i++ {\n\t\tname := fmt.Sprintf(\"tag-%d\", i)\n\t\tlabelTags[name] = &Tag{\n\t\t\tName: name,\n\t\t\tFlat: 10,\n\t\t\tCum:  10,\n\t\t}\n\t}\n\tnumTags := make(TagMap)\n\tfor i := 0; i < 10; i++ {\n\t\tname := fmt.Sprintf(\"num-tag-%d\", i)\n\t\tnumTags[name] = &Tag{\n\t\t\tName:  name,\n\t\t\tUnit:  \"mb\",\n\t\t\tValue: 16,\n\t\t\tFlat:  10,\n\t\t\tCum:   10,\n\t\t}\n\t}\n\tnode1 := &Node{\n\t\tInfo:        NodeInfo{Name: \"node1-with-tags\"},\n\t\tFlat:        10,\n\t\tCum:         10,\n\t\tNumericTags: map[string]TagMap{\"\": numTags},\n\t\tLabelTags:   labelTags,\n\t}\n\tnode2 := &Node{\n\t\tInfo: NodeInfo{Name: \"node2\"},\n\t\tFlat: 15,\n\t\tCum:  15,\n\t}\n\tnode3 := &Node{\n\t\tInfo: NodeInfo{Name: \"node3\"},\n\t\tFlat: 15,\n\t\tCum:  15,\n\t}\n\tg := &Graph{\n\t\tNodes: Nodes{\n\t\t\tnode1,\n\t\t\tnode2,\n\t\t\tnode3,\n\t\t},\n\t}\n\tfor n := 1; n <= 3; n++ {\n\t\tinput := maxNodelets + n\n\t\tif got, want := len(g.SelectTopNodes(input, true)), n; got != want {\n\t\t\tt.Errorf(\"SelectTopNodes(%d): got %d nodes, want %d\", input, got, want)\n\t\t}\n\t}\n}\n\nfunc TestMultilinePrintableName(t *testing.T) {\n\tni := &NodeInfo{\n\t\tName:    \"test1.test2::test3\",\n\t\tFile:    \"src/file.cc\",\n\t\tAddress: 123,\n\t\tLineno:  999,\n\t}\n\n\twant := fmt.Sprintf(`%016x\\ntest1\\ntest2\\ntest3\\nfile.cc:999\\n`, 123)\n\tif got := multilinePrintableName(ni); got != want {\n\t\tt.Errorf(\"multilinePrintableName(%#v) == %q, want %q\", ni, got, want)\n\t}\n}\n\nfunc TestTagCollapse(t *testing.T) {\n\n\tmakeTag := func(name, unit string, value, flat, cum int64) *Tag {\n\t\treturn &Tag{name, unit, value, flat, 0, cum, 0}\n\t}\n\n\ttagSource := []*Tag{\n\t\tmakeTag(\"12mb\", \"mb\", 12, 100, 100),\n\t\tmakeTag(\"1kb\", \"kb\", 1, 1, 1),\n\t\tmakeTag(\"1mb\", \"mb\", 1, 1000, 1000),\n\t\tmakeTag(\"2048mb\", \"mb\", 2048, 1000, 1000),\n\t\tmakeTag(\"1b\", \"b\", 1, 100, 100),\n\t\tmakeTag(\"2b\", \"b\", 2, 100, 100),\n\t\tmakeTag(\"7b\", \"b\", 7, 100, 100),\n\t}\n\n\ttagWant := [][]*Tag{\n\t\t{\n\t\t\tmakeTag(\"1B..2GB\", \"\", 0, 2401, 2401),\n\t\t},\n\t\t{\n\t\t\tmakeTag(\"2GB\", \"\", 0, 1000, 1000),\n\t\t\tmakeTag(\"1B..12MB\", \"\", 0, 1401, 1401),\n\t\t},\n\t\t{\n\t\t\tmakeTag(\"2GB\", \"\", 0, 1000, 1000),\n\t\t\tmakeTag(\"12MB\", \"\", 0, 100, 100),\n\t\t\tmakeTag(\"1B..1MB\", \"\", 0, 1301, 1301),\n\t\t},\n\t\t{\n\t\t\tmakeTag(\"2GB\", \"\", 0, 1000, 1000),\n\t\t\tmakeTag(\"1MB\", \"\", 0, 1000, 1000),\n\t\t\tmakeTag(\"2B..1kB\", \"\", 0, 201, 201),\n\t\t\tmakeTag(\"1B\", \"\", 0, 100, 100),\n\t\t\tmakeTag(\"12MB\", \"\", 0, 100, 100),\n\t\t},\n\t}\n\n\tfor _, tc := range tagWant {\n\t\tvar got, want []*Tag\n\t\tb := builder{nil, &DotAttributes{}, &DotConfig{}}\n\t\tgot = b.collapsedTags(tagSource, len(tc), true)\n\t\twant = SortTags(tc, true)\n\n\t\tif !reflect.DeepEqual(got, want) {\n\t\t\tt.Errorf(\"collapse to %d, got:\\n%v\\nwant:\\n%v\", len(tc), tagString(got), tagString(want))\n\t\t}\n\t}\n}\n\nfunc TestEscapeForDot(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc  string\n\t\tinput []string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tdesc:  \"with multiple doubles quotes\",\n\t\t\tinput: []string{`label: \"foo\" and \"bar\"`},\n\t\t\twant:  []string{`label: \\\"foo\\\" and \\\"bar\\\"`},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"with graphviz center line character\",\n\t\t\tinput: []string{\"label: foo \\n bar\"},\n\t\t\twant:  []string{`label: foo \\l bar`},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"with two backslashes\",\n\t\t\tinput: []string{`label: \\\\`},\n\t\t\twant:  []string{`label: \\\\\\\\`},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"with two double quotes together\",\n\t\t\tinput: []string{`label: \"\"`},\n\t\t\twant:  []string{`label: \\\"\\\"`},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"with multiple labels\",\n\t\t\tinput: []string{`label1: \"foo\"`, `label2: \"bar\"`},\n\t\t\twant:  []string{`label1: \\\"foo\\\"`, `label2: \\\"bar\\\"`},\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tif got := escapeAllForDot(tc.input); !reflect.DeepEqual(got, tc.want) {\n\t\t\t\tt.Errorf(\"escapeAllForDot(%s) = %s, want %s\", tc.input, got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc tagString(t []*Tag) string {\n\tvar ret []string\n\tfor _, s := range t {\n\t\tret = append(ret, fmt.Sprintln(s))\n\t}\n\treturn strings.Join(ret, \":\")\n}\n"
  },
  {
    "path": "internal/graph/graph.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package graph collects a set of samples into a directed graph.\npackage graph\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\nvar (\n\t// Removes package name and method arguments for Java method names.\n\t// See tests for examples.\n\tjavaRegExp = regexp.MustCompile(`^(?:[a-z]\\w*\\.)*([A-Z][\\w\\$]*\\.(?:<init>|[a-z][\\w\\$]*(?:\\$\\d+)?))(?:(?:\\()|$)`)\n\t// Removes package name and method arguments for Go function names.\n\t// See tests for examples.\n\tgoRegExp = regexp.MustCompile(`^(?:[\\w\\-\\.]+\\/)+([^.]+\\..+)`)\n\t// Removes potential module versions in a package path.\n\tgoVerRegExp = regexp.MustCompile(`^(.*?)/v(?:[2-9]|[1-9][0-9]+)([./].*)$`)\n\t// Strips C++ namespace prefix from a C++ function / method name.\n\t// NOTE: Make sure to keep the template parameters in the name. Normally,\n\t// template parameters are stripped from the C++ names but when\n\t// -symbolize=demangle=templates flag is used, they will not be.\n\t// See tests for examples.\n\tcppRegExp                = regexp.MustCompile(`^(?:[_a-zA-Z]\\w*::)+(_*[A-Z]\\w*::~?[_a-zA-Z]\\w*(?:<.*>)?)`)\n\tcppAnonymousPrefixRegExp = regexp.MustCompile(`^\\(anonymous namespace\\)::`)\n)\n\n// Graph summarizes a performance profile into a format that is\n// suitable for visualization.\ntype Graph struct {\n\tNodes Nodes\n}\n\n// Options encodes the options for constructing a graph\ntype Options struct {\n\tSampleValue       func(s []int64) int64      // Function to compute the value of a sample\n\tSampleMeanDivisor func(s []int64) int64      // Function to compute the divisor for mean graphs, or nil\n\tFormatTag         func(int64, string) string // Function to format a sample tag value into a string\n\tObjNames          bool                       // Always preserve obj filename\n\tOrigFnNames       bool                       // Preserve original (eg mangled) function names\n\n\tCallTree     bool // Build a tree instead of a graph\n\tDropNegative bool // Drop nodes with overall negative values\n\n\tKeptNodes NodeSet // If non-nil, only use nodes in this set\n}\n\n// Nodes is an ordered collection of graph nodes.\ntype Nodes []*Node\n\n// Node is an entry on a profiling report. It represents a unique\n// program location.\ntype Node struct {\n\t// Info describes the source location associated to this node.\n\tInfo NodeInfo\n\n\t// Function represents the function that this node belongs to. On\n\t// graphs with sub-function resolution (eg line number or\n\t// addresses), two nodes in a NodeMap that are part of the same\n\t// function have the same value of Node.Function. If the Node\n\t// represents the whole function, it points back to itself.\n\tFunction *Node\n\n\t// Values associated to this node. Flat is exclusive to this node,\n\t// Cum includes all descendents.\n\tFlat, FlatDiv, Cum, CumDiv int64\n\n\t// In and out Contains the nodes immediately reaching or reached by\n\t// this node.\n\tIn, Out EdgeMap\n\n\t// LabelTags provide additional information about subsets of a sample.\n\tLabelTags TagMap\n\n\t// NumericTags provide additional values for subsets of a sample.\n\t// Numeric tags are optionally associated to a label tag. The key\n\t// for NumericTags is the name of the LabelTag they are associated\n\t// to, or \"\" for numeric tags not associated to a label tag.\n\tNumericTags map[string]TagMap\n}\n\n// FlatValue returns the exclusive value for this node, computing the\n// mean if a divisor is available.\nfunc (n *Node) FlatValue() int64 {\n\tif n.FlatDiv == 0 {\n\t\treturn n.Flat\n\t}\n\treturn n.Flat / n.FlatDiv\n}\n\n// CumValue returns the inclusive value for this node, computing the\n// mean if a divisor is available.\nfunc (n *Node) CumValue() int64 {\n\tif n.CumDiv == 0 {\n\t\treturn n.Cum\n\t}\n\treturn n.Cum / n.CumDiv\n}\n\n// AddToEdge increases the weight of an edge between two nodes. If\n// there isn't such an edge one is created.\nfunc (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) {\n\tn.AddToEdgeDiv(to, 0, v, residual, inline)\n}\n\n// AddToEdgeDiv increases the weight of an edge between two nodes. If\n// there isn't such an edge one is created.\nfunc (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) {\n\tif n.Out[to] != to.In[n] {\n\t\tpanic(fmt.Errorf(\"asymmetric edges %v %v\", *n, *to))\n\t}\n\n\tif e := n.Out[to]; e != nil {\n\t\te.WeightDiv += dv\n\t\te.Weight += v\n\t\tif residual {\n\t\t\te.Residual = true\n\t\t}\n\t\tif !inline {\n\t\t\te.Inline = false\n\t\t}\n\t\treturn\n\t}\n\n\tinfo := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline}\n\tn.Out[to] = info\n\tto.In[n] = info\n}\n\n// NodeInfo contains the attributes for a node.\ntype NodeInfo struct {\n\tName              string\n\tOrigName          string\n\tAddress           uint64\n\tFile              string\n\tStartLine, Lineno int\n\tColumnno          int\n\tObjfile           string\n}\n\n// PrintableName calls the Node's Formatter function with a single space separator.\nfunc (i *NodeInfo) PrintableName() string {\n\treturn strings.Join(i.NameComponents(), \" \")\n}\n\n// NameComponents returns the components of the printable name to be used for a node.\nfunc (i *NodeInfo) NameComponents() []string {\n\tvar name []string\n\tif i.Address != 0 {\n\t\tname = append(name, fmt.Sprintf(\"%016x\", i.Address))\n\t}\n\tif fun := i.Name; fun != \"\" {\n\t\tname = append(name, fun)\n\t}\n\n\tswitch {\n\tcase i.Lineno != 0:\n\t\ts := fmt.Sprintf(\"%s:%d\", i.File, i.Lineno)\n\t\tif i.Columnno != 0 {\n\t\t\ts += fmt.Sprintf(\":%d\", i.Columnno)\n\t\t}\n\t\t// User requested line numbers, provide what we have.\n\t\tname = append(name, s)\n\tcase i.File != \"\":\n\t\t// User requested file name, provide it.\n\t\tname = append(name, i.File)\n\tcase i.Name != \"\":\n\t\t// User requested function name. It was already included.\n\tcase i.Objfile != \"\":\n\t\t// Only binary name is available\n\t\tname = append(name, \"[\"+filepath.Base(i.Objfile)+\"]\")\n\tdefault:\n\t\t// Do not leave it empty if there is no information at all.\n\t\tname = append(name, \"<unknown>\")\n\t}\n\treturn name\n}\n\n// comparePrintableName compares NodeInfo lexicographically the same way as `i.PrintableName() < right.PrintableName()`, but much more performant.\nfunc (i *NodeInfo) comparePrintableName(right NodeInfo) (equal bool, less bool) {\n\tif right == *i {\n\t\treturn true, false\n\t}\n\n\tif i.Address != 0 && right.Address != 0 && i.Address != right.Address {\n\t\t// comparing ints directly is the same as comparing padded hex from fmt.Sprintf(\"%016x\", Address)\n\t\treturn false, i.Address < right.Address\n\t}\n\n\t// fallback\n\treturn false, i.PrintableName() < right.PrintableName()\n}\n\n// NodeMap maps from a node info struct to a node. It is used to merge\n// report entries with the same info.\ntype NodeMap map[NodeInfo]*Node\n\n// NodeSet is a collection of node info structs.\ntype NodeSet map[NodeInfo]bool\n\n// NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set\n// of objects which uniquely identify the nodes to keep. In a graph, NodeInfo\n// works as a unique identifier; however, in a tree multiple nodes may share\n// identical NodeInfos. A *Node does uniquely identify a node so we can use that\n// instead. Though a *Node also uniquely identifies a node in a graph,\n// currently, during trimming, graphs are rebuilt from scratch using only the\n// NodeSet, so there would not be the required context of the initial graph to\n// allow for the use of *Node.\ntype NodePtrSet map[*Node]bool\n\n// FindOrInsertNode takes the info for a node and either returns a matching node\n// from the node map if one exists, or adds one to the map if one does not.\n// If kept is non-nil, nodes are only added if they can be located on it.\nfunc (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {\n\tif kept != nil {\n\t\tif _, ok := kept[info]; !ok {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif n, ok := nm[info]; ok {\n\t\treturn n\n\t}\n\n\tn := &Node{\n\t\tInfo:        info,\n\t\tIn:          make(EdgeMap),\n\t\tOut:         make(EdgeMap),\n\t\tLabelTags:   make(TagMap),\n\t\tNumericTags: make(map[string]TagMap),\n\t}\n\tnm[info] = n\n\tif info.Address == 0 && info.Lineno == 0 {\n\t\t// This node represents the whole function, so point Function\n\t\t// back to itself.\n\t\tn.Function = n\n\t\treturn n\n\t}\n\t// Find a node that represents the whole function.\n\tinfo.Address = 0\n\tinfo.Lineno = 0\n\tinfo.Columnno = 0\n\tn.Function = nm.FindOrInsertNode(info, nil)\n\treturn n\n}\n\n// EdgeMap is used to represent the incoming/outgoing edges from a node.\ntype EdgeMap map[*Node]*Edge\n\n// Edge contains any attributes to be represented about edges in a graph.\ntype Edge struct {\n\tSrc, Dest *Node\n\t// The summary weight of the edge\n\tWeight, WeightDiv int64\n\n\t// residual edges connect nodes that were connected through a\n\t// separate node, which has been removed from the report.\n\tResidual bool\n\t// An inline edge represents a call that was inlined into the caller.\n\tInline bool\n}\n\n// WeightValue returns the weight value for this edge, normalizing if a\n// divisor is available.\nfunc (e *Edge) WeightValue() int64 {\n\tif e.WeightDiv == 0 {\n\t\treturn e.Weight\n\t}\n\treturn e.Weight / e.WeightDiv\n}\n\n// Tag represent sample annotations\ntype Tag struct {\n\tName          string\n\tUnit          string // Describe the value, \"\" for non-numeric tags\n\tValue         int64\n\tFlat, FlatDiv int64\n\tCum, CumDiv   int64\n}\n\n// FlatValue returns the exclusive value for this tag, computing the\n// mean if a divisor is available.\nfunc (t *Tag) FlatValue() int64 {\n\tif t.FlatDiv == 0 {\n\t\treturn t.Flat\n\t}\n\treturn t.Flat / t.FlatDiv\n}\n\n// CumValue returns the inclusive value for this tag, computing the\n// mean if a divisor is available.\nfunc (t *Tag) CumValue() int64 {\n\tif t.CumDiv == 0 {\n\t\treturn t.Cum\n\t}\n\treturn t.Cum / t.CumDiv\n}\n\n// TagMap is a collection of tags, classified by their name.\ntype TagMap map[string]*Tag\n\n// SortTags sorts a slice of tags based on their weight.\nfunc SortTags(t []*Tag, flat bool) []*Tag {\n\tts := tags{t, flat}\n\tsort.Sort(ts)\n\treturn ts.t\n}\n\n// New summarizes performance data from a profile into a graph.\nfunc New(prof *profile.Profile, o *Options) *Graph {\n\tif o.CallTree {\n\t\treturn newTree(prof, o)\n\t}\n\tg, _ := newGraph(prof, o)\n\treturn g\n}\n\n// newGraph computes a graph from a profile. It returns the graph, and\n// a map from the profile location indices to the corresponding graph\n// nodes.\nfunc newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) {\n\tnodes, locationMap := CreateNodes(prof, o)\n\tseenNode := make(map[*Node]bool)\n\tseenEdge := make(map[nodePair]bool)\n\tfor _, sample := range prof.Sample {\n\t\tvar w, dw int64\n\t\tw = o.SampleValue(sample.Value)\n\t\tif o.SampleMeanDivisor != nil {\n\t\t\tdw = o.SampleMeanDivisor(sample.Value)\n\t\t}\n\t\tif dw == 0 && w == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tclear(seenNode)\n\t\tclear(seenEdge)\n\t\tvar parent *Node\n\t\t// A residual edge goes over one or more nodes that were not kept.\n\t\tresidual := false\n\n\t\tlabels := joinLabels(sample)\n\t\t// Group the sample frames, based on a global map.\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tl := sample.Location[i]\n\t\t\tlocNodes := locationMap[l.ID]\n\t\t\tfor ni := len(locNodes) - 1; ni >= 0; ni-- {\n\t\t\t\tn := locNodes[ni]\n\t\t\t\tif n == nil {\n\t\t\t\t\tresidual = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// Add cum weight to all nodes in stack, avoiding double counting.\n\t\t\t\tif _, ok := seenNode[n]; !ok {\n\t\t\t\t\tseenNode[n] = true\n\t\t\t\t\tn.addSample(dw, w, labels, sample.NumLabel, sample.NumUnit, o.FormatTag, false)\n\t\t\t\t}\n\t\t\t\t// Update edge weights for all edges in stack, avoiding double counting.\n\t\t\t\tif _, ok := seenEdge[nodePair{n, parent}]; !ok && parent != nil && n != parent {\n\t\t\t\t\tseenEdge[nodePair{n, parent}] = true\n\t\t\t\t\tparent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1)\n\t\t\t\t}\n\t\t\t\tparent = n\n\t\t\t\tresidual = false\n\t\t\t}\n\t\t}\n\t\tif parent != nil && !residual {\n\t\t\t// Add flat weight to leaf node.\n\t\t\tparent.addSample(dw, w, labels, sample.NumLabel, sample.NumUnit, o.FormatTag, true)\n\t\t}\n\t}\n\n\treturn selectNodesForGraph(nodes, o.DropNegative), locationMap\n}\n\nfunc selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {\n\t// Collect nodes into a graph.\n\tgNodes := make(Nodes, 0, len(nodes))\n\tfor _, n := range nodes {\n\t\tif n == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif n.Cum == 0 && n.Flat == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif dropNegative && isNegative(n) {\n\t\t\tcontinue\n\t\t}\n\t\tgNodes = append(gNodes, n)\n\t}\n\treturn &Graph{gNodes}\n}\n\ntype nodePair struct {\n\tsrc, dest *Node\n}\n\nfunc newTree(prof *profile.Profile, o *Options) (g *Graph) {\n\tparentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))\n\tfor _, sample := range prof.Sample {\n\t\tvar w, dw int64\n\t\tw = o.SampleValue(sample.Value)\n\t\tif o.SampleMeanDivisor != nil {\n\t\t\tdw = o.SampleMeanDivisor(sample.Value)\n\t\t}\n\t\tif dw == 0 && w == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tvar parent *Node\n\t\tlabels := joinLabels(sample)\n\t\t// Group the sample frames, based on a per-node map.\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tl := sample.Location[i]\n\t\t\tlines := l.Line\n\t\t\tif len(lines) == 0 {\n\t\t\t\tlines = []profile.Line{{}} // Create empty line to include location info.\n\t\t\t}\n\t\t\tfor lidx := len(lines) - 1; lidx >= 0; lidx-- {\n\t\t\t\tnodeMap := parentNodeMap[parent]\n\t\t\t\tif nodeMap == nil {\n\t\t\t\t\tnodeMap = make(NodeMap)\n\t\t\t\t\tparentNodeMap[parent] = nodeMap\n\t\t\t\t}\n\t\t\t\tn := nodeMap.findOrInsertLine(l, lines[lidx], o)\n\t\t\t\tif n == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tn.addSample(dw, w, labels, sample.NumLabel, sample.NumUnit, o.FormatTag, false)\n\t\t\t\tif parent != nil {\n\t\t\t\t\tparent.AddToEdgeDiv(n, dw, w, false, lidx != len(lines)-1)\n\t\t\t\t}\n\t\t\t\tparent = n\n\t\t\t}\n\t\t}\n\t\tif parent != nil {\n\t\t\tparent.addSample(dw, w, labels, sample.NumLabel, sample.NumUnit, o.FormatTag, true)\n\t\t}\n\t}\n\n\tnodes := make(Nodes, 0, len(prof.Location))\n\tfor _, nm := range parentNodeMap {\n\t\tnodes = append(nodes, nm.nodes()...)\n\t}\n\treturn selectNodesForGraph(nodes, o.DropNegative)\n}\n\n// ShortenFunctionName returns a shortened version of a function's name.\nfunc ShortenFunctionName(f string) string {\n\tf = cppAnonymousPrefixRegExp.ReplaceAllString(f, \"\")\n\tf = goVerRegExp.ReplaceAllString(f, `${1}${2}`)\n\tfor _, re := range []*regexp.Regexp{goRegExp, javaRegExp, cppRegExp} {\n\t\tif matches := re.FindStringSubmatch(f); len(matches) >= 2 {\n\t\t\treturn strings.Join(matches[1:], \"\")\n\t\t}\n\t}\n\treturn f\n}\n\n// TrimTree trims a Graph in forest form, keeping only the nodes in kept. This\n// will not work correctly if even a single node has multiple parents.\nfunc (g *Graph) TrimTree(kept NodePtrSet) {\n\t// Creates a new list of nodes\n\toldNodes := g.Nodes\n\tg.Nodes = make(Nodes, 0, len(kept))\n\n\tfor _, cur := range oldNodes {\n\t\t// A node may not have multiple parents\n\t\tif len(cur.In) > 1 {\n\t\t\tpanic(\"TrimTree only works on trees\")\n\t\t}\n\n\t\t// If a node should be kept, add it to the new list of nodes\n\t\tif _, ok := kept[cur]; ok {\n\t\t\tg.Nodes = append(g.Nodes, cur)\n\t\t\tcontinue\n\t\t}\n\n\t\t// If a node has no parents, then delete all of the in edges of its\n\t\t// children to make them each roots of their own trees.\n\t\tif len(cur.In) == 0 {\n\t\t\tfor _, outEdge := range cur.Out {\n\t\t\t\tdelete(outEdge.Dest.In, cur)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get the parent. This works since at this point cur.In must contain only\n\t\t// one element.\n\t\tif len(cur.In) != 1 {\n\t\t\tpanic(\"Get parent assertion failed. cur.In expected to be of length 1.\")\n\t\t}\n\t\tvar parent *Node\n\t\tfor _, edge := range cur.In {\n\t\t\tparent = edge.Src\n\t\t}\n\n\t\tparentEdgeInline := parent.Out[cur].Inline\n\n\t\t// Remove the edge from the parent to this node\n\t\tdelete(parent.Out, cur)\n\n\t\t// Reconfigure every edge from the current node to now begin at the parent.\n\t\tfor _, outEdge := range cur.Out {\n\t\t\tchild := outEdge.Dest\n\n\t\t\tdelete(child.In, cur)\n\t\t\tchild.In[parent] = outEdge\n\t\t\tparent.Out[child] = outEdge\n\n\t\t\toutEdge.Src = parent\n\t\t\toutEdge.Residual = true\n\t\t\t// If the edge from the parent to the current node and the edge from the\n\t\t\t// current node to the child are both inline, then this resulting residual\n\t\t\t// edge should also be inline\n\t\t\toutEdge.Inline = parentEdgeInline && outEdge.Inline\n\t\t}\n\t}\n\tg.RemoveRedundantEdges()\n}\n\nfunc joinLabels(s *profile.Sample) string {\n\tif len(s.Label) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar labels []string\n\tfor key, vals := range s.Label {\n\t\tfor _, v := range vals {\n\t\t\tlabels = append(labels, key+\":\"+v)\n\t\t}\n\t}\n\tsort.Strings(labels)\n\treturn strings.Join(labels, `\\n`)\n}\n\n// isNegative returns true if the node is considered as \"negative\" for the\n// purposes of drop_negative.\nfunc isNegative(n *Node) bool {\n\tswitch {\n\tcase n.Flat < 0:\n\t\treturn true\n\tcase n.Flat == 0 && n.Cum < 0:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// CreateNodes creates graph nodes for all locations in a profile. It\n// returns set of all nodes, plus a mapping of each location to the\n// set of corresponding nodes (one per location.Line).\nfunc CreateNodes(prof *profile.Profile, o *Options) (Nodes, map[uint64]Nodes) {\n\tlocations := make(map[uint64]Nodes, len(prof.Location))\n\tnm := make(NodeMap, len(prof.Location))\n\tfor _, l := range prof.Location {\n\t\tlines := l.Line\n\t\tif len(lines) == 0 {\n\t\t\tlines = []profile.Line{{}} // Create empty line to include location info.\n\t\t}\n\t\tnodes := make(Nodes, len(lines))\n\t\tfor ln := range lines {\n\t\t\tnodes[ln] = nm.findOrInsertLine(l, lines[ln], o)\n\t\t}\n\t\tlocations[l.ID] = nodes\n\t}\n\treturn nm.nodes(), locations\n}\n\nfunc (nm NodeMap) nodes() Nodes {\n\tnodes := make(Nodes, 0, len(nm))\n\tfor _, n := range nm {\n\t\tnodes = append(nodes, n)\n\t}\n\treturn nodes\n}\n\nfunc (nm NodeMap) findOrInsertLine(l *profile.Location, li profile.Line, o *Options) *Node {\n\tvar objfile string\n\tif m := l.Mapping; m != nil && m.File != \"\" {\n\t\tobjfile = m.File\n\t}\n\n\tni := nodeInfo(l, li, objfile, o)\n\n\treturn nm.FindOrInsertNode(ni, o.KeptNodes)\n}\n\nfunc nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options) NodeInfo {\n\tif line.Function == nil {\n\t\treturn NodeInfo{Address: l.Address, Objfile: objfile}\n\t}\n\tni := NodeInfo{\n\t\tAddress:  l.Address,\n\t\tLineno:   int(line.Line),\n\t\tColumnno: int(line.Column),\n\t\tName:     line.Function.Name,\n\t}\n\tif fname := line.Function.Filename; fname != \"\" {\n\t\tni.File = filepath.Clean(fname)\n\t}\n\tif o.OrigFnNames {\n\t\tni.OrigName = line.Function.SystemName\n\t}\n\tif o.ObjNames || (ni.Name == \"\" && ni.OrigName == \"\") {\n\t\tni.Objfile = objfile\n\t\tni.StartLine = int(line.Function.StartLine)\n\t}\n\treturn ni\n}\n\ntype tags struct {\n\tt    []*Tag\n\tflat bool\n}\n\nfunc (t tags) Len() int      { return len(t.t) }\nfunc (t tags) Swap(i, j int) { t.t[i], t.t[j] = t.t[j], t.t[i] }\nfunc (t tags) Less(i, j int) bool {\n\tif !t.flat {\n\t\tif t.t[i].Cum != t.t[j].Cum {\n\t\t\treturn abs64(t.t[i].Cum) > abs64(t.t[j].Cum)\n\t\t}\n\t}\n\tif t.t[i].Flat != t.t[j].Flat {\n\t\treturn abs64(t.t[i].Flat) > abs64(t.t[j].Flat)\n\t}\n\treturn t.t[i].Name < t.t[j].Name\n}\n\n// Sum adds the flat and cum values of a set of nodes.\nfunc (ns Nodes) Sum() (flat int64, cum int64) {\n\tfor _, n := range ns {\n\t\tflat += n.Flat\n\t\tcum += n.Cum\n\t}\n\treturn\n}\n\nfunc (n *Node) addSample(dw, w int64, labels string, numLabel map[string][]int64, numUnit map[string][]string, format func(int64, string) string, flat bool) {\n\t// Update sample value\n\tif flat {\n\t\tn.FlatDiv += dw\n\t\tn.Flat += w\n\t} else {\n\t\tn.CumDiv += dw\n\t\tn.Cum += w\n\t}\n\n\t// Add string tags\n\tif labels != \"\" {\n\t\tt := n.LabelTags.findOrAddTag(labels, \"\", 0)\n\t\tif flat {\n\t\t\tt.FlatDiv += dw\n\t\t\tt.Flat += w\n\t\t} else {\n\t\t\tt.CumDiv += dw\n\t\t\tt.Cum += w\n\t\t}\n\t}\n\n\tnumericTags := n.NumericTags[labels]\n\tif numericTags == nil {\n\t\tnumericTags = TagMap{}\n\t\tn.NumericTags[labels] = numericTags\n\t}\n\t// Add numeric tags\n\tif format == nil {\n\t\tformat = defaultLabelFormat\n\t}\n\tfor k, nvals := range numLabel {\n\t\tunits := numUnit[k]\n\t\tfor i, v := range nvals {\n\t\t\tvar t *Tag\n\t\t\tif len(units) > 0 {\n\t\t\t\tt = numericTags.findOrAddTag(format(v, units[i]), units[i], v)\n\t\t\t} else {\n\t\t\t\tt = numericTags.findOrAddTag(format(v, k), k, v)\n\t\t\t}\n\t\t\tif flat {\n\t\t\t\tt.FlatDiv += dw\n\t\t\t\tt.Flat += w\n\t\t\t} else {\n\t\t\t\tt.CumDiv += dw\n\t\t\t\tt.Cum += w\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc defaultLabelFormat(v int64, key string) string {\n\treturn strconv.FormatInt(v, 10)\n}\n\nfunc (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {\n\tl := m[label]\n\tif l == nil {\n\t\tl = &Tag{\n\t\t\tName:  label,\n\t\t\tUnit:  unit,\n\t\t\tValue: value,\n\t\t}\n\t\tm[label] = l\n\t}\n\treturn l\n}\n\n// String returns a text representation of a graph, for debugging purposes.\nfunc (g *Graph) String() string {\n\tvar s []string\n\n\tnodeIndex := make(map[*Node]int, len(g.Nodes))\n\n\tfor i, n := range g.Nodes {\n\t\tnodeIndex[n] = i + 1\n\t}\n\n\tfor i, n := range g.Nodes {\n\t\tname := n.Info.PrintableName()\n\t\tvar in, out []int\n\n\t\tfor _, from := range n.In {\n\t\t\tin = append(in, nodeIndex[from.Src])\n\t\t}\n\t\tfor _, to := range n.Out {\n\t\t\tout = append(out, nodeIndex[to.Dest])\n\t\t}\n\t\ts = append(s, fmt.Sprintf(\"%d: %s[flat=%d cum=%d] %x -> %v \", i+1, name, n.Flat, n.Cum, in, out))\n\t}\n\treturn strings.Join(s, \"\\n\")\n}\n\n// DiscardLowFrequencyNodes returns a set of the nodes at or over a\n// specific cum value cutoff.\nfunc (g *Graph) DiscardLowFrequencyNodes(nodeCutoff int64) NodeSet {\n\treturn makeNodeSet(g.Nodes, nodeCutoff)\n}\n\n// DiscardLowFrequencyNodePtrs returns a NodePtrSet of nodes at or over a\n// specific cum value cutoff.\nfunc (g *Graph) DiscardLowFrequencyNodePtrs(nodeCutoff int64) NodePtrSet {\n\tcutNodes := getNodesAboveCumCutoff(g.Nodes, nodeCutoff)\n\tkept := make(NodePtrSet, len(cutNodes))\n\tfor _, n := range cutNodes {\n\t\tkept[n] = true\n\t}\n\treturn kept\n}\n\nfunc makeNodeSet(nodes Nodes, nodeCutoff int64) NodeSet {\n\tcutNodes := getNodesAboveCumCutoff(nodes, nodeCutoff)\n\tkept := make(NodeSet, len(cutNodes))\n\tfor _, n := range cutNodes {\n\t\tkept[n.Info] = true\n\t}\n\treturn kept\n}\n\n// getNodesAboveCumCutoff returns all the nodes which have a Cum value greater\n// than or equal to cutoff.\nfunc getNodesAboveCumCutoff(nodes Nodes, nodeCutoff int64) Nodes {\n\tcutoffNodes := make(Nodes, 0, len(nodes))\n\tfor _, n := range nodes {\n\t\tif abs64(n.Cum) < nodeCutoff {\n\t\t\tcontinue\n\t\t}\n\t\tcutoffNodes = append(cutoffNodes, n)\n\t}\n\treturn cutoffNodes\n}\n\n// TrimLowFrequencyTags removes tags that have less than\n// the specified weight.\nfunc (g *Graph) TrimLowFrequencyTags(tagCutoff int64) {\n\t// Remove nodes with value <= total*nodeFraction\n\tfor _, n := range g.Nodes {\n\t\tn.LabelTags = trimLowFreqTags(n.LabelTags, tagCutoff)\n\t\tfor s, nt := range n.NumericTags {\n\t\t\tn.NumericTags[s] = trimLowFreqTags(nt, tagCutoff)\n\t\t}\n\t}\n}\n\nfunc trimLowFreqTags(tags TagMap, minValue int64) TagMap {\n\tkept := TagMap{}\n\tfor s, t := range tags {\n\t\tif abs64(t.Flat) >= minValue || abs64(t.Cum) >= minValue {\n\t\t\tkept[s] = t\n\t\t}\n\t}\n\treturn kept\n}\n\n// TrimLowFrequencyEdges removes edges that have less than\n// the specified weight. Returns the number of edges removed\nfunc (g *Graph) TrimLowFrequencyEdges(edgeCutoff int64) int {\n\tvar droppedEdges int\n\tfor _, n := range g.Nodes {\n\t\tfor src, e := range n.In {\n\t\t\tif abs64(e.Weight) < edgeCutoff {\n\t\t\t\tdelete(n.In, src)\n\t\t\t\tdelete(src.Out, n)\n\t\t\t\tdroppedEdges++\n\t\t\t}\n\t\t}\n\t}\n\treturn droppedEdges\n}\n\n// SortNodes sorts the nodes in a graph based on a specific heuristic.\nfunc (g *Graph) SortNodes(cum bool, visualMode bool) {\n\t// Sort nodes based on requested mode\n\tswitch {\n\tcase visualMode:\n\t\t// Specialized sort to produce a more visually-interesting graph\n\t\tg.Nodes.Sort(EntropyOrder)\n\tcase cum:\n\t\tg.Nodes.Sort(CumNameOrder)\n\tdefault:\n\t\tg.Nodes.Sort(FlatNameOrder)\n\t}\n}\n\n// SelectTopNodePtrs returns a set of the top maxNodes *Node in a graph.\nfunc (g *Graph) SelectTopNodePtrs(maxNodes int, visualMode bool) NodePtrSet {\n\tset := make(NodePtrSet)\n\tfor _, node := range g.selectTopNodes(maxNodes, visualMode) {\n\t\tset[node] = true\n\t}\n\treturn set\n}\n\n// SelectTopNodes returns a set of the top maxNodes nodes in a graph.\nfunc (g *Graph) SelectTopNodes(maxNodes int, visualMode bool) NodeSet {\n\treturn makeNodeSet(g.selectTopNodes(maxNodes, visualMode), 0)\n}\n\n// selectTopNodes returns a slice of the top maxNodes nodes in a graph.\nfunc (g *Graph) selectTopNodes(maxNodes int, visualMode bool) Nodes {\n\tif maxNodes > 0 {\n\t\tif visualMode {\n\t\t\tvar count int\n\t\t\t// If generating a visual graph, count tags as nodes. Update\n\t\t\t// maxNodes to account for them.\n\t\t\tfor i, n := range g.Nodes {\n\t\t\t\ttags := min(countTags(n), maxNodelets)\n\t\t\t\tif count += tags + 1; count >= maxNodes {\n\t\t\t\t\tmaxNodes = i + 1\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif maxNodes > len(g.Nodes) {\n\t\tmaxNodes = len(g.Nodes)\n\t}\n\treturn g.Nodes[:maxNodes]\n}\n\n// countTags counts the tags with flat count. This underestimates the\n// number of tags being displayed, but in practice is close enough.\nfunc countTags(n *Node) int {\n\tcount := 0\n\tfor _, e := range n.LabelTags {\n\t\tif e.Flat != 0 {\n\t\t\tcount++\n\t\t}\n\t}\n\tfor _, t := range n.NumericTags {\n\t\tfor _, e := range t {\n\t\t\tif e.Flat != 0 {\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t}\n\treturn count\n}\n\n// RemoveRedundantEdges removes residual edges if the destination can\n// be reached through another path. This is done to simplify the graph\n// while preserving connectivity.\nfunc (g *Graph) RemoveRedundantEdges() {\n\t// Walk the nodes and outgoing edges in reverse order to prefer\n\t// removing edges with the lowest weight.\n\tfor i := len(g.Nodes); i > 0; i-- {\n\t\tn := g.Nodes[i-1]\n\t\tin := n.In.Sort()\n\t\tfor j := len(in); j > 0; j-- {\n\t\t\te := in[j-1]\n\t\t\tif !e.Residual {\n\t\t\t\t// Do not remove edges heavier than a non-residual edge, to\n\t\t\t\t// avoid potential confusion.\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif isRedundantEdge(e) {\n\t\t\t\tdelete(e.Src.Out, e.Dest)\n\t\t\t\tdelete(e.Dest.In, e.Src)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// isRedundantEdge determines if there is a path that allows e.Src\n// to reach e.Dest after removing e.\nfunc isRedundantEdge(e *Edge) bool {\n\tsrc, n := e.Src, e.Dest\n\tseen := map[*Node]bool{n: true}\n\tqueue := Nodes{n}\n\tfor len(queue) > 0 {\n\t\tn := queue[0]\n\t\tqueue = queue[1:]\n\t\tfor _, ie := range n.In {\n\t\t\tif e == ie || seen[ie.Src] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif ie.Src == src {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tseen[ie.Src] = true\n\t\t\tqueue = append(queue, ie.Src)\n\t\t}\n\t}\n\treturn false\n}\n\n// nodeSorter is a mechanism used to allow a report to be sorted\n// in different ways.\ntype nodeSorter struct {\n\trs   Nodes\n\tless func(l, r *Node) bool\n}\n\nfunc (s nodeSorter) Len() int           { return len(s.rs) }\nfunc (s nodeSorter) Swap(i, j int)      { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] }\nfunc (s nodeSorter) Less(i, j int) bool { return s.less(s.rs[i], s.rs[j]) }\n\n// Sort reorders a slice of nodes based on the specified ordering\n// criteria. The result is sorted in decreasing order for (absolute)\n// numeric quantities, alphabetically for text, and increasing for\n// addresses.\nfunc (ns Nodes) Sort(o NodeOrder) error {\n\tvar s nodeSorter\n\n\tswitch o {\n\tcase FlatNameOrder:\n\t\ts = nodeSorter{ns,\n\t\t\tfunc(l, r *Node) bool {\n\t\t\t\tif iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {\n\t\t\t\t\treturn iv > jv\n\t\t\t\t}\n\t\t\t\tequal, leftLess := l.Info.comparePrintableName(r.Info)\n\t\t\t\tif !equal {\n\t\t\t\t\treturn leftLess\n\t\t\t\t}\n\t\t\t\tif iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv {\n\t\t\t\t\treturn iv > jv\n\t\t\t\t}\n\t\t\t\treturn compareNodes(l, r)\n\t\t\t},\n\t\t}\n\tcase FlatCumNameOrder:\n\t\ts = nodeSorter{ns,\n\t\t\tfunc(l, r *Node) bool {\n\t\t\t\tif iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {\n\t\t\t\t\treturn iv > jv\n\t\t\t\t}\n\t\t\t\tif iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv {\n\t\t\t\t\treturn iv > jv\n\t\t\t\t}\n\t\t\t\tequal, leftLess := l.Info.comparePrintableName(r.Info)\n\t\t\t\tif !equal {\n\t\t\t\t\treturn leftLess\n\t\t\t\t}\n\t\t\t\treturn compareNodes(l, r)\n\t\t\t},\n\t\t}\n\tcase NameOrder:\n\t\ts = nodeSorter{ns,\n\t\t\tfunc(l, r *Node) bool {\n\t\t\t\tif iv, jv := l.Info.Name, r.Info.Name; iv != jv {\n\t\t\t\t\treturn iv < jv\n\t\t\t\t}\n\t\t\t\treturn compareNodes(l, r)\n\t\t\t},\n\t\t}\n\tcase FileOrder:\n\t\ts = nodeSorter{ns,\n\t\t\tfunc(l, r *Node) bool {\n\t\t\t\tif iv, jv := l.Info.File, r.Info.File; iv != jv {\n\t\t\t\t\treturn iv < jv\n\t\t\t\t}\n\t\t\t\tif iv, jv := l.Info.StartLine, r.Info.StartLine; iv != jv {\n\t\t\t\t\treturn iv < jv\n\t\t\t\t}\n\t\t\t\treturn compareNodes(l, r)\n\t\t\t},\n\t\t}\n\tcase AddressOrder:\n\t\ts = nodeSorter{ns,\n\t\t\tfunc(l, r *Node) bool {\n\t\t\t\tif iv, jv := l.Info.Address, r.Info.Address; iv != jv {\n\t\t\t\t\treturn iv < jv\n\t\t\t\t}\n\t\t\t\treturn compareNodes(l, r)\n\t\t\t},\n\t\t}\n\tcase CumNameOrder, EntropyOrder:\n\t\t// Hold scoring for score-based ordering\n\t\tvar score map[*Node]int64\n\t\tscoreOrder := func(l, r *Node) bool {\n\t\t\tif iv, jv := abs64(score[l]), abs64(score[r]); iv != jv {\n\t\t\t\treturn iv > jv\n\t\t\t}\n\t\t\tequal, leftLess := l.Info.comparePrintableName(r.Info)\n\t\t\tif !equal {\n\t\t\t\treturn leftLess\n\t\t\t}\n\t\t\tif iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {\n\t\t\t\treturn iv > jv\n\t\t\t}\n\t\t\treturn compareNodes(l, r)\n\t\t}\n\n\t\tswitch o {\n\t\tcase CumNameOrder:\n\t\t\tscore = make(map[*Node]int64, len(ns))\n\t\t\tfor _, n := range ns {\n\t\t\t\tscore[n] = n.Cum\n\t\t\t}\n\t\t\ts = nodeSorter{ns, scoreOrder}\n\t\tcase EntropyOrder:\n\t\t\tscore = make(map[*Node]int64, len(ns))\n\t\t\tfor _, n := range ns {\n\t\t\t\tscore[n] = entropyScore(n)\n\t\t\t}\n\t\t\ts = nodeSorter{ns, scoreOrder}\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"report: unrecognized sort ordering: %d\", o)\n\t}\n\tsort.Sort(s)\n\treturn nil\n}\n\n// compareNodes compares two nodes to provide a deterministic ordering\n// between them. Two nodes cannot have the same Node.Info value.\nfunc compareNodes(l, r *Node) bool {\n\treturn fmt.Sprint(l.Info) < fmt.Sprint(r.Info)\n}\n\n// entropyScore computes a score for a node representing how important\n// it is to include this node on a graph visualization. It is used to\n// sort the nodes and select which ones to display if we have more\n// nodes than desired in the graph. This number is computed by looking\n// at the flat and cum weights of the node and the incoming/outgoing\n// edges. The fundamental idea is to penalize nodes that have a simple\n// fallthrough from their incoming to the outgoing edge.\nfunc entropyScore(n *Node) int64 {\n\tscore := float64(0)\n\n\tif len(n.In) == 0 {\n\t\tscore++ // Favor entry nodes\n\t} else {\n\t\tscore += edgeEntropyScore(n, n.In, 0)\n\t}\n\n\tif len(n.Out) == 0 {\n\t\tscore++ // Favor leaf nodes\n\t} else {\n\t\tscore += edgeEntropyScore(n, n.Out, n.Flat)\n\t}\n\n\treturn int64(score*float64(n.Cum)) + n.Flat\n}\n\n// edgeEntropyScore computes the entropy value for a set of edges\n// coming in or out of a node. Entropy (as defined in information\n// theory) refers to the amount of information encoded by the set of\n// edges. A set of edges that have a more interesting distribution of\n// samples gets a higher score.\nfunc edgeEntropyScore(n *Node, edges EdgeMap, self int64) float64 {\n\tscore := float64(0)\n\ttotal := self\n\tfor _, e := range edges {\n\t\tif e.Weight > 0 {\n\t\t\ttotal += abs64(e.Weight)\n\t\t}\n\t}\n\tif total != 0 {\n\t\tfor _, e := range edges {\n\t\t\tfrac := float64(abs64(e.Weight)) / float64(total)\n\t\t\tscore += -frac * math.Log2(frac)\n\t\t}\n\t\tif self > 0 {\n\t\t\tfrac := float64(abs64(self)) / float64(total)\n\t\t\tscore += -frac * math.Log2(frac)\n\t\t}\n\t}\n\treturn score\n}\n\n// NodeOrder sets the ordering for a Sort operation\ntype NodeOrder int\n\n// Sorting options for node sort.\nconst (\n\tFlatNameOrder NodeOrder = iota\n\tFlatCumNameOrder\n\tCumNameOrder\n\tNameOrder\n\tFileOrder\n\tAddressOrder\n\tEntropyOrder\n)\n\n// Sort returns a slice of the edges in the map, in a consistent\n// order. The sort order is first based on the edge weight\n// (higher-to-lower) and then by the node names to avoid flakiness.\nfunc (e EdgeMap) Sort() []*Edge {\n\tel := make(edgeList, 0, len(e))\n\tfor _, w := range e {\n\t\tel = append(el, w)\n\t}\n\n\tsort.Sort(el)\n\treturn el\n}\n\n// Sum returns the total weight for a set of nodes.\nfunc (e EdgeMap) Sum() int64 {\n\tvar ret int64\n\tfor _, edge := range e {\n\t\tret += edge.Weight\n\t}\n\treturn ret\n}\n\ntype edgeList []*Edge\n\nfunc (el edgeList) Len() int {\n\treturn len(el)\n}\n\nfunc (el edgeList) Less(i, j int) bool {\n\tif el[i].Weight != el[j].Weight {\n\t\treturn abs64(el[i].Weight) > abs64(el[j].Weight)\n\t}\n\n\tfrom1 := el[i].Src.Info.PrintableName()\n\tfrom2 := el[j].Src.Info.PrintableName()\n\tif from1 != from2 {\n\t\treturn from1 < from2\n\t}\n\n\tto1 := el[i].Dest.Info.PrintableName()\n\tto2 := el[j].Dest.Info.PrintableName()\n\n\treturn to1 < to2\n}\n\nfunc (el edgeList) Swap(i, j int) {\n\tel[i], el[j] = el[j], el[i]\n}\n\nfunc abs64(i int64) int64 {\n\tif i < 0 {\n\t\treturn -i\n\t}\n\treturn i\n}\n"
  },
  {
    "path": "internal/graph/graph_test.go",
    "content": "//  Copyright 2016 Google Inc. All Rights Reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\npackage graph\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc edgeDebugString(edge *Edge) string {\n\tdebug := \"\"\n\tdebug += fmt.Sprintf(\"\\t\\tSrc: %p\\n\", edge.Src)\n\tdebug += fmt.Sprintf(\"\\t\\tDest: %p\\n\", edge.Dest)\n\tdebug += fmt.Sprintf(\"\\t\\tWeight: %d\\n\", edge.Weight)\n\tdebug += fmt.Sprintf(\"\\t\\tResidual: %t\\n\", edge.Residual)\n\tdebug += fmt.Sprintf(\"\\t\\tInline: %t\\n\", edge.Inline)\n\treturn debug\n}\n\nfunc edgeMapsDebugString(in, out EdgeMap) string {\n\tdebug := \"\"\n\tdebug += \"In Edges:\\n\"\n\tfor parent, edge := range in {\n\t\tdebug += fmt.Sprintf(\"\\tParent: %p\\n\", parent)\n\t\tdebug += edgeDebugString(edge)\n\t}\n\tdebug += \"Out Edges:\\n\"\n\tfor child, edge := range out {\n\t\tdebug += fmt.Sprintf(\"\\tChild: %p\\n\", child)\n\t\tdebug += edgeDebugString(edge)\n\t}\n\treturn debug\n}\n\nfunc graphDebugString(graph *Graph) string {\n\tdebug := \"\"\n\tfor i, node := range graph.Nodes {\n\t\tdebug += fmt.Sprintf(\"Node %d: %p\\n\", i, node)\n\t}\n\n\tfor i, node := range graph.Nodes {\n\t\tdebug += \"\\n\"\n\t\tdebug += fmt.Sprintf(\"===  Node %d: %p  ===\\n\", i, node)\n\t\tdebug += edgeMapsDebugString(node.In, node.Out)\n\t}\n\treturn debug\n}\n\nfunc expectedNodesDebugString(expected []expectedNode) string {\n\tdebug := \"\"\n\tfor i, node := range expected {\n\t\tdebug += fmt.Sprintf(\"Node %d: %p\\n\", i, node.node)\n\t}\n\n\tfor i, node := range expected {\n\t\tdebug += \"\\n\"\n\t\tdebug += fmt.Sprintf(\"===  Node %d: %p  ===\\n\", i, node.node)\n\t\tdebug += edgeMapsDebugString(node.in, node.out)\n\t}\n\treturn debug\n}\n\n// edgeMapsEqual checks if all the edges in this equal all the edges in that.\nfunc edgeMapsEqual(this, that EdgeMap) bool {\n\tif len(this) != len(that) {\n\t\treturn false\n\t}\n\tfor node, thisEdge := range this {\n\t\tif *thisEdge != *that[node] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// nodesEqual checks if node is equal to expected.\nfunc nodesEqual(node *Node, expected expectedNode) bool {\n\treturn node == expected.node && edgeMapsEqual(node.In, expected.in) &&\n\t\tedgeMapsEqual(node.Out, expected.out)\n}\n\n// graphsEqual checks if graph is equivalent to the graph templated by expected.\nfunc graphsEqual(graph *Graph, expected []expectedNode) bool {\n\tif len(graph.Nodes) != len(expected) {\n\t\treturn false\n\t}\n\texpectedSet := make(map[*Node]expectedNode)\n\tfor i := range expected {\n\t\texpectedSet[expected[i].node] = expected[i]\n\t}\n\n\tfor _, node := range graph.Nodes {\n\t\texpectedNode, found := expectedSet[node]\n\t\tif !found || !nodesEqual(node, expectedNode) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\ntype expectedNode struct {\n\tnode    *Node\n\tin, out EdgeMap\n}\n\ntype trimTreeTestcase struct {\n\tinitial  *Graph\n\texpected []expectedNode\n\tkeep     NodePtrSet\n}\n\n// makeExpectedEdgeResidual makes the edge from parent to child residual.\nfunc makeExpectedEdgeResidual(parent, child expectedNode) {\n\tparent.out[child.node].Residual = true\n\tchild.in[parent.node].Residual = true\n}\n\nfunc makeEdgeInline(edgeMap EdgeMap, node *Node) {\n\tedgeMap[node].Inline = true\n}\n\nfunc setEdgeWeight(edgeMap EdgeMap, node *Node, weight int64) {\n\tedgeMap[node].Weight = weight\n}\n\n// createEdges creates directed edges from the parent to each of the children.\nfunc createEdges(parent *Node, children ...*Node) {\n\tfor _, child := range children {\n\t\tedge := &Edge{\n\t\t\tSrc:  parent,\n\t\t\tDest: child,\n\t\t}\n\t\tparent.Out[child] = edge\n\t\tchild.In[parent] = edge\n\t}\n}\n\n// createEmptyNode creates a node without any edges.\nfunc createEmptyNode() *Node {\n\treturn &Node{\n\t\tIn:  make(EdgeMap),\n\t\tOut: make(EdgeMap),\n\t}\n}\n\n// createExpectedNodes creates a slice of expectedNodes from nodes.\nfunc createExpectedNodes(nodes ...*Node) ([]expectedNode, NodePtrSet) {\n\texpected := make([]expectedNode, len(nodes))\n\tkeep := make(NodePtrSet, len(nodes))\n\n\tfor i, node := range nodes {\n\t\texpected[i] = expectedNode{\n\t\t\tnode: node,\n\t\t\tin:   make(EdgeMap),\n\t\t\tout:  make(EdgeMap),\n\t\t}\n\t\tkeep[node] = true\n\t}\n\n\treturn expected, keep\n}\n\n// createExpectedEdges creates directed edges from the parent to each of the\n// children.\nfunc createExpectedEdges(parent expectedNode, children ...expectedNode) {\n\tfor _, child := range children {\n\t\tedge := &Edge{\n\t\t\tSrc:  parent.node,\n\t\t\tDest: child.node,\n\t\t}\n\t\tparent.out[child.node] = edge\n\t\tchild.in[parent.node] = edge\n\t}\n}\n\n// createTestCase1 creates a test case that initially looks like:\n//\n//\t    0\n//\t    |(5)\n//\t    1\n//\t(3)/ \\(4)\n//\t  2   3.\n//\n// After keeping 0, 2, and 3, it expects the graph:\n//\n//\t    0\n//\t(3)/ \\(4)\n//\t  2   3.\nfunc createTestCase1() trimTreeTestcase {\n\t// Create initial graph\n\tgraph := &Graph{make(Nodes, 4)}\n\tnodes := graph.Nodes\n\tfor i := range nodes {\n\t\tnodes[i] = createEmptyNode()\n\t}\n\tcreateEdges(nodes[0], nodes[1])\n\tcreateEdges(nodes[1], nodes[2], nodes[3])\n\tmakeEdgeInline(nodes[0].Out, nodes[1])\n\tmakeEdgeInline(nodes[1].Out, nodes[2])\n\tsetEdgeWeight(nodes[0].Out, nodes[1], 5)\n\tsetEdgeWeight(nodes[1].Out, nodes[2], 3)\n\tsetEdgeWeight(nodes[1].Out, nodes[3], 4)\n\n\t// Create expected graph\n\texpected, keep := createExpectedNodes(nodes[0], nodes[2], nodes[3])\n\tcreateExpectedEdges(expected[0], expected[1], expected[2])\n\tmakeEdgeInline(expected[0].out, expected[1].node)\n\tmakeExpectedEdgeResidual(expected[0], expected[1])\n\tmakeExpectedEdgeResidual(expected[0], expected[2])\n\tsetEdgeWeight(expected[0].out, expected[1].node, 3)\n\tsetEdgeWeight(expected[0].out, expected[2].node, 4)\n\treturn trimTreeTestcase{\n\t\tinitial:  graph,\n\t\texpected: expected,\n\t\tkeep:     keep,\n\t}\n}\n\n// createTestCase2 creates a test case that initially looks like:\n//\n//\t3\n//\t| (12)\n//\t1\n//\t| (8)\n//\t2\n//\t| (15)\n//\t0\n//\t| (10)\n//\t4.\n//\n// After keeping 3 and 4, it expects the graph:\n//\n//\t3\n//\t| (10)\n//\t4.\nfunc createTestCase2() trimTreeTestcase {\n\t// Create initial graph\n\tgraph := &Graph{make(Nodes, 5)}\n\tnodes := graph.Nodes\n\tfor i := range nodes {\n\t\tnodes[i] = createEmptyNode()\n\t}\n\tcreateEdges(nodes[3], nodes[1])\n\tcreateEdges(nodes[1], nodes[2])\n\tcreateEdges(nodes[2], nodes[0])\n\tcreateEdges(nodes[0], nodes[4])\n\tsetEdgeWeight(nodes[3].Out, nodes[1], 12)\n\tsetEdgeWeight(nodes[1].Out, nodes[2], 8)\n\tsetEdgeWeight(nodes[2].Out, nodes[0], 15)\n\tsetEdgeWeight(nodes[0].Out, nodes[4], 10)\n\n\t// Create expected graph\n\texpected, keep := createExpectedNodes(nodes[3], nodes[4])\n\tcreateExpectedEdges(expected[0], expected[1])\n\tmakeExpectedEdgeResidual(expected[0], expected[1])\n\tsetEdgeWeight(expected[0].out, expected[1].node, 10)\n\treturn trimTreeTestcase{\n\t\tinitial:  graph,\n\t\texpected: expected,\n\t\tkeep:     keep,\n\t}\n}\n\n// createTestCase3 creates an initially empty graph and expects an empty graph\n// after trimming.\nfunc createTestCase3() trimTreeTestcase {\n\tgraph := &Graph{make(Nodes, 0)}\n\texpected, keep := createExpectedNodes()\n\treturn trimTreeTestcase{\n\t\tinitial:  graph,\n\t\texpected: expected,\n\t\tkeep:     keep,\n\t}\n}\n\n// createTestCase4 creates a test case that initially looks like:\n//\n//\t0.\n//\n// After keeping 0, it expects the graph:\n//\n//\t0.\nfunc createTestCase4() trimTreeTestcase {\n\tgraph := &Graph{make(Nodes, 1)}\n\tnodes := graph.Nodes\n\tfor i := range nodes {\n\t\tnodes[i] = createEmptyNode()\n\t}\n\texpected, keep := createExpectedNodes(nodes[0])\n\treturn trimTreeTestcase{\n\t\tinitial:  graph,\n\t\texpected: expected,\n\t\tkeep:     keep,\n\t}\n}\n\nfunc createTrimTreeTestCases() []trimTreeTestcase {\n\tcaseGenerators := []func() trimTreeTestcase{\n\t\tcreateTestCase1,\n\t\tcreateTestCase2,\n\t\tcreateTestCase3,\n\t\tcreateTestCase4,\n\t}\n\tcases := make([]trimTreeTestcase, len(caseGenerators))\n\tfor i, gen := range caseGenerators {\n\t\tcases[i] = gen()\n\t}\n\treturn cases\n}\n\nfunc TestTrimTree(t *testing.T) {\n\ttests := createTrimTreeTestCases()\n\tfor _, test := range tests {\n\t\tgraph := test.initial\n\t\tgraph.TrimTree(test.keep)\n\t\tif !graphsEqual(graph, test.expected) {\n\t\t\tt.Fatalf(\"Graphs do not match.\\nExpected: %s\\nFound: %s\\n\",\n\t\t\t\texpectedNodesDebugString(test.expected),\n\t\t\t\tgraphDebugString(graph))\n\t\t}\n\t}\n}\n\nfunc nodeTestProfile() *profile.Profile {\n\tmappings := []*profile.Mapping{\n\t\t{\n\t\t\tID:   1,\n\t\t\tFile: \"symbolized_binary\",\n\t\t},\n\t\t{\n\t\t\tID:   2,\n\t\t\tFile: \"unsymbolized_library_1\",\n\t\t},\n\t\t{\n\t\t\tID:   3,\n\t\t\tFile: \"unsymbolized_library_2\",\n\t\t},\n\t}\n\tfunctions := []*profile.Function{\n\t\t{ID: 1, Name: \"symname\"},\n\t\t{ID: 2},\n\t}\n\tlocations := []*profile.Location{\n\t\t{\n\t\t\tID:      1,\n\t\t\tMapping: mappings[0],\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: functions[0]},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      2,\n\t\t\tMapping: mappings[1],\n\t\t\tLine: []profile.Line{\n\t\t\t\t{Function: functions[1]},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:      3,\n\t\t\tMapping: mappings[2],\n\t\t},\n\t}\n\treturn &profile.Profile{\n\t\tPeriodType: &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"type\", Unit: \"unit\"},\n\t\t},\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locations[0]},\n\t\t\t\tValue:    []int64{1},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locations[1]},\n\t\t\t\tValue:    []int64{1},\n\t\t\t},\n\t\t\t{\n\t\t\t\tLocation: []*profile.Location{locations[2]},\n\t\t\t\tValue:    []int64{1},\n\t\t\t},\n\t\t},\n\t\tLocation: locations,\n\t\tFunction: functions,\n\t\tMapping:  mappings,\n\t}\n}\n\n// TestCreateNodes checks that nodes are properly created for a simple profile.\nfunc TestCreateNodes(t *testing.T) {\n\ttestProfile := nodeTestProfile()\n\twantNodeSet := NodeSet{\n\t\t{Name: \"symname\"}:                   true,\n\t\t{Objfile: \"unsymbolized_library_1\"}: true,\n\t\t{Objfile: \"unsymbolized_library_2\"}: true,\n\t}\n\n\tnodes, _ := CreateNodes(testProfile, &Options{})\n\tif len(nodes) != len(wantNodeSet) {\n\t\tt.Errorf(\"got %d nodes, want %d\", len(nodes), len(wantNodeSet))\n\t}\n\tfor _, node := range nodes {\n\t\tif !wantNodeSet[node.Info] {\n\t\t\tt.Errorf(\"unexpected node %v\", node.Info)\n\t\t}\n\t}\n}\n\nfunc TestShortenFunctionName(t *testing.T) {\n\ttype testCase struct {\n\t\tname string\n\t\twant string\n\t}\n\ttestcases := []testCase{\n\t\t{\n\t\t\t\"root\",\n\t\t\t\"root\",\n\t\t},\n\t\t{\n\t\t\t\"syscall.Syscall\",\n\t\t\t\"syscall.Syscall\",\n\t\t},\n\t\t{\n\t\t\t\"net/http.(*conn).serve\",\n\t\t\t\"http.(*conn).serve\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/blahBlah/foo.Foo\",\n\t\t\t\"foo.Foo\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/BlahBlah/foo.Foo\",\n\t\t\t\"foo.Foo\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/BlahBlah/foo.Foo[...]\",\n\t\t\t\"foo.Foo[...]\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/blah-blah/foo_bar.(*FooBar).Foo\",\n\t\t\t\"foo_bar.(*FooBar).Foo\",\n\t\t},\n\t\t{\n\t\t\t\"encoding/json.(*structEncoder).(encoding/json.encode)-fm\",\n\t\t\t\"json.(*structEncoder).(encoding/json.encode)-fm\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/blah/blah/vendor/gopkg.in/redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm\",\n\t\t\t\"redis.v3.(*baseClient).(github.com/blah/blah/vendor/gopkg.in/redis.v3.process)-fm\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foo/bar/v4.(*Foo).Bar\",\n\t\t\t\"bar.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foo/bar/v4/baz.Foo.Bar\",\n\t\t\t\"baz.Foo.Bar\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foo/bar/v123.(*Foo).Bar\",\n\t\t\t\"bar.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foobar/v0.(*Foo).Bar\",\n\t\t\t\"v0.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foobar/v1.(*Foo).Bar\",\n\t\t\t\"v1.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"example.org/v2xyz.Foo\",\n\t\t\t\"v2xyz.Foo\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foo/bar/v4/v4.(*Foo).Bar\",\n\t\t\t\"v4.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"github.com/foo/bar/v4/foo/bar/v4.(*Foo).Bar\",\n\t\t\t\"v4.(*Foo).Bar\",\n\t\t},\n\t\t{\n\t\t\t\"java.util.concurrent.ThreadPoolExecutor$Worker.run\",\n\t\t\t\"ThreadPoolExecutor$Worker.run\",\n\t\t},\n\t\t{\n\t\t\t\"java.bar.foo.FooBar.run(java.lang.Runnable)\",\n\t\t\t\"FooBar.run\",\n\t\t},\n\t\t{\n\t\t\t\"(anonymous namespace)::Bar::Foo\",\n\t\t\t\"Bar::Foo\",\n\t\t},\n\t\t{\n\t\t\t\"(anonymous namespace)::foo\",\n\t\t\t\"foo\",\n\t\t},\n\t\t{\n\t\t\t\"cpp::namespace::Class::method()::$_100::operator()\",\n\t\t\t\"Class::method\",\n\t\t},\n\t\t{\n\t\t\t\"foo_bar::Foo::bar\",\n\t\t\t\"Foo::bar\",\n\t\t},\n\t\t{\n\t\t\t\"cpp::namespace::Class::method<float, long, int>()\",\n\t\t\t\"Class::method<float, long, int>\",\n\t\t},\n\t\t{\n\t\t\t\"foo\",\n\t\t\t\"foo\",\n\t\t},\n\t\t{\n\t\t\t\"foo/xyz\",\n\t\t\t\"foo/xyz\",\n\t\t},\n\t\t{\n\t\t\t\"com.google.perftools.gwp.benchmark.FloatBench.lambda$run$0\",\n\t\t\t\"FloatBench.lambda$run$0\",\n\t\t},\n\t\t{\n\t\t\t\"java.bar.foo.FooBar.run$0\",\n\t\t\t\"FooBar.run$0\",\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tname := ShortenFunctionName(tc.name)\n\t\tif got, want := name, tc.want; got != want {\n\t\t\tt.Errorf(\"ShortenFunctionName(%q) = %q, want %q\", tc.name, got, want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose1.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"src\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN2 [label=\"dest\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src -> dest (10)\" labeltooltip=\"src -> dest (10)\"]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose2.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"SRC10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=24 shape=folder tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\" style=\"bold,filled\" peripheries=2 URL=\"www.google.com\" target=\"_blank\"]\nN2 [label=\"dest\\n0 of 25 (25.00%)\" id=\"node2\" fontsize=8 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src -> dest (10)\" labeltooltip=\"src -> dest (10)\"]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose3.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"src\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1_0 [label = \"tag1\" id=\"N1_0\" fontsize=8 shape=box3d tooltip=\"10\"]\nN1 -> N1_0 [label=\" 10\" weight=100 tooltip=\"10\" labeltooltip=\"10\"]\nNN1_0 [label = \"tag2\" id=\"NN1_0\" fontsize=8 shape=box3d tooltip=\"20\"]\nN1 -> NN1_0 [label=\" 20\" weight=100 tooltip=\"20\" labeltooltip=\"20\"]\nN2 [label=\"dest\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src ... dest (10)\" labeltooltip=\"src ... dest (10)\" style=\"dotted\" minlen=2]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose4.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose5.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"src\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1_0 [label = \"tag1\" id=\"N1_0\" fontsize=8 shape=box3d tooltip=\"10\"]\nN1 -> N1_0 [label=\" 10\" weight=100 tooltip=\"10\" labeltooltip=\"10\"]\nNN1_0_0 [label = \"tag2\" id=\"NN1_0_0\" fontsize=8 shape=box3d tooltip=\"20\"]\nN1_0 -> NN1_0_0 [label=\" 20\" weight=100 tooltip=\"20\" labeltooltip=\"20\"]\nN2 [label=\"dest\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src -> dest (10)\" labeltooltip=\"src -> dest (10)\" minlen=2]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose6.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" URL=\"http://example.com\" target=\"_blank\" tooltip=\"testtitle\"] }\nN1 [label=\"src\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN2 [label=\"dest\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src -> dest (10)\" labeltooltip=\"src -> dest (10)\"]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose7.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"label1\" [shape=box fontsize=16 label=\"label1\\llabel2\\llabel3: \\\"foo\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"var\\\"src\\\"\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"var\\\"src\\\" (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN2 [label=\"var\\\"#dest#\\\"\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"var\\\"#dest#\\\" (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"var\\\"src\\\" -> var\\\"#dest#\\\" (10)\" labeltooltip=\"var\\\"src\\\" -> var\\\"#dest#\\\" (10)\"]\n}\n"
  },
  {
    "path": "internal/graph/testdata/compose9.dot",
    "content": "digraph \"testtitle\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph cluster_L { \"comment line 1\\lcomment line 2 \\\"unterminated double quote\" [shape=box fontsize=16 label=\"comment line 1\\lcomment line 2 \\\"unterminated double quote\\lsecond comment \\\"double quote\\\"\\l\" tooltip=\"testtitle\"] }\nN1 [label=\"src\\n10 (10.00%)\\nof 25 (25.00%)\" id=\"node1\" fontsize=22 shape=box tooltip=\"src (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN2 [label=\"dest\\n15 (15.00%)\\nof 25 (25.00%)\" id=\"node2\" fontsize=24 shape=box tooltip=\"dest (25)\" color=\"#b23c00\" fillcolor=\"#edddd5\"]\nN1 -> N2 [label=\" 10\" weight=11 color=\"#b28559\" tooltip=\"src -> dest (10)\" labeltooltip=\"src -> dest (10)\"]\n}\n"
  },
  {
    "path": "internal/measurement/measurement.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package measurement export utility functions to manipulate/format performance profile sample values.\npackage measurement\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\n// ScaleProfiles updates the units in a set of profiles to make them\n// compatible. It scales the profiles to the smallest unit to preserve\n// data.\nfunc ScaleProfiles(profiles []*profile.Profile) error {\n\tif len(profiles) == 0 {\n\t\treturn nil\n\t}\n\tperiodTypes := make([]*profile.ValueType, 0, len(profiles))\n\tfor _, p := range profiles {\n\t\tif p.PeriodType != nil {\n\t\t\tperiodTypes = append(periodTypes, p.PeriodType)\n\t\t}\n\t}\n\tperiodType, err := CommonValueType(periodTypes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"period type: %v\", err)\n\t}\n\n\t// Identify common sample types\n\tnumSampleTypes := len(profiles[0].SampleType)\n\tfor _, p := range profiles[1:] {\n\t\tif numSampleTypes != len(p.SampleType) {\n\t\t\treturn fmt.Errorf(\"inconsistent samples type count: %d != %d\", numSampleTypes, len(p.SampleType))\n\t\t}\n\t}\n\tsampleType := make([]*profile.ValueType, numSampleTypes)\n\tfor i := 0; i < numSampleTypes; i++ {\n\t\tsampleTypes := make([]*profile.ValueType, len(profiles))\n\t\tfor j, p := range profiles {\n\t\t\tsampleTypes[j] = p.SampleType[i]\n\t\t}\n\t\tsampleType[i], err = CommonValueType(sampleTypes)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"sample types: %v\", err)\n\t\t}\n\t}\n\n\tfor _, p := range profiles {\n\t\tif p.PeriodType != nil && periodType != nil {\n\t\t\tperiod, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)\n\t\t\tp.Period, p.PeriodType.Unit = int64(period), periodType.Unit\n\t\t}\n\t\tratios := make([]float64, len(p.SampleType))\n\t\tfor i, st := range p.SampleType {\n\t\t\tif sampleType[i] == nil {\n\t\t\t\tratios[i] = 1\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)\n\t\t\tp.SampleType[i].Unit = sampleType[i].Unit\n\t\t}\n\t\tif err := p.ScaleN(ratios); err != nil {\n\t\t\treturn fmt.Errorf(\"scale: %v\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// CommonValueType returns the finest type from a set of compatible\n// types.\nfunc CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {\n\tif len(ts) <= 1 {\n\t\treturn nil, nil\n\t}\n\tminType := ts[0]\n\tfor _, t := range ts[1:] {\n\t\tif !compatibleValueTypes(minType, t) {\n\t\t\treturn nil, fmt.Errorf(\"incompatible types: %v %v\", *minType, *t)\n\t\t}\n\t\tif ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {\n\t\t\tminType = t\n\t\t}\n\t}\n\trcopy := *minType\n\treturn &rcopy, nil\n}\n\nfunc compatibleValueTypes(v1, v2 *profile.ValueType) bool {\n\tif v1 == nil || v2 == nil {\n\t\treturn true // No grounds to disqualify.\n\t}\n\t// Remove trailing 's' to permit minor mismatches.\n\tif t1, t2 := strings.TrimSuffix(v1.Type, \"s\"), strings.TrimSuffix(v2.Type, \"s\"); t1 != t2 {\n\t\treturn false\n\t}\n\n\tif v1.Unit == v2.Unit {\n\t\treturn true\n\t}\n\tfor _, ut := range UnitTypes {\n\t\tif ut.sniffUnit(v1.Unit) != nil && ut.sniffUnit(v2.Unit) != nil {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Scale a measurement from a unit to a different unit and returns\n// the scaled value and the target unit. The returned target unit\n// will be empty if uninteresting (could be skipped).\nfunc Scale(value int64, fromUnit, toUnit string) (float64, string) {\n\t// Avoid infinite recursion on overflow.\n\tif value < 0 && -value > 0 {\n\t\tv, u := Scale(-value, fromUnit, toUnit)\n\t\treturn -v, u\n\t}\n\tfor _, ut := range UnitTypes {\n\t\tif v, u, ok := ut.convertUnit(value, fromUnit, toUnit); ok {\n\t\t\treturn v, u\n\t\t}\n\t}\n\t// Skip non-interesting units.\n\tswitch toUnit {\n\tcase \"count\", \"sample\", \"unit\", \"minimum\", \"auto\":\n\t\treturn float64(value), \"\"\n\tdefault:\n\t\treturn float64(value), toUnit\n\t}\n}\n\n// Label returns the label used to describe a certain measurement.\nfunc Label(value int64, unit string) string {\n\treturn ScaledLabel(value, unit, \"auto\")\n}\n\n// ScaledLabel scales the passed-in measurement (if necessary) and\n// returns the label used to describe a float measurement.\nfunc ScaledLabel(value int64, fromUnit, toUnit string) string {\n\tv, u := Scale(value, fromUnit, toUnit)\n\tsv := strings.TrimSuffix(fmt.Sprintf(\"%.2f\", v), \".00\")\n\tif sv == \"0\" || sv == \"-0\" {\n\t\treturn \"0\"\n\t}\n\treturn sv + u\n}\n\n// Percentage computes the percentage of total of a value, and encodes\n// it as a string. At least two digits of precision are printed.\nfunc Percentage(value, total int64) string {\n\tvar ratio float64\n\tif total != 0 {\n\t\tratio = math.Abs(float64(value)/float64(total)) * 100\n\t}\n\tswitch {\n\tcase math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:\n\t\treturn \"  100%\"\n\tcase math.Abs(ratio) >= 1.0:\n\t\treturn fmt.Sprintf(\"%5.2f%%\", ratio)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%5.2g%%\", ratio)\n\t}\n}\n\n// Unit includes a list of aliases representing a specific unit and a factor\n// which one can multiple a value in the specified unit by to get the value\n// in terms of the base unit.\ntype Unit struct {\n\tCanonicalName string\n\taliases       []string\n\tFactor        float64\n}\n\n// UnitType includes a list of units that are within the same category (i.e.\n// memory or time units) and a default unit to use for this type of unit.\ntype UnitType struct {\n\tDefaultUnit Unit\n\tUnits       []Unit\n}\n\n// findByAlias returns the unit associated with the specified alias. It returns\n// nil if the unit with such alias is not found.\nfunc (ut UnitType) findByAlias(alias string) *Unit {\n\tfor _, u := range ut.Units {\n\t\tif slices.Contains(u.aliases, alias) {\n\t\t\treturn &u\n\t\t}\n\t}\n\treturn nil\n}\n\n// sniffUnit simplifies the input alias and returns the unit associated with the\n// specified alias. It returns nil if the unit with such alias is not found.\nfunc (ut UnitType) sniffUnit(unit string) *Unit {\n\tunit = strings.ToLower(unit)\n\tif len(unit) > 2 {\n\t\tunit = strings.TrimSuffix(unit, \"s\")\n\t}\n\treturn ut.findByAlias(unit)\n}\n\n// autoScale takes in the value with units of the base unit and returns\n// that value scaled to a reasonable unit if a reasonable unit is\n// found.\nfunc (ut UnitType) autoScale(value float64) (float64, string, bool) {\n\tvar f float64\n\tvar unit string\n\tfor _, u := range ut.Units {\n\t\tif u.Factor >= f && (value/u.Factor) >= 1.0 {\n\t\t\tf = u.Factor\n\t\t\tunit = u.CanonicalName\n\t\t}\n\t}\n\tif f == 0 {\n\t\treturn 0, \"\", false\n\t}\n\treturn value / f, unit, true\n}\n\n// convertUnit converts a value from the fromUnit to the toUnit, autoscaling\n// the value if the toUnit is \"minimum\" or \"auto\". If the fromUnit is not\n// included in the unitType, then a false boolean will be returned. If the\n// toUnit is not in the unitType, the value will be returned in terms of the\n// default unitType.\nfunc (ut UnitType) convertUnit(value int64, fromUnitStr, toUnitStr string) (float64, string, bool) {\n\tfromUnit := ut.sniffUnit(fromUnitStr)\n\tif fromUnit == nil {\n\t\treturn 0, \"\", false\n\t}\n\tv := float64(value) * fromUnit.Factor\n\tif toUnitStr == \"minimum\" || toUnitStr == \"auto\" {\n\t\tif v, u, ok := ut.autoScale(v); ok {\n\t\t\treturn v, u, true\n\t\t}\n\t\treturn v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true\n\t}\n\ttoUnit := ut.sniffUnit(toUnitStr)\n\tif toUnit == nil {\n\t\treturn v / ut.DefaultUnit.Factor, ut.DefaultUnit.CanonicalName, true\n\t}\n\treturn v / toUnit.Factor, toUnit.CanonicalName, true\n}\n\n// UnitTypes holds the definition of units known to pprof.\nvar UnitTypes = []UnitType{{\n\tUnits: []Unit{\n\t\t{\"B\", []string{\"b\", \"byte\"}, 1},\n\t\t{\"kB\", []string{\"kb\", \"kbyte\", \"kilobyte\"}, float64(1 << 10)},\n\t\t{\"MB\", []string{\"mb\", \"mbyte\", \"megabyte\"}, float64(1 << 20)},\n\t\t{\"GB\", []string{\"gb\", \"gbyte\", \"gigabyte\"}, float64(1 << 30)},\n\t\t{\"TB\", []string{\"tb\", \"tbyte\", \"terabyte\"}, float64(1 << 40)},\n\t\t{\"PB\", []string{\"pb\", \"pbyte\", \"petabyte\"}, float64(1 << 50)},\n\t},\n\tDefaultUnit: Unit{\"B\", []string{\"b\", \"byte\"}, 1},\n}, {\n\tUnits: []Unit{\n\t\t{\"ns\", []string{\"ns\", \"nanosecond\"}, float64(time.Nanosecond)},\n\t\t{\"us\", []string{\"μs\", \"us\", \"microsecond\"}, float64(time.Microsecond)},\n\t\t{\"ms\", []string{\"ms\", \"millisecond\"}, float64(time.Millisecond)},\n\t\t{\"s\", []string{\"s\", \"sec\", \"second\"}, float64(time.Second)},\n\t\t{\"hrs\", []string{\"hour\", \"hr\"}, float64(time.Hour)},\n\t},\n\tDefaultUnit: Unit{\"s\", []string{}, float64(time.Second)},\n}, {\n\tUnits: []Unit{\n\t\t{\"n*GCU\", []string{\"nanogcu\"}, 1e-9},\n\t\t{\"u*GCU\", []string{\"microgcu\"}, 1e-6},\n\t\t{\"m*GCU\", []string{\"milligcu\"}, 1e-3},\n\t\t{\"GCU\", []string{\"gcu\"}, 1},\n\t\t{\"k*GCU\", []string{\"kilogcu\"}, 1e3},\n\t\t{\"M*GCU\", []string{\"megagcu\"}, 1e6},\n\t\t{\"G*GCU\", []string{\"gigagcu\"}, 1e9},\n\t\t{\"T*GCU\", []string{\"teragcu\"}, 1e12},\n\t\t{\"P*GCU\", []string{\"petagcu\"}, 1e15},\n\t},\n\tDefaultUnit: Unit{\"GCU\", []string{}, 1.0},\n}}\n"
  },
  {
    "path": "internal/measurement/measurement_test.go",
    "content": "// Copyright 2017 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage measurement\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc TestScale(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tvalue            int64\n\t\tfromUnit, toUnit string\n\t\twantValue        float64\n\t\twantUnit         string\n\t}{\n\t\t{1, \"s\", \"ms\", 1000, \"ms\"},\n\t\t{1, \"kb\", \"b\", 1024, \"B\"},\n\t\t{1, \"kbyte\", \"b\", 1024, \"B\"},\n\t\t{1, \"kilobyte\", \"b\", 1024, \"B\"},\n\t\t{1, \"mb\", \"kb\", 1024, \"kB\"},\n\t\t{1, \"gb\", \"mb\", 1024, \"MB\"},\n\t\t{1024, \"gb\", \"tb\", 1, \"TB\"},\n\t\t{1024, \"tb\", \"pb\", 1, \"PB\"},\n\t\t{2048, \"mb\", \"auto\", 2, \"GB\"},\n\t\t{3.1536e7, \"s\", \"auto\", 8760, \"hrs\"},\n\t\t{-1, \"s\", \"ms\", -1000, \"ms\"},\n\t\t{1, \"foo\", \"count\", 1, \"\"},\n\t\t{1, \"foo\", \"bar\", 1, \"bar\"},\n\t\t{2000, \"count\", \"count\", 2000, \"\"},\n\t\t{2000, \"count\", \"auto\", 2000, \"\"},\n\t\t{2000, \"count\", \"minimum\", 2000, \"\"},\n\t\t{8e10, \"nanogcu\", \"petagcus\", 8e-14, \"P*GCU\"},\n\t\t{1.5e10, \"microGCU\", \"teraGCU\", 1.5e-8, \"T*GCU\"},\n\t\t{3e6, \"milliGCU\", \"gigagcu\", 3e-6, \"G*GCU\"},\n\t\t{1000, \"kilogcu\", \"megagcu\", 1, \"M*GCU\"},\n\t\t{2000, \"GCU\", \"kiloGCU\", 2, \"k*GCU\"},\n\t\t{7, \"megaGCU\", \"gcu\", 7e6, \"GCU\"},\n\t\t{5, \"gigagcus\", \"milligcu\", 5e12, \"m*GCU\"},\n\t\t{7, \"teragcus\", \"microGCU\", 7e18, \"u*GCU\"},\n\t\t{1, \"petaGCU\", \"nanogcus\", 1e24, \"n*GCU\"},\n\t\t{100, \"NanoGCU\", \"auto\", 100, \"n*GCU\"},\n\t\t{5000, \"nanogcu\", \"auto\", 5, \"u*GCU\"},\n\t\t{3000, \"MicroGCU\", \"auto\", 3, \"m*GCU\"},\n\t\t{4000, \"MilliGCU\", \"auto\", 4, \"GCU\"},\n\t\t{4000, \"GCU\", \"auto\", 4, \"k*GCU\"},\n\t\t{5000, \"KiloGCU\", \"auto\", 5, \"M*GCU\"},\n\t\t{6000, \"MegaGCU\", \"auto\", 6, \"G*GCU\"},\n\t\t{7000, \"GigaGCU\", \"auto\", 7, \"T*GCU\"},\n\t\t{8000, \"TeraGCU\", \"auto\", 8, \"P*GCU\"},\n\t\t{9000, \"PetaGCU\", \"auto\", 9000, \"P*GCU\"},\n\t} {\n\t\tif gotValue, gotUnit := Scale(tc.value, tc.fromUnit, tc.toUnit); !floatEqual(gotValue, tc.wantValue) || gotUnit != tc.wantUnit {\n\t\t\tt.Errorf(\"Scale(%d, %q, %q) = (%g, %q), want (%g, %q)\",\n\t\t\t\ttc.value, tc.fromUnit, tc.toUnit, gotValue, gotUnit, tc.wantValue, tc.wantUnit)\n\t\t}\n\t}\n}\n\nfunc floatEqual(a, b float64) bool {\n\tdiff := math.Abs(a - b)\n\tavg := (math.Abs(a) + math.Abs(b)) / 2\n\treturn diff/avg < 0.0001\n}\n"
  },
  {
    "path": "internal/plugin/plugin.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package plugin defines the plugin implementations that the main pprof driver requires.\npackage plugin\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\n// Options groups all the optional plugins into pprof.\ntype Options struct {\n\tWriter  Writer\n\tFlagset FlagSet\n\tFetch   Fetcher\n\tSym     Symbolizer\n\tObj     ObjTool\n\tUI      UI\n\n\t// HTTPServer is a function that should block serving http requests,\n\t// including the handlers specified in args.  If non-nil, pprof will\n\t// invoke this function if necessary to provide a web interface.\n\t//\n\t// If HTTPServer is nil, pprof will use its own internal HTTP server.\n\t//\n\t// A common use for a custom HTTPServer is to provide custom\n\t// authentication checks.\n\tHTTPServer    func(args *HTTPServerArgs) error\n\tHTTPTransport http.RoundTripper\n}\n\n// Writer provides a mechanism to write data under a certain name,\n// typically a filename.\ntype Writer interface {\n\tOpen(name string) (io.WriteCloser, error)\n}\n\n// A FlagSet creates and parses command-line flags.\n// It is similar to the standard flag.FlagSet.\ntype FlagSet interface {\n\t// Bool, Int, Float64, and String define new flags,\n\t// like the functions of the same name in package flag.\n\tBool(name string, def bool, usage string) *bool\n\tInt(name string, def int, usage string) *int\n\tFloat64(name string, def float64, usage string) *float64\n\tString(name string, def string, usage string) *string\n\n\t// StringList is similar to String but allows multiple values for a\n\t// single flag\n\tStringList(name string, def string, usage string) *[]*string\n\n\t// ExtraUsage returns any additional text that should be printed after the\n\t// standard usage message. The extra usage message returned includes all text\n\t// added with AddExtraUsage().\n\t// The typical use of ExtraUsage is to show any custom flags defined by the\n\t// specific pprof plugins being used.\n\tExtraUsage() string\n\n\t// AddExtraUsage appends additional text to the end of the extra usage message.\n\tAddExtraUsage(eu string)\n\n\t// Parse initializes the flags with their values for this run\n\t// and returns the non-flag command line arguments.\n\t// If an unknown flag is encountered or there are no arguments,\n\t// Parse should call usage and return nil.\n\tParse(usage func()) []string\n}\n\n// A Fetcher reads and returns the profile named by src. src can be a\n// local file path or a URL. duration and timeout are units specified\n// by the end user, or 0 by default. duration refers to the length of\n// the profile collection, if applicable, and timeout is the amount of\n// time to wait for a profile before returning an error. Returns the\n// fetched profile, the URL of the actual source of the profile, or an\n// error.\ntype Fetcher interface {\n\tFetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error)\n}\n\n// A Symbolizer introduces symbol information into a profile.\ntype Symbolizer interface {\n\tSymbolize(mode string, srcs MappingSources, prof *profile.Profile) error\n}\n\n// MappingSources map each profile.Mapping to the source of the profile.\n// The key is either Mapping.File or Mapping.BuildId.\ntype MappingSources map[string][]struct {\n\tSource string // URL of the source the mapping was collected from\n\tStart  uint64 // delta applied to addresses from this source (to represent Merge adjustments)\n}\n\n// An ObjTool inspects shared libraries and executable files.\ntype ObjTool interface {\n\t// Open opens the named object file. If the object is a shared\n\t// library, start/limit/offset are the addresses where it is mapped\n\t// into memory in the address space being inspected. If the object\n\t// is a linux kernel, relocationSymbol is the name of the symbol\n\t// corresponding to the start address.\n\tOpen(file string, start, limit, offset uint64, relocationSymbol string) (ObjFile, error)\n\n\t// Disasm disassembles the named object file, starting at\n\t// the start address and stopping at (before) the end address.\n\tDisasm(file string, start, end uint64, intelSyntax bool) ([]Inst, error)\n}\n\n// An Inst is a single instruction in an assembly listing.\ntype Inst struct {\n\tAddr     uint64 // virtual address of instruction\n\tText     string // instruction text\n\tFunction string // function name\n\tFile     string // source file\n\tLine     int    // source line\n}\n\n// An ObjFile is a single object file: a shared library or executable.\ntype ObjFile interface {\n\t// Name returns the underlyinf file name, if available\n\tName() string\n\n\t// ObjAddr returns the objdump (linker) address corresponding to a runtime\n\t// address, and an error.\n\tObjAddr(addr uint64) (uint64, error)\n\n\t// BuildID returns the GNU build ID of the file, or an empty string.\n\tBuildID() string\n\n\t// SourceLine reports the source line information for a given\n\t// address in the file. Due to inlining, the source line information\n\t// is in general a list of positions representing a call stack,\n\t// with the leaf function first.\n\tSourceLine(addr uint64) ([]Frame, error)\n\n\t// Symbols returns a list of symbols in the object file.\n\t// If r is not nil, Symbols restricts the list to symbols\n\t// with names matching the regular expression.\n\t// If addr is not zero, Symbols restricts the list to symbols\n\t// containing that address.\n\tSymbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)\n\n\t// Close closes the file, releasing associated resources.\n\tClose() error\n}\n\n// A Frame describes a location in a single line in a source file.\ntype Frame struct {\n\tFunc      string // name of function\n\tFile      string // source file name\n\tLine      int    // line in file\n\tColumn    int    // column in line (if available)\n\tStartLine int    // start line of function (if available)\n}\n\n// A Sym describes a single symbol in an object file.\ntype Sym struct {\n\tName  []string // names of symbol (many if symbol was dedup'ed)\n\tFile  string   // object file containing symbol\n\tStart uint64   // start virtual address\n\tEnd   uint64   // virtual address of last byte in sym (Start+size-1)\n}\n\n// A UI manages user interactions.\ntype UI interface {\n\t// ReadLine returns a line of text (a command) read from the user.\n\t// prompt is printed before reading the command.\n\tReadLine(prompt string) (string, error)\n\n\t// Print shows a message to the user.\n\t// It formats the text as fmt.Print would and adds a final \\n if not already present.\n\t// For line-based UI, Print writes to standard error.\n\t// (Standard output is reserved for report data.)\n\tPrint(...interface{})\n\n\t// PrintErr shows an error message to the user.\n\t// It formats the text as fmt.Print would and adds a final \\n if not already present.\n\t// For line-based UI, PrintErr writes to standard error.\n\tPrintErr(...interface{})\n\n\t// IsTerminal returns whether the UI is known to be tied to an\n\t// interactive terminal (as opposed to being redirected to a file).\n\tIsTerminal() bool\n\n\t// WantBrowser indicates whether a browser should be opened with the -http option.\n\tWantBrowser() bool\n\n\t// SetAutoComplete instructs the UI to call complete(cmd) to obtain\n\t// the auto-completion of cmd, if the UI supports auto-completion at all.\n\tSetAutoComplete(complete func(string) string)\n}\n\n// HTTPServerArgs contains arguments needed by an HTTP server that\n// is exporting a pprof web interface.\ntype HTTPServerArgs struct {\n\t// Hostport contains the http server address (derived from flags).\n\tHostport string\n\n\tHost string // Host portion of Hostport\n\tPort int    // Port portion of Hostport\n\n\t// Handlers maps from URL paths to the handler to invoke to\n\t// serve that path.\n\tHandlers map[string]http.Handler\n}\n"
  },
  {
    "path": "internal/proftest/BUILD",
    "content": "# Description:\n#   Auto-imported from github.com/google/pprof/internal/proftest\n\nlicenses([\"notice\"])\n\npackage(\n    default_applicable_licenses = [\"//third_party/golang/pprof:license\"],\n    default_visibility = [\"//third_party/golang/pprof/internal:friends\"],\n)\n\ngo_library(\n    name = \"proftest\",\n    srcs = [\"proftest.go\"],\n    embedsrcs = [\"testdata/large.cpu\"],\n)\n"
  },
  {
    "path": "internal/proftest/proftest.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package proftest provides some utility routines to test other\n// packages related to profiles.\npackage proftest\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"testing\"\n\n\t_ \"embed\" // For embedding profiles needed by tests and benchmarks\n)\n\nvar flagLargeProfile = flag.String(\"large_profile\", \"\", \"The name of a file that contains a profile to use in benchmarks. If empty, a profile of a synthetic program is used.\")\n\n// Diff compares two byte arrays using the diff tool to highlight the\n// differences. It is meant for testing purposes to display the\n// differences between expected and actual output.\nfunc Diff(b1, b2 []byte) (data []byte, err error) {\n\tf1, err := os.CreateTemp(\"\", \"proto_test\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer os.Remove(f1.Name())\n\tdefer f1.Close()\n\n\tf2, err := os.CreateTemp(\"\", \"proto_test\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer os.Remove(f2.Name())\n\tdefer f2.Close()\n\n\tf1.Write(b1)\n\tf2.Write(b2)\n\n\tdata, err = exec.Command(\"diff\", \"-u\", f1.Name(), f2.Name()).CombinedOutput()\n\tif len(data) > 0 {\n\t\t// diff exits with a non-zero status when the files don't match.\n\t\t// Ignore that failure as long as we get output.\n\t\terr = nil\n\t}\n\tif err != nil {\n\t\tdata = fmt.Appendf(nil, \"diff failed: %v\\nb1: %q\\nb2: %q\\n\", err, b1, b2)\n\t\terr = nil\n\t}\n\treturn\n}\n\n// EncodeJSON encodes a value into a byte array. This is intended for\n// testing purposes.\nfunc EncodeJSON(x interface{}) []byte {\n\tdata, err := json.MarshalIndent(x, \"\", \"    \")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdata = append(data, '\\n')\n\treturn data\n}\n\n// TestUI implements the plugin.UI interface, triggering test failures\n// if more than Ignore errors not matching AllowRx are printed.\n// Also tracks the number of times the error matches AllowRx in\n// NumAllowRxMatches.\ntype TestUI struct {\n\tT                 testing.TB\n\tIgnore            int\n\tAllowRx           string\n\tNumAllowRxMatches int\n\tInput             []string\n\tindex             int\n}\n\n// ReadLine returns no input, as no input is expected during testing.\nfunc (ui *TestUI) ReadLine(_ string) (string, error) {\n\tif ui.index >= len(ui.Input) {\n\t\treturn \"\", io.EOF\n\t}\n\tinput := ui.Input[ui.index]\n\tui.index++\n\tif input == \"**error**\" {\n\t\treturn \"\", fmt.Errorf(\"error: %s\", input)\n\t}\n\treturn input, nil\n}\n\n// Print messages are discarded by the test UI.\nfunc (ui *TestUI) Print(args ...interface{}) {\n}\n\n// PrintErr messages may trigger an error failure. A fixed number of\n// error messages are permitted when appropriate.\nfunc (ui *TestUI) PrintErr(args ...interface{}) {\n\tif ui.AllowRx != \"\" {\n\t\tif matched, err := regexp.MatchString(ui.AllowRx, fmt.Sprint(args...)); matched || err != nil {\n\t\t\tif err != nil {\n\t\t\t\tui.T.Errorf(\"failed to match against regex %q: %v\", ui.AllowRx, err)\n\t\t\t}\n\t\t\tui.NumAllowRxMatches++\n\t\t\treturn\n\t\t}\n\t}\n\tif ui.Ignore > 0 {\n\t\tui.Ignore--\n\t\treturn\n\t}\n\t// Stringify arguments with fmt.Sprint() to match what default UI\n\t// implementation does. Without this Error() calls fmt.Sprintln() which\n\t// _always_ adds spaces between arguments, unlike fmt.Sprint() which only\n\t// adds them between arguments if neither is string.\n\tui.T.Error(\"unexpected error: \" + fmt.Sprint(args...))\n}\n\n// IsTerminal indicates if the UI is an interactive terminal.\nfunc (ui *TestUI) IsTerminal() bool {\n\treturn false\n}\n\n// WantBrowser indicates whether a browser should be opened with the -http option.\nfunc (ui *TestUI) WantBrowser() bool {\n\treturn false\n}\n\n// SetAutoComplete is not supported by the test UI.\nfunc (ui *TestUI) SetAutoComplete(_ func(string) string) {\n}\n\n// LargeProfile returns a large profile that may be useful in benchmarks.\n//\n// If the flag --large_profile is set, the contents of the file\n// named by the flag are returned. Otherwise an embedded profile (~1.2MB)\n// for a synthetic program is returned.\nfunc LargeProfile(tb testing.TB) []byte {\n\ttb.Helper()\n\tif f := *flagLargeProfile; f != \"\" {\n\t\t// Use custom profile.\n\t\tdata, err := os.ReadFile(f)\n\t\tif err != nil {\n\t\t\ttb.Fatalf(\"custom profile file: %v\\n\", err)\n\t\t}\n\t\treturn data\n\t}\n\n\treturn largeProfileData\n}\n\n//go:embed testdata/large.cpu\nvar largeProfileData []byte\n"
  },
  {
    "path": "internal/report/package.go",
    "content": "package report\n\nimport \"regexp\"\n\n// pkgRE extracts package name, It looks for the first \".\" or \"::\" that occurs\n// after the last \"/\". (Searching after the last / allows us to correctly handle\n// names that look like \"some.url.com/foo.bar\".)\nvar pkgRE = regexp.MustCompile(`^((.*/)?[\\w\\d_]+)(\\.|::)([^/]*)$`)\n\n// packageName returns the package name of the named symbol, or \"\" if not found.\nfunc packageName(name string) string {\n\tm := pkgRE.FindStringSubmatch(name)\n\tif m == nil {\n\t\treturn \"\"\n\t}\n\treturn m[1]\n}\n"
  },
  {
    "path": "internal/report/package_test.go",
    "content": "package report\n\nimport (\n\t\"testing\"\n)\n\nfunc TestPackageName(t *testing.T) {\n\ttype testCase struct {\n\t\tname   string\n\t\texpect string\n\t}\n\n\tfor _, c := range []testCase{\n\t\t// Unrecognized packages:\n\t\t{``, ``},\n\t\t{`name`, ``},\n\t\t{`[libjvm.so]`, ``},\n\t\t{`prefix/name/suffix`, ``},\n\t\t{`prefix(a.b.c,x.y.z)`, ``},\n\t\t{`<undefined>.a.b`, ``},\n\t\t{`(a.b)`, ``},\n\n\t\t// C++ symbols:\n\t\t{`Math.number`, `Math`},\n\t\t{`std::vector`, `std`},\n\t\t{`std::internal::vector`, `std`},\n\n\t\t// Java symbols:\n\t\t{`pkg.Class.name`, `pkg`},\n\t\t{`pkg.pkg.Class.name`, `pkg`},\n\t\t{`pkg.Class.name(a.b.c, x.y.z)`, `pkg`},\n\t\t{`pkg.pkg.Class.<init>`, `pkg`},\n\t\t{`pkg.pkg.Class.<init>(a.b.c, x.y.z)`, `pkg`},\n\n\t\t// Go symbols:\n\t\t{`pkg.name`, `pkg`},\n\t\t{`pkg.(*type).name`, `pkg`},\n\t\t{`path/pkg.name`, `path/pkg`},\n\t\t{`path/pkg.(*type).name`, `path/pkg`},\n\t\t{`path/path/pkg.name`, `path/path/pkg`},\n\t\t{`path/path/pkg.(*type).name`, `path/path/pkg`},\n\t\t{`some.url.com/path/pkg.fnID`, `some.url.com/path/pkg`},\n\t\t{`parent-dir/dir/google.golang.org/grpc/transport.NewFramer`, `parent-dir/dir/google.golang.org/grpc/transport`},\n\t\t{`parent-dir/dir/google.golang.org/grpc.(*Server).handleRawConn`, `parent-dir/dir/google.golang.org/grpc`},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif got := packageName(c.name); got != c.expect {\n\t\t\t\tt.Errorf(\"packageName(%q) = %#v, expecting %#v\", c.name, got, c.expect)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/report/report.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package report summarizes a performance profile into a\n// human-readable report.\npackage report\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/graph\"\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// Output formats.\nconst (\n\tCallgrind = iota\n\tComments\n\tDis\n\tDot\n\tList\n\tProto\n\tRaw\n\tTags\n\tText\n\tTopProto\n\tTraces\n\tTree\n\tWebList\n)\n\n// Options are the formatting and filtering options used to generate a\n// profile.\ntype Options struct {\n\tOutputFormat int\n\n\tCumSort       bool\n\tCallTree      bool\n\tDropNegative  bool\n\tCompactLabels bool\n\tRatio         float64\n\tTitle         string\n\tProfileLabels []string\n\tActiveFilters []string\n\tNumLabelUnits map[string]string\n\n\tNodeCount    int\n\tNodeFraction float64\n\tEdgeFraction float64\n\n\tSampleValue       func(s []int64) int64\n\tSampleMeanDivisor func(s []int64) int64\n\tSampleType        string\n\tSampleUnit        string // Unit for the sample data from the profile.\n\n\tOutputUnit string // Units for data formatting in report.\n\n\tSymbol     *regexp.Regexp // Symbols to include on disassembly report.\n\tSourcePath string         // Search path for source files.\n\tTrimPath   string         // Paths to trim from source file paths.\n\n\tIntelSyntax bool // Whether or not to print assembly in Intel syntax.\n}\n\n// Generate generates a report as directed by the Report.\nfunc Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {\n\to := rpt.options\n\n\tswitch o.OutputFormat {\n\tcase Comments:\n\t\treturn printComments(w, rpt)\n\tcase Dot:\n\t\treturn printDOT(w, rpt)\n\tcase Tree:\n\t\treturn printTree(w, rpt)\n\tcase Text:\n\t\treturn printText(w, rpt)\n\tcase Traces:\n\t\treturn printTraces(w, rpt)\n\tcase Raw:\n\t\tfmt.Fprint(w, rpt.prof.String())\n\t\treturn nil\n\tcase Tags:\n\t\treturn printTags(w, rpt)\n\tcase Proto:\n\t\treturn printProto(w, rpt)\n\tcase TopProto:\n\t\treturn printTopProto(w, rpt)\n\tcase Dis:\n\t\treturn printAssembly(w, rpt, obj)\n\tcase List:\n\t\treturn printSource(w, rpt)\n\tcase Callgrind:\n\t\treturn printCallgrind(w, rpt)\n\t}\n\t// Note: WebList handling is in driver package.\n\treturn fmt.Errorf(\"unexpected output format %v\", o.OutputFormat)\n}\n\n// newTrimmedGraph creates a graph for this report, trimmed according\n// to the report options.\nfunc (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, droppedEdges int) {\n\to := rpt.options\n\n\t// Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,\n\t// as the graph itself doesn't contain enough information to preserve full precision.\n\tvisualMode := o.OutputFormat == Dot\n\tcumSort := o.CumSort\n\n\t// The call_tree option is only honored when generating visual representations of the callgraph.\n\tcallTree := o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind)\n\n\t// First step: Build complete graph to identify low frequency nodes, based on their cum weight.\n\tg = rpt.newGraph(nil)\n\ttotalValue, _ := g.Nodes.Sum()\n\tnodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))\n\tedgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))\n\n\t// Filter out nodes with cum value below nodeCutoff.\n\tif nodeCutoff > 0 {\n\t\tif callTree {\n\t\t\tif nodesKept := g.DiscardLowFrequencyNodePtrs(nodeCutoff); len(g.Nodes) != len(nodesKept) {\n\t\t\t\tdroppedNodes = len(g.Nodes) - len(nodesKept)\n\t\t\t\tg.TrimTree(nodesKept)\n\t\t\t}\n\t\t} else {\n\t\t\tif nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {\n\t\t\t\tdroppedNodes = len(g.Nodes) - len(nodesKept)\n\t\t\t\tg = rpt.newGraph(nodesKept)\n\t\t\t}\n\t\t}\n\t}\n\torigCount = len(g.Nodes)\n\n\t// Second step: Limit the total number of nodes. Apply specialized heuristics to improve\n\t// visualization when generating dot output.\n\tg.SortNodes(cumSort, visualMode)\n\tif nodeCount := o.NodeCount; nodeCount > 0 {\n\t\t// Remove low frequency tags and edges as they affect selection.\n\t\tg.TrimLowFrequencyTags(nodeCutoff)\n\t\tg.TrimLowFrequencyEdges(edgeCutoff)\n\t\tif callTree {\n\t\t\tif nodesKept := g.SelectTopNodePtrs(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {\n\t\t\t\tg.TrimTree(nodesKept)\n\t\t\t\tg.SortNodes(cumSort, visualMode)\n\t\t\t}\n\t\t} else {\n\t\t\tif nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(g.Nodes) != len(nodesKept) {\n\t\t\t\tg = rpt.newGraph(nodesKept)\n\t\t\t\tg.SortNodes(cumSort, visualMode)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Final step: Filter out low frequency tags and edges, and remove redundant edges that clutter\n\t// the graph.\n\tg.TrimLowFrequencyTags(nodeCutoff)\n\tdroppedEdges = g.TrimLowFrequencyEdges(edgeCutoff)\n\tif visualMode {\n\t\tg.RemoveRedundantEdges()\n\t}\n\treturn\n}\n\nfunc (rpt *Report) selectOutputUnit(g *graph.Graph) {\n\to := rpt.options\n\n\t// Select best unit for profile output.\n\t// Find the appropriate units for the smallest non-zero sample\n\tif o.OutputUnit != \"minimum\" || len(g.Nodes) == 0 {\n\t\treturn\n\t}\n\tvar minValue int64\n\n\tfor _, n := range g.Nodes {\n\t\tnodeMin := abs64(n.FlatValue())\n\t\tif nodeMin == 0 {\n\t\t\tnodeMin = abs64(n.CumValue())\n\t\t}\n\t\tif nodeMin > 0 && (minValue == 0 || nodeMin < minValue) {\n\t\t\tminValue = nodeMin\n\t\t}\n\t}\n\tmaxValue := rpt.total\n\tif minValue == 0 {\n\t\tminValue = maxValue\n\t}\n\n\tif r := o.Ratio; r > 0 && r != 1 {\n\t\tminValue = int64(float64(minValue) * r)\n\t\tmaxValue = int64(float64(maxValue) * r)\n\t}\n\n\t_, minUnit := measurement.Scale(minValue, o.SampleUnit, \"minimum\")\n\t_, maxUnit := measurement.Scale(maxValue, o.SampleUnit, \"minimum\")\n\n\tunit := minUnit\n\tif minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {\n\t\t// Minimum and maximum values have different units. Scale\n\t\t// minimum by 100 to use larger units, allowing minimum value to\n\t\t// be scaled down to 0.01, except for callgrind reports since\n\t\t// they can only represent integer values.\n\t\t_, unit = measurement.Scale(100*minValue, o.SampleUnit, \"minimum\")\n\t}\n\n\tif unit != \"\" {\n\t\to.OutputUnit = unit\n\t} else {\n\t\to.OutputUnit = o.SampleUnit\n\t}\n}\n\n// newGraph creates a new graph for this report. If nodes is non-nil,\n// only nodes whose info matches are included. Otherwise, all nodes\n// are included, without trimming.\nfunc (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {\n\to := rpt.options\n\n\t// Clean up file paths using heuristics.\n\tprof := rpt.prof\n\tfor _, f := range prof.Function {\n\t\tf.Filename = trimPath(f.Filename, o.TrimPath, o.SourcePath)\n\t}\n\t// Removes all numeric tags except for the bytes tag prior\n\t// to making graph.\n\t// TODO: modify to select first numeric tag if no bytes tag\n\tfor _, s := range prof.Sample {\n\t\tnumLabels := make(map[string][]int64, len(s.NumLabel))\n\t\tnumUnits := make(map[string][]string, len(s.NumLabel))\n\t\tfor k, vs := range s.NumLabel {\n\t\t\tif k == \"bytes\" {\n\t\t\t\tunit := o.NumLabelUnits[k]\n\t\t\t\tnumValues := make([]int64, len(vs))\n\t\t\t\tnumUnit := make([]string, len(vs))\n\t\t\t\tfor i, v := range vs {\n\t\t\t\t\tnumValues[i] = v\n\t\t\t\t\tnumUnit[i] = unit\n\t\t\t\t}\n\t\t\t\tnumLabels[k] = append(numLabels[k], numValues...)\n\t\t\t\tnumUnits[k] = append(numUnits[k], numUnit...)\n\t\t\t}\n\t\t}\n\t\ts.NumLabel = numLabels\n\t\ts.NumUnit = numUnits\n\t}\n\n\t// Remove label marking samples from the base profiles, so it does not appear\n\t// as a nodelet in the graph view.\n\tprof.RemoveLabel(\"pprof::base\")\n\n\tformatTag := func(v int64, key string) string {\n\t\treturn measurement.ScaledLabel(v, key, o.OutputUnit)\n\t}\n\n\tgopt := &graph.Options{\n\t\tSampleValue:       o.SampleValue,\n\t\tSampleMeanDivisor: o.SampleMeanDivisor,\n\t\tFormatTag:         formatTag,\n\t\tCallTree:          o.CallTree && (o.OutputFormat == Dot || o.OutputFormat == Callgrind),\n\t\tDropNegative:      o.DropNegative,\n\t\tKeptNodes:         nodes,\n\t}\n\n\t// Only keep binary names for disassembly-based reports, otherwise\n\t// remove it to allow merging of functions across binaries.\n\tswitch o.OutputFormat {\n\tcase Raw, List, WebList, Dis, Callgrind:\n\t\tgopt.ObjNames = true\n\t}\n\n\treturn graph.New(rpt.prof, gopt)\n}\n\n// printProto writes the incoming proto via the writer w.\n// If the divide_by option has been specified, samples are scaled appropriately.\nfunc printProto(w io.Writer, rpt *Report) error {\n\tp, o := rpt.prof, rpt.options\n\n\t// Apply the sample ratio to all samples before saving the profile.\n\tif r := o.Ratio; r > 0 && r != 1 {\n\t\tfor _, sample := range p.Sample {\n\t\t\tfor i, v := range sample.Value {\n\t\t\t\tsample.Value[i] = int64(float64(v) * r)\n\t\t\t}\n\t\t}\n\t}\n\treturn p.Write(w)\n}\n\n// printTopProto writes a list of the hottest routines in a profile as a profile.proto.\nfunc printTopProto(w io.Writer, rpt *Report) error {\n\tp := rpt.prof\n\to := rpt.options\n\tg, _, _, _ := rpt.newTrimmedGraph()\n\trpt.selectOutputUnit(g)\n\n\tout := profile.Profile{\n\t\tSampleType: []*profile.ValueType{\n\t\t\t{Type: \"cum\", Unit: o.OutputUnit},\n\t\t\t{Type: \"flat\", Unit: o.OutputUnit},\n\t\t},\n\t\tTimeNanos:     p.TimeNanos,\n\t\tDurationNanos: p.DurationNanos,\n\t\tPeriodType:    p.PeriodType,\n\t\tPeriod:        p.Period,\n\t}\n\tfunctionMap := make(functionMap)\n\tfor i, n := range g.Nodes {\n\t\tf, added := functionMap.findOrAdd(n.Info)\n\t\tif added {\n\t\t\tout.Function = append(out.Function, f)\n\t\t}\n\t\tflat, cum := n.FlatValue(), n.CumValue()\n\t\tl := &profile.Location{\n\t\t\tID:      uint64(i + 1),\n\t\t\tAddress: n.Info.Address,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{\n\t\t\t\t\tLine:     int64(n.Info.Lineno),\n\t\t\t\t\tColumn:   int64(n.Info.Columnno),\n\t\t\t\t\tFunction: f,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tfv, _ := measurement.Scale(flat, o.SampleUnit, o.OutputUnit)\n\t\tcv, _ := measurement.Scale(cum, o.SampleUnit, o.OutputUnit)\n\t\ts := &profile.Sample{\n\t\t\tLocation: []*profile.Location{l},\n\t\t\tValue:    []int64{int64(cv), int64(fv)},\n\t\t}\n\t\tout.Location = append(out.Location, l)\n\t\tout.Sample = append(out.Sample, s)\n\t}\n\n\treturn out.Write(w)\n}\n\ntype functionMap map[string]*profile.Function\n\n// findOrAdd takes a node representing a function, adds the function\n// represented by the node to the map if the function is not already present,\n// and returns the function the node represents. This also returns a boolean,\n// which is true if the function was added and false otherwise.\nfunc (fm functionMap) findOrAdd(ni graph.NodeInfo) (*profile.Function, bool) {\n\tfName := fmt.Sprintf(\"%q%q%q%d\", ni.Name, ni.OrigName, ni.File, ni.StartLine)\n\n\tif f := fm[fName]; f != nil {\n\t\treturn f, false\n\t}\n\n\tf := &profile.Function{\n\t\tID:         uint64(len(fm) + 1),\n\t\tName:       ni.Name,\n\t\tSystemName: ni.OrigName,\n\t\tFilename:   ni.File,\n\t\tStartLine:  int64(ni.StartLine),\n\t}\n\tfm[fName] = f\n\treturn f, true\n}\n\n// printAssembly prints an annotated assembly listing.\nfunc printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {\n\treturn PrintAssembly(w, rpt, obj, -1)\n}\n\n// PrintAssembly prints annotated disassembly of rpt to w.\nfunc PrintAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool, maxFuncs int) error {\n\to := rpt.options\n\tprof := rpt.prof\n\n\tg := rpt.newGraph(nil)\n\n\t// If the regexp source can be parsed as an address, also match\n\t// functions that land on that address.\n\tvar address *uint64\n\tif hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {\n\t\taddress = &hex\n\t}\n\n\tfmt.Fprintln(w, \"Total:\", rpt.formatValue(rpt.total))\n\tsymbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)\n\tsymNodes := nodesPerSymbol(g.Nodes, symbols)\n\n\t// Sort for printing.\n\tvar syms []*objSymbol\n\tfor s := range symNodes {\n\t\tsyms = append(syms, s)\n\t}\n\tbyName := func(a, b *objSymbol) bool {\n\t\tif na, nb := a.sym.Name[0], b.sym.Name[0]; na != nb {\n\t\t\treturn na < nb\n\t\t}\n\t\treturn a.sym.Start < b.sym.Start\n\t}\n\tif maxFuncs < 0 {\n\t\tsort.Sort(orderSyms{syms, byName})\n\t} else {\n\t\tbyFlatSum := func(a, b *objSymbol) bool {\n\t\t\tsuma, _ := symNodes[a].Sum()\n\t\t\tsumb, _ := symNodes[b].Sum()\n\t\t\tif suma != sumb {\n\t\t\t\treturn suma > sumb\n\t\t\t}\n\t\t\treturn byName(a, b)\n\t\t}\n\t\tsort.Sort(orderSyms{syms, byFlatSum})\n\t\tif len(syms) > maxFuncs {\n\t\t\tsyms = syms[:maxFuncs]\n\t\t}\n\t}\n\n\tif len(syms) == 0 {\n\t\t// The symbol regexp case\n\t\tif address == nil {\n\t\t\treturn fmt.Errorf(\"no matches found for regexp %s\", o.Symbol)\n\t\t}\n\n\t\t// The address case\n\t\tif len(symbols) == 0 {\n\t\t\treturn fmt.Errorf(\"no matches found for address 0x%x\", *address)\n\t\t}\n\t\treturn fmt.Errorf(\"address 0x%x found in binary, but the corresponding symbols do not have samples in the profile\", *address)\n\t}\n\n\t// Correlate the symbols from the binary with the profile samples.\n\tfor _, s := range syms {\n\t\tsns := symNodes[s]\n\n\t\t// Gather samples for this symbol.\n\t\tflatSum, cumSum := sns.Sum()\n\n\t\t// Get the function assembly.\n\t\tinsts, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End, o.IntelSyntax)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tns := annotateAssembly(insts, sns, s.file)\n\n\t\tfmt.Fprintf(w, \"ROUTINE ======================== %s\\n\", s.sym.Name[0])\n\t\tfor _, name := range s.sym.Name[1:] {\n\t\t\tfmt.Fprintf(w, \"    AKA ======================== %s\\n\", name)\n\t\t}\n\t\tfmt.Fprintf(w, \"%10s %10s (flat, cum) %s of Total\\n\",\n\t\t\trpt.formatValue(flatSum), rpt.formatValue(cumSum),\n\t\t\tmeasurement.Percentage(cumSum, rpt.total))\n\n\t\tfunction, file, line := \"\", \"\", 0\n\t\tfor _, n := range ns {\n\t\t\tlocStr := \"\"\n\t\t\t// Skip loc information if it hasn't changed from previous instruction.\n\t\t\tif n.function != function || n.file != file || n.line != line {\n\t\t\t\tfunction, file, line = n.function, n.file, n.line\n\t\t\t\tif n.function != \"\" {\n\t\t\t\t\tlocStr = n.function + \" \"\n\t\t\t\t}\n\t\t\t\tif n.file != \"\" {\n\t\t\t\t\tlocStr += n.file\n\t\t\t\t\tif n.line != 0 {\n\t\t\t\t\t\tlocStr += fmt.Sprintf(\":%d\", n.line)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tswitch {\n\t\t\tcase locStr == \"\":\n\t\t\t\t// No location info, just print the instruction.\n\t\t\t\tfmt.Fprintf(w, \"%10s %10s %10x: %s\\n\",\n\t\t\t\t\tvalueOrDot(n.flatValue(), rpt),\n\t\t\t\t\tvalueOrDot(n.cumValue(), rpt),\n\t\t\t\t\tn.address, n.instruction,\n\t\t\t\t)\n\t\t\tcase len(n.instruction) < 40:\n\t\t\t\t// Short instruction, print loc on the same line.\n\t\t\t\tfmt.Fprintf(w, \"%10s %10s %10x: %-40s;%s\\n\",\n\t\t\t\t\tvalueOrDot(n.flatValue(), rpt),\n\t\t\t\t\tvalueOrDot(n.cumValue(), rpt),\n\t\t\t\t\tn.address, n.instruction,\n\t\t\t\t\tlocStr,\n\t\t\t\t)\n\t\t\tdefault:\n\t\t\t\t// Long instruction, print loc on a separate line.\n\t\t\t\tfmt.Fprintf(w, \"%74s;%s\\n\", \"\", locStr)\n\t\t\t\tfmt.Fprintf(w, \"%10s %10s %10x: %s\\n\",\n\t\t\t\t\tvalueOrDot(n.flatValue(), rpt),\n\t\t\t\t\tvalueOrDot(n.cumValue(), rpt),\n\t\t\t\t\tn.address, n.instruction,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// symbolsFromBinaries examines the binaries listed on the profile that have\n// associated samples, and returns the identified symbols matching rx.\nfunc symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {\n\t// fileHasSamplesAndMatched is for optimization to speed up pprof: when later\n\t// walking through the profile mappings, it will only examine the ones that have\n\t// samples and are matched to the regexp.\n\tfileHasSamplesAndMatched := make(map[string]bool)\n\tfor _, n := range g.Nodes {\n\t\tif name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != \"\" {\n\t\t\tfileHasSamplesAndMatched[n.Info.Objfile] = true\n\t\t}\n\t}\n\n\t// Walk all mappings looking for matching functions with samples.\n\tvar objSyms []*objSymbol\n\tfor _, m := range prof.Mapping {\n\t\t// Skip the mapping if its file does not have samples or is not matched to\n\t\t// the regexp (unless the regexp is an address and the mapping's range covers\n\t\t// the address)\n\t\tif !fileHasSamplesAndMatched[m.File] {\n\t\t\tif address == nil || m.Start > *address || *address > m.Limit {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tf, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"%v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\t// Find symbols in this binary matching the user regexp.\n\t\tvar addr uint64\n\t\tif address != nil {\n\t\t\taddr = *address\n\t\t}\n\t\tmsyms, err := f.Symbols(rx, addr)\n\t\tf.Close()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, ms := range msyms {\n\t\t\tobjSyms = append(objSyms,\n\t\t\t\t&objSymbol{\n\t\t\t\t\tsym:  ms,\n\t\t\t\t\tfile: f,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n\n\treturn objSyms\n}\n\n// objSymbol represents a symbol identified from a binary. It includes\n// the SymbolInfo from the disasm package and the base that must be\n// added to correspond to sample addresses\ntype objSymbol struct {\n\tsym  *plugin.Sym\n\tfile plugin.ObjFile\n}\n\n// orderSyms is a wrapper type to sort []*objSymbol by a supplied comparator.\ntype orderSyms struct {\n\tv    []*objSymbol\n\tless func(a, b *objSymbol) bool\n}\n\nfunc (o orderSyms) Len() int           { return len(o.v) }\nfunc (o orderSyms) Less(i, j int) bool { return o.less(o.v[i], o.v[j]) }\nfunc (o orderSyms) Swap(i, j int)      { o.v[i], o.v[j] = o.v[j], o.v[i] }\n\n// nodesPerSymbol classifies nodes into a group of symbols.\nfunc nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {\n\tsymNodes := make(map[*objSymbol]graph.Nodes)\n\tfor _, s := range symbols {\n\t\t// Gather samples for this symbol.\n\t\tfor _, n := range ns {\n\t\t\tif address, err := s.file.ObjAddr(n.Info.Address); err == nil && address >= s.sym.Start && address < s.sym.End {\n\t\t\t\tsymNodes[s] = append(symNodes[s], n)\n\t\t\t}\n\t\t}\n\t}\n\treturn symNodes\n}\n\ntype assemblyInstruction struct {\n\taddress         uint64\n\tinstruction     string\n\tfunction        string\n\tfile            string\n\tline            int\n\tflat, cum       int64\n\tflatDiv, cumDiv int64\n\tstartsBlock     bool\n\tinlineCalls     []callID\n}\n\ntype callID struct {\n\tfile string\n\tline int\n}\n\nfunc (a *assemblyInstruction) flatValue() int64 {\n\tif a.flatDiv != 0 {\n\t\treturn a.flat / a.flatDiv\n\t}\n\treturn a.flat\n}\n\nfunc (a *assemblyInstruction) cumValue() int64 {\n\tif a.cumDiv != 0 {\n\t\treturn a.cum / a.cumDiv\n\t}\n\treturn a.cum\n}\n\n// annotateAssembly annotates a set of assembly instructions with a\n// set of samples. It returns a set of nodes to display. base is an\n// offset to adjust the sample addresses.\nfunc annotateAssembly(insts []plugin.Inst, samples graph.Nodes, file plugin.ObjFile) []assemblyInstruction {\n\t// Add end marker to simplify printing loop.\n\tinsts = append(insts, plugin.Inst{\n\t\tAddr: ^uint64(0),\n\t})\n\n\t// Ensure samples are sorted by address.\n\tsamples.Sort(graph.AddressOrder)\n\n\ts := 0\n\tasm := make([]assemblyInstruction, 0, len(insts))\n\tfor ix, in := range insts[:len(insts)-1] {\n\t\tn := assemblyInstruction{\n\t\t\taddress:     in.Addr,\n\t\t\tinstruction: in.Text,\n\t\t\tfunction:    in.Function,\n\t\t\tline:        in.Line,\n\t\t}\n\t\tif in.File != \"\" {\n\t\t\tn.file = filepath.Base(in.File)\n\t\t}\n\n\t\t// Sum all the samples until the next instruction (to account\n\t\t// for samples attributed to the middle of an instruction).\n\t\tfor next := insts[ix+1].Addr; s < len(samples); s++ {\n\t\t\tif addr, err := file.ObjAddr(samples[s].Info.Address); err != nil || addr >= next {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tsample := samples[s]\n\t\t\tn.flatDiv += sample.FlatDiv\n\t\t\tn.flat += sample.Flat\n\t\t\tn.cumDiv += sample.CumDiv\n\t\t\tn.cum += sample.Cum\n\t\t\tif f := sample.Info.File; f != \"\" && n.file == \"\" {\n\t\t\t\tn.file = filepath.Base(f)\n\t\t\t}\n\t\t\tif ln := sample.Info.Lineno; ln != 0 && n.line == 0 {\n\t\t\t\tn.line = ln\n\t\t\t}\n\t\t\tif f := sample.Info.Name; f != \"\" && n.function == \"\" {\n\t\t\t\tn.function = f\n\t\t\t}\n\t\t}\n\t\tasm = append(asm, n)\n\t}\n\n\treturn asm\n}\n\n// valueOrDot formats a value according to a report, intercepting zero\n// values.\nfunc valueOrDot(value int64, rpt *Report) string {\n\tif value == 0 {\n\t\treturn \".\"\n\t}\n\treturn rpt.formatValue(value)\n}\n\n// printTags collects all tags referenced in the profile and prints\n// them in a sorted table.\nfunc printTags(w io.Writer, rpt *Report) error {\n\tp := rpt.prof\n\n\to := rpt.options\n\tformatTag := func(v int64, unit string) string {\n\t\treturn measurement.ScaledLabel(v, unit, o.OutputUnit)\n\t}\n\n\t// Accumulate tags as key,value,count.\n\ttagMap := make(map[string]map[string]int64)\n\t// Note that we assume single value per tag per sample. Multiple values are\n\t// encodable in the format but are discouraged.\n\ttagTotalMap := make(map[string]int64)\n\tfor _, s := range p.Sample {\n\t\tsampleValue := o.SampleValue(s.Value)\n\t\tfor key, vals := range s.Label {\n\t\t\tfor _, val := range vals {\n\t\t\t\tvalueMap, ok := tagMap[key]\n\t\t\t\tif !ok {\n\t\t\t\t\tvalueMap = make(map[string]int64)\n\t\t\t\t\ttagMap[key] = valueMap\n\t\t\t\t}\n\t\t\t\tvalueMap[val] += sampleValue\n\t\t\t\ttagTotalMap[key] += sampleValue\n\t\t\t}\n\t\t}\n\t\tfor key, vals := range s.NumLabel {\n\t\t\tunit := o.NumLabelUnits[key]\n\t\t\tfor _, nval := range vals {\n\t\t\t\tval := formatTag(nval, unit)\n\t\t\t\tvalueMap, ok := tagMap[key]\n\t\t\t\tif !ok {\n\t\t\t\t\tvalueMap = make(map[string]int64)\n\t\t\t\t\ttagMap[key] = valueMap\n\t\t\t\t}\n\t\t\t\tvalueMap[val] += sampleValue\n\t\t\t\ttagTotalMap[key] += sampleValue\n\t\t\t}\n\t\t}\n\t}\n\n\ttagKeys := make([]*graph.Tag, 0, len(tagMap))\n\tfor key := range tagMap {\n\t\ttagKeys = append(tagKeys, &graph.Tag{Name: key})\n\t}\n\ttabw := tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriter.AlignRight)\n\tfor _, tagKey := range graph.SortTags(tagKeys, true) {\n\t\tkey := tagKey.Name\n\t\ttags := make([]*graph.Tag, 0, len(tagMap[key]))\n\t\tfor t, c := range tagMap[key] {\n\t\t\ttags = append(tags, &graph.Tag{Name: t, Flat: c})\n\t\t}\n\n\t\ttagTotal, profileTotal := tagTotalMap[key], rpt.Total()\n\t\tif profileTotal > 0 {\n\t\t\tfmt.Fprintf(tabw, \"%s:\\t Total %s of %s (%s)\\n\", key, rpt.formatValue(tagTotal), rpt.formatValue(profileTotal), measurement.Percentage(tagTotal, profileTotal))\n\t\t} else {\n\t\t\tfmt.Fprintf(tabw, \"%s:\\t Total %s of %s\\n\", key, rpt.formatValue(tagTotal), rpt.formatValue(profileTotal))\n\t\t}\n\t\tfor _, t := range graph.SortTags(tags, true) {\n\t\t\tif profileTotal > 0 {\n\t\t\t\tfmt.Fprintf(tabw, \" \\t%s (%s):\\t %s\\n\", rpt.formatValue(t.FlatValue()), measurement.Percentage(t.FlatValue(), profileTotal), t.Name)\n\t\t\t} else {\n\t\t\t\tfmt.Fprintf(tabw, \" \\t%s:\\t %s\\n\", rpt.formatValue(t.FlatValue()), t.Name)\n\t\t\t}\n\t\t}\n\t\tfmt.Fprintln(tabw)\n\t}\n\treturn tabw.Flush()\n}\n\n// printComments prints all freeform comments in the profile.\nfunc printComments(w io.Writer, rpt *Report) error {\n\tp := rpt.prof\n\n\tfor _, c := range p.Comments {\n\t\tfmt.Fprintln(w, c)\n\t}\n\treturn nil\n}\n\n// TextItem holds a single text report entry.\ntype TextItem struct {\n\tName                  string\n\tInlineLabel           string // Not empty if inlined\n\tFlat, Cum             int64  // Raw values\n\tFlatFormat, CumFormat string // Formatted values\n}\n\n// TextItems returns a list of text items from the report and a list\n// of labels that describe the report.\nfunc TextItems(rpt *Report) ([]TextItem, []string) {\n\tg, origCount, droppedNodes, _ := rpt.newTrimmedGraph()\n\trpt.selectOutputUnit(g)\n\tlabels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false)\n\n\tvar items []TextItem\n\tvar flatSum int64\n\tfor _, n := range g.Nodes {\n\t\tname, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()\n\n\t\tvar inline, noinline bool\n\t\tfor _, e := range n.In {\n\t\t\tif e.Inline {\n\t\t\t\tinline = true\n\t\t\t} else {\n\t\t\t\tnoinline = true\n\t\t\t}\n\t\t}\n\n\t\tvar inl string\n\t\tif inline {\n\t\t\tif noinline {\n\t\t\t\tinl = \"(partial-inline)\"\n\t\t\t} else {\n\t\t\t\tinl = \"(inline)\"\n\t\t\t}\n\t\t}\n\n\t\tflatSum += flat\n\t\titems = append(items, TextItem{\n\t\t\tName:        name,\n\t\t\tInlineLabel: inl,\n\t\t\tFlat:        flat,\n\t\t\tCum:         cum,\n\t\t\tFlatFormat:  rpt.formatValue(flat),\n\t\t\tCumFormat:   rpt.formatValue(cum),\n\t\t})\n\t}\n\treturn items, labels\n}\n\n// printText prints a flat text report for a profile.\nfunc printText(w io.Writer, rpt *Report) error {\n\titems, labels := TextItems(rpt)\n\tfmt.Fprintln(w, strings.Join(labels, \"\\n\"))\n\tfmt.Fprintf(w, \"%10s %5s%% %5s%% %10s %5s%%\\n\",\n\t\t\"flat\", \"flat\", \"sum\", \"cum\", \"cum\")\n\tvar flatSum int64\n\tfor _, item := range items {\n\t\tinl := item.InlineLabel\n\t\tif inl != \"\" {\n\t\t\tinl = \" \" + inl\n\t\t}\n\t\tflatSum += item.Flat\n\t\tfmt.Fprintf(w, \"%10s %s %s %10s %s  %s%s\\n\",\n\t\t\titem.FlatFormat, measurement.Percentage(item.Flat, rpt.total),\n\t\t\tmeasurement.Percentage(flatSum, rpt.total),\n\t\t\titem.CumFormat, measurement.Percentage(item.Cum, rpt.total),\n\t\t\titem.Name, inl)\n\t}\n\treturn nil\n}\n\n// printTraces prints all traces from a profile.\nfunc printTraces(w io.Writer, rpt *Report) error {\n\tfmt.Fprintln(w, strings.Join(ProfileLabels(rpt), \"\\n\"))\n\n\tprof := rpt.prof\n\to := rpt.options\n\n\tconst separator = \"-----------+-------------------------------------------------------\"\n\n\t_, locations := graph.CreateNodes(prof, &graph.Options{})\n\tfor _, sample := range prof.Sample {\n\t\ttype stk struct {\n\t\t\t*graph.NodeInfo\n\t\t\tinline bool\n\t\t}\n\t\tvar stack []stk\n\t\tfor _, loc := range sample.Location {\n\t\t\tnodes := locations[loc.ID]\n\t\t\tfor i, n := range nodes {\n\t\t\t\t// The inline flag may be inaccurate if 'show' or 'hide' filter is\n\t\t\t\t// used. See https://github.com/google/pprof/issues/511.\n\t\t\t\tinline := i != len(nodes)-1\n\t\t\t\tstack = append(stack, stk{&n.Info, inline})\n\t\t\t}\n\t\t}\n\n\t\tif len(stack) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Fprintln(w, separator)\n\t\t// Print any text labels for the sample.\n\t\tvar labels []string\n\t\tfor s, vs := range sample.Label {\n\t\t\tlabels = append(labels, fmt.Sprintf(\"%10s:  %s\\n\", s, strings.Join(vs, \" \")))\n\t\t}\n\t\tsort.Strings(labels)\n\t\tfmt.Fprint(w, strings.Join(labels, \"\"))\n\n\t\t// Print any numeric labels for the sample\n\t\tvar numLabels []string\n\t\tfor key, vals := range sample.NumLabel {\n\t\t\tunit := o.NumLabelUnits[key]\n\t\t\tnumValues := make([]string, len(vals))\n\t\t\tfor i, vv := range vals {\n\t\t\t\tnumValues[i] = measurement.Label(vv, unit)\n\t\t\t}\n\t\t\tnumLabels = append(numLabels, fmt.Sprintf(\"%10s:  %s\\n\", key, strings.Join(numValues, \" \")))\n\t\t}\n\t\tsort.Strings(numLabels)\n\t\tfmt.Fprint(w, strings.Join(numLabels, \"\"))\n\n\t\tvar d, v int64\n\t\tv = o.SampleValue(sample.Value)\n\t\tif o.SampleMeanDivisor != nil {\n\t\t\td = o.SampleMeanDivisor(sample.Value)\n\t\t}\n\t\t// Print call stack.\n\t\tif d != 0 {\n\t\t\tv = v / d\n\t\t}\n\t\tfor i, s := range stack {\n\t\t\tvar vs, inline string\n\t\t\tif i == 0 {\n\t\t\t\tvs = rpt.formatValue(v)\n\t\t\t}\n\t\t\tif s.inline {\n\t\t\t\tinline = \" (inline)\"\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%10s   %s%s\\n\", vs, s.PrintableName(), inline)\n\t\t}\n\t}\n\tfmt.Fprintln(w, separator)\n\treturn nil\n}\n\n// printCallgrind prints a graph for a profile on callgrind format.\nfunc printCallgrind(w io.Writer, rpt *Report) error {\n\to := rpt.options\n\trpt.options.NodeFraction = 0\n\trpt.options.EdgeFraction = 0\n\trpt.options.NodeCount = 0\n\n\tg, _, _, _ := rpt.newTrimmedGraph()\n\trpt.selectOutputUnit(g)\n\n\tnodeNames := getDisambiguatedNames(g)\n\n\tfmt.Fprintln(w, \"positions: instr line\")\n\tfmt.Fprintln(w, \"events:\", o.SampleType+\"(\"+o.OutputUnit+\")\")\n\n\tobjfiles := make(map[string]int)\n\tfiles := make(map[string]int)\n\tnames := make(map[string]int)\n\n\t// prevInfo points to the previous NodeInfo.\n\t// It is used to group cost lines together as much as possible.\n\tvar prevInfo *graph.NodeInfo\n\tfor _, n := range g.Nodes {\n\t\tif prevInfo == nil || n.Info.Objfile != prevInfo.Objfile || n.Info.File != prevInfo.File || n.Info.Name != prevInfo.Name {\n\t\t\tfmt.Fprintln(w)\n\t\t\tfmt.Fprintln(w, \"ob=\"+callgrindName(objfiles, n.Info.Objfile))\n\t\t\tfmt.Fprintln(w, \"fl=\"+callgrindName(files, n.Info.File))\n\t\t\tfmt.Fprintln(w, \"fn=\"+callgrindName(names, n.Info.Name))\n\t\t}\n\n\t\taddr := callgrindAddress(prevInfo, n.Info.Address)\n\t\tsv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit)\n\t\tfmt.Fprintf(w, \"%s %d %d\\n\", addr, n.Info.Lineno, int64(sv))\n\n\t\t// Print outgoing edges.\n\t\tfor _, out := range n.Out.Sort() {\n\t\t\tc, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)\n\t\t\tcallee := out.Dest\n\t\t\tfmt.Fprintln(w, \"cfl=\"+callgrindName(files, callee.Info.File))\n\t\t\tfmt.Fprintln(w, \"cfn=\"+callgrindName(names, nodeNames[callee]))\n\t\t\t// pprof doesn't have a flat weight for a call, leave as 0.\n\t\t\tfmt.Fprintf(w, \"calls=0 %s %d\\n\", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno)\n\t\t\t// TODO: This address may be in the middle of a call\n\t\t\t// instruction. It would be best to find the beginning\n\t\t\t// of the instruction, but the tools seem to handle\n\t\t\t// this OK.\n\t\t\tfmt.Fprintf(w, \"* * %d\\n\", int64(c))\n\t\t}\n\n\t\tprevInfo = &n.Info\n\t}\n\n\treturn nil\n}\n\n// getDisambiguatedNames returns a map from each node in the graph to\n// the name to use in the callgrind output. Callgrind merges all\n// functions with the same [file name, function name]. Add a [%d/n]\n// suffix to disambiguate nodes with different values of\n// node.Function, which we want to keep separate. In particular, this\n// affects graphs created with --call_tree, where nodes from different\n// contexts are associated to different Functions.\nfunc getDisambiguatedNames(g *graph.Graph) map[*graph.Node]string {\n\tnodeName := make(map[*graph.Node]string, len(g.Nodes))\n\n\ttype names struct {\n\t\tfile, function string\n\t}\n\n\t// nameFunctionIndex maps the callgrind names (filename, function)\n\t// to the node.Function values found for that name, and each\n\t// node.Function value to a sequential index to be used on the\n\t// disambiguated name.\n\tnameFunctionIndex := make(map[names]map[*graph.Node]int)\n\tfor _, n := range g.Nodes {\n\t\tnm := names{n.Info.File, n.Info.Name}\n\t\tp, ok := nameFunctionIndex[nm]\n\t\tif !ok {\n\t\t\tp = make(map[*graph.Node]int)\n\t\t\tnameFunctionIndex[nm] = p\n\t\t}\n\t\tif _, ok := p[n.Function]; !ok {\n\t\t\tp[n.Function] = len(p)\n\t\t}\n\t}\n\n\tfor _, n := range g.Nodes {\n\t\tnm := names{n.Info.File, n.Info.Name}\n\t\tnodeName[n] = n.Info.Name\n\t\tif p := nameFunctionIndex[nm]; len(p) > 1 {\n\t\t\t// If there is more than one function, add suffix to disambiguate.\n\t\t\tnodeName[n] += fmt.Sprintf(\" [%d/%d]\", p[n.Function]+1, len(p))\n\t\t}\n\t}\n\treturn nodeName\n}\n\n// callgrindName implements the callgrind naming compression scheme.\n// For names not previously seen returns \"(N) name\", where N is a\n// unique index. For names previously seen returns \"(N)\" where N is\n// the index returned the first time.\nfunc callgrindName(names map[string]int, name string) string {\n\tif name == \"\" {\n\t\treturn \"\"\n\t}\n\tif id, ok := names[name]; ok {\n\t\treturn fmt.Sprintf(\"(%d)\", id)\n\t}\n\tid := len(names) + 1\n\tnames[name] = id\n\treturn fmt.Sprintf(\"(%d) %s\", id, name)\n}\n\n// callgrindAddress implements the callgrind subposition compression scheme if\n// possible. If prevInfo != nil, it contains the previous address. The current\n// address can be given relative to the previous address, with an explicit +/-\n// to indicate it is relative, or * for the same address.\nfunc callgrindAddress(prevInfo *graph.NodeInfo, curr uint64) string {\n\tabs := fmt.Sprintf(\"%#x\", curr)\n\tif prevInfo == nil {\n\t\treturn abs\n\t}\n\n\tprev := prevInfo.Address\n\tif prev == curr {\n\t\treturn \"*\"\n\t}\n\n\tdiff := int64(curr - prev)\n\trelative := fmt.Sprintf(\"%+d\", diff)\n\n\t// Only bother to use the relative address if it is actually shorter.\n\tif len(relative) < len(abs) {\n\t\treturn relative\n\t}\n\n\treturn abs\n}\n\n// printTree prints a tree-based report in text form.\nfunc printTree(w io.Writer, rpt *Report) error {\n\tconst separator = \"----------------------------------------------------------+-------------\"\n\tconst legend = \"      flat  flat%   sum%        cum   cum%   calls calls% + context \t \t \"\n\n\tg, origCount, droppedNodes, _ := rpt.newTrimmedGraph()\n\trpt.selectOutputUnit(g)\n\n\tfmt.Fprintln(w, strings.Join(reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false), \"\\n\"))\n\n\tfmt.Fprintln(w, separator)\n\tfmt.Fprintln(w, legend)\n\tvar flatSum int64\n\n\trx := rpt.options.Symbol\n\tmatched := 0\n\tfor _, n := range g.Nodes {\n\t\tname, flat, cum := n.Info.PrintableName(), n.FlatValue(), n.CumValue()\n\n\t\t// Skip any entries that do not match the regexp (for the \"peek\" command).\n\t\tif rx != nil && !rx.MatchString(name) {\n\t\t\tcontinue\n\t\t}\n\t\tmatched++\n\n\t\tfmt.Fprintln(w, separator)\n\t\t// Print incoming edges.\n\t\tinEdges := n.In.Sort()\n\t\tfor _, in := range inEdges {\n\t\t\tvar inline string\n\t\t\tif in.Inline {\n\t\t\t\tinline = \" (inline)\"\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%50s %s |   %s%s\\n\", rpt.formatValue(in.Weight),\n\t\t\t\tmeasurement.Percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline)\n\t\t}\n\n\t\t// Print current node.\n\t\tflatSum += flat\n\t\tfmt.Fprintf(w, \"%10s %s %s %10s %s                | %s\\n\",\n\t\t\trpt.formatValue(flat),\n\t\t\tmeasurement.Percentage(flat, rpt.total),\n\t\t\tmeasurement.Percentage(flatSum, rpt.total),\n\t\t\trpt.formatValue(cum),\n\t\t\tmeasurement.Percentage(cum, rpt.total),\n\t\t\tname)\n\n\t\t// Print outgoing edges.\n\t\toutEdges := n.Out.Sort()\n\t\tfor _, out := range outEdges {\n\t\t\tvar inline string\n\t\t\tif out.Inline {\n\t\t\t\tinline = \" (inline)\"\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%50s %s |   %s%s\\n\", rpt.formatValue(out.Weight),\n\t\t\t\tmeasurement.Percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline)\n\t\t}\n\t}\n\tif len(g.Nodes) > 0 {\n\t\tfmt.Fprintln(w, separator)\n\t}\n\tif rx != nil && matched == 0 {\n\t\treturn fmt.Errorf(\"no matches found for regexp: %s\", rx)\n\t}\n\treturn nil\n}\n\n// GetDOT returns a graph suitable for dot processing along with some\n// configuration information.\nfunc GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) {\n\tg, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()\n\trpt.selectOutputUnit(g)\n\tlabels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, droppedEdges, true)\n\n\tc := &graph.DotConfig{\n\t\tTitle:       rpt.options.Title,\n\t\tLabels:      labels,\n\t\tFormatValue: rpt.formatValue,\n\t\tTotal:       rpt.total,\n\t}\n\treturn g, c\n}\n\n// printDOT prints an annotated callgraph in DOT format.\nfunc printDOT(w io.Writer, rpt *Report) error {\n\tg, c := GetDOT(rpt)\n\tgraph.ComposeDot(w, g, &graph.DotAttributes{}, c)\n\treturn nil\n}\n\n// ProfileLabels returns printable labels for a profile.\nfunc ProfileLabels(rpt *Report) []string {\n\tlabel := []string{}\n\tprof := rpt.prof\n\to := rpt.options\n\tif len(prof.Mapping) > 0 {\n\t\tif prof.Mapping[0].File != \"\" {\n\t\t\tlabel = append(label, \"File: \"+filepath.Base(prof.Mapping[0].File))\n\t\t}\n\t\tif prof.Mapping[0].BuildID != \"\" {\n\t\t\tlabel = append(label, \"Build ID: \"+prof.Mapping[0].BuildID)\n\t\t}\n\t}\n\t// Only include comments that do not start with '#'.\n\tfor _, c := range prof.Comments {\n\t\tif !strings.HasPrefix(c, \"#\") {\n\t\t\tlabel = append(label, c)\n\t\t}\n\t}\n\tif o.SampleType != \"\" {\n\t\tlabel = append(label, \"Type: \"+o.SampleType)\n\t}\n\tif url := prof.DocURL; url != \"\" {\n\t\tlabel = append(label, \"Doc: \"+url)\n\t}\n\tif prof.TimeNanos != 0 {\n\t\tconst layout = \"2006-01-02 15:04:05 MST\"\n\t\tlabel = append(label, \"Time: \"+time.Unix(0, prof.TimeNanos).Format(layout))\n\t}\n\tif prof.DurationNanos != 0 {\n\t\tduration := measurement.Label(prof.DurationNanos, \"nanoseconds\")\n\t\ttotalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, \"nanoseconds\")\n\t\tvar ratio string\n\t\tif totalUnit == \"ns\" && totalNanos != 0 {\n\t\t\tratio = \"(\" + measurement.Percentage(int64(totalNanos), prof.DurationNanos) + \")\"\n\t\t}\n\t\tlabel = append(label, fmt.Sprintf(\"Duration: %s, Total samples = %s %s\", duration, rpt.formatValue(rpt.total), ratio))\n\t}\n\treturn label\n}\n\nfunc graphTotal(g *graph.Graph) int64 {\n\tvar total int64\n\tfor _, n := range g.Nodes {\n\t\ttotal += n.FlatValue()\n\t}\n\treturn total\n}\n\n// reportLabels returns printable labels for a report. Includes\n// profileLabels.\nfunc reportLabels(rpt *Report, shownTotal int64, nodeCount, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {\n\tnodeFraction := rpt.options.NodeFraction\n\tedgeFraction := rpt.options.EdgeFraction\n\n\tvar label []string\n\tif len(rpt.options.ProfileLabels) > 0 {\n\t\tlabel = append(label, rpt.options.ProfileLabels...)\n\t} else if fullHeaders || !rpt.options.CompactLabels {\n\t\tlabel = ProfileLabels(rpt)\n\t}\n\n\tif len(rpt.options.ActiveFilters) > 0 {\n\t\tactiveFilters := legendActiveFilters(rpt.options.ActiveFilters)\n\t\tlabel = append(label, activeFilters...)\n\t}\n\n\tlabel = append(label, fmt.Sprintf(\"Showing nodes accounting for %s, %s of %s total\", rpt.formatValue(shownTotal), strings.TrimSpace(measurement.Percentage(shownTotal, rpt.total)), rpt.formatValue(rpt.total)))\n\n\tif rpt.total != 0 {\n\t\tif droppedNodes > 0 {\n\t\t\tlabel = append(label, genLabel(droppedNodes, \"node\", \"cum\",\n\t\t\t\trpt.formatValue(abs64(int64(float64(rpt.total)*nodeFraction)))))\n\t\t}\n\t\tif droppedEdges > 0 {\n\t\t\tlabel = append(label, genLabel(droppedEdges, \"edge\", \"freq\",\n\t\t\t\trpt.formatValue(abs64(int64(float64(rpt.total)*edgeFraction)))))\n\t\t}\n\t\tif nodeCount > 0 && nodeCount < origCount {\n\t\t\tlabel = append(label, fmt.Sprintf(\"Showing top %d nodes out of %d\",\n\t\t\t\tnodeCount, origCount))\n\t\t}\n\t}\n\n\t// Help new users understand the graph.\n\t// A new line is intentionally added here to better show this message.\n\tif fullHeaders {\n\t\tlabel = append(label, \"\\nSee https://git.io/JfYMW for how to read the graph\")\n\t}\n\n\treturn label\n}\n\nfunc legendActiveFilters(activeFilters []string) []string {\n\tlegendActiveFilters := make([]string, len(activeFilters)+1)\n\tlegendActiveFilters[0] = \"Active filters:\"\n\tfor i, s := range activeFilters {\n\t\tif len(s) > 80 {\n\t\t\ts = s[:80] + \"…\"\n\t\t}\n\t\tlegendActiveFilters[i+1] = \"   \" + s\n\t}\n\treturn legendActiveFilters\n}\n\nfunc genLabel(d int, n, l, f string) string {\n\tif d > 1 {\n\t\tn = n + \"s\"\n\t}\n\treturn fmt.Sprintf(\"Dropped %d %s (%s <= %s)\", d, n, l, f)\n}\n\n// New builds a new report indexing the sample values interpreting the\n// samples with the provided function.\nfunc New(prof *profile.Profile, o *Options) *Report {\n\tformat := func(v int64) string {\n\t\tif r := o.Ratio; r > 0 && r != 1 {\n\t\t\tfv := float64(v) * r\n\t\t\tv = int64(fv)\n\t\t}\n\t\treturn measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)\n\t}\n\treturn &Report{prof, computeTotal(prof, o.SampleValue, o.SampleMeanDivisor),\n\t\to, format}\n}\n\n// NewDefault builds a new report indexing the last sample value\n// available.\nfunc NewDefault(prof *profile.Profile, options Options) *Report {\n\tindex := len(prof.SampleType) - 1\n\to := &options\n\tif o.Title == \"\" && len(prof.Mapping) > 0 && prof.Mapping[0].File != \"\" {\n\t\to.Title = filepath.Base(prof.Mapping[0].File)\n\t}\n\to.SampleType = prof.SampleType[index].Type\n\to.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)\n\to.SampleValue = func(v []int64) int64 {\n\t\treturn v[index]\n\t}\n\treturn New(prof, o)\n}\n\n// computeTotal computes the sum of the absolute value of all sample values.\n// If any samples have label indicating they belong to the diff base, then the\n// total will only include samples with that label.\nfunc computeTotal(prof *profile.Profile, value, meanDiv func(v []int64) int64) int64 {\n\tvar div, total, diffDiv, diffTotal int64\n\tfor _, sample := range prof.Sample {\n\t\tvar d, v int64\n\t\tv = value(sample.Value)\n\t\tif meanDiv != nil {\n\t\t\td = meanDiv(sample.Value)\n\t\t}\n\t\tif v < 0 {\n\t\t\tv = -v\n\t\t}\n\t\ttotal += v\n\t\tdiv += d\n\t\tif sample.DiffBaseSample() {\n\t\t\tdiffTotal += v\n\t\t\tdiffDiv += d\n\t\t}\n\t}\n\tif diffTotal > 0 {\n\t\ttotal = diffTotal\n\t\tdiv = diffDiv\n\t}\n\tif div != 0 {\n\t\treturn total / div\n\t}\n\treturn total\n}\n\n// Report contains the data and associated routines to extract a\n// report from a profile.\ntype Report struct {\n\tprof        *profile.Profile\n\ttotal       int64\n\toptions     *Options\n\tformatValue func(int64) string\n}\n\n// Total returns the total number of samples in a report.\nfunc (rpt *Report) Total() int64 { return rpt.total }\n\n// OutputFormat returns the output format for the report.\nfunc (rpt *Report) OutputFormat() int { return rpt.options.OutputFormat }\n\n// DocURL returns the documentation URL for Report, or \"\" if not available.\nfunc (rpt *Report) DocURL() string {\n\tu := rpt.prof.DocURL\n\tif u == \"\" || !absoluteURL(u) {\n\t\treturn \"\"\n\t}\n\treturn u\n}\n\nfunc absoluteURL(str string) bool {\n\t// Avoid returning relative URLs to prevent unwanted local navigation\n\t// within pprof server.\n\tu, err := url.Parse(str)\n\treturn err == nil && (u.Scheme == \"https\" || u.Scheme == \"http\")\n}\n\nfunc abs64(i int64) int64 {\n\tif i < 0 {\n\t\treturn -i\n\t}\n\treturn i\n}\n"
  },
  {
    "path": "internal/report/report_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage report\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/internal/graph\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/profile\"\n)\n\ntype testcase struct {\n\trpt  *Report\n\twant string\n}\n\nfunc TestSource(t *testing.T) {\n\tconst path = \"testdata/\"\n\n\tsampleValue1 := func(v []int64) int64 {\n\t\treturn v[1]\n\t}\n\n\tfor _, tc := range []testcase{\n\t\t{\n\t\t\trpt: New(\n\t\t\t\ttestProfile.Copy(),\n\t\t\t\t&Options{\n\t\t\t\t\tOutputFormat: List,\n\t\t\t\t\tSymbol:       regexp.MustCompile(`.`),\n\t\t\t\t\tTrimPath:     \"/some/path\",\n\n\t\t\t\t\tSampleValue: sampleValue1,\n\t\t\t\t\tSampleUnit:  testProfile.SampleType[1].Unit,\n\t\t\t\t},\n\t\t\t),\n\t\t\twant: path + \"source.rpt\",\n\t\t},\n\t\t{\n\t\t\trpt: New(\n\t\t\t\ttestProfile.Copy(),\n\t\t\t\t&Options{\n\t\t\t\t\tOutputFormat: Dot,\n\t\t\t\t\tCallTree:     true,\n\t\t\t\t\tSymbol:       regexp.MustCompile(`.`),\n\t\t\t\t\tTrimPath:     \"/some/path\",\n\n\t\t\t\t\tSampleValue: sampleValue1,\n\t\t\t\t\tSampleUnit:  testProfile.SampleType[1].Unit,\n\t\t\t\t},\n\t\t\t),\n\t\t\twant: path + \"source.dot\",\n\t\t},\n\t} {\n\t\tvar b bytes.Buffer\n\t\tif err := Generate(&b, tc.rpt, &binutils.Binutils{}); err != nil {\n\t\t\tt.Fatalf(\"%s: %v\", tc.want, err)\n\t\t}\n\n\t\tgold, err := os.ReadFile(tc.want)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s: %v\", tc.want, err)\n\t\t}\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tif tc.rpt.options.OutputFormat == Dot {\n\t\t\t\t// The .dot test has the paths inside strings, so \\ must be escaped.\n\t\t\t\tgold = bytes.ReplaceAll(gold, []byte(\"testdata/\"), []byte(`testdata\\\\`))\n\t\t\t} else {\n\t\t\t\tgold = bytes.ReplaceAll(gold, []byte(\"testdata/\"), []byte(`testdata\\`))\n\t\t\t}\n\t\t}\n\t\tif string(b.String()) != string(gold) {\n\t\t\td, err := proftest.Diff(gold, b.Bytes())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"%s: %v\", \"source\", err)\n\t\t\t}\n\t\t\tt.Error(\"source\" + \"\\n\" + string(d) + \"\\n\" + \"gold:\\n\" + tc.want)\n\t\t}\n\t}\n}\n\n// TestFilter ensures that commands with a regexp filter argument return an\n// error if there are no results.\nfunc TestFilter(t *testing.T) {\n\tconst filter = \"doesNotExist\"\n\n\ttests := []struct {\n\t\tname   string\n\t\tformat int\n\t}{\n\t\t{\n\t\t\tname:   \"list\",\n\t\t\tformat: List,\n\t\t},\n\t\t{\n\t\t\tname:   \"disasm\",\n\t\t\tformat: Dis,\n\t\t},\n\t\t{\n\t\t\t// N.B. Tree with a Symbol is \"peek\".\n\t\t\tname:   \"peek\",\n\t\t\tformat: Tree,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trpt := New(testProfile.Copy(), &Options{\n\t\t\t\tOutputFormat: tc.format,\n\t\t\t\tSymbol:       regexp.MustCompile(filter),\n\t\t\t\tSampleValue:  func(v []int64) int64 { return v[1] },\n\t\t\t\tSampleUnit:   testProfile.SampleType[1].Unit,\n\t\t\t})\n\n\t\t\tvar buf bytes.Buffer\n\t\t\terr := Generate(&buf, rpt, &binutils.Binutils{})\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Generate got nil, want error; buf = %s\", buf.String())\n\t\t\t}\n\t\t\tif !strings.Contains(err.Error(), filter) {\n\t\t\t\tt.Errorf(\"Error got %v, want it to contain %q\", err, filter)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// testM contains mappings for fake profiles used in tests.\nvar testM = []*profile.Mapping{\n\t{\n\t\tID:              1,\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n}\n\n// testF contains functions for fake profiles used in tests.\nvar testF = []*profile.Function{\n\t{\n\t\tID:       1,\n\t\tName:     \"main\",\n\t\tFilename: \"testdata/source1\",\n\t},\n\t{\n\t\tID:       2,\n\t\tName:     \"foo\",\n\t\tFilename: \"testdata/source1\",\n\t},\n\t{\n\t\tID:       3,\n\t\tName:     \"bar\",\n\t\tFilename: \"testdata/source1\",\n\t},\n\t{\n\t\tID:       4,\n\t\tName:     \"tee\",\n\t\tFilename: \"/some/path/testdata/source2\",\n\t},\n}\n\n// testL contains locations for fake profiles used in tests.\nvar testL = []*profile.Location{\n\t{\n\t\tID:      1,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[0],\n\t\t\t\tLine:     2,\n\t\t\t\tColumn:   2,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      2,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[1],\n\t\t\t\tLine:     4,\n\t\t\t\tColumn:   4,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      3,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[2],\n\t\t\t\tLine:     10,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      4,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[3],\n\t\t\t\tLine:     2,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      5,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[3],\n\t\t\t\tLine:     8,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      6,\n\t\tMapping: testM[0],\n\t\tLine: []profile.Line{\n\t\t\t{\n\t\t\t\tFunction: testF[3],\n\t\t\t\tLine:     7,\n\t\t\t},\n\t\t\t{\n\t\t\t\tFunction: testF[2],\n\t\t\t\tLine:     6,\n\t\t\t},\n\t\t},\n\t},\n}\n\n// testSample returns a profile sample with specified value and stack.\n// Note: callees come first in sample stacks.\nfunc testSample(value int64, locs ...*profile.Location) *profile.Sample {\n\treturn &profile.Sample{\n\t\tValue:    []int64{value},\n\t\tLocation: locs,\n\t}\n}\n\n// makeTestProfile returns a profile with specified samples that uses testL/testF/testM\n// (defined in report_test.go).\nfunc makeTestProfile(samples ...*profile.Sample) *profile.Profile {\n\treturn &profile.Profile{\n\t\tSampleType: []*profile.ValueType{{Type: \"samples\", Unit: \"count\"}},\n\t\tSample:     samples,\n\t\tLocation:   testL,\n\t\tFunction:   testF,\n\t\tMapping:    testM,\n\t}\n}\n\n// testProfile contains a fake profile used in tests.\n// Various report methods modify profiles so tests should operate on testProfile.Copy().\nvar testProfile = &profile.Profile{\n\tPeriodType:    &profile.ValueType{Type: \"cpu\", Unit: \"millisecond\"},\n\tPeriod:        10,\n\tDurationNanos: 10e9,\n\tSampleType: []*profile.ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"cycles\"},\n\t},\n\tSample: []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{1, 10},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[2], testL[0]},\n\t\t\tValue:    []int64{1, 100},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[3], testL[0]},\n\t\t\tValue:    []int64{1, 1000},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[3], testL[0]},\n\t\t\tValue:    []int64{1, 10000},\n\t\t},\n\t},\n\tLocation: testL,\n\tFunction: testF,\n\tMapping:  testM,\n}\n\nfunc TestDisambiguation(t *testing.T) {\n\tparent1 := &graph.Node{Info: graph.NodeInfo{Name: \"parent1\"}}\n\tparent2 := &graph.Node{Info: graph.NodeInfo{Name: \"parent2\"}}\n\tchild1 := &graph.Node{Info: graph.NodeInfo{Name: \"child\"}, Function: parent1}\n\tchild2 := &graph.Node{Info: graph.NodeInfo{Name: \"child\"}, Function: parent2}\n\tchild3 := &graph.Node{Info: graph.NodeInfo{Name: \"child\"}, Function: parent1}\n\tsibling := &graph.Node{Info: graph.NodeInfo{Name: \"sibling\"}, Function: parent1}\n\n\tn := []*graph.Node{parent1, parent2, child1, child2, child3, sibling}\n\n\twanted := map[*graph.Node]string{\n\t\tparent1: \"parent1\",\n\t\tparent2: \"parent2\",\n\t\tchild1:  \"child [1/2]\",\n\t\tchild2:  \"child [2/2]\",\n\t\tchild3:  \"child [1/2]\",\n\t\tsibling: \"sibling\",\n\t}\n\n\tg := &graph.Graph{Nodes: n}\n\n\tnames := getDisambiguatedNames(g)\n\n\tfor node, want := range wanted {\n\t\tif got := names[node]; got != want {\n\t\t\tt.Errorf(\"name %s, got %s, want %s\", node.Info.Name, got, want)\n\t\t}\n\t}\n}\n\nfunc TestFunctionMap(t *testing.T) {\n\n\tfm := make(functionMap)\n\tnodes := []graph.NodeInfo{\n\t\t{Name: \"fun1\"},\n\t\t{Name: \"fun2\", File: \"filename\"},\n\t\t{Name: \"fun1\"},\n\t\t{Name: \"fun2\", File: \"filename2\"},\n\t}\n\n\twant := []struct {\n\t\twantFunction profile.Function\n\t\twantAdded    bool\n\t}{\n\t\t{profile.Function{ID: 1, Name: \"fun1\"}, true},\n\t\t{profile.Function{ID: 2, Name: \"fun2\", Filename: \"filename\"}, true},\n\t\t{profile.Function{ID: 1, Name: \"fun1\"}, false},\n\t\t{profile.Function{ID: 3, Name: \"fun2\", Filename: \"filename2\"}, true},\n\t}\n\n\tfor i, tc := range nodes {\n\t\tgotFunc, gotAdded := fm.findOrAdd(tc)\n\t\tif got, want := gotFunc, want[i].wantFunction; *got != want {\n\t\t\tt.Errorf(\"%d: got %v, want %v\", i, got, want)\n\t\t}\n\t\tif got, want := gotAdded, want[i].wantAdded; got != want {\n\t\t\tt.Errorf(\"%d: got %v, want %v\", i, got, want)\n\t\t}\n\t}\n}\n\nfunc TestLegendActiveFilters(t *testing.T) {\n\tactiveFilterInput := []string{\n\t\t\"focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536|363738|acbdefghijklmnop\",\n\t\t\"show=short filter\",\n\t}\n\texpectedLegendActiveFilter := []string{\n\t\t\"Active filters:\",\n\t\t\"   focus=123|456|789|101112|131415|161718|192021|222324|252627|282930|313233|343536…\",\n\t\t\"   show=short filter\",\n\t}\n\tlegendActiveFilter := legendActiveFilters(activeFilterInput)\n\tif len(legendActiveFilter) != len(expectedLegendActiveFilter) {\n\t\tt.Errorf(\"wanted length %v got length %v\", len(expectedLegendActiveFilter), len(legendActiveFilter))\n\t}\n\tfor i := range legendActiveFilter {\n\t\tif legendActiveFilter[i] != expectedLegendActiveFilter[i] {\n\t\t\tt.Errorf(\"%d: want \\\"%v\\\", got \\\"%v\\\"\", i, expectedLegendActiveFilter[i], legendActiveFilter[i])\n\t\t}\n\t}\n}\n\nfunc TestComputeTotal(t *testing.T) {\n\tp1 := testProfile.Copy()\n\tp1.Sample = []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{1, 10},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[2], testL[0]},\n\t\t\tValue:    []int64{1, 100},\n\t\t},\n\t}\n\n\tp2 := testProfile.Copy()\n\tp2.Sample = []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{1, -10},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[2], testL[0]},\n\t\t\tValue:    []int64{1, 100},\n\t\t},\n\t}\n\n\tp3 := testProfile.Copy()\n\tp3.Sample = []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[0]},\n\t\t\tValue:    []int64{10000, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{-10, 3},\n\t\t\tLabel:    map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{1000, -10},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{-9000, 3},\n\t\t\tLabel:    map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{-1, 3},\n\t\t\tLabel:    map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[2], testL[0]},\n\t\t\tValue:    []int64{100, 100},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[1], testL[0]},\n\t\t\tValue:    []int64{100, 3},\n\t\t\tLabel:    map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t},\n\t}\n\n\ttestcases := []struct {\n\t\tdesc           string\n\t\tprof           *profile.Profile\n\t\tvalue, meanDiv func(v []int64) int64\n\t\twantTotal      int64\n\t}{\n\t\t{\n\t\t\tdesc: \"no diff base, all positive values, index 1\",\n\t\t\tprof: p1,\n\t\t\tvalue: func(v []int64) int64 {\n\t\t\t\treturn v[0]\n\t\t\t},\n\t\t\twantTotal: 3,\n\t\t},\n\t\t{\n\t\t\tdesc: \"no diff base, all positive values, index 2\",\n\t\t\tprof: p1,\n\t\t\tvalue: func(v []int64) int64 {\n\t\t\t\treturn v[1]\n\t\t\t},\n\t\t\twantTotal: 111,\n\t\t},\n\t\t{\n\t\t\tdesc: \"no diff base, some negative values\",\n\t\t\tprof: p2,\n\t\t\tvalue: func(v []int64) int64 {\n\t\t\t\treturn v[1]\n\t\t\t},\n\t\t\twantTotal: 111,\n\t\t},\n\t\t{\n\t\t\tdesc: \"diff base, some negative values\",\n\t\t\tprof: p3,\n\t\t\tvalue: func(v []int64) int64 {\n\t\t\t\treturn v[0]\n\t\t\t},\n\t\t\twantTotal: 9111,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tif gotTotal := computeTotal(tc.prof, tc.value, tc.meanDiv); gotTotal != tc.wantTotal {\n\t\t\t\tt.Errorf(\"got total %d, want %v\", gotTotal, tc.wantTotal)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPrintAssemblyErrorMessage(t *testing.T) {\n\tprofile := readProfile(filepath.Join(\"testdata\", \"sample.cpu\"), t)\n\n\tfor _, tc := range []struct {\n\t\tdesc   string\n\t\tsymbol string\n\t\twant   string\n\t}{\n\t\t{\n\t\t\tdesc:   \"no matched symbol in binary\",\n\t\t\tsymbol: \"symbol-not-exist\",\n\t\t\twant:   \"no matches found for regexp symbol-not-exist\",\n\t\t},\n\t\t{\n\t\t\tdesc:   \"no matched address in binary\",\n\t\t\tsymbol: \"0xffffaaaa\",\n\t\t\twant:   \"no matches found for address 0xffffaaaa\",\n\t\t},\n\t\t{\n\t\t\tdesc:   \"matched address in binary but not in the profile\",\n\t\t\tsymbol: \"0x400000\",\n\t\t\twant:   \"address 0x400000 found in binary, but the corresponding symbols do not have samples in the profile\",\n\t\t},\n\t} {\n\t\trpt := New(\n\t\t\tprofile.Copy(),\n\t\t\t&Options{\n\t\t\t\tOutputFormat: List,\n\t\t\t\tSymbol:       regexp.MustCompile(tc.symbol),\n\t\t\t\tSampleValue: func(v []int64) int64 {\n\t\t\t\t\treturn v[1]\n\t\t\t\t},\n\t\t\t\tSampleUnit: profile.SampleType[1].Unit,\n\t\t\t},\n\t\t)\n\n\t\tif err := PrintAssembly(os.Stdout, rpt, &binutils.Binutils{}, -1); err == nil || err.Error() != tc.want {\n\t\t\tt.Errorf(`Got \"%v\", want %q`, err, tc.want)\n\t\t}\n\t}\n}\n\nfunc TestDocURL(t *testing.T) {\n\ttype testCase struct {\n\t\tinput string\n\t\twant  string\n\t}\n\tfor name, c := range map[string]testCase{\n\t\t\"empty\":    {\"\", \"\"},\n\t\t\"http\":     {\"http://example.com/pprof-help\", \"http://example.com/pprof-help\"},\n\t\t\"https\":    {\"https://example.com/pprof-help\", \"https://example.com/pprof-help\"},\n\t\t\"relative\": {\"/foo\", \"\"},\n\t\t\"nonhttp\":  {\"mailto:nobody@example.com\", \"\"},\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprofile := testProfile.Copy()\n\t\t\tprofile.DocURL = c.input\n\t\t\trpt := New(profile, &Options{\n\t\t\t\tOutputFormat: Dot,\n\t\t\t\tSymbol:       regexp.MustCompile(`.`),\n\t\t\t\tTrimPath:     \"/some/path\",\n\t\t\t\tSampleValue:  func(v []int64) int64 { return v[1] },\n\t\t\t\tSampleUnit:   testProfile.SampleType[1].Unit,\n\t\t\t})\n\t\t\tif got := rpt.DocURL(); got != c.want {\n\t\t\t\tt.Errorf(\"bad doc URL %q, expecting %q\", got, c.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDocURLInLabels(t *testing.T) {\n\tconst url = \"http://example.com/pprof-help\"\n\tprofile := testProfile.Copy()\n\tprofile.DocURL = url\n\trpt := New(profile, &Options{\n\t\tOutputFormat: Text,\n\t\tSymbol:       regexp.MustCompile(`.`),\n\t\tTrimPath:     \"/some/path\",\n\t\tSampleValue:  func(v []int64) int64 { return v[1] },\n\t\tSampleUnit:   testProfile.SampleType[1].Unit,\n\t})\n\n\tlabels := fmt.Sprintf(\"%v\", ProfileLabels(rpt))\n\tif !strings.Contains(labels, url) {\n\t\tt.Errorf(\"expected URL %q not found in %s\", url, labels)\n\t}\n}\n\nfunc TestProfileLabels(t *testing.T) {\n\t// Force the local timezone to UTC for the duration of this function to get a\n\t// predictable result out of timezone printing.\n\tdefer func(prev *time.Location) { time.Local = prev }(time.Local)\n\ttime.Local = time.UTC\n\n\tprofile := testProfile.Copy()\n\tprofile.TimeNanos = time.Unix(131, 0).UnixNano()\n\trpt := New(profile, &Options{\n\t\tSampleValue: func(v []int64) int64 { return v[1] },\n\t})\n\n\tconst want = \"Time: 1970-01-01 00:02:11 UTC\"\n\tif labels := ProfileLabels(rpt); !slices.Contains(labels, want) {\n\t\tt.Errorf(\"wanted to find a label containing %q, but found none in %v\", want, labels)\n\t}\n}\n\nfunc BenchmarkReportNewTrimmedGraph(b *testing.B) {\n\tdata := proftest.LargeProfile(b)\n\tprof, err := profile.Parse(bytes.NewBuffer(data))\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\trep := NewDefault(prof, Options{})\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tg, _, _, _ := rep.newTrimmedGraph()\n\t\tif g == nil {\n\t\t\tb.Fatal(\"empty graph\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/report/shortnames.go",
    "content": "// Copyright 2022 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage report\n\nimport (\n\t\"path/filepath\"\n\t\"regexp\"\n\n\t\"github.com/google/pprof/internal/graph\"\n)\n\nvar (\n\tsepRE     = regexp.MustCompile(`::|\\.`)\n\tfileSepRE = regexp.MustCompile(`/`)\n)\n\n// fileNameSuffixes returns a non-empty sequence of shortened file names\n// (in decreasing preference) that can be used to represent name.\nfunc fileNameSuffixes(name string) []string {\n\tif name == \"\" {\n\t\t// Avoid returning \".\" when symbol info is missing\n\t\treturn []string{\"\"}\n\t}\n\treturn allSuffixes(filepath.ToSlash(filepath.Clean(name)), fileSepRE)\n}\n\n// shortNameList returns a non-empty sequence of shortened names\n// (in decreasing preference) that can be used to represent name.\nfunc shortNameList(name string) []string {\n\tname = graph.ShortenFunctionName(name)\n\treturn allSuffixes(name, sepRE)\n}\n\n// allSuffixes returns a list of suffixes (in order of decreasing length)\n// found by splitting at re.\nfunc allSuffixes(name string, re *regexp.Regexp) []string {\n\tseps := re.FindAllStringIndex(name, -1)\n\tresult := make([]string, 0, len(seps)+1)\n\tresult = append(result, name)\n\tfor _, sep := range seps {\n\t\t// Suffix starting just after sep\n\t\tif sep[1] < len(name) {\n\t\t\tresult = append(result, name[sep[1]:])\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "internal/report/shortnames_test.go",
    "content": "package report\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestShortNames(t *testing.T) {\n\ttype testCase struct {\n\t\tname string\n\t\tin   string\n\t\tout  []string\n\t}\n\ttest := func(name, in string, out ...string) testCase {\n\t\treturn testCase{name, in, out}\n\t}\n\n\tfor _, c := range []testCase{\n\t\ttest(\"empty\", \"\", \"\"),\n\t\ttest(\"simple\", \"foo\", \"foo\"),\n\t\ttest(\"trailingsep\", \"foo.bar.\", \"foo.bar.\", \"bar.\"),\n\t\ttest(\"cplusplus\", \"a::b::c\", \"a::b::c\", \"b::c\", \"c\"),\n\t\ttest(\"dotted\", \"a.b.c\", \"a.b.c\", \"b.c\", \"c\"),\n\t\ttest(\"mixed_separators\", \"a::b.c::d\", \"a::b.c::d\", \"b.c::d\", \"c::d\", \"d\"),\n\t\ttest(\"call_operator\", \"foo::operator()\", \"foo::operator()\", \"operator()\"),\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tgot := shortNameList(c.in)\n\t\t\tif !reflect.DeepEqual(c.out, got) {\n\t\t\t\tt.Errorf(\"shortNameList(%q) = %#v, expecting %#v\", c.in, got, c.out)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFileNameSuffixes(t *testing.T) {\n\ttype testCase struct {\n\t\tname string\n\t\tin   string\n\t\tout  []string\n\t}\n\ttest := func(name, in string, out ...string) testCase {\n\t\treturn testCase{name, in, out}\n\t}\n\n\tfor _, c := range []testCase{\n\t\ttest(\"empty\", \"\", \"\"),\n\t\ttest(\"simple\", \"foo\", \"foo\"),\n\t\ttest(\"manypaths\", \"a/b/c\", \"a/b/c\", \"b/c\", \"c\"),\n\t\ttest(\"leading\", \"/a/b\", \"/a/b\", \"a/b\", \"b\"),\n\t\ttest(\"trailing\", \"a/b\", \"a/b\", \"b\"),\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tgot := fileNameSuffixes(c.in)\n\t\t\tif !reflect.DeepEqual(c.out, got) {\n\t\t\t\tt.Errorf(\"fileNameSuffixes(%q) = %#v, expecting %#v\", c.in, got, c.out)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/report/source.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage report\n\n// This file contains routines related to the generation of annotated\n// source listings.\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/graph\"\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// printSource prints an annotated source listing, include all\n// functions with samples that match the regexp rpt.options.symbol.\n// The sources are sorted by function name and then by filename to\n// eliminate potential nondeterminism.\nfunc printSource(w io.Writer, rpt *Report) error {\n\to := rpt.options\n\tg := rpt.newGraph(nil)\n\n\t// Identify all the functions that match the regexp provided.\n\t// Group nodes for each matching function.\n\tvar functions graph.Nodes\n\tfunctionNodes := make(map[string]graph.Nodes)\n\tfor _, n := range g.Nodes {\n\t\tif !o.Symbol.MatchString(n.Info.Name) {\n\t\t\tcontinue\n\t\t}\n\t\tif functionNodes[n.Info.Name] == nil {\n\t\t\tfunctions = append(functions, n)\n\t\t}\n\t\tfunctionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)\n\t}\n\tfunctions.Sort(graph.NameOrder)\n\n\tif len(functionNodes) == 0 {\n\t\treturn fmt.Errorf(\"no matches found for regexp: %s\", o.Symbol)\n\t}\n\n\tsourcePath := o.SourcePath\n\tif sourcePath == \"\" {\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not stat current dir: %v\", err)\n\t\t}\n\t\tsourcePath = wd\n\t}\n\treader := newSourceReader(sourcePath, o.TrimPath)\n\n\tfmt.Fprintf(w, \"Total: %s\\n\", rpt.formatValue(rpt.total))\n\tfor _, fn := range functions {\n\t\tname := fn.Info.Name\n\n\t\t// Identify all the source files associated to this function.\n\t\t// Group nodes for each source file.\n\t\tvar sourceFiles graph.Nodes\n\t\tfileNodes := make(map[string]graph.Nodes)\n\t\tfor _, n := range functionNodes[name] {\n\t\t\tif n.Info.File == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif fileNodes[n.Info.File] == nil {\n\t\t\t\tsourceFiles = append(sourceFiles, n)\n\t\t\t}\n\t\t\tfileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)\n\t\t}\n\n\t\tif len(sourceFiles) == 0 {\n\t\t\tfmt.Fprintf(w, \"No source information for %s\\n\", name)\n\t\t\tcontinue\n\t\t}\n\n\t\tsourceFiles.Sort(graph.FileOrder)\n\n\t\t// Print each file associated with this function.\n\t\tfor _, fl := range sourceFiles {\n\t\t\tfilename := fl.Info.File\n\t\t\tfns := fileNodes[filename]\n\t\t\tflatSum, cumSum := fns.Sum()\n\n\t\t\tfnodes, _, err := getSourceFromFile(filename, reader, fns, 0, 0)\n\t\t\tfmt.Fprintf(w, \"ROUTINE ======================== %s in %s\\n\", name, filename)\n\t\t\tfmt.Fprintf(w, \"%10s %10s (flat, cum) %s of Total\\n\",\n\t\t\t\trpt.formatValue(flatSum), rpt.formatValue(cumSum),\n\t\t\t\tmeasurement.Percentage(cumSum, rpt.total))\n\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(w, \" Error: %v\\n\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, fn := range fnodes {\n\t\t\t\tfmt.Fprintf(w, \"%10s %10s %6d:%s\\n\", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// sourcePrinter holds state needed for generating source+asm HTML listing.\ntype sourcePrinter struct {\n\treader     *sourceReader\n\tsynth      *synthCode\n\tobjectTool plugin.ObjTool\n\tobjects    map[string]plugin.ObjFile  // Opened object files\n\tsym        *regexp.Regexp             // May be nil\n\tfiles      map[string]*sourceFile     // Set of files to print.\n\tinsts      map[uint64]instructionInfo // Instructions of interest (keyed by address).\n\n\t// Set of function names that we are interested in (because they had\n\t// a sample and match sym).\n\tinterest map[string]bool\n\n\t// Mapping from system function names to printable names.\n\tprettyNames map[string]string\n}\n\n// addrInfo holds information for an address we are interested in.\ntype addrInfo struct {\n\tloc *profile.Location // Always non-nil\n\tobj plugin.ObjFile    // May be nil\n}\n\n// instructionInfo holds collected information for an instruction.\ntype instructionInfo struct {\n\tobjAddr   uint64 // Address in object file (with base subtracted out)\n\tlength    int    // Instruction length in bytes\n\tdisasm    string // Disassembly of instruction\n\tfile      string // For top-level function in which instruction occurs\n\tline      int    // For top-level function in which instruction occurs\n\tflat, cum int64  // Samples to report (divisor already applied)\n}\n\n// sourceFile contains collected information for files we will print.\ntype sourceFile struct {\n\tfname    string\n\tcum      int64\n\tflat     int64\n\tlines    map[int][]sourceInst // Instructions to show per line\n\tfuncName map[int]string       // Function name per line\n}\n\n// sourceInst holds information for an instruction to be displayed.\ntype sourceInst struct {\n\taddr  uint64\n\tstack []callID // Inlined call-stack\n}\n\n// sourceFunction contains information for a contiguous range of lines per function we\n// will print.\ntype sourceFunction struct {\n\tname       string\n\tbegin, end int // Line numbers (end is not included in the range)\n\tflat, cum  int64\n}\n\n// addressRange is a range of addresses plus the object file that contains it.\ntype addressRange struct {\n\tbegin, end uint64\n\tobj        plugin.ObjFile\n\tmapping    *profile.Mapping\n\tscore      int64 // Used to order ranges for processing\n}\n\n// WebListData holds the data needed to generate HTML source code listing.\ntype WebListData struct {\n\tTotal string\n\tFiles []WebListFile\n}\n\n// WebListFile holds the per-file information for HTML source code listing.\ntype WebListFile struct {\n\tFuncs []WebListFunc\n}\n\n// WebListFunc holds the per-function information for HTML source code listing.\ntype WebListFunc struct {\n\tName       string\n\tFile       string\n\tFlat       string\n\tCumulative string\n\tPercent    string\n\tLines      []WebListLine\n}\n\n// WebListLine holds the per-source-line information for HTML source code listing.\ntype WebListLine struct {\n\tSrcLine      string\n\tHTMLClass    string\n\tLine         int\n\tFlat         string\n\tCumulative   string\n\tInstructions []WebListInstruction\n}\n\n// WebListInstruction holds the per-instruction information for HTML source code listing.\ntype WebListInstruction struct {\n\tNewBlock     bool // Insert marker that indicates separation from previous block\n\tFlat         string\n\tCumulative   string\n\tSynthetic    bool\n\tAddress      uint64\n\tDisasm       string\n\tFileLine     string\n\tInlinedCalls []WebListCall\n}\n\n// WebListCall holds the per-inlined-call information for HTML source code listing.\ntype WebListCall struct {\n\tSrcLine  string\n\tFileBase string\n\tLine     int\n}\n\n// MakeWebList returns an annotated source listing of rpt.\n// rpt.prof should contain inlined call info.\nfunc MakeWebList(rpt *Report, obj plugin.ObjTool, maxFiles int) (WebListData, error) {\n\tsourcePath := rpt.options.SourcePath\n\tif sourcePath == \"\" {\n\t\twd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn WebListData{}, fmt.Errorf(\"could not stat current dir: %v\", err)\n\t\t}\n\t\tsourcePath = wd\n\t}\n\tsp := newSourcePrinter(rpt, obj, sourcePath)\n\tif len(sp.interest) == 0 {\n\t\treturn WebListData{}, fmt.Errorf(\"no matches found for regexp: %s\", rpt.options.Symbol)\n\t}\n\tdefer sp.close()\n\treturn sp.generate(maxFiles, rpt), nil\n}\n\nfunc newSourcePrinter(rpt *Report, obj plugin.ObjTool, sourcePath string) *sourcePrinter {\n\tsp := &sourcePrinter{\n\t\treader:      newSourceReader(sourcePath, rpt.options.TrimPath),\n\t\tsynth:       newSynthCode(rpt.prof.Mapping),\n\t\tobjectTool:  obj,\n\t\tobjects:     map[string]plugin.ObjFile{},\n\t\tsym:         rpt.options.Symbol,\n\t\tfiles:       map[string]*sourceFile{},\n\t\tinsts:       map[uint64]instructionInfo{},\n\t\tprettyNames: map[string]string{},\n\t\tinterest:    map[string]bool{},\n\t}\n\n\t// If the regexp source can be parsed as an address, also match\n\t// functions that land on that address.\n\tvar address *uint64\n\tif sp.sym != nil {\n\t\tif hex, err := strconv.ParseUint(sp.sym.String(), 0, 64); err == nil {\n\t\t\taddress = &hex\n\t\t}\n\t}\n\n\taddrs := map[uint64]addrInfo{}\n\tflat := map[uint64]int64{}\n\tcum := map[uint64]int64{}\n\n\t// Record an interest in the function corresponding to lines[index].\n\tmarkInterest := func(addr uint64, loc *profile.Location, index int) {\n\t\tfn := loc.Line[index]\n\t\tif fn.Function == nil {\n\t\t\treturn\n\t\t}\n\t\tsp.interest[fn.Function.Name] = true\n\t\tsp.interest[fn.Function.SystemName] = true\n\t\tif _, ok := addrs[addr]; !ok {\n\t\t\taddrs[addr] = addrInfo{loc, sp.objectFile(loc.Mapping)}\n\t\t}\n\t}\n\n\t// See if sp.sym matches line.\n\tmatches := func(line profile.Line) bool {\n\t\tif line.Function == nil {\n\t\t\treturn false\n\t\t}\n\t\treturn sp.sym.MatchString(line.Function.Name) ||\n\t\t\tsp.sym.MatchString(line.Function.SystemName) ||\n\t\t\tsp.sym.MatchString(line.Function.Filename)\n\t}\n\n\t// Extract sample counts and compute set of interesting functions.\n\tfor _, sample := range rpt.prof.Sample {\n\t\tvalue := rpt.options.SampleValue(sample.Value)\n\t\tif rpt.options.SampleMeanDivisor != nil {\n\t\t\tdiv := rpt.options.SampleMeanDivisor(sample.Value)\n\t\t\tif div != 0 {\n\t\t\t\tvalue /= div\n\t\t\t}\n\t\t}\n\n\t\t// Find call-sites matching sym.\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tloc := sample.Location[i]\n\t\t\tfor _, line := range loc.Line {\n\t\t\t\tif line.Function == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsp.prettyNames[line.Function.SystemName] = line.Function.Name\n\t\t\t}\n\n\t\t\taddr := loc.Address\n\t\t\tif addr == 0 {\n\t\t\t\t// Some profiles are missing valid addresses.\n\t\t\t\taddr = sp.synth.address(loc)\n\t\t\t}\n\n\t\t\tcum[addr] += value\n\t\t\tif i == 0 {\n\t\t\t\tflat[addr] += value\n\t\t\t}\n\n\t\t\tif sp.sym == nil || (address != nil && addr == *address) {\n\t\t\t\t// Interested in top-level entry of stack.\n\t\t\t\tif len(loc.Line) > 0 {\n\t\t\t\t\tmarkInterest(addr, loc, len(loc.Line)-1)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Search in inlined stack for a match.\n\t\t\tmatchFile := (loc.Mapping != nil && sp.sym.MatchString(loc.Mapping.File))\n\t\t\tfor j, line := range loc.Line {\n\t\t\t\tif (j == 0 && matchFile) || matches(line) {\n\t\t\t\t\tmarkInterest(addr, loc, j)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsp.expandAddresses(rpt, addrs, flat)\n\tsp.initSamples(flat, cum)\n\treturn sp\n}\n\nfunc (sp *sourcePrinter) close() {\n\tfor _, objFile := range sp.objects {\n\t\tif objFile != nil {\n\t\t\tobjFile.Close()\n\t\t}\n\t}\n}\n\nfunc (sp *sourcePrinter) expandAddresses(rpt *Report, addrs map[uint64]addrInfo, flat map[uint64]int64) {\n\t// We found interesting addresses (ones with non-zero samples) above.\n\t// Get covering address ranges and disassemble the ranges.\n\tranges, unprocessed := sp.splitIntoRanges(rpt.prof, addrs, flat)\n\tsp.handleUnprocessed(addrs, unprocessed)\n\n\t// Trim ranges if there are too many.\n\tconst maxRanges = 25\n\tsort.Slice(ranges, func(i, j int) bool {\n\t\treturn ranges[i].score > ranges[j].score\n\t})\n\tif len(ranges) > maxRanges {\n\t\tranges = ranges[:maxRanges]\n\t}\n\n\tfor _, r := range ranges {\n\t\tobjBegin, err := r.obj.ObjAddr(r.begin)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Failed to compute objdump address for range start %x: %v\\n\", r.begin, err)\n\t\t\tcontinue\n\t\t}\n\t\tobjEnd, err := r.obj.ObjAddr(r.end)\n\t\tif err != nil {\n\t\t\tfmt.Fprintf(os.Stderr, \"Failed to compute objdump address for range end %x: %v\\n\", r.end, err)\n\t\t\tcontinue\n\t\t}\n\t\tbase := r.begin - objBegin\n\t\tinsts, err := sp.objectTool.Disasm(r.mapping.File, objBegin, objEnd, rpt.options.IntelSyntax)\n\t\tif err != nil {\n\t\t\t// TODO(sanjay): Report that the covered addresses are missing.\n\t\t\tcontinue\n\t\t}\n\n\t\tvar lastFrames []plugin.Frame\n\t\tvar lastAddr, maxAddr uint64\n\t\tfor i, inst := range insts {\n\t\t\taddr := inst.Addr + base\n\n\t\t\t// Guard against duplicate output from Disasm.\n\t\t\tif addr <= maxAddr {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmaxAddr = addr\n\n\t\t\tlength := 1\n\t\t\tif i+1 < len(insts) && insts[i+1].Addr > inst.Addr {\n\t\t\t\t// Extend to next instruction.\n\t\t\t\tlength = int(insts[i+1].Addr - inst.Addr)\n\t\t\t}\n\n\t\t\t// Get inlined-call-stack for address.\n\t\t\tframes, err := r.obj.SourceLine(addr)\n\t\t\tif err != nil {\n\t\t\t\t// Construct a frame from disassembler output.\n\t\t\t\tframes = []plugin.Frame{{Func: inst.Function, File: inst.File, Line: inst.Line}}\n\t\t\t}\n\n\t\t\tx := instructionInfo{objAddr: inst.Addr, length: length, disasm: inst.Text}\n\t\t\tif len(frames) > 0 {\n\t\t\t\t// We could consider using the outer-most caller's source\n\t\t\t\t// location so we give the some hint as to where the\n\t\t\t\t// inlining happened that led to this instruction. So for\n\t\t\t\t// example, suppose we have the following (inlined) call\n\t\t\t\t// chains for this instruction:\n\t\t\t\t//   F1->G->H\n\t\t\t\t//   F2->G->H\n\t\t\t\t// We could tag the instructions from the first call with\n\t\t\t\t// F1 and instructions from the second call with F2. But\n\t\t\t\t// that leads to a somewhat confusing display. So for now,\n\t\t\t\t// we stick with just the inner-most location (i.e., H).\n\t\t\t\t// In the future we will consider changing the display to\n\t\t\t\t// make caller info more visible.\n\t\t\t\tindex := 0 // Inner-most frame\n\t\t\t\tx.file = frames[index].File\n\t\t\t\tx.line = frames[index].Line\n\t\t\t}\n\t\t\tsp.insts[addr] = x\n\n\t\t\t// We sometimes get instructions with a zero reported line number.\n\t\t\t// Make such instructions have the same line info as the preceding\n\t\t\t// instruction, if an earlier instruction is found close enough.\n\t\t\tconst neighborhood = 32\n\t\t\tif len(frames) > 0 && frames[0].Line != 0 {\n\t\t\t\tlastFrames = frames\n\t\t\t\tlastAddr = addr\n\t\t\t} else if (addr-lastAddr <= neighborhood) && lastFrames != nil {\n\t\t\t\tframes = lastFrames\n\t\t\t}\n\n\t\t\tsp.addStack(addr, frames)\n\t\t}\n\t}\n}\n\nfunc (sp *sourcePrinter) addStack(addr uint64, frames []plugin.Frame) {\n\t// See if the stack contains a function we are interested in.\n\tfor i, f := range frames {\n\t\tif !sp.interest[f.Func] {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Record sub-stack under frame's file/line.\n\t\tfname := canonicalizeFileName(f.File)\n\t\tfile := sp.files[fname]\n\t\tif file == nil {\n\t\t\tfile = &sourceFile{\n\t\t\t\tfname:    fname,\n\t\t\t\tlines:    map[int][]sourceInst{},\n\t\t\t\tfuncName: map[int]string{},\n\t\t\t}\n\t\t\tsp.files[fname] = file\n\t\t}\n\t\tcallees := frames[:i]\n\t\tstack := make([]callID, 0, len(callees))\n\t\tfor j := len(callees) - 1; j >= 0; j-- { // Reverse so caller is first\n\t\t\tstack = append(stack, callID{\n\t\t\t\tfile: callees[j].File,\n\t\t\t\tline: callees[j].Line,\n\t\t\t})\n\t\t}\n\t\tfile.lines[f.Line] = append(file.lines[f.Line], sourceInst{addr, stack})\n\n\t\t// Remember the first function name encountered per source line\n\t\t// and assume that line belongs to that function.\n\t\tif _, ok := file.funcName[f.Line]; !ok {\n\t\t\tfile.funcName[f.Line] = f.Func\n\t\t}\n\t}\n}\n\n// synthAsm is the special disassembler value used for instructions without an object file.\nconst synthAsm = \"\"\n\n// handleUnprocessed handles addresses that were skipped by splitIntoRanges because they\n// did not belong to a known object file.\nfunc (sp *sourcePrinter) handleUnprocessed(addrs map[uint64]addrInfo, unprocessed []uint64) {\n\t// makeFrames synthesizes a []plugin.Frame list for the specified address.\n\t// The result will typically have length 1, but may be longer if address corresponds\n\t// to inlined calls.\n\tmakeFrames := func(addr uint64) []plugin.Frame {\n\t\tloc := addrs[addr].loc\n\t\tstack := make([]plugin.Frame, 0, len(loc.Line))\n\t\tfor _, line := range loc.Line {\n\t\t\tfn := line.Function\n\t\t\tif fn == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tstack = append(stack, plugin.Frame{\n\t\t\t\tFunc: fn.Name,\n\t\t\t\tFile: fn.Filename,\n\t\t\t\tLine: int(line.Line),\n\t\t\t})\n\t\t}\n\t\treturn stack\n\t}\n\n\tfor _, addr := range unprocessed {\n\t\tframes := makeFrames(addr)\n\t\tx := instructionInfo{\n\t\t\tobjAddr: addr,\n\t\t\tlength:  1,\n\t\t\tdisasm:  synthAsm,\n\t\t}\n\t\tif len(frames) > 0 {\n\t\t\tx.file = frames[0].File\n\t\t\tx.line = frames[0].Line\n\t\t}\n\t\tsp.insts[addr] = x\n\n\t\tsp.addStack(addr, frames)\n\t}\n}\n\n// splitIntoRanges converts the set of addresses we are interested in into a set of address\n// ranges to disassemble. It also returns the set of addresses found that did not have an\n// associated object file and were therefore not added to an address range.\nfunc (sp *sourcePrinter) splitIntoRanges(prof *profile.Profile, addrMap map[uint64]addrInfo, flat map[uint64]int64) ([]addressRange, []uint64) {\n\t// Partition addresses into two sets: ones with a known object file, and ones without.\n\tvar addrs, unprocessed []uint64\n\tfor addr, info := range addrMap {\n\t\tif info.obj != nil {\n\t\t\taddrs = append(addrs, addr)\n\t\t} else {\n\t\t\tunprocessed = append(unprocessed, addr)\n\t\t}\n\t}\n\tslices.Sort(addrs)\n\n\tconst expand = 500 // How much to expand range to pick up nearby addresses.\n\tvar result []addressRange\n\tfor i, n := 0, len(addrs); i < n; {\n\t\tbegin, end := addrs[i], addrs[i]\n\t\tsum := flat[begin]\n\t\ti++\n\n\t\tinfo := addrMap[begin]\n\t\tm := info.loc.Mapping\n\t\tobj := info.obj // Non-nil because of the partitioning done above.\n\n\t\t// Find following addresses that are close enough to addrs[i].\n\t\tfor i < n && addrs[i] <= end+2*expand && addrs[i] < m.Limit {\n\t\t\t// When we expand ranges by \"expand\" on either side, the ranges\n\t\t\t// for addrs[i] and addrs[i-1] will merge.\n\t\t\tend = addrs[i]\n\t\t\tsum += flat[end]\n\t\t\ti++\n\t\t}\n\t\tif m.Start-begin >= expand {\n\t\t\tbegin -= expand\n\t\t} else {\n\t\t\tbegin = m.Start\n\t\t}\n\t\tif m.Limit-end >= expand {\n\t\t\tend += expand\n\t\t} else {\n\t\t\tend = m.Limit\n\t\t}\n\n\t\tresult = append(result, addressRange{begin, end, obj, m, sum})\n\t}\n\treturn result, unprocessed\n}\n\nfunc (sp *sourcePrinter) initSamples(flat, cum map[uint64]int64) {\n\tfor addr, inst := range sp.insts {\n\t\t// Move all samples that were assigned to the middle of an instruction to the\n\t\t// beginning of that instruction. This takes care of samples that were recorded\n\t\t// against pc+1.\n\t\tinstEnd := addr + uint64(inst.length)\n\t\tfor p := addr; p < instEnd; p++ {\n\t\t\tinst.flat += flat[p]\n\t\t\tinst.cum += cum[p]\n\t\t}\n\t\tsp.insts[addr] = inst\n\t}\n}\n\nfunc (sp *sourcePrinter) generate(maxFiles int, rpt *Report) WebListData {\n\t// Finalize per-file counts.\n\tfor _, file := range sp.files {\n\t\tseen := map[uint64]bool{}\n\t\tfor _, line := range file.lines {\n\t\t\tfor _, x := range line {\n\t\t\t\tif seen[x.addr] {\n\t\t\t\t\t// Same address can be displayed multiple times in a file\n\t\t\t\t\t// (e.g., if we show multiple inlined functions).\n\t\t\t\t\t// Avoid double-counting samples in this case.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tseen[x.addr] = true\n\t\t\t\tinst := sp.insts[x.addr]\n\t\t\t\tfile.cum += inst.cum\n\t\t\t\tfile.flat += inst.flat\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get sorted list of files to print.\n\tvar files []*sourceFile\n\tfor _, f := range sp.files {\n\t\tfiles = append(files, f)\n\t}\n\torder := func(i, j int) bool { return files[i].flat > files[j].flat }\n\tif maxFiles < 0 {\n\t\t// Order by name for compatibility with old code.\n\t\torder = func(i, j int) bool { return files[i].fname < files[j].fname }\n\t\tmaxFiles = len(files)\n\t}\n\tsort.Slice(files, order)\n\tresult := WebListData{\n\t\tTotal: rpt.formatValue(rpt.total),\n\t}\n\tfor i, f := range files {\n\t\tif i < maxFiles {\n\t\t\tresult.Files = append(result.Files, sp.generateFile(f, rpt))\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (sp *sourcePrinter) generateFile(f *sourceFile, rpt *Report) WebListFile {\n\tvar result WebListFile\n\tfor _, fn := range sp.functions(f) {\n\t\tif fn.cum == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tlistfn := WebListFunc{\n\t\t\tName:       fn.name,\n\t\t\tFile:       f.fname,\n\t\t\tFlat:       rpt.formatValue(fn.flat),\n\t\t\tCumulative: rpt.formatValue(fn.cum),\n\t\t\tPercent:    measurement.Percentage(fn.cum, rpt.total),\n\t\t}\n\t\tvar asm []assemblyInstruction\n\t\tfor l := fn.begin; l < fn.end; l++ {\n\t\t\tlineContents, ok := sp.reader.line(f.fname, l)\n\t\t\tif !ok {\n\t\t\t\tif len(f.lines[l]) == 0 {\n\t\t\t\t\t// Outside of range of valid lines and nothing to print.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif l == 0 {\n\t\t\t\t\t// Line number 0 shows up if line number is not known.\n\t\t\t\t\tlineContents = \"<instructions with unknown line numbers>\"\n\t\t\t\t} else {\n\t\t\t\t\t// Past end of file, but have data to print.\n\t\t\t\t\tlineContents = \"???\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make list of assembly instructions.\n\t\t\tasm = asm[:0]\n\t\t\tvar flatSum, cumSum int64\n\t\t\tvar lastAddr uint64\n\t\t\tfor _, inst := range f.lines[l] {\n\t\t\t\taddr := inst.addr\n\t\t\t\tx := sp.insts[addr]\n\t\t\t\tflatSum += x.flat\n\t\t\t\tcumSum += x.cum\n\t\t\t\tstartsBlock := (addr != lastAddr+uint64(sp.insts[lastAddr].length))\n\t\t\t\tlastAddr = addr\n\n\t\t\t\t// divisors already applied, so leave flatDiv,cumDiv as 0\n\t\t\t\tasm = append(asm, assemblyInstruction{\n\t\t\t\t\taddress:     x.objAddr,\n\t\t\t\t\tinstruction: x.disasm,\n\t\t\t\t\tfunction:    fn.name,\n\t\t\t\t\tfile:        x.file,\n\t\t\t\t\tline:        x.line,\n\t\t\t\t\tflat:        x.flat,\n\t\t\t\t\tcum:         x.cum,\n\t\t\t\t\tstartsBlock: startsBlock,\n\t\t\t\t\tinlineCalls: inst.stack,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tlistfn.Lines = append(listfn.Lines, makeWebListLine(l, flatSum, cumSum, lineContents, asm, sp.reader, rpt))\n\t\t}\n\n\t\tresult.Funcs = append(result.Funcs, listfn)\n\t}\n\treturn result\n}\n\n// functions splits apart the lines to show in a file into a list of per-function ranges.\nfunc (sp *sourcePrinter) functions(f *sourceFile) []sourceFunction {\n\tvar funcs []sourceFunction\n\n\t// Get interesting lines in sorted order.\n\tlines := make([]int, 0, len(f.lines))\n\tfor l := range f.lines {\n\t\tlines = append(lines, l)\n\t}\n\tsort.Ints(lines)\n\n\t// Merge adjacent lines that are in same function and not too far apart.\n\tconst mergeLimit = 20\n\tfor _, l := range lines {\n\t\tname := f.funcName[l]\n\t\tif pretty, ok := sp.prettyNames[name]; ok {\n\t\t\t// Use demangled name if available.\n\t\t\tname = pretty\n\t\t}\n\n\t\tfn := sourceFunction{name: name, begin: l, end: l + 1}\n\t\tfor _, x := range f.lines[l] {\n\t\t\tinst := sp.insts[x.addr]\n\t\t\tfn.flat += inst.flat\n\t\t\tfn.cum += inst.cum\n\t\t}\n\n\t\t// See if we should merge into preceding function.\n\t\tif len(funcs) > 0 {\n\t\t\tlast := funcs[len(funcs)-1]\n\t\t\tif l-last.end < mergeLimit && last.name == name {\n\t\t\t\tlast.end = l + 1\n\t\t\t\tlast.flat += fn.flat\n\t\t\t\tlast.cum += fn.cum\n\t\t\t\tfuncs[len(funcs)-1] = last\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Add new function.\n\t\tfuncs = append(funcs, fn)\n\t}\n\n\t// Expand function boundaries to show neighborhood.\n\tconst expand = 5\n\tfor i, f := range funcs {\n\t\tif i == 0 {\n\t\t\t// Extend backwards, stopping at line number 1, but do not disturb 0\n\t\t\t// since that is a special line number that can show up when addr2line\n\t\t\t// cannot determine the real line number.\n\t\t\tif f.begin > expand {\n\t\t\t\tf.begin -= expand\n\t\t\t} else if f.begin > 1 {\n\t\t\t\tf.begin = 1\n\t\t\t}\n\t\t} else {\n\t\t\t// Find gap from predecessor and divide between predecessor and f.\n\t\t\thalfGap := min((f.begin-funcs[i-1].end)/2, expand)\n\t\t\tfuncs[i-1].end += halfGap\n\t\t\tf.begin -= halfGap\n\t\t}\n\t\tfuncs[i] = f\n\t}\n\n\t// Also extend the ending point of the last function.\n\tif len(funcs) > 0 {\n\t\tfuncs[len(funcs)-1].end += expand\n\t}\n\n\treturn funcs\n}\n\n// objectFile return the object for the specified mapping, opening it if necessary.\n// It returns nil on error.\nfunc (sp *sourcePrinter) objectFile(m *profile.Mapping) plugin.ObjFile {\n\tif m == nil {\n\t\treturn nil\n\t}\n\tif object, ok := sp.objects[m.File]; ok {\n\t\treturn object // May be nil if we detected an error earlier.\n\t}\n\tobject, err := sp.objectTool.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)\n\tif err != nil {\n\t\tobject = nil\n\t}\n\tsp.objects[m.File] = object // Cache even on error.\n\treturn object\n}\n\n// makeWebListLine returns the contents of a single line in a web listing. This includes\n// the source line and the corresponding assembly.\nfunc makeWebListLine(lineNo int, flat, cum int64, lineContents string,\n\tassembly []assemblyInstruction, reader *sourceReader, rpt *Report) WebListLine {\n\tline := WebListLine{\n\t\tSrcLine:    lineContents,\n\t\tLine:       lineNo,\n\t\tFlat:       valueOrDot(flat, rpt),\n\t\tCumulative: valueOrDot(cum, rpt),\n\t}\n\n\tif len(assembly) == 0 {\n\t\tline.HTMLClass = \"nop\"\n\t\treturn line\n\t}\n\n\tnestedInfo := false\n\tline.HTMLClass = \"deadsrc\"\n\tfor _, an := range assembly {\n\t\tif len(an.inlineCalls) > 0 || an.instruction != synthAsm {\n\t\t\tnestedInfo = true\n\t\t\tline.HTMLClass = \"livesrc\"\n\t\t}\n\t}\n\n\tif nestedInfo {\n\t\tsrcIndent := indentation(lineContents)\n\t\tline.Instructions = makeWebListInstructions(srcIndent, assembly, reader, rpt)\n\t}\n\treturn line\n}\n\nfunc makeWebListInstructions(srcIndent int, assembly []assemblyInstruction, reader *sourceReader, rpt *Report) []WebListInstruction {\n\tvar result []WebListInstruction\n\tvar curCalls []callID\n\tfor i, an := range assembly {\n\t\tvar fileline string\n\t\tif an.file != \"\" {\n\t\t\tfileline = fmt.Sprintf(\"%s:%d\", template.HTMLEscapeString(filepath.Base(an.file)), an.line)\n\t\t}\n\t\ttext := strings.Repeat(\" \", srcIndent+4+4*len(an.inlineCalls)) + an.instruction\n\t\tinst := WebListInstruction{\n\t\t\tNewBlock:   (an.startsBlock && i != 0),\n\t\t\tFlat:       valueOrDot(an.flat, rpt),\n\t\t\tCumulative: valueOrDot(an.cum, rpt),\n\t\t\tSynthetic:  (an.instruction == synthAsm),\n\t\t\tAddress:    an.address,\n\t\t\tDisasm:     rightPad(text, 80),\n\t\t\tFileLine:   fileline,\n\t\t}\n\n\t\t// Add inlined call context.\n\t\tfor j, c := range an.inlineCalls {\n\t\t\tif j < len(curCalls) && curCalls[j] == c {\n\t\t\t\t// Skip if same as previous instruction.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcurCalls = nil\n\t\t\tfline, ok := reader.line(c.file, c.line)\n\t\t\tif !ok {\n\t\t\t\tfline = \"\"\n\t\t\t}\n\t\t\tsrcCode := strings.Repeat(\" \", srcIndent+4+4*j) + strings.TrimSpace(fline)\n\t\t\tinst.InlinedCalls = append(inst.InlinedCalls, WebListCall{\n\t\t\t\tSrcLine:  rightPad(srcCode, 80),\n\t\t\t\tFileBase: filepath.Base(c.file),\n\t\t\t\tLine:     c.line,\n\t\t\t})\n\t\t}\n\t\tcurCalls = an.inlineCalls\n\n\t\tresult = append(result, inst)\n\t}\n\treturn result\n}\n\n// getSourceFromFile collects the sources of a function from a source\n// file and annotates it with the samples in fns. Returns the sources\n// as nodes, using the info.name field to hold the source code.\nfunc getSourceFromFile(file string, reader *sourceReader, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {\n\tlineNodes := make(map[int]graph.Nodes)\n\n\t// Collect source coordinates from profile.\n\tconst margin = 5 // Lines before first/after last sample.\n\tif start == 0 {\n\t\tif fns[0].Info.StartLine != 0 {\n\t\t\tstart = fns[0].Info.StartLine\n\t\t} else {\n\t\t\tstart = fns[0].Info.Lineno - margin\n\t\t}\n\t} else {\n\t\tstart -= margin\n\t}\n\tif end == 0 {\n\t\tend = fns[0].Info.Lineno\n\t}\n\tend += margin\n\tfor _, n := range fns {\n\t\tlineno := n.Info.Lineno\n\t\tnodeStart := n.Info.StartLine\n\t\tif nodeStart == 0 {\n\t\t\tnodeStart = lineno - margin\n\t\t}\n\t\tnodeEnd := lineno + margin\n\t\tif nodeStart < start {\n\t\t\tstart = nodeStart\n\t\t} else if nodeEnd > end {\n\t\t\tend = nodeEnd\n\t\t}\n\t\tlineNodes[lineno] = append(lineNodes[lineno], n)\n\t}\n\tif start < 1 {\n\t\tstart = 1\n\t}\n\n\tvar src graph.Nodes\n\tfor lineno := start; lineno <= end; lineno++ {\n\t\tline, ok := reader.line(file, lineno)\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tflat, cum := lineNodes[lineno].Sum()\n\t\tsrc = append(src, &graph.Node{\n\t\t\tInfo: graph.NodeInfo{\n\t\t\t\tName:   strings.TrimRight(line, \"\\n\"),\n\t\t\t\tLineno: lineno,\n\t\t\t},\n\t\t\tFlat: flat,\n\t\t\tCum:  cum,\n\t\t})\n\t}\n\tif err := reader.fileError(file); err != nil {\n\t\treturn nil, file, err\n\t}\n\treturn src, file, nil\n}\n\n// sourceReader provides access to source code with caching of file contents.\ntype sourceReader struct {\n\t// searchPath is a filepath.ListSeparator-separated list of directories where\n\t// source files should be searched.\n\tsearchPath string\n\n\t// trimPath is a filepath.ListSeparator-separated list of paths to trim.\n\ttrimPath string\n\n\t// files maps from path name to a list of lines.\n\t// files[*][0] is unused since line numbering starts at 1.\n\tfiles map[string][]string\n\n\t// errors collects errors encountered per file. These errors are\n\t// consulted before returning out of these module.\n\terrors map[string]error\n}\n\nfunc newSourceReader(searchPath, trimPath string) *sourceReader {\n\treturn &sourceReader{\n\t\tsearchPath,\n\t\ttrimPath,\n\t\tmake(map[string][]string),\n\t\tmake(map[string]error),\n\t}\n}\n\nfunc (reader *sourceReader) fileError(path string) error {\n\treturn reader.errors[path]\n}\n\n// line returns the line numbered \"lineno\" in path, or _,false if lineno is out of range.\nfunc (reader *sourceReader) line(path string, lineno int) (string, bool) {\n\tlines, ok := reader.files[path]\n\tif !ok {\n\t\t// Read and cache file contents.\n\t\tlines = []string{\"\"} // Skip 0th line\n\t\tf, err := openSourceFile(path, reader.searchPath, reader.trimPath)\n\t\tif err != nil {\n\t\t\treader.errors[path] = err\n\t\t} else {\n\t\t\ts := bufio.NewScanner(f)\n\t\t\tfor s.Scan() {\n\t\t\t\tlines = append(lines, s.Text())\n\t\t\t}\n\t\t\tf.Close()\n\t\t\tif s.Err() != nil {\n\t\t\t\treader.errors[path] = err\n\t\t\t}\n\t\t}\n\t\treader.files[path] = lines\n\t}\n\tif lineno <= 0 || lineno >= len(lines) {\n\t\treturn \"\", false\n\t}\n\treturn lines[lineno], true\n}\n\n// openSourceFile opens a source file from a name encoded in a profile. File\n// names in a profile after can be relative paths, so search them in each of\n// the paths in searchPath and their parents. In case the profile contains\n// absolute paths, additional paths may be configured to trim from the source\n// paths in the profile. This effectively turns the path into a relative path\n// searching it using searchPath as usual).\nfunc openSourceFile(path, searchPath, trim string) (*os.File, error) {\n\tpath = trimPath(path, trim, searchPath)\n\t// If file is still absolute, require file to exist.\n\tif filepath.IsAbs(path) {\n\t\tf, err := os.Open(path)\n\t\treturn f, err\n\t}\n\t// Scan each component of the path.\n\tfor _, dir := range filepath.SplitList(searchPath) {\n\t\t// Search up for every parent of each possible path.\n\t\tfor {\n\t\t\tfilename := filepath.Join(dir, path)\n\t\t\tif f, err := os.Open(filename); err == nil {\n\t\t\t\treturn f, nil\n\t\t\t}\n\t\t\tparent := filepath.Dir(dir)\n\t\t\tif parent == dir {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdir = parent\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"could not find file %s on path %s\", path, searchPath)\n}\n\n// trimPath cleans up a path by removing prefixes that are commonly\n// found on profiles plus configured prefixes.\n// TODO(aalexand): Consider optimizing out the redundant work done in this\n// function if it proves to matter.\nfunc trimPath(path, trimPath, searchPath string) string {\n\t// Keep path variable intact as it's used below to form the return value.\n\tsPath, searchPath := filepath.ToSlash(path), filepath.ToSlash(searchPath)\n\tif trimPath == \"\" {\n\t\t// If the trim path is not configured, try to guess it heuristically:\n\t\t// search for basename of each search path in the original path and, if\n\t\t// found, strip everything up to and including the basename. So, for\n\t\t// example, given original path \"/some/remote/path/my-project/foo/bar.c\"\n\t\t// and search path \"/my/local/path/my-project\" the heuristic will return\n\t\t// \"/my/local/path/my-project/foo/bar.c\".\n\t\tfor _, dir := range filepath.SplitList(searchPath) {\n\t\t\twant := \"/\" + filepath.Base(dir) + \"/\"\n\t\t\tif found := strings.Index(sPath, want); found != -1 {\n\t\t\t\treturn path[found+len(want):]\n\t\t\t}\n\t\t}\n\t}\n\t// Trim configured trim prefixes.\n\ttrimPaths := append(filepath.SplitList(filepath.ToSlash(trimPath)), \"/proc/self/cwd/./\", \"/proc/self/cwd/\")\n\tfor _, trimPath := range trimPaths {\n\t\tif !strings.HasSuffix(trimPath, \"/\") {\n\t\t\ttrimPath += \"/\"\n\t\t}\n\t\tif strings.HasPrefix(sPath, trimPath) {\n\t\t\treturn path[len(trimPath):]\n\t\t}\n\t}\n\treturn path\n}\n\nfunc indentation(line string) int {\n\tcolumn := 0\n\tfor _, c := range line {\n\t\tswitch c {\n\t\tcase ' ':\n\t\t\tcolumn++\n\t\tcase '\\t':\n\t\t\tcolumn++\n\t\t\tfor column%8 != 0 {\n\t\t\t\tcolumn++\n\t\t\t}\n\t\tdefault:\n\t\t\treturn column\n\t\t}\n\t}\n\treturn column\n}\n\n// rightPad pads the input with spaces on the right-hand-side to make it have\n// at least width n. It treats tabs as enough spaces that lead to the next\n// 8-aligned tab-stop.\nfunc rightPad(s string, n int) string {\n\tvar str strings.Builder\n\n\t// Convert tabs to spaces as we go so padding works regardless of what prefix\n\t// is placed before the result.\n\tcolumn := 0\n\tfor _, c := range s {\n\t\tcolumn++\n\t\tif c == '\\t' {\n\t\t\tstr.WriteRune(' ')\n\t\t\tfor column%8 != 0 {\n\t\t\t\tcolumn++\n\t\t\t\tstr.WriteRune(' ')\n\t\t\t}\n\t\t} else {\n\t\t\tstr.WriteRune(c)\n\t\t}\n\t}\n\tfor column < n {\n\t\tcolumn++\n\t\tstr.WriteRune(' ')\n\t}\n\treturn str.String()\n}\n\nfunc canonicalizeFileName(fname string) string {\n\tfname = strings.TrimPrefix(fname, \"/proc/self/cwd/\")\n\tfname = strings.TrimPrefix(fname, \"./\")\n\treturn filepath.Clean(fname)\n}\n"
  },
  {
    "path": "internal/report/source_html.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage report\n\nimport (\n\t\"html/template\"\n)\n\n// AddSourceTemplates adds templates used by PrintWebList to t.\nfunc AddSourceTemplates(t *template.Template) {\n\ttemplate.Must(t.Parse(`{{define \"weblistcss\"}}` + weblistPageCSS + `{{end}}`))\n\ttemplate.Must(t.Parse(`{{define \"weblistjs\"}}` + weblistPageScript + `{{end}}`))\n}\n\nconst weblistPageCSS = `<style type=\"text/css\">\nbody #content{\nfont-family: sans-serif;\n}\nh1 {\n  font-size: 1.5em;\n}\n.legend {\n  font-size: 1.25em;\n}\n.line, .nop, .unimportant {\n  color: #aaaaaa;\n}\n.inlinesrc {\n  color: #000066;\n}\n.livesrc {\ncursor: pointer;\n}\n.livesrc:hover {\nbackground-color: #eeeeee;\n}\n.asm {\ncolor: #008800;\ndisplay: none;\n}\n</style>`\n\nconst weblistPageScript = `<script type=\"text/javascript\">\nfunction pprof_toggle_asm(e) {\n  var target;\n  if (!e) e = window.event;\n  if (e.target) target = e.target;\n  else if (e.srcElement) target = e.srcElement;\n\n  if (target) {\n    var asm = target.nextSibling;\n    if (asm && asm.className == \"asm\") {\n      asm.style.display = (asm.style.display == \"block\" ? \"\" : \"block\");\n      e.preventDefault();\n      return false;\n    }\n  }\n}\n</script>`\n"
  },
  {
    "path": "internal/report/source_test.go",
    "content": "//  Copyright 2017 Google Inc. All Rights Reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\npackage report\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestWebList(t *testing.T) {\n\tif runtime.GOOS != \"linux\" || runtime.GOARCH != \"amd64\" {\n\t\tt.Skip(\"weblist only tested on x86-64 linux\")\n\t}\n\n\tcpu := readProfile(filepath.Join(\"testdata\", \"sample.cpu\"), t)\n\trpt := New(cpu, &Options{\n\t\tOutputFormat: WebList,\n\t\tSymbol:       regexp.MustCompile(\"busyLoop\"),\n\t\tSampleValue:  func(v []int64) int64 { return v[1] },\n\t\tSampleUnit:   cpu.SampleType[1].Unit,\n\t})\n\tresult, err := MakeWebList(rpt, &binutils.Binutils{}, -1)\n\tif err != nil {\n\t\tt.Fatalf(\"could not generate weblist: %v\", err)\n\t}\n\toutput := fmt.Sprint(result)\n\n\tfor _, expect := range []string{\"func busyLoop\", \"call.*mapassign\"} {\n\t\tif match, _ := regexp.MatchString(expect, output); !match {\n\t\t\tt.Errorf(\"weblist output does not contain '%s':\\n%s\", expect, output)\n\t\t}\n\t}\n}\n\nfunc TestSourceSyntheticAddress(t *testing.T) {\n\ttestSourceMapping(t, true)\n}\n\nfunc TestSourceMissingMapping(t *testing.T) {\n\ttestSourceMapping(t, false)\n}\n\n// testSourceMapping checks that source info is found even when no applicable\n// Mapping/objectFile exists. The locations used in the test are either zero\n// (if zeroAddress is true), or non-zero (otherwise).\nfunc testSourceMapping(t *testing.T, zeroAddress bool) {\n\tnextAddr := uint64(0)\n\n\tmakeLoc := func(name, fname string, line int64) *profile.Location {\n\t\tif !zeroAddress {\n\t\t\tnextAddr++\n\t\t}\n\t\treturn &profile.Location{\n\t\t\tAddress: nextAddr,\n\t\t\tLine: []profile.Line{\n\t\t\t\t{\n\t\t\t\t\tFunction: &profile.Function{Name: name, Filename: fname},\n\t\t\t\t\tLine:     line,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create profile that will need synthetic addresses since it has no mappings.\n\tfoo100 := makeLoc(\"foo\", \"foo.go\", 100)\n\tbar50 := makeLoc(\"bar\", \"bar.go\", 50)\n\tprof := &profile.Profile{\n\t\tSample: []*profile.Sample{\n\t\t\t{\n\t\t\t\tValue:    []int64{9},\n\t\t\t\tLocation: []*profile.Location{foo100, bar50},\n\t\t\t},\n\t\t\t{\n\t\t\t\tValue:    []int64{17},\n\t\t\t\tLocation: []*profile.Location{bar50},\n\t\t\t},\n\t\t},\n\t}\n\trpt := &Report{\n\t\tprof: prof,\n\t\toptions: &Options{\n\t\t\tSymbol:      regexp.MustCompile(\"foo|bar\"),\n\t\t\tSampleValue: func(s []int64) int64 { return s[0] },\n\t\t},\n\t\tformatValue: func(v int64) string { return fmt.Sprint(v) },\n\t}\n\n\tresult, err := MakeWebList(rpt, nil, -1)\n\tif err != nil {\n\t\tt.Fatalf(\"MakeWebList returned unexpected error: %v\", err)\n\t}\n\tgot := fmt.Sprint(result)\n\n\texpect := regexp.MustCompile(\n\t\t`(?s)` + // Allow \".\" to match newline\n\t\t\t`bar\\.go.* 50\\b.* 17 +26 .*` +\n\t\t\t`foo\\.go.* 100\\b.* 9 +9 `)\n\tif !expect.MatchString(got) {\n\t\tt.Errorf(\"expected regular expression %v does not match  output:\\n%s\\n\", expect, got)\n\t}\n}\n\nfunc TestOpenSourceFile(t *testing.T) {\n\ttempdir, err := os.MkdirTemp(\"\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create temp dir: %v\", err)\n\t}\n\tconst lsep = string(filepath.ListSeparator)\n\tfor _, tc := range []struct {\n\t\tdesc       string\n\t\tsearchPath string\n\t\ttrimPath   string\n\t\tfs         []string\n\t\tpath       string\n\t\twantPath   string // If empty, error is wanted.\n\t}{\n\t\t{\n\t\t\tdesc:     \"exact absolute path is found\",\n\t\t\tfs:       []string{\"foo/bar.cc\"},\n\t\t\tpath:     \"$dir/foo/bar.cc\",\n\t\t\twantPath: \"$dir/foo/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"exact relative path is found\",\n\t\t\tsearchPath: \"$dir\",\n\t\t\tfs:         []string{\"foo/bar.cc\"},\n\t\t\tpath:       \"foo/bar.cc\",\n\t\t\twantPath:   \"$dir/foo/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"multiple search path\",\n\t\t\tsearchPath: \"some/path\" + lsep + \"$dir\",\n\t\t\tfs:         []string{\"foo/bar.cc\"},\n\t\t\tpath:       \"foo/bar.cc\",\n\t\t\twantPath:   \"$dir/foo/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"relative path is found in parent dir\",\n\t\t\tsearchPath: \"$dir/foo/bar\",\n\t\t\tfs:         []string{\"bar.cc\", \"foo/bar/baz.cc\"},\n\t\t\tpath:       \"bar.cc\",\n\t\t\twantPath:   \"$dir/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"trims configured prefix\",\n\t\t\tsearchPath: \"$dir\",\n\t\t\ttrimPath:   \"some-path\" + lsep + \"/some/remote/path\",\n\t\t\tfs:         []string{\"my-project/foo/bar.cc\"},\n\t\t\tpath:       \"/some/remote/path/my-project/foo/bar.cc\",\n\t\t\twantPath:   \"$dir/my-project/foo/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc:       \"trims heuristically\",\n\t\t\tsearchPath: \"$dir/my-project\",\n\t\t\tfs:         []string{\"my-project/foo/bar.cc\"},\n\t\t\tpath:       \"/some/remote/path/my-project/foo/bar.cc\",\n\t\t\twantPath:   \"$dir/my-project/foo/bar.cc\",\n\t\t},\n\t\t{\n\t\t\tdesc: \"error when not found\",\n\t\t\tpath: \"foo.cc\",\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif err := os.RemoveAll(tempdir); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to remove dir %q: %v\", tempdir, err)\n\t\t\t\t}\n\t\t\t}()\n\t\t\tfor _, f := range tc.fs {\n\t\t\t\tpath := filepath.Join(tempdir, filepath.FromSlash(f))\n\t\t\t\tdir := filepath.Dir(path)\n\t\t\t\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to create dir %q: %v\", dir, err)\n\t\t\t\t}\n\t\t\t\tif err := os.WriteFile(path, nil, 0644); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to create file %q: %v\", path, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\ttc.searchPath = filepath.FromSlash(strings.ReplaceAll(tc.searchPath, \"$dir\", tempdir))\n\t\t\ttc.path = filepath.FromSlash(strings.Replace(tc.path, \"$dir\", tempdir, 1))\n\t\t\ttc.wantPath = filepath.FromSlash(strings.Replace(tc.wantPath, \"$dir\", tempdir, 1))\n\t\t\tif file, err := openSourceFile(tc.path, tc.searchPath, tc.trimPath); err != nil && tc.wantPath != \"\" {\n\t\t\t\tt.Errorf(\"openSourceFile(%q, %q, %q) = err %v, want path %q\", tc.path, tc.searchPath, tc.trimPath, err, tc.wantPath)\n\t\t\t} else if err == nil {\n\t\t\t\tdefer file.Close()\n\t\t\t\tgotPath := file.Name()\n\t\t\t\tif tc.wantPath == \"\" {\n\t\t\t\t\tt.Errorf(\"openSourceFile(%q, %q, %q) = %q, want error\", tc.path, tc.searchPath, tc.trimPath, gotPath)\n\t\t\t\t} else if gotPath != tc.wantPath {\n\t\t\t\t\tt.Errorf(\"openSourceFile(%q, %q, %q) = %q, want path %q\", tc.path, tc.searchPath, tc.trimPath, gotPath, tc.wantPath)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIndentation(t *testing.T) {\n\tfor _, c := range []struct {\n\t\tstr        string\n\t\twantIndent int\n\t}{\n\t\t{\"\", 0},\n\t\t{\"foobar\", 0},\n\t\t{\"  foo\", 2},\n\t\t{\"\\tfoo\", 8},\n\t\t{\"\\t foo\", 9},\n\t\t{\"  \\tfoo\", 8},\n\t\t{\"       \\tfoo\", 8},\n\t\t{\"        \\tfoo\", 16},\n\t} {\n\t\tif n := indentation(c.str); n != c.wantIndent {\n\t\t\tt.Errorf(\"indentation(%v): got %d, want %d\", c.str, n, c.wantIndent)\n\t\t}\n\t}\n}\n\nfunc TestRightPad(t *testing.T) {\n\tfor _, c := range []struct {\n\t\tpad    int\n\t\tin     string\n\t\texpect string\n\t}{\n\t\t{0, \"\", \"\"},\n\t\t{4, \"\", \"    \"},\n\t\t{4, \"x\", \"x   \"},\n\t\t{4, \"abcd\", \"abcd\"},   // No padding because of overflow\n\t\t{4, \"abcde\", \"abcde\"}, // No padding because of overflow\n\t\t{10, \"\\tx\", \"        x \"},\n\t\t{10, \"w\\txy\\tz\", \"w       xy      z\"},\n\t\t{20, \"w\\txy\\tz\", \"w       xy      z   \"},\n\t} {\n\t\tout := rightPad(c.in, c.pad)\n\t\tif out != c.expect {\n\t\t\tt.Errorf(\"rightPad(%q, %d): got %q, want %q\", c.in, c.pad, out, c.expect)\n\t\t}\n\t}\n}\n\nfunc readProfile(fname string, t *testing.T) *profile.Profile {\n\tfile, err := os.Open(fname)\n\tif err != nil {\n\t\tt.Fatalf(\"%s: could not open profile: %v\", fname, err)\n\t}\n\tdefer file.Close()\n\tp, err := profile.Parse(file)\n\tif err != nil {\n\t\tt.Fatalf(\"%s: could not parse profile: %v\", fname, err)\n\t}\n\n\t// Fix file names so they do not include absolute path names.\n\tfix := func(s string) string {\n\t\tconst testdir = \"/internal/report/\"\n\t\tpos := strings.Index(s, testdir)\n\t\tif pos == -1 {\n\t\t\treturn s\n\t\t}\n\t\treturn s[pos+len(testdir):]\n\t}\n\tfor _, m := range p.Mapping {\n\t\tm.File = fix(m.File)\n\t}\n\tfor _, f := range p.Function {\n\t\tf.Filename = fix(f.Filename)\n\t}\n\n\treturn p\n}\n"
  },
  {
    "path": "internal/report/stacks.go",
    "content": "// Copyright 2022 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage report\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"path/filepath\"\n\n\t\"github.com/google/pprof/internal/measurement\"\n\t\"github.com/google/pprof/profile\"\n)\n\n// StackSet holds a set of stacks corresponding to a profile.\n//\n// Slices in StackSet and the types it contains are always non-nil,\n// which makes Javascript code that uses the JSON encoding less error-prone.\ntype StackSet struct {\n\tTotal   int64         // Total value of the profile.\n\tScale   float64       // Multiplier to generate displayed value\n\tType    string        // Profile type. E.g., \"cpu\".\n\tUnit    string        // One of \"B\", \"s\", \"GCU\", or \"\" (if unknown)\n\tStacks  []Stack       // List of stored stacks\n\tSources []StackSource // Mapping from source index to info\n\treport  *Report\n}\n\n// Stack holds a single stack instance.\ntype Stack struct {\n\tValue   int64 // Total value for all samples of this stack.\n\tSources []int // Indices in StackSet.Sources (callers before callees).\n}\n\n// StackSource holds function/location info for a stack entry.\ntype StackSource struct {\n\tFullName   string\n\tFileName   string\n\tUniqueName string // Disambiguates functions with same names\n\tInlined    bool   // If true this source was inlined into its caller\n\n\t// Alternative names to display (with decreasing lengths) to make text fit.\n\t// Guaranteed to be non-empty.\n\tDisplay []string\n\n\t// Places holds the list of stack slots where this source occurs.\n\t// In particular, if [a,b] is an element in Places,\n\t// StackSet.Stacks[a].Sources[b] points to this source.\n\t//\n\t// No stack will be referenced twice in the Places slice for a given\n\t// StackSource. In case of recursion, Places will contain the outer-most\n\t// entry in the recursive stack. E.g., if stack S has source X at positions\n\t// 4,6,9,10, the Places entry for X will contain [S,4].\n\tPlaces []StackSlot\n\n\t// Combined count of stacks where this source is the leaf.\n\tSelf int64\n\n\t// Color number to use for this source.\n\t// Colors with high numbers than supported may be treated as zero.\n\tColor int\n}\n\n// StackSlot identifies a particular StackSlot.\ntype StackSlot struct {\n\tStack int // Index in StackSet.Stacks\n\tPos   int // Index in Stack.Sources\n}\n\n// Stacks returns a StackSet for the profile in rpt.\nfunc (rpt *Report) Stacks() StackSet {\n\t// Get scale for converting to default unit of the right type.\n\tscale, unit := measurement.Scale(1, rpt.options.SampleUnit, \"default\")\n\tif unit == \"default\" {\n\t\tunit = \"\"\n\t}\n\tif rpt.options.Ratio > 0 {\n\t\tscale *= rpt.options.Ratio\n\t}\n\ts := &StackSet{\n\t\tTotal:   rpt.total,\n\t\tScale:   scale,\n\t\tType:    rpt.options.SampleType,\n\t\tUnit:    unit,\n\t\tStacks:  []Stack{},       // Ensure non-nil\n\t\tSources: []StackSource{}, // Ensure non-nil\n\t\treport:  rpt,\n\t}\n\ts.makeInitialStacks(rpt)\n\ts.fillPlaces()\n\treturn *s\n}\n\nfunc (s *StackSet) makeInitialStacks(rpt *Report) {\n\ttype key struct {\n\t\tfuncName string\n\t\tfileName string\n\t\tline     int64\n\t\tcolumn   int64\n\t\tinlined  bool\n\t}\n\tsrcs := map[key]int{} // Sources identified so far.\n\tseenFunctions := map[string]bool{}\n\tunknownIndex := 1\n\n\tgetSrc := func(line profile.Line, inlined bool) int {\n\t\tfn := line.Function\n\t\tif fn == nil {\n\t\t\tfn = &profile.Function{Name: fmt.Sprintf(\"?%d?\", unknownIndex)}\n\t\t\tunknownIndex++\n\t\t}\n\n\t\tk := key{fn.Name, fn.Filename, line.Line, line.Column, inlined}\n\t\tif i, ok := srcs[k]; ok {\n\t\t\treturn i\n\t\t}\n\n\t\tfileName := trimPath(fn.Filename, rpt.options.TrimPath, rpt.options.SourcePath)\n\t\tx := StackSource{\n\t\t\tFileName: fileName,\n\t\t\tInlined:  inlined,\n\t\t\tPlaces:   []StackSlot{}, // Ensure Places is non-nil\n\t\t}\n\t\tif fn.Name != \"\" {\n\t\t\tx.FullName = addLineInfo(fn.Name, line)\n\t\t\tx.Display = shortNameList(x.FullName)\n\t\t\tx.Color = pickColor(packageName(fn.Name))\n\t\t} else { // Use file name, e.g., for file granularity display.\n\t\t\tx.FullName = addLineInfo(fileName, line)\n\t\t\tx.Display = fileNameSuffixes(x.FullName)\n\t\t\tx.Color = pickColor(filepath.Dir(fileName))\n\t\t}\n\n\t\tif !seenFunctions[x.FullName] {\n\t\t\tx.UniqueName = x.FullName\n\t\t\tseenFunctions[x.FullName] = true\n\t\t} else {\n\t\t\t// Assign a different name so pivoting picks this function.\n\t\t\tx.UniqueName = fmt.Sprint(x.FullName, \"#\", fn.ID)\n\t\t}\n\n\t\ts.Sources = append(s.Sources, x)\n\t\tsrcs[k] = len(s.Sources) - 1\n\t\treturn len(s.Sources) - 1\n\t}\n\n\t// Synthesized root location that will be placed at the beginning of each stack.\n\ts.Sources = []StackSource{{\n\t\tFullName: \"root\",\n\t\tDisplay:  []string{\"root\"},\n\t\tPlaces:   []StackSlot{},\n\t}}\n\n\tfor _, sample := range rpt.prof.Sample {\n\t\tvalue := rpt.options.SampleValue(sample.Value)\n\t\tstack := Stack{Value: value, Sources: []int{0}} // Start with the root\n\n\t\t// Note: we need to reverse the order in the produced stack.\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tloc := sample.Location[i]\n\t\t\tfor j := len(loc.Line) - 1; j >= 0; j-- {\n\t\t\t\tline := loc.Line[j]\n\t\t\t\tinlined := (j != len(loc.Line)-1)\n\t\t\t\tstack.Sources = append(stack.Sources, getSrc(line, inlined))\n\t\t\t}\n\t\t}\n\n\t\tleaf := stack.Sources[len(stack.Sources)-1]\n\t\ts.Sources[leaf].Self += value\n\t\ts.Stacks = append(s.Stacks, stack)\n\t}\n}\n\nfunc (s *StackSet) fillPlaces() {\n\tfor i, stack := range s.Stacks {\n\t\tseenSrcs := map[int]bool{}\n\t\tfor j, src := range stack.Sources {\n\t\t\tif seenSrcs[src] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseenSrcs[src] = true\n\t\t\ts.Sources[src].Places = append(s.Sources[src].Places, StackSlot{i, j})\n\t\t}\n\t}\n}\n\n// pickColor picks a color for key.\nfunc pickColor(key string) int {\n\tconst numColors = 1048576\n\th := sha256.Sum256([]byte(key))\n\tindex := binary.LittleEndian.Uint32(h[:])\n\treturn int(index % numColors)\n}\n\n// Legend returns the list of lines to display as the legend.\nfunc (s *StackSet) Legend() []string {\n\treturn reportLabels(s.report, s.report.total, len(s.Sources), len(s.Sources), 0, 0, false)\n}\n\nfunc addLineInfo(str string, line profile.Line) string {\n\tif line.Column != 0 {\n\t\treturn fmt.Sprint(str, \":\", line.Line, \":\", line.Column)\n\t}\n\tif line.Line != 0 {\n\t\treturn fmt.Sprint(str, \":\", line.Line)\n\t}\n\treturn str\n}\n"
  },
  {
    "path": "internal/report/stacks_test.go",
    "content": "package report\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\n// makeTestStacks generates a StackSet from a supplied list of samples.\nfunc makeTestStacks(samples ...*profile.Sample) StackSet {\n\tprof := makeTestProfile(samples...)\n\trpt := NewDefault(prof, Options{OutputFormat: Tree, CallTree: true})\n\treturn rpt.Stacks()\n}\n\nfunc TestStacks(t *testing.T) {\n\t// See report_test.go for the functions available to use in tests.\n\tlocs := clearLineAndColumn(testL)\n\tmain, foo, bar, tee := locs[0], locs[1], locs[2], locs[3]\n\n\t// Also make some file-only locations to test file granularity.\n\tfileMain := makeFileLocation(main)\n\tfileFoo := makeFileLocation(foo)\n\tfileBar := makeFileLocation(bar)\n\n\t// stack holds an expected stack value found in StackSet.\n\ttype stack struct {\n\t\tvalue int64\n\t\tnames []string\n\t}\n\tmakeStack := func(value int64, names ...string) stack {\n\t\treturn stack{value, names}\n\t}\n\n\tfor _, c := range []struct {\n\t\tname   string\n\t\tstacks StackSet\n\t\texpect []stack\n\t}{\n\t\t{\n\t\t\t\"simple\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, bar, foo, main),\n\t\t\t\ttestSample(200, tee, foo, main),\n\t\t\t),\n\t\t\t[]stack{\n\t\t\t\tmakeStack(100, \"0:root\", \"1:main\", \"2:foo\", \"3:bar\"),\n\t\t\t\tmakeStack(200, \"0:root\", \"1:main\", \"2:foo\", \"4:tee\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"recursion\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, bar, foo, foo, foo, main),\n\t\t\t\ttestSample(200, bar, foo, foo, main),\n\t\t\t),\n\t\t\t[]stack{\n\t\t\t\t// Note: Recursive calls to foo have different source indices.\n\t\t\t\tmakeStack(100, \"0:root\", \"1:main\", \"2:foo\", \"2:foo\", \"2:foo\", \"3:bar\"),\n\t\t\t\tmakeStack(200, \"0:root\", \"1:main\", \"2:foo\", \"2:foo\", \"3:bar\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"files\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, fileFoo, fileMain),\n\t\t\t\ttestSample(200, fileBar, fileMain),\n\t\t\t),\n\t\t\t[]stack{\n\t\t\t\tmakeStack(100, \"0:root\", \"1:dir/main\", \"2:dir/foo\"),\n\t\t\t\tmakeStack(200, \"0:root\", \"1:dir/main\", \"3:dir/bar\"),\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tvar got []stack\n\t\t\tfor _, s := range c.stacks.Stacks {\n\t\t\t\tstk := stack{\n\t\t\t\t\tvalue: s.Value,\n\t\t\t\t\tnames: make([]string, len(s.Sources)),\n\t\t\t\t}\n\t\t\t\tfor i, src := range s.Sources {\n\t\t\t\t\tstk.names[i] = fmt.Sprint(src, \":\", c.stacks.Sources[src].FullName)\n\t\t\t\t}\n\t\t\t\tgot = append(got, stk)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(c.expect, got) {\n\t\t\t\tt.Errorf(\"expecting source %+v, got %+v\", c.expect, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestStackSources(t *testing.T) {\n\t// See report_test.go for the functions available to use in tests.\n\tlocs := clearLineAndColumn(testL)\n\tmain, foo, bar, tee, inl := locs[0], locs[1], locs[2], locs[3], locs[5]\n\n\ttype srcInfo struct {\n\t\tname    string\n\t\tself    int64\n\t\tinlined bool\n\t}\n\n\tsource := func(stacks StackSet, name string) srcInfo {\n\t\tsrc := findSource(stacks, name)\n\t\treturn srcInfo{src.FullName, src.Self, src.Inlined}\n\t}\n\n\tfor _, c := range []struct {\n\t\tname   string\n\t\tstacks StackSet\n\t\tsrcs   []srcInfo\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\tmakeTestStacks(),\n\t\t\t[]srcInfo{},\n\t\t},\n\t\t{\n\t\t\t\"two-leaves\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, bar, foo, main),\n\t\t\t\ttestSample(200, tee, bar, foo, main),\n\t\t\t\ttestSample(1000, tee, main),\n\t\t\t),\n\t\t\t[]srcInfo{\n\t\t\t\t{\"main\", 0, false},\n\t\t\t\t{\"bar\", 100, false},\n\t\t\t\t{\"foo\", 0, false},\n\t\t\t\t{\"tee\", 1200, false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"inlined\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, inl),\n\t\t\t\ttestSample(200, inl),\n\t\t\t),\n\t\t\t[]srcInfo{\n\t\t\t\t// inl has bar->tee\n\t\t\t\t{\"tee\", 300, true},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"recursion\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, foo, foo, foo, main),\n\t\t\t\ttestSample(100, foo, foo, main),\n\t\t\t),\n\t\t\t[]srcInfo{\n\t\t\t\t{\"main\", 0, false},\n\t\t\t\t{\"foo\", 200, false},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"flat\",\n\t\t\tmakeTestStacks(\n\t\t\t\ttestSample(100, main),\n\t\t\t\ttestSample(100, foo),\n\t\t\t\ttestSample(100, bar),\n\t\t\t\ttestSample(100, tee),\n\t\t\t),\n\t\t\t[]srcInfo{\n\t\t\t\t{\"main\", 100, false},\n\t\t\t\t{\"bar\", 100, false},\n\t\t\t\t{\"foo\", 100, false},\n\t\t\t\t{\"tee\", 100, false},\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tfor _, expect := range c.srcs {\n\t\t\t\tgot := source(c.stacks, expect.name)\n\t\t\t\tif !reflect.DeepEqual(expect, got) {\n\t\t\t\t\tt.Errorf(\"expecting source %+v, got %+v\", expect, got)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLegend(t *testing.T) {\n\t// See report_test.go for the functions available to use in tests.\n\tmain, foo, bar, tee := testL[0], testL[1], testL[2], testL[3]\n\tstacks := makeTestStacks(\n\t\ttestSample(100, bar, foo, main),\n\t\ttestSample(200, tee, foo, main),\n\t)\n\tgot := strings.Join(stacks.Legend(), \"\\n\")\n\texpectStrings := []string{\"Type: samples\", \"Showing nodes\", \"100% of 300 total\"}\n\tfor _, expect := range expectStrings {\n\t\tif !strings.Contains(got, expect) {\n\t\t\tt.Errorf(\"missing expected string %q in legend %q\", expect, got)\n\t\t}\n\t}\n}\n\nfunc findSource(stacks StackSet, name string) StackSource {\n\tfor _, src := range stacks.Sources {\n\t\tif src.FullName == name {\n\t\t\treturn src\n\t\t}\n\t}\n\treturn StackSource{}\n}\n\n// clearLineAndColumn drops line and column numbers to simplify tests that\n// do not care about line and column numbers.\nfunc clearLineAndColumn(locs []*profile.Location) []*profile.Location {\n\tresult := make([]*profile.Location, len(locs))\n\tfor i, loc := range locs {\n\t\tnewLoc := *loc\n\t\tnewLoc.Line = slices.Clone(loc.Line)\n\t\tfor j := range newLoc.Line {\n\t\t\tnewLoc.Line[j].Line = 0\n\t\t\tnewLoc.Line[j].Column = 0\n\t\t}\n\t\tresult[i] = &newLoc\n\t}\n\treturn result\n}\n\n// makeFileLocation switches loc from function to file-granularity.\nfunc makeFileLocation(loc *profile.Location) *profile.Location {\n\tresult := *loc\n\tresult.ID += 1000\n\tresult.Line = slices.Clone(loc.Line)\n\tfor i := range result.Line {\n\t\tfn := *result.Line[i].Function\n\t\tfn.Filename = \"dir/\" + fn.Name\n\t\tfn.Name = \"\"\n\t\tresult.Line[i].Function = &fn\n\t}\n\treturn &result\n}\n"
  },
  {
    "path": "internal/report/synth.go",
    "content": "package report\n\nimport (\n\t\"github.com/google/pprof/profile\"\n)\n\n// synthCode assigns addresses to locations without an address.\ntype synthCode struct {\n\tnext uint64\n\taddr map[*profile.Location]uint64 // Synthesized address assigned to a location\n}\n\nfunc newSynthCode(mappings []*profile.Mapping) *synthCode {\n\t// Find a larger address than any mapping.\n\ts := &synthCode{next: 1}\n\tfor _, m := range mappings {\n\t\tif s.next < m.Limit {\n\t\t\ts.next = m.Limit\n\t\t}\n\t}\n\treturn s\n}\n\n// address returns the synthetic address for loc, creating one if needed.\nfunc (s *synthCode) address(loc *profile.Location) uint64 {\n\tif loc.Address != 0 {\n\t\tpanic(\"can only synthesize addresses for locations without an address\")\n\t}\n\tif addr, ok := s.addr[loc]; ok {\n\t\treturn addr\n\t}\n\tif s.addr == nil {\n\t\ts.addr = map[*profile.Location]uint64{}\n\t}\n\taddr := s.next\n\ts.next++\n\ts.addr[loc] = addr\n\treturn addr\n}\n"
  },
  {
    "path": "internal/report/synth_test.go",
    "content": "package report\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestSynthAddresses(t *testing.T) {\n\ts := newSynthCode(nil)\n\tl1 := &profile.Location{}\n\taddr1 := s.address(l1)\n\tif s.address(l1) != addr1 {\n\t\tt.Errorf(\"different calls with same location returned different addresses\")\n\t}\n\n\tl2 := &profile.Location{}\n\taddr2 := s.address(l2)\n\tif addr2 == addr1 {\n\t\tt.Errorf(\"same address assigned to different locations\")\n\t}\n\n}\n\nfunc TestSynthAvoidsMapping(t *testing.T) {\n\tmappings := []*profile.Mapping{\n\t\t{Start: 100, Limit: 200},\n\t\t{Start: 300, Limit: 400},\n\t}\n\ts := newSynthCode(mappings)\n\tloc := &profile.Location{}\n\taddr := s.address(loc)\n\tif addr >= 100 && addr < 200 || addr >= 300 && addr < 400 {\n\t\tt.Errorf(\"synthetic location %d overlaps mapping %v\", addr, mappings)\n\t}\n}\n"
  },
  {
    "path": "internal/report/testdata/README.md",
    "content": "sample/ contains a sample program that can be profiled.\nsample.bin is its x86-64 binary.\nsample.cpu is a profile generated by sample.bin.\n\nTo update the binary and profile:\n\n```shell\ngo build -o sample.bin ./sample\n./sample.bin -cpuprofile sample.cpu\n```\n"
  },
  {
    "path": "internal/report/testdata/sample/sample.go",
    "content": "//  Copyright 2017 Google Inc. All Rights Reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\n// sample program that is used to produce some of the files in\n// pprof/internal/report/testdata.\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"runtime/pprof\"\n)\n\nvar cpuProfile = flag.String(\"cpuprofile\", \"\", \"where to write cpu profile\")\n\nfunc main() {\n\tflag.Parse()\n\tf, err := os.Create(*cpuProfile)\n\tif err != nil {\n\t\tlog.Fatal(\"could not create CPU profile: \", err)\n\t}\n\tif err := pprof.StartCPUProfile(f); err != nil {\n\t\tlog.Fatal(\"could not start CPU profile: \", err)\n\t}\n\tdefer pprof.StopCPUProfile()\n\tbusyLoop()\n}\n\nfunc busyLoop() {\n\tm := make(map[int]int)\n\tfor i := 0; i < 1000000; i++ {\n\t\tm[i] = i + 10\n\t}\n\tvar sum float64\n\tfor i := 0; i < 100; i++ {\n\t\tfor _, v := range m {\n\t\t\tsum += math.Abs(float64(v))\n\t\t}\n\t}\n\tfmt.Println(\"Sum\", sum)\n}\n"
  },
  {
    "path": "internal/report/testdata/source.dot",
    "content": "digraph \"unnamed\" {\nnode [style=filled fillcolor=\"#f8f8f8\"]\nsubgraph 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\\lSee https://git.io/JfYMW for how to read the graph\\l\"] }\nN1 [label=\"tee\\nsource2:8\\n10000 (90.00%)\" id=\"node1\" fontsize=24 shape=box tooltip=\"tee testdata/source2:8 (10000)\" color=\"#b20500\" fillcolor=\"#edd6d5\"]\nN2 [label=\"main\\nsource1:2:2\\n1 (0.009%)\\nof 11111 (100%)\" id=\"node2\" fontsize=9 shape=box tooltip=\"main testdata/source1:2:2 (11111)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN3 [label=\"tee\\nsource2:2\\n1000 (9.00%)\\nof 11000 (99.00%)\" id=\"node3\" fontsize=14 shape=box tooltip=\"tee testdata/source2:2 (11000)\" color=\"#b20000\" fillcolor=\"#edd5d5\"]\nN4 [label=\"tee\\nsource2:8\\n100 (0.9%)\" id=\"node4\" fontsize=10 shape=box tooltip=\"tee testdata/source2:8 (100)\" color=\"#b2b0aa\" fillcolor=\"#edecec\"]\nN5 [label=\"bar\\nsource1:10\\n10 (0.09%)\" id=\"node5\" fontsize=9 shape=box tooltip=\"bar testdata/source1:10 (10)\" color=\"#b2b2b1\" fillcolor=\"#ededed\"]\nN6 [label=\"bar\\nsource1:10\\n0 of 100 (0.9%)\" id=\"node6\" fontsize=8 shape=box tooltip=\"bar testdata/source1:10 (100)\" color=\"#b2b0aa\" fillcolor=\"#edecec\"]\nN7 [label=\"foo\\nsource1:4:4\\n0 of 10 (0.09%)\" id=\"node7\" fontsize=8 shape=box tooltip=\"foo testdata/source1:4:4 (10)\" color=\"#b2b2b1\" fillcolor=\"#ededed\"]\nN2 -> N3 [label=\" 11000\" weight=100 penwidth=5 color=\"#b20000\" tooltip=\"main testdata/source1:2:2 -> tee testdata/source2:2 (11000)\" labeltooltip=\"main testdata/source1:2:2 -> tee testdata/source2:2 (11000)\"]\nN3 -> 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)\"]\nN6 -> 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)\"]\nN2 -> N6 [label=\" 100\" color=\"#b2b0aa\" tooltip=\"main testdata/source1:2:2 -> bar testdata/source1:10 (100)\" labeltooltip=\"main testdata/source1:2:2 -> bar testdata/source1:10 (100)\"]\nN7 -> N5 [label=\" 10\" color=\"#b2b2b1\" tooltip=\"foo testdata/source1:4:4 -> bar testdata/source1:10 (10)\" labeltooltip=\"foo testdata/source1:4:4 -> bar testdata/source1:10 (10)\"]\nN2 -> N7 [label=\" 10\" color=\"#b2b2b1\" tooltip=\"main testdata/source1:2:2 -> foo testdata/source1:4:4 (10)\" labeltooltip=\"main testdata/source1:2:2 -> foo testdata/source1:4:4 (10)\"]\n}\n"
  },
  {
    "path": "internal/report/testdata/source.rpt",
    "content": "Total: 11111\nROUTINE ======================== bar in testdata/source1\n        10        110 (flat, cum)  0.99% of Total\n         .          .      5:source1 line 5;\n         .          .      6:source1 line 6;\n         .          .      7:source1 line 7;\n         .          .      8:source1 line 8;\n         .          .      9:source1 line 9;\n        10        110     10:source1 line 10;\n         .          .     11:source1 line 11;\n         .          .     12:source1 line 12;\n         .          .     13:source1 line 13;\n         .          .     14:source1 line 14;\n         .          .     15:source1 line 15;\nROUTINE ======================== foo in testdata/source1\n         0         10 (flat, cum)  0.09% of Total\n         .          .      1:source1 line 1;\n         .          .      2:source1 line 2;\n         .          .      3:source1 line 3;\n         .         10      4:source1 line 4;\n         .          .      5:source1 line 5;\n         .          .      6:source1 line 6;\n         .          .      7:source1 line 7;\n         .          .      8:source1 line 8;\n         .          .      9:source1 line 9;\nROUTINE ======================== main in testdata/source1\n         1      11111 (flat, cum)   100% of Total\n         .          .      1:source1 line 1;\n         1      11111      2:source1 line 2;\n         .          .      3:source1 line 3;\n         .          .      4:source1 line 4;\n         .          .      5:source1 line 5;\n         .          .      6:source1 line 6;\n         .          .      7:source1 line 7;\nROUTINE ======================== tee in testdata/source2\n     11100      21100 (flat, cum) 189.90% of Total\n         .          .      1:source2 line 1;\n      1000      11000      2:source2 line 2;\n         .          .      3:source2 line 3;\n         .          .      4:source2 line 4;\n         .          .      5:source2 line 5;\n         .          .      6:source2 line 6;\n         .          .      7:source2 line 7;\n     10100      10100      8:source2 line 8;\n         .          .      9:source2 line 9;\n         .          .     10:source2 line 10;\n         .          .     11:source2 line 11;\n         .          .     12:source2 line 12;\n         .          .     13:source2 line 13;\n"
  },
  {
    "path": "internal/report/testdata/source1",
    "content": "source1 line 1;\nsource1 line 2;\nsource1 line 3;\nsource1 line 4;\nsource1 line 5;\nsource1 line 6;\nsource1 line 7;\nsource1 line 8;\nsource1 line 9;\nsource1 line 10;\nsource1 line 11;\nsource1 line 12;\nsource1 line 13;\nsource1 line 14;\nsource1 line 15;\nsource1 line 16;\nsource1 line 17;\nsource1 line 18;\n\n"
  },
  {
    "path": "internal/report/testdata/source2",
    "content": "source2 line 1;\nsource2 line 2;\nsource2 line 3;\nsource2 line 4;\nsource2 line 5;\nsource2 line 6;\nsource2 line 7;\nsource2 line 8;\nsource2 line 9;\nsource2 line 10;\nsource2 line 11;\nsource2 line 12;\nsource2 line 13;\nsource2 line 14;\nsource2 line 15;\nsource2 line 16;\nsource2 line 17;\nsource2 line 18;\n\n"
  },
  {
    "path": "internal/symbolizer/symbolizer.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package symbolizer provides a routine to populate a profile with\n// symbol, file and line number information. It relies on the\n// addr2liner and demangle packages to do the actual work.\npackage symbolizer\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/binutils\"\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/symbolz\"\n\t\"github.com/google/pprof/profile\"\n\t\"github.com/ianlancetaylor/demangle\"\n)\n\n// Symbolizer implements the plugin.Symbolize interface.\ntype Symbolizer struct {\n\tObj       plugin.ObjTool\n\tUI        plugin.UI\n\tTransport http.RoundTripper\n}\n\n// test taps for dependency injection\nvar symbolzSymbolize = symbolz.Symbolize\nvar localSymbolize = doLocalSymbolize\nvar demangleFunction = Demangle\n\n// Symbolize attempts to symbolize profile p. First uses binutils on\n// local binaries; if the source is a URL it attempts to get any\n// missed entries using symbolz.\nfunc (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {\n\tremote, local, fast, force, demanglerMode := true, true, false, false, \"\"\n\tfor _, o := range strings.Split(strings.ToLower(mode), \":\") {\n\t\tswitch o {\n\t\tcase \"\":\n\t\t\tcontinue\n\t\tcase \"none\", \"no\":\n\t\t\treturn nil\n\t\tcase \"local\":\n\t\t\tremote, local = false, true\n\t\tcase \"fastlocal\":\n\t\t\tremote, local, fast = false, true, true\n\t\tcase \"remote\":\n\t\t\tremote, local = true, false\n\t\tcase \"force\":\n\t\t\tforce = true\n\t\tdefault:\n\t\t\tswitch d := strings.TrimPrefix(o, \"demangle=\"); d {\n\t\t\tcase \"full\", \"none\", \"templates\":\n\t\t\t\tdemanglerMode = d\n\t\t\t\tforce = true\n\t\t\t\tcontinue\n\t\t\tcase \"default\":\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.UI.PrintErr(\"ignoring unrecognized symbolization option: \" + mode)\n\t\t\ts.UI.PrintErr(\"expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]\")\n\t\t}\n\t}\n\n\tvar err error\n\tif local {\n\t\t// Symbolize locally using binutils.\n\t\tif err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil {\n\t\t\ts.UI.PrintErr(\"local symbolization: \" + err.Error())\n\t\t}\n\t}\n\tif remote {\n\t\tpost := func(source, post string) ([]byte, error) {\n\t\t\treturn postURL(source, post, s.Transport)\n\t\t}\n\t\tif err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {\n\t\t\treturn err // Ran out of options.\n\t\t}\n\t}\n\n\tdemangleFunction(p, force, demanglerMode)\n\treturn nil\n}\n\n// postURL issues a POST to a URL over HTTP.\nfunc postURL(source, post string, tr http.RoundTripper) ([]byte, error) {\n\tclient := &http.Client{\n\t\tTransport: tr,\n\t}\n\tresp, err := client.Post(source, \"application/octet-stream\", strings.NewReader(post))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"http post %s: %v\", source, err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"http post %s: %v\", source, statusCodeError(resp))\n\t}\n\treturn io.ReadAll(resp.Body)\n}\n\nfunc statusCodeError(resp *http.Response) error {\n\tif resp.Header.Get(\"X-Go-Pprof\") != \"\" && strings.Contains(resp.Header.Get(\"Content-Type\"), \"text/plain\") {\n\t\t// error is from pprof endpoint\n\t\tif body, err := io.ReadAll(resp.Body); err == nil {\n\t\t\treturn fmt.Errorf(\"server response: %s - %s\", resp.Status, body)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"server response: %s\", resp.Status)\n}\n\n// doLocalSymbolize adds symbol and line number information to all locations\n// in a profile. mode enables some options to control\n// symbolization.\nfunc doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {\n\tif fast {\n\t\tif bu, ok := obj.(*binutils.Binutils); ok {\n\t\t\tbu.SetFastSymbolization(true)\n\t\t}\n\t}\n\n\tfunctions := map[profile.Function]*profile.Function{}\n\taddFunction := func(f *profile.Function) *profile.Function {\n\t\tif fp := functions[*f]; fp != nil {\n\t\t\treturn fp\n\t\t}\n\t\tfunctions[*f] = f\n\t\tf.ID = uint64(len(prof.Function)) + 1\n\t\tprof.Function = append(prof.Function, f)\n\t\treturn f\n\t}\n\n\tmissingBinaries := false\n\tmappingLocs := map[*profile.Mapping][]*profile.Location{}\n\tfor _, l := range prof.Location {\n\t\tmappingLocs[l.Mapping] = append(mappingLocs[l.Mapping], l)\n\t}\n\tfor midx, m := range prof.Mapping {\n\t\tlocs := mappingLocs[m]\n\t\tif len(locs) == 0 {\n\t\t\t// The mapping is dangling and has no locations pointing to it.\n\t\t\tcontinue\n\t\t}\n\t\t// Do not attempt to re-symbolize a mapping that has already been symbolized.\n\t\tif !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {\n\t\t\tcontinue\n\t\t}\n\t\tif m.File == \"\" {\n\t\t\tif midx == 0 {\n\t\t\t\tui.PrintErr(\"Main binary filename not available.\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmissingBinaries = true\n\t\t\tcontinue\n\t\t}\n\t\tif m.Unsymbolizable() {\n\t\t\t// Skip well-known system mappings\n\t\t\tcontinue\n\t\t}\n\t\tif m.BuildID == \"\" {\n\t\t\tif u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), \"http\") {\n\t\t\t\t// Skip mappings pointing to a source URL\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tname := filepath.Base(m.File)\n\t\tif m.BuildID != \"\" {\n\t\t\tname += fmt.Sprintf(\" (build ID %s)\", m.BuildID)\n\t\t}\n\t\tf, err := obj.Open(m.File, m.Start, m.Limit, m.Offset, m.KernelRelocationSymbol)\n\t\tif err != nil {\n\t\t\tui.PrintErr(\"Local symbolization failed for \", name, \": \", err)\n\t\t\tmissingBinaries = true\n\t\t\tcontinue\n\t\t}\n\t\tif fid := f.BuildID(); m.BuildID != \"\" && fid != \"\" && fid != m.BuildID {\n\t\t\tui.PrintErr(\"Local symbolization failed for \", name, \": build ID mismatch\")\n\t\t\tf.Close()\n\t\t\tcontinue\n\t\t}\n\t\tsymbolizeOneMapping(m, locs, f, addFunction)\n\t\tf.Close()\n\t}\n\n\tif missingBinaries {\n\t\tui.PrintErr(\"Some binary filenames not available. Symbolization may be incomplete.\\n\" +\n\t\t\t\"Try setting PPROF_BINARY_PATH to the search path for local binaries.\")\n\t}\n\treturn nil\n}\n\nfunc symbolizeOneMapping(m *profile.Mapping, locs []*profile.Location, obj plugin.ObjFile, addFunction func(*profile.Function) *profile.Function) {\n\tfor _, l := range locs {\n\t\tstack, err := obj.SourceLine(l.Address)\n\t\tif err != nil || len(stack) == 0 {\n\t\t\t// No answers from addr2line.\n\t\t\tcontinue\n\t\t}\n\n\t\tl.Line = make([]profile.Line, len(stack))\n\t\tl.IsFolded = false\n\t\tfor i, frame := range stack {\n\t\t\tif frame.Func != \"\" {\n\t\t\t\tm.HasFunctions = true\n\t\t\t}\n\t\t\tif frame.File != \"\" {\n\t\t\t\tm.HasFilenames = true\n\t\t\t}\n\t\t\tif frame.Line != 0 {\n\t\t\t\tm.HasLineNumbers = true\n\t\t\t}\n\t\t\tf := addFunction(&profile.Function{\n\t\t\t\tName:       frame.Func,\n\t\t\t\tSystemName: frame.Func,\n\t\t\t\tFilename:   frame.File,\n\t\t\t\tStartLine:  int64(frame.StartLine),\n\t\t\t})\n\t\t\tl.Line[i] = profile.Line{\n\t\t\t\tFunction: f,\n\t\t\t\tLine:     int64(frame.Line),\n\t\t\t\tColumn:   int64(frame.Column),\n\t\t\t}\n\t\t}\n\n\t\tif len(stack) > 0 {\n\t\t\tm.HasInlineFrames = true\n\t\t}\n\t}\n}\n\n// Demangle updates the function names in a profile with demangled C++\n// names, simplified according to demanglerMode. If force is set,\n// overwrite any names that appear already demangled.\nfunc Demangle(prof *profile.Profile, force bool, demanglerMode string) {\n\tif force {\n\t\t// Remove the current demangled names to force demangling\n\t\tfor _, f := range prof.Function {\n\t\t\tif f.Name != \"\" && f.SystemName != \"\" {\n\t\t\t\tf.Name = f.SystemName\n\t\t\t}\n\t\t}\n\t}\n\n\toptions := demanglerModeToOptions(demanglerMode)\n\t// Bail out fast to avoid any parsing, if we really don't want any demangling.\n\tif len(options) == 0 {\n\t\treturn\n\t}\n\tfor _, fn := range prof.Function {\n\t\tdemangleSingleFunction(fn, options)\n\t}\n}\n\nfunc demanglerModeToOptions(demanglerMode string) []demangle.Option {\n\tswitch demanglerMode {\n\tcase \"\": // demangled, simplified: no parameters, no templates, no return type\n\t\treturn []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams, demangle.NoTemplateParams}\n\tcase \"templates\": // demangled, simplified: no parameters, no return type\n\t\treturn []demangle.Option{demangle.NoParams, demangle.NoEnclosingParams}\n\tcase \"full\":\n\t\treturn []demangle.Option{demangle.NoClones}\n\tcase \"none\": // no demangling\n\t\treturn []demangle.Option{}\n\t}\n\n\tpanic(fmt.Sprintf(\"unknown demanglerMode %s\", demanglerMode))\n}\n\nfunc demangleSingleFunction(fn *profile.Function, opts []demangle.Option) {\n\tif fn.Name != \"\" && fn.SystemName != fn.Name {\n\t\treturn // Already demangled.\n\t}\n\tif demangled := demangle.Filter(fn.SystemName, opts...); demangled != fn.SystemName {\n\t\tfn.Name = demangled\n\t\treturn\n\t}\n\n\t// OSX has all the symbols prefixed with extra '_' so lets try\n\t// once more without it\n\tif strings.HasPrefix(fn.SystemName, \"_\") {\n\t\tif demangled := demangle.Filter(fn.SystemName[1:], opts...); demangled != fn.SystemName[1:] {\n\t\t\tfn.Name = demangled\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Could not demangle. Apply heuristics in case the name is\n\t// already demangled.\n\tname := fn.SystemName\n\tif looksLikeDemangledCPlusPlus(name) {\n\t\tfor _, o := range opts {\n\t\t\tswitch o {\n\t\t\tcase demangle.NoParams:\n\t\t\t\tname = removeMatching(name, '(', ')')\n\t\t\tcase demangle.NoTemplateParams:\n\t\t\t\tname = removeMatching(name, '<', '>')\n\t\t\t}\n\t\t}\n\t}\n\tfn.Name = name\n}\n\n// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is\n// the result of demangling C++. If so, further heuristics will be\n// applied to simplify the name.\nfunc looksLikeDemangledCPlusPlus(demangled string) bool {\n\t// Skip java names of the form \"class.<init>\".\n\tif strings.Contains(demangled, \".<\") {\n\t\treturn false\n\t}\n\t// Skip Go names of the form \"foo.(*Bar[...]).Method\".\n\tif strings.Contains(demangled, \"]).\") {\n\t\treturn false\n\t}\n\treturn strings.ContainsAny(demangled, \"<>[]\") || strings.Contains(demangled, \"::\")\n}\n\n// removeMatching removes nested instances of start..end from name.\nfunc removeMatching(name string, start, end byte) string {\n\ts := string(start) + string(end)\n\tvar nesting, first, current int\n\tfor index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {\n\t\tswitch current += index; name[current] {\n\t\tcase start:\n\t\t\tnesting++\n\t\t\tif nesting == 1 {\n\t\t\t\tfirst = current\n\t\t\t}\n\t\tcase end:\n\t\t\tnesting--\n\t\t\tswitch {\n\t\t\tcase nesting < 0:\n\t\t\t\treturn name // Mismatch, abort\n\t\t\tcase nesting == 0:\n\t\t\t\tname = name[:first] + name[current+1:]\n\t\t\t\tcurrent = first - 1\n\t\t\t}\n\t\t}\n\t\tcurrent++\n\t}\n\treturn name\n}\n"
  },
  {
    "path": "internal/symbolizer/symbolizer_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage symbolizer\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/profile\"\n)\n\nconst filePath = \"mapping\"\nconst buildID = \"build-id\"\n\nvar testM = []*profile.Mapping{\n\t{\n\t\tID:      1,\n\t\tStart:   0x1000,\n\t\tLimit:   0x5000,\n\t\tFile:    filePath,\n\t\tBuildID: buildID,\n\t},\n}\n\nvar testL = []*profile.Location{\n\t{\n\t\tID:      1,\n\t\tMapping: testM[0],\n\t\tAddress: 1000,\n\t},\n\t{\n\t\tID:      2,\n\t\tMapping: testM[0],\n\t\tAddress: 2000,\n\t},\n\t{\n\t\tID:      3,\n\t\tMapping: testM[0],\n\t\tAddress: 3000,\n\t},\n\t{\n\t\tID:      4,\n\t\tMapping: testM[0],\n\t\tAddress: 4000,\n\t},\n\t{\n\t\tID:      5,\n\t\tMapping: testM[0],\n\t\tAddress: 5000,\n\t},\n}\n\nvar testProfile = profile.Profile{\n\tDurationNanos: 10e9,\n\tSampleType: []*profile.ValueType{\n\t\t{Type: \"cpu\", Unit: \"cycles\"},\n\t},\n\tSample: []*profile.Sample{\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[0]},\n\t\t\tValue:    []int64{1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[1], testL[0]},\n\t\t\tValue:    []int64{10},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[2], testL[0]},\n\t\t\tValue:    []int64{100},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[3], testL[0]},\n\t\t\tValue:    []int64{1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*profile.Location{testL[4], testL[3], testL[0]},\n\t\t\tValue:    []int64{10000},\n\t\t},\n\t},\n\tLocation:   testL,\n\tMapping:    testM,\n\tPeriodType: &profile.ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:     10,\n}\n\nfunc TestSymbolization(t *testing.T) {\n\tsSym := symbolzSymbolize\n\tlSym := localSymbolize\n\tdefer func() {\n\t\tsymbolzSymbolize = sSym\n\t\tlocalSymbolize = lSym\n\t\tdemangleFunction = Demangle\n\t}()\n\tsymbolzSymbolize = symbolzMock\n\tlocalSymbolize = localMock\n\tdemangleFunction = demangleMock\n\n\ttype testcase struct {\n\t\tmode        string\n\t\twantComment string\n\t}\n\n\ts := Symbolizer{\n\t\tObj: mockObjTool{},\n\t\tUI:  &proftest.TestUI{T: t},\n\t}\n\tfor i, tc := range []testcase{\n\t\t{\n\t\t\t\"local\",\n\t\t\t\"local=[]\",\n\t\t},\n\t\t{\n\t\t\t\"fastlocal\",\n\t\t\t\"local=[fast]\",\n\t\t},\n\t\t{\n\t\t\t\"remote\",\n\t\t\t\"symbolz=[]\",\n\t\t},\n\t\t{\n\t\t\t\"\",\n\t\t\t\"local=[]:symbolz=[]\",\n\t\t},\n\t\t{\n\t\t\t\"demangle=none\",\n\t\t\t\"demangle=[none]:force:local=[force]:symbolz=[force]\",\n\t\t},\n\t\t{\n\t\t\t\"remote:demangle=full\",\n\t\t\t\"demangle=[full]:force:symbolz=[force]\",\n\t\t},\n\t\t{\n\t\t\t\"local:demangle=templates\",\n\t\t\t\"demangle=[templates]:force:local=[force]\",\n\t\t},\n\t\t{\n\t\t\t\"force:remote\",\n\t\t\t\"force:symbolz=[force]\",\n\t\t},\n\t} {\n\t\tprof := testProfile.Copy()\n\t\tif err := s.Symbolize(tc.mode, nil, prof); err != nil {\n\t\t\tt.Errorf(\"symbolize #%d: %v\", i, err)\n\t\t\tcontinue\n\t\t}\n\t\tsort.Strings(prof.Comments)\n\t\tif got, want := strings.Join(prof.Comments, \":\"), tc.wantComment; got != want {\n\t\t\tt.Errorf(\"%q: got %s, want %s\", tc.mode, got, want)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\nfunc symbolzMock(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {\n\tvar args []string\n\tif force {\n\t\targs = append(args, \"force\")\n\t}\n\tp.Comments = append(p.Comments, \"symbolz=[\"+strings.Join(args, \",\")+\"]\")\n\treturn nil\n}\n\nfunc localMock(p *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {\n\tvar args []string\n\tif fast {\n\t\targs = append(args, \"fast\")\n\t}\n\tif force {\n\t\targs = append(args, \"force\")\n\t}\n\tp.Comments = append(p.Comments, \"local=[\"+strings.Join(args, \",\")+\"]\")\n\treturn nil\n}\n\nfunc demangleMock(p *profile.Profile, force bool, mode string) {\n\tif force {\n\t\tp.Comments = append(p.Comments, \"force\")\n\t}\n\tif mode != \"\" {\n\t\tp.Comments = append(p.Comments, \"demangle=[\"+mode+\"]\")\n\t}\n}\n\nfunc TestLocalSymbolization(t *testing.T) {\n\tprof := testProfile.Copy()\n\n\tif prof.HasFunctions() {\n\t\tt.Error(\"unexpected function names\")\n\t}\n\tif prof.HasFileLines() {\n\t\tt.Error(\"unexpected filenames or line numbers\")\n\t}\n\n\tb := mockObjTool{}\n\tif err := localSymbolize(prof, false, false, b, &proftest.TestUI{T: t}); err != nil {\n\t\tt.Fatalf(\"localSymbolize(): %v\", err)\n\t}\n\n\tfor _, loc := range prof.Location {\n\t\tif err := checkSymbolizedLocation(loc.Address, loc.Line); err != nil {\n\t\t\tt.Errorf(\"location %d: %v\", loc.Address, err)\n\t\t}\n\t}\n\tif !prof.HasFunctions() {\n\t\tt.Error(\"missing function names\")\n\t}\n\tif !prof.HasFileLines() {\n\t\tt.Error(\"missing filenames or line numbers\")\n\t}\n}\n\nfunc TestLocalSymbolizationHandlesSpecialCases(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc, file, buildID, allowOutputRx string\n\t\twantNumOutputRegexMatches          int\n\t}{{\n\t\tdesc:    \"Unsymbolizable files are skipped\",\n\t\tfile:    \"[some unsymbolizable file]\",\n\t\tbuildID: \"\",\n\t}, {\n\t\tdesc:    \"HTTP URL like paths are skipped\",\n\t\tfile:    \"http://original-url-source-of-profile-fetch\",\n\t\tbuildID: \"\",\n\t}, {\n\t\tdesc:                      \"Non-existent files are ignored\",\n\t\tfile:                      \"/does-not-exist\",\n\t\tbuildID:                   buildID,\n\t\tallowOutputRx:             \"(?s)unknown or non-existent file|Some binary filenames not available.*Try setting PPROF_BINARY_PATH\",\n\t\twantNumOutputRegexMatches: 2,\n\t}, {\n\t\tdesc:                      \"Missing main binary is detected\",\n\t\tfile:                      \"\",\n\t\tbuildID:                   buildID,\n\t\tallowOutputRx:             \"Main binary filename not available\",\n\t\twantNumOutputRegexMatches: 1,\n\t}, {\n\t\tdesc:                      \"Different build ID is detected\",\n\t\tfile:                      filePath,\n\t\tbuildID:                   \"unexpected-build-id\",\n\t\tallowOutputRx:             \"build ID mismatch\",\n\t\twantNumOutputRegexMatches: 1,\n\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tprof := testProfile.Copy()\n\t\t\tprof.Mapping[0].File = tc.file\n\t\t\tprof.Mapping[0].BuildID = tc.buildID\n\t\t\torigProf := prof.Copy()\n\n\t\t\tif prof.HasFunctions() {\n\t\t\t\tt.Error(\"unexpected function names\")\n\t\t\t}\n\t\t\tif prof.HasFileLines() {\n\t\t\t\tt.Error(\"unexpected filenames or line numbers\")\n\t\t\t}\n\n\t\t\tb := mockObjTool{}\n\t\t\tui := &proftest.TestUI{T: t, AllowRx: tc.allowOutputRx}\n\t\t\tif err := localSymbolize(prof, false, false, b, ui); err != nil {\n\t\t\t\tt.Fatalf(\"localSymbolize(): %v\", err)\n\t\t\t}\n\t\t\tif ui.NumAllowRxMatches != tc.wantNumOutputRegexMatches {\n\t\t\t\tt.Errorf(\"localSymbolize(): got %d matches for %q UI regexp, want %d\", ui.NumAllowRxMatches, tc.allowOutputRx, tc.wantNumOutputRegexMatches)\n\t\t\t}\n\n\t\t\tif diff, err := proftest.Diff([]byte(origProf.String()), []byte(prof.String())); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to get diff: %v\", err)\n\t\t\t} else if string(diff) != \"\" {\n\t\t\t\tt.Errorf(\"Profile changed unexpectedly, diff(want->got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkSymbolizedLocation(a uint64, got []profile.Line) error {\n\twant, ok := mockAddresses[a]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unexpected address\")\n\t}\n\tif len(want) != len(got) {\n\t\treturn fmt.Errorf(\"want len %d, got %d\", len(want), len(got))\n\t}\n\n\tfor i, w := range want {\n\t\tg := got[i]\n\t\tif g.Function.Name != w.Func {\n\t\t\treturn fmt.Errorf(\"want function: %q, got %q\", w.Func, g.Function.Name)\n\t\t}\n\t\tif g.Function.Filename != w.File {\n\t\t\treturn fmt.Errorf(\"want filename: %q, got %q\", w.File, g.Function.Filename)\n\t\t}\n\t\tif g.Line != int64(w.Line) {\n\t\t\treturn fmt.Errorf(\"want lineno: %d, got %d\", w.Line, g.Line)\n\t\t}\n\t\tif g.Column != int64(w.Column) {\n\t\t\treturn fmt.Errorf(\"want columnno: %d, got %d\", w.Column, g.Column)\n\t\t}\n\t}\n\treturn nil\n}\n\nvar mockAddresses = map[uint64][]plugin.Frame{\n\t1000: {frame(\"fun11\", \"file11.src\", 10, 1)},\n\t2000: {frame(\"fun21\", \"file21.src\", 20, 2), frame(\"fun22\", \"file22.src\", 20, 2)},\n\t3000: {frame(\"fun31\", \"file31.src\", 30, 3), frame(\"fun32\", \"file32.src\", 30, 3), frame(\"fun33\", \"file33.src\", 30, 3)},\n\t4000: {frame(\"fun41\", \"file41.src\", 40, 4), frame(\"fun42\", \"file42.src\", 40, 4), frame(\"fun43\", \"file43.src\", 40, 4), frame(\"fun44\", \"file44.src\", 40, 4)},\n\t5000: {frame(\"fun51\", \"file51.src\", 50, 5), frame(\"fun52\", \"file52.src\", 50, 5), frame(\"fun53\", \"file53.src\", 50, 5), frame(\"fun54\", \"file54.src\", 50, 5), frame(\"fun55\", \"file55.src\", 50, 5)},\n}\n\nfunc frame(fname, file string, line int, column int) plugin.Frame {\n\treturn plugin.Frame{\n\t\tFunc:   fname,\n\t\tFile:   file,\n\t\tLine:   line,\n\t\tColumn: column}\n}\n\nfunc TestDemangleSingleFunction(t *testing.T) {\n\t// All tests with default mode.\n\tdemanglerMode := \"\"\n\toptions := demanglerModeToOptions(demanglerMode)\n\n\tcases := []struct {\n\t\tsymbol string\n\t\twant   string\n\t}{\n\t\t{\n\t\t\t// Trivial C symbol.\n\t\t\tsymbol: \"printf\",\n\t\t\twant:   \"printf\",\n\t\t},\n\t\t{\n\t\t\t// foo::bar(int)\n\t\t\tsymbol: \"_ZN3foo3barEi\",\n\t\t\twant:   \"foo::bar\",\n\t\t},\n\t\t{\n\t\t\t// Already demangled.\n\t\t\tsymbol: \"foo::bar(int)\",\n\t\t\twant:   \"foo::bar\",\n\t\t},\n\t\t{\n\t\t\t// int foo::baz<double>(double)\n\t\t\tsymbol: \"_ZN3foo3bazIdEEiT\",\n\t\t\twant:   \"foo::baz\",\n\t\t},\n\t\t{\n\t\t\t// Already demangled.\n\t\t\t//\n\t\t\t// TODO: The demangled form of this is actually\n\t\t\t// 'int foo::baz<double>(double)', but our heuristic\n\t\t\t// can't strip the return type. Should it be able to?\n\t\t\tsymbol: \"foo::baz<double>(double)\",\n\t\t\twant:   \"foo::baz\",\n\t\t},\n\t\t{\n\t\t\t// operator delete[](void*)\n\t\t\tsymbol: \"_ZdaPv\",\n\t\t\twant:   \"operator delete[]\",\n\t\t},\n\t\t{\n\t\t\t// OSX prepends extra '_', which we're not able to remove. But we handle it when demangling.\n\t\t\tsymbol: \"__ZdaPv\",\n\t\t\twant:   \"operator delete[]\",\n\t\t},\n\t\t{\n\t\t\t// Leave special double underscore symbols as is.\n\t\t\tsymbol: \"__some_special_name\",\n\t\t\twant:   \"__some_special_name\",\n\t\t},\n\t\t{\n\t\t\t// Already demangled.\n\t\t\tsymbol: \"operator delete[](void*)\",\n\t\t\twant:   \"operator delete[]\",\n\t\t},\n\t\t{\n\t\t\t// bar(int (*) [5])\n\t\t\tsymbol: \"_Z3barPA5_i\",\n\t\t\twant:   \"bar\",\n\t\t},\n\t\t{\n\t\t\t// Already demangled.\n\t\t\tsymbol: \"bar(int (*) [5])\",\n\t\t\twant:   \"bar\",\n\t\t},\n\t\t// Java symbols, do not demangle.\n\t\t{\n\t\t\tsymbol: \"java.lang.Float.parseFloat\",\n\t\t\twant:   \"java.lang.Float.parseFloat\",\n\t\t},\n\t\t{\n\t\t\tsymbol: \"java.lang.Float.<init>\",\n\t\t\twant:   \"java.lang.Float.<init>\",\n\t\t},\n\t\t// Go symbols, do not demangle.\n\t\t{\n\t\t\tsymbol: \"example.com/foo.Bar\",\n\t\t\twant:   \"example.com/foo.Bar\",\n\t\t},\n\t\t{\n\t\t\tsymbol: \"example.com/foo.(*Bar).Bat\",\n\t\t\twant:   \"example.com/foo.(*Bar).Bat\",\n\t\t},\n\t\t{\n\t\t\t// Method on type with type parameters, as reported by\n\t\t\t// Go pprof profiles (simplified symbol name).\n\t\t\tsymbol: \"example.com/foo.(*Bar[...]).Bat\",\n\t\t\twant:   \"example.com/foo.(*Bar[...]).Bat\",\n\t\t},\n\t\t{\n\t\t\t// Method on type with type parameters, as reported by\n\t\t\t// perf profiles (actual symbol name).\n\t\t\tsymbol: \"example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat\",\n\t\t\twant:   \"example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat\",\n\t\t},\n\t\t{\n\t\t\t// Function with type parameters, as reported by Go\n\t\t\t// pprof profiles (simplified symbol name).\n\t\t\tsymbol: \"example.com/foo.Bar[...]\",\n\t\t\twant:   \"example.com/foo.Bar[...]\",\n\t\t},\n\t\t{\n\t\t\t// Function with type parameters, as reported by perf\n\t\t\t// profiles (actual symbol name).\n\t\t\tsymbol: \"example.com/foo.Bar[go.shape.string_0,go.shape.int_1]\",\n\t\t\twant:   \"example.com/foo.Bar[go.shape.string_0,go.shape.int_1]\",\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tfn := &profile.Function{\n\t\t\tSystemName: tc.symbol,\n\t\t}\n\t\tdemangleSingleFunction(fn, options)\n\t\tif fn.Name != tc.want {\n\t\t\tt.Errorf(\"demangleSingleFunction(%s) got %s want %s\", tc.symbol, fn.Name, tc.want)\n\t\t}\n\t}\n}\n\ntype mockObjTool struct{}\n\nfunc (mockObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {\n\tif file != filePath {\n\t\treturn nil, fmt.Errorf(\"unknown or non-existent file %q\", file)\n\t}\n\treturn mockObjFile{frames: mockAddresses}, nil\n}\n\nfunc (mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {\n\tif file != filePath {\n\t\treturn nil, fmt.Errorf(\"unknown or non-existent file %q\", file)\n\t}\n\treturn nil, fmt.Errorf(\"disassembly not supported\")\n}\n\ntype mockObjFile struct {\n\tframes map[uint64][]plugin.Frame\n}\n\nfunc (mockObjFile) Name() string {\n\treturn filePath\n}\n\nfunc (mockObjFile) ObjAddr(addr uint64) (uint64, error) {\n\treturn addr, nil\n}\n\nfunc (mockObjFile) BuildID() string {\n\treturn buildID\n}\n\nfunc (mf mockObjFile) SourceLine(addr uint64) ([]plugin.Frame, error) {\n\treturn mf.frames[addr], nil\n}\n\nfunc (mockObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {\n\treturn []*plugin.Sym{}, nil\n}\n\nfunc (mockObjFile) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "internal/symbolz/symbolz.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package symbolz symbolizes a profile using the output from the symbolz\n// service.\npackage symbolz\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"path\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/profile\"\n)\n\nvar (\n\tsymbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\\s+(.*)`)\n)\n\n// Symbolize symbolizes profile p by parsing data returned by a symbolz\n// handler. syms receives the symbolz query (hex addresses separated by '+')\n// and returns the symbolz output in a string. If force is false, it will only\n// symbolize locations from mappings not already marked as HasFunctions. Does\n// not skip unsymbolizable files since the symbolz handler can be flexible\n// enough to handle some of those cases such as JIT locations in //anon.\nfunc Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {\n\tfor _, m := range p.Mapping {\n\t\tif !force && m.HasFunctions {\n\t\t\t// Only check for HasFunctions as symbolz only populates function names.\n\t\t\tcontinue\n\t\t}\n\t\tmappingSources := sources[m.File]\n\t\tif m.BuildID != \"\" {\n\t\t\tmappingSources = append(mappingSources, sources[m.BuildID]...)\n\t\t}\n\t\tfor _, source := range mappingSources {\n\t\t\tif symz := symbolz(source.Source); symz != \"\" {\n\t\t\t\tif err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tm.HasFunctions = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// hasGperftoolsSuffix checks whether path ends with one of the suffixes listed in\n// pprof_remote_servers.html from the gperftools distribution\nfunc hasGperftoolsSuffix(path string) bool {\n\tsuffixes := []string{\n\t\t\"/pprof/heap\",\n\t\t\"/pprof/growth\",\n\t\t\"/pprof/profile\",\n\t\t\"/pprof/pmuprofile\",\n\t\t\"/pprof/contention\",\n\t}\n\tfor _, s := range suffixes {\n\t\tif strings.HasSuffix(path, s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// symbolz returns the corresponding symbolz source for a profile URL.\nfunc symbolz(source string) string {\n\tif url, err := url.Parse(source); err == nil && url.Host != \"\" {\n\t\t// All paths in the net/http/pprof Go package contain /debug/pprof/\n\t\tif strings.Contains(url.Path, \"/debug/pprof/\") || hasGperftoolsSuffix(url.Path) {\n\t\t\turl.Path = path.Clean(url.Path + \"/../symbol\")\n\t\t} else {\n\t\t\turl.Path = path.Clean(url.Path + \"/../symbolz\")\n\t\t}\n\t\turl.RawQuery = \"\"\n\t\treturn url.String()\n\t}\n\n\treturn \"\"\n}\n\n// symbolizeMapping symbolizes locations belonging to a Mapping by querying\n// a symbolz handler. An offset is applied to all addresses to take care of\n// normalization occurred for merged Mappings.\nfunc symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error {\n\t// Construct query of addresses to symbolize.\n\tvar a []string\n\tfor _, l := range p.Location {\n\t\tif l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {\n\t\t\t// Compensate for normalization.\n\t\t\taddr, overflow := adjust(l.Address, offset)\n\t\t\tif overflow {\n\t\t\t\treturn fmt.Errorf(\"cannot adjust address %d by %d, it would overflow (mapping %v)\", l.Address, offset, l.Mapping)\n\t\t\t}\n\t\t\ta = append(a, fmt.Sprintf(\"%#x\", addr))\n\t\t}\n\t}\n\n\tif len(a) == 0 {\n\t\t// No addresses to symbolize.\n\t\treturn nil\n\t}\n\n\tlines := make(map[uint64]profile.Line)\n\tfunctions := make(map[string]*profile.Function)\n\n\tb, err := syms(source, strings.Join(a, \"+\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbuf := bytes.NewBuffer(b)\n\tfor {\n\t\tl, err := buf.ReadString('\\n')\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tif symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {\n\t\t\torigAddr, err := strconv.ParseUint(symbol[1], 0, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unexpected parse failure %s: %v\", symbol[1], err)\n\t\t\t}\n\t\t\t// Reapply offset expected by the profile.\n\t\t\taddr, overflow := adjust(origAddr, -offset)\n\t\t\tif overflow {\n\t\t\t\treturn fmt.Errorf(\"cannot adjust symbolz address %d by %d, it would overflow\", origAddr, -offset)\n\t\t\t}\n\n\t\t\tname := symbol[2]\n\t\t\tfn := functions[name]\n\t\t\tif fn == nil {\n\t\t\t\tfn = &profile.Function{\n\t\t\t\t\tID:         uint64(len(p.Function) + 1),\n\t\t\t\t\tName:       name,\n\t\t\t\t\tSystemName: name,\n\t\t\t\t}\n\t\t\t\tfunctions[name] = fn\n\t\t\t\tp.Function = append(p.Function, fn)\n\t\t\t}\n\n\t\t\tlines[addr] = profile.Line{Function: fn}\n\t\t}\n\t}\n\n\tfor _, l := range p.Location {\n\t\tif l.Mapping != m {\n\t\t\tcontinue\n\t\t}\n\t\tif line, ok := lines[l.Address]; ok {\n\t\t\tl.Line = []profile.Line{line}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// adjust shifts the specified address by the signed offset. It returns the\n// adjusted address. It signals that the address cannot be adjusted without an\n// overflow by returning true in the second return value.\nfunc adjust(addr uint64, offset int64) (uint64, bool) {\n\tadj := uint64(int64(addr) + offset)\n\tif offset < 0 {\n\t\tif adj >= addr {\n\t\t\treturn 0, true\n\t\t}\n\t} else {\n\t\tif adj < addr {\n\t\t\treturn 0, true\n\t\t}\n\t}\n\treturn adj, false\n}\n"
  },
  {
    "path": "internal/symbolz/symbolz_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage symbolz\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n\t\"github.com/google/pprof/internal/proftest\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc TestSymbolzURL(t *testing.T) {\n\tfor try, want := range map[string]string{\n\t\t\"http://host:8000/profilez\":                                               \"http://host:8000/symbolz\",\n\t\t\"http://host:8000/profilez?seconds=5\":                                     \"http://host:8000/symbolz\",\n\t\t\"http://host:8000/profilez?seconds=5&format=proto\":                        \"http://host:8000/symbolz\",\n\t\t\"http://host:8000/heapz?format=legacy\":                                    \"http://host:8000/symbolz\",\n\t\t\"http://host:8000/some/deeper/path/profilez?seconds=5\":                    \"http://host:8000/some/deeper/path/symbolz\",\n\t\t\"http://host:8000/debug/pprof/profile\":                                    \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://host:8000/debug/pprof/profile?seconds=10\":                         \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://host:8000/debug/pprof/heap\":                                       \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://some.host:8080/some/deeper/path/debug/pprof/endpoint?param=value\": \"http://some.host:8080/some/deeper/path/debug/pprof/symbol\",\n\t\t\"http://host:8000/pprof/profile\":                                          \"http://host:8000/pprof/symbol\",\n\t\t\"http://host:8000/pprof/profile?seconds=15\":                               \"http://host:8000/pprof/symbol\",\n\t\t\"http://host:8000/pprof/heap\":                                             \"http://host:8000/pprof/symbol\",\n\t\t\"http://host:8000/debug/pprof/block\":                                      \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://host:8000/debug/pprof/trace?seconds=5\":                            \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://host:8000/debug/pprof/mutex\":                                      \"http://host:8000/debug/pprof/symbol\",\n\t\t\"http://host/whatever/pprof/heap\":                                         \"http://host/whatever/pprof/symbol\",\n\t\t\"http://host/whatever/pprof/growth\":                                       \"http://host/whatever/pprof/symbol\",\n\t\t\"http://host/whatever/pprof/profile\":                                      \"http://host/whatever/pprof/symbol\",\n\t\t\"http://host/whatever/pprof/pmuprofile\":                                   \"http://host/whatever/pprof/symbol\",\n\t\t\"http://host/whatever/pprof/contention\":                                   \"http://host/whatever/pprof/symbol\",\n\t} {\n\t\tif got := symbolz(try); got != want {\n\t\t\tt.Errorf(`symbolz(%s)=%s, want \"%s\"`, try, got, want)\n\t\t}\n\t}\n}\n\nfunc TestSymbolize(t *testing.T) {\n\ts := plugin.MappingSources{\n\t\t\"buildid\": []struct {\n\t\t\tSource string\n\t\t\tStart  uint64\n\t\t}{\n\t\t\t{Source: \"http://localhost:80/profilez\"},\n\t\t},\n\t}\n\n\tfor _, hasFunctions := range []bool{false, true} {\n\t\tfor _, force := range []bool{false, true} {\n\t\t\tp := testProfile(hasFunctions)\n\n\t\t\tif err := Symbolize(p, force, s, fetchSymbols, &proftest.TestUI{T: t}); err != nil {\n\t\t\t\tt.Errorf(\"symbolz: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar wantSym, wantNoSym []*profile.Location\n\t\t\tif force || !hasFunctions {\n\t\t\t\twantNoSym = p.Location[:1]\n\t\t\t\twantSym = p.Location[1:]\n\t\t\t} else {\n\t\t\t\twantNoSym = p.Location\n\t\t\t}\n\n\t\t\tif err := checkSymbolized(wantSym, true); err != nil {\n\t\t\t\tt.Errorf(\"symbolz hasFns=%v force=%v: %v\", hasFunctions, force, err)\n\t\t\t}\n\t\t\tif err := checkSymbolized(wantNoSym, false); err != nil {\n\t\t\t\tt.Errorf(\"symbolz hasFns=%v force=%v: %v\", hasFunctions, force, err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc testProfile(hasFunctions bool) *profile.Profile {\n\tm := []*profile.Mapping{\n\t\t{\n\t\t\tID:           1,\n\t\t\tStart:        0x1000,\n\t\t\tLimit:        0x5000,\n\t\t\tBuildID:      \"buildid\",\n\t\t\tHasFunctions: hasFunctions,\n\t\t},\n\t}\n\tp := &profile.Profile{\n\t\tLocation: []*profile.Location{\n\t\t\t{ID: 1, Mapping: m[0], Address: 0x1000},\n\t\t\t{ID: 2, Mapping: m[0], Address: 0x2000},\n\t\t\t{ID: 3, Mapping: m[0], Address: 0x3000},\n\t\t\t{ID: 4, Mapping: m[0], Address: 0x4000},\n\t\t},\n\t\tMapping: m,\n\t}\n\n\treturn p\n}\n\nfunc checkSymbolized(locs []*profile.Location, wantSymbolized bool) error {\n\tfor _, loc := range locs {\n\t\tif !wantSymbolized && len(loc.Line) != 0 {\n\t\t\treturn fmt.Errorf(\"unexpected symbolization for %#x: %v\", loc.Address, loc.Line)\n\t\t}\n\t\tif wantSymbolized {\n\t\t\tif len(loc.Line) != 1 {\n\t\t\t\treturn fmt.Errorf(\"expected symbolization for %#x: %v\", loc.Address, loc.Line)\n\t\t\t}\n\t\t\taddress := loc.Address - loc.Mapping.Start\n\t\t\tif got, want := loc.Line[0].Function.Name, fmt.Sprintf(\"%#x\", address); got != want {\n\t\t\t\treturn fmt.Errorf(\"symbolz %#x, got %s, want %s\", address, got, want)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc fetchSymbols(source, post string) ([]byte, error) {\n\tvar symbolz string\n\n\taddresses := strings.Split(post, \"+\")\n\t// Do not symbolize the first symbol.\n\tfor _, address := range addresses[1:] {\n\t\tsymbolz += fmt.Sprintf(\"%s\\t%s\\n\", address, address)\n\t}\n\treturn []byte(symbolz), nil\n}\n\nfunc TestAdjust(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\taddr         uint64\n\t\toffset       int64\n\t\twantAdj      uint64\n\t\twantOverflow bool\n\t}{{math.MaxUint64, 0, math.MaxUint64, false},\n\t\t{math.MaxUint64, 1, 0, true},\n\t\t{math.MaxUint64 - 1, 1, math.MaxUint64, false},\n\t\t{math.MaxUint64 - 1, 2, 0, true},\n\t\t{math.MaxInt64 + 1, math.MaxInt64, math.MaxUint64, false},\n\t\t{0, 0, 0, false},\n\t\t{0, -1, 0, true},\n\t\t{1, -1, 0, false},\n\t\t{2, -1, 1, false},\n\t\t{2, -2, 0, false},\n\t\t{2, -3, 0, true},\n\t\t{-math.MinInt64, math.MinInt64, 0, false},\n\t\t{-math.MinInt64 + 1, math.MinInt64, 1, false},\n\t\t{-math.MinInt64 - 1, math.MinInt64, 0, true},\n\t} {\n\t\tif adj, overflow := adjust(tc.addr, tc.offset); adj != tc.wantAdj || overflow != tc.wantOverflow {\n\t\t\tt.Errorf(\"adjust(%d, %d) = (%d, %t), want (%d, %t)\", tc.addr, tc.offset, adj, overflow, tc.wantAdj, tc.wantOverflow)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/transport/transport.go",
    "content": "// Copyright 2018 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package transport provides a mechanism to send requests with https cert,\n// key, and CA.\npackage transport\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/google/pprof/internal/plugin\"\n)\n\ntype transport struct {\n\tcert       *string\n\tkey        *string\n\tca         *string\n\tcaCertPool *x509.CertPool\n\tcerts      []tls.Certificate\n\tinitOnce   sync.Once\n\tinitErr    error\n}\n\nconst extraUsage = `    -tls_cert             TLS client certificate file for fetching profile and symbols\n    -tls_key              TLS private key file for fetching profile and symbols\n    -tls_ca               TLS CA certs file for fetching profile and symbols`\n\n// New returns a round tripper for making requests with the\n// specified cert, key, and ca. The flags tls_cert, tls_key, and tls_ca are\n// added to the flagset to allow a user to specify the cert, key, and ca. If\n// the flagset is nil, no flags will be added, and users will not be able to\n// use these flags.\nfunc New(flagset plugin.FlagSet) http.RoundTripper {\n\tif flagset == nil {\n\t\treturn &transport{}\n\t}\n\tflagset.AddExtraUsage(extraUsage)\n\treturn &transport{\n\t\tcert: flagset.String(\"tls_cert\", \"\", \"TLS client certificate file for fetching profile and symbols\"),\n\t\tkey:  flagset.String(\"tls_key\", \"\", \"TLS private key file for fetching profile and symbols\"),\n\t\tca:   flagset.String(\"tls_ca\", \"\", \"TLS CA certs file for fetching profile and symbols\"),\n\t}\n}\n\n// initialize uses the cert, key, and ca to initialize the certs\n// to use these when making requests.\nfunc (tr *transport) initialize() error {\n\tvar cert, key, ca string\n\tif tr.cert != nil {\n\t\tcert = *tr.cert\n\t}\n\tif tr.key != nil {\n\t\tkey = *tr.key\n\t}\n\tif tr.ca != nil {\n\t\tca = *tr.ca\n\t}\n\n\tif cert != \"\" && key != \"\" {\n\t\ttlsCert, err := tls.LoadX509KeyPair(cert, key)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not load certificate/key pair specified by -tls_cert and -tls_key: %v\", err)\n\t\t}\n\t\ttr.certs = []tls.Certificate{tlsCert}\n\t} else if cert == \"\" && key != \"\" {\n\t\treturn fmt.Errorf(\"-tls_key is specified, so -tls_cert must also be specified\")\n\t} else if cert != \"\" && key == \"\" {\n\t\treturn fmt.Errorf(\"-tls_cert is specified, so -tls_key must also be specified\")\n\t}\n\n\tif ca != \"\" {\n\t\tcaCertPool := x509.NewCertPool()\n\t\tcaCert, err := os.ReadFile(ca)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not load CA specified by -tls_ca: %v\", err)\n\t\t}\n\t\tcaCertPool.AppendCertsFromPEM(caCert)\n\t\ttr.caCertPool = caCertPool\n\t}\n\n\treturn nil\n}\n\n// RoundTrip executes a single HTTP transaction, returning\n// a Response for the provided Request.\nfunc (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) {\n\ttr.initOnce.Do(func() {\n\t\ttr.initErr = tr.initialize()\n\t})\n\tif tr.initErr != nil {\n\t\treturn nil, tr.initErr\n\t}\n\n\ttlsConfig := &tls.Config{\n\t\tRootCAs:      tr.caCertPool,\n\t\tCertificates: tr.certs,\n\t}\n\n\tif req.URL.Scheme == \"https+insecure\" {\n\t\t// Make shallow copy of request, and req.URL, so the request's URL can be\n\t\t// modified.\n\t\tr := *req\n\t\t*r.URL = *req.URL\n\t\treq = &r\n\t\ttlsConfig.InsecureSkipVerify = true\n\t\treq.URL.Scheme = \"https\"\n\t}\n\n\ttransport := http.Transport{\n\t\tProxy:           http.ProxyFromEnvironment,\n\t\tTLSClientConfig: tlsConfig,\n\t}\n\n\treturn transport.RoundTrip(req)\n}\n"
  },
  {
    "path": "pprof.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// pprof is a tool for collection, manipulation and visualization\n// of performance profiles.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/chzyer/readline\"\n\t\"github.com/google/pprof/driver\"\n)\n\nfunc main() {\n\tif err := driver.PProf(&driver.Options{UI: newUI()}); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"pprof: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n}\n\n// readlineUI implements the driver.UI interface using the\n// github.com/chzyer/readline library.\n// This is contained in pprof.go to avoid adding the readline\n// dependency in the vendored copy of pprof in the Go distribution,\n// which does not use this file.\ntype readlineUI struct {\n\trl *readline.Instance\n}\n\nfunc newUI() driver.UI {\n\trl, err := readline.New(\"\")\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"readline: %v\", err)\n\t\treturn nil\n\t}\n\treturn &readlineUI{\n\t\trl: rl,\n\t}\n}\n\n// ReadLine returns a line of text (a command) read from the user.\n// prompt is printed before reading the command.\nfunc (r *readlineUI) ReadLine(prompt string) (string, error) {\n\tr.rl.SetPrompt(prompt)\n\treturn r.rl.Readline()\n}\n\n// Print shows a message to the user.\n// It is printed over stderr as stdout is reserved for regular output.\nfunc (r *readlineUI) Print(args ...interface{}) {\n\ttext := fmt.Sprint(args...)\n\tif !strings.HasSuffix(text, \"\\n\") {\n\t\ttext += \"\\n\"\n\t}\n\tfmt.Fprint(r.rl.Stderr(), text)\n}\n\n// PrintErr shows a message to the user, colored in red for emphasis.\n// It is printed over stderr as stdout is reserved for regular output.\nfunc (r *readlineUI) PrintErr(args ...interface{}) {\n\ttext := fmt.Sprint(args...)\n\tif !strings.HasSuffix(text, \"\\n\") {\n\t\ttext += \"\\n\"\n\t}\n\tif readline.IsTerminal(int(syscall.Stderr)) {\n\t\ttext = colorize(text)\n\t}\n\tfmt.Fprint(r.rl.Stderr(), text)\n}\n\n// colorize the msg using ANSI color escapes.\nfunc colorize(msg string) string {\n\tvar red = 31\n\tvar colorEscape = fmt.Sprintf(\"\\033[0;%dm\", red)\n\tvar colorResetEscape = \"\\033[0m\"\n\treturn colorEscape + msg + colorResetEscape\n}\n\n// IsTerminal returns whether the UI is known to be tied to an\n// interactive terminal (as opposed to being redirected to a file).\nfunc (r *readlineUI) IsTerminal() bool {\n\treturn readline.IsTerminal(int(syscall.Stdout))\n}\n\n// WantBrowser starts a browser on interactive mode.\nfunc (r *readlineUI) WantBrowser() bool {\n\treturn r.IsTerminal()\n}\n\n// SetAutoComplete instructs the UI to call complete(cmd) to obtain\n// the auto-completion of cmd, if the UI supports auto-completion at all.\nfunc (r *readlineUI) SetAutoComplete(complete func(string) string) {\n\t// TODO: Implement auto-completion support.\n}\n"
  },
  {
    "path": "profile/encode.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"strings\"\n)\n\nfunc (p *Profile) decoder() []decoder {\n\treturn profileDecoder\n}\n\n// preEncode populates the unexported fields to be used by encode\n// (with suffix X) from the corresponding exported fields. The\n// exported fields are cleared up to facilitate testing.\nfunc (p *Profile) preEncode() {\n\tstrings := make(map[string]int)\n\taddString(strings, \"\")\n\n\tfor _, st := range p.SampleType {\n\t\tst.typeX = addString(strings, st.Type)\n\t\tst.unitX = addString(strings, st.Unit)\n\t}\n\n\tfor _, s := range p.Sample {\n\t\ts.labelX = nil\n\t\tvar keys []string\n\t\tfor k := range s.Label {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\tsort.Strings(keys)\n\t\tfor _, k := range keys {\n\t\t\tvs := s.Label[k]\n\t\t\tfor _, v := range vs {\n\t\t\t\ts.labelX = append(s.labelX,\n\t\t\t\t\tlabel{\n\t\t\t\t\t\tkeyX: addString(strings, k),\n\t\t\t\t\t\tstrX: addString(strings, v),\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tvar numKeys []string\n\t\tfor k := range s.NumLabel {\n\t\t\tnumKeys = append(numKeys, k)\n\t\t}\n\t\tsort.Strings(numKeys)\n\t\tfor _, k := range numKeys {\n\t\t\tkeyX := addString(strings, k)\n\t\t\tvs := s.NumLabel[k]\n\t\t\tunits := s.NumUnit[k]\n\t\t\tfor i, v := range vs {\n\t\t\t\tvar unitX int64\n\t\t\t\tif len(units) != 0 {\n\t\t\t\t\tunitX = addString(strings, units[i])\n\t\t\t\t}\n\t\t\t\ts.labelX = append(s.labelX,\n\t\t\t\t\tlabel{\n\t\t\t\t\t\tkeyX:  keyX,\n\t\t\t\t\t\tnumX:  v,\n\t\t\t\t\t\tunitX: unitX,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\ts.locationIDX = make([]uint64, len(s.Location))\n\t\tfor i, loc := range s.Location {\n\t\t\ts.locationIDX[i] = loc.ID\n\t\t}\n\t}\n\n\tfor _, m := range p.Mapping {\n\t\tm.fileX = addString(strings, m.File)\n\t\tm.buildIDX = addString(strings, m.BuildID)\n\t}\n\n\tfor _, l := range p.Location {\n\t\tfor i, ln := range l.Line {\n\t\t\tif ln.Function != nil {\n\t\t\t\tl.Line[i].functionIDX = ln.Function.ID\n\t\t\t} else {\n\t\t\t\tl.Line[i].functionIDX = 0\n\t\t\t}\n\t\t}\n\t\tif l.Mapping != nil {\n\t\t\tl.mappingIDX = l.Mapping.ID\n\t\t} else {\n\t\t\tl.mappingIDX = 0\n\t\t}\n\t}\n\tfor _, f := range p.Function {\n\t\tf.nameX = addString(strings, f.Name)\n\t\tf.systemNameX = addString(strings, f.SystemName)\n\t\tf.filenameX = addString(strings, f.Filename)\n\t}\n\n\tp.dropFramesX = addString(strings, p.DropFrames)\n\tp.keepFramesX = addString(strings, p.KeepFrames)\n\n\tif pt := p.PeriodType; pt != nil {\n\t\tpt.typeX = addString(strings, pt.Type)\n\t\tpt.unitX = addString(strings, pt.Unit)\n\t}\n\n\tp.commentX = nil\n\tfor _, c := range p.Comments {\n\t\tp.commentX = append(p.commentX, addString(strings, c))\n\t}\n\n\tp.defaultSampleTypeX = addString(strings, p.DefaultSampleType)\n\tp.docURLX = addString(strings, p.DocURL)\n\n\tp.stringTable = make([]string, len(strings))\n\tfor s, i := range strings {\n\t\tp.stringTable[i] = s\n\t}\n}\n\nfunc (p *Profile) encode(b *buffer) {\n\tfor _, x := range p.SampleType {\n\t\tencodeMessage(b, 1, x)\n\t}\n\tfor _, x := range p.Sample {\n\t\tencodeMessage(b, 2, x)\n\t}\n\tfor _, x := range p.Mapping {\n\t\tencodeMessage(b, 3, x)\n\t}\n\tfor _, x := range p.Location {\n\t\tencodeMessage(b, 4, x)\n\t}\n\tfor _, x := range p.Function {\n\t\tencodeMessage(b, 5, x)\n\t}\n\tencodeStrings(b, 6, p.stringTable)\n\tencodeInt64Opt(b, 7, p.dropFramesX)\n\tencodeInt64Opt(b, 8, p.keepFramesX)\n\tencodeInt64Opt(b, 9, p.TimeNanos)\n\tencodeInt64Opt(b, 10, p.DurationNanos)\n\tif pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {\n\t\tencodeMessage(b, 11, p.PeriodType)\n\t}\n\tencodeInt64Opt(b, 12, p.Period)\n\tencodeInt64s(b, 13, p.commentX)\n\tencodeInt64(b, 14, p.defaultSampleTypeX)\n\tencodeInt64Opt(b, 15, p.docURLX)\n}\n\nvar profileDecoder = []decoder{\n\tnil, // 0\n\t// repeated ValueType sample_type = 1\n\tfunc(b *buffer, m message) error {\n\t\tx := new(ValueType)\n\t\tpp := m.(*Profile)\n\t\tpp.SampleType = append(pp.SampleType, x)\n\t\treturn decodeMessage(b, x)\n\t},\n\t// repeated Sample sample = 2\n\tfunc(b *buffer, m message) error {\n\t\tx := new(Sample)\n\t\tpp := m.(*Profile)\n\t\tpp.Sample = append(pp.Sample, x)\n\t\treturn decodeMessage(b, x)\n\t},\n\t// repeated Mapping mapping = 3\n\tfunc(b *buffer, m message) error {\n\t\tx := new(Mapping)\n\t\tpp := m.(*Profile)\n\t\tpp.Mapping = append(pp.Mapping, x)\n\t\treturn decodeMessage(b, x)\n\t},\n\t// repeated Location location = 4\n\tfunc(b *buffer, m message) error {\n\t\tx := new(Location)\n\t\tx.Line = b.tmpLines[:0] // Use shared space temporarily\n\t\tpp := m.(*Profile)\n\t\tpp.Location = append(pp.Location, x)\n\t\terr := decodeMessage(b, x)\n\t\tb.tmpLines = x.Line[:0]\n\t\t// Copy to shrink size and detach from shared space.\n\t\tx.Line = append([]Line(nil), x.Line...)\n\t\treturn err\n\t},\n\t// repeated Function function = 5\n\tfunc(b *buffer, m message) error {\n\t\tx := new(Function)\n\t\tpp := m.(*Profile)\n\t\tpp.Function = append(pp.Function, x)\n\t\treturn decodeMessage(b, x)\n\t},\n\t// repeated string string_table = 6\n\tfunc(b *buffer, m message) error {\n\t\terr := decodeStrings(b, &m.(*Profile).stringTable)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif m.(*Profile).stringTable[0] != \"\" {\n\t\t\treturn errors.New(\"string_table[0] must be ''\")\n\t\t}\n\t\treturn nil\n\t},\n\t// int64 drop_frames = 7\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },\n\t// int64 keep_frames = 8\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },\n\t// int64 time_nanos = 9\n\tfunc(b *buffer, m message) error {\n\t\tif m.(*Profile).TimeNanos != 0 {\n\t\t\treturn errConcatProfile\n\t\t}\n\t\treturn decodeInt64(b, &m.(*Profile).TimeNanos)\n\t},\n\t// int64 duration_nanos = 10\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },\n\t// ValueType period_type = 11\n\tfunc(b *buffer, m message) error {\n\t\tx := new(ValueType)\n\t\tpp := m.(*Profile)\n\t\tpp.PeriodType = x\n\t\treturn decodeMessage(b, x)\n\t},\n\t// int64 period = 12\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },\n\t// repeated int64 comment = 13\n\tfunc(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) },\n\t// int64 defaultSampleType = 14\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).defaultSampleTypeX) },\n\t// string doc_link = 15;\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).docURLX) },\n}\n\n// postDecode takes the unexported fields populated by decode (with\n// suffix X) and populates the corresponding exported fields.\n// The unexported fields are cleared up to facilitate testing.\nfunc (p *Profile) postDecode() error {\n\tvar err error\n\tmappings := make(map[uint64]*Mapping, len(p.Mapping))\n\tmappingIds := make([]*Mapping, len(p.Mapping)+1)\n\tfor _, m := range p.Mapping {\n\t\tm.File, err = getString(p.stringTable, &m.fileX, err)\n\t\tm.BuildID, err = getString(p.stringTable, &m.buildIDX, err)\n\t\tif m.ID < uint64(len(mappingIds)) {\n\t\t\tmappingIds[m.ID] = m\n\t\t} else {\n\t\t\tmappings[m.ID] = m\n\t\t}\n\n\t\t// If this a main linux kernel mapping with a relocation symbol suffix\n\t\t// (\"[kernel.kallsyms]_text\"), extract said suffix.\n\t\t// It is fairly hacky to handle at this level, but the alternatives appear even worse.\n\t\tconst prefix = \"[kernel.kallsyms]\"\n\t\tif strings.HasPrefix(m.File, prefix) {\n\t\t\tm.KernelRelocationSymbol = m.File[len(prefix):]\n\t\t}\n\t}\n\n\tfunctions := make(map[uint64]*Function, len(p.Function))\n\tfunctionIds := make([]*Function, len(p.Function)+1)\n\tfor _, f := range p.Function {\n\t\tf.Name, err = getString(p.stringTable, &f.nameX, err)\n\t\tf.SystemName, err = getString(p.stringTable, &f.systemNameX, err)\n\t\tf.Filename, err = getString(p.stringTable, &f.filenameX, err)\n\t\tif f.ID < uint64(len(functionIds)) {\n\t\t\tfunctionIds[f.ID] = f\n\t\t} else {\n\t\t\tfunctions[f.ID] = f\n\t\t}\n\t}\n\n\tlocations := make(map[uint64]*Location, len(p.Location))\n\tlocationIds := make([]*Location, len(p.Location)+1)\n\tfor _, l := range p.Location {\n\t\tif id := l.mappingIDX; id < uint64(len(mappingIds)) {\n\t\t\tl.Mapping = mappingIds[id]\n\t\t} else {\n\t\t\tl.Mapping = mappings[id]\n\t\t}\n\t\tl.mappingIDX = 0\n\t\tfor i, ln := range l.Line {\n\t\t\tif id := ln.functionIDX; id != 0 {\n\t\t\t\tl.Line[i].functionIDX = 0\n\t\t\t\tif id < uint64(len(functionIds)) {\n\t\t\t\t\tl.Line[i].Function = functionIds[id]\n\t\t\t\t} else {\n\t\t\t\t\tl.Line[i].Function = functions[id]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif l.ID < uint64(len(locationIds)) {\n\t\t\tlocationIds[l.ID] = l\n\t\t} else {\n\t\t\tlocations[l.ID] = l\n\t\t}\n\t}\n\n\tfor _, st := range p.SampleType {\n\t\tst.Type, err = getString(p.stringTable, &st.typeX, err)\n\t\tst.Unit, err = getString(p.stringTable, &st.unitX, err)\n\t}\n\n\t// Pre-allocate space for all locations.\n\tnumLocations := 0\n\tfor _, s := range p.Sample {\n\t\tnumLocations += len(s.locationIDX)\n\t}\n\tlocBuffer := make([]*Location, numLocations)\n\n\tfor _, s := range p.Sample {\n\t\tif len(s.labelX) > 0 {\n\t\t\tlabels := make(map[string][]string, len(s.labelX))\n\t\t\tnumLabels := make(map[string][]int64, len(s.labelX))\n\t\t\tnumUnits := make(map[string][]string, len(s.labelX))\n\t\t\tfor _, l := range s.labelX {\n\t\t\t\tvar key, value string\n\t\t\t\tkey, err = getString(p.stringTable, &l.keyX, err)\n\t\t\t\tif l.strX != 0 {\n\t\t\t\t\tvalue, err = getString(p.stringTable, &l.strX, err)\n\t\t\t\t\tlabels[key] = append(labels[key], value)\n\t\t\t\t} else if l.numX != 0 || l.unitX != 0 {\n\t\t\t\t\tnumValues := numLabels[key]\n\t\t\t\t\tunits := numUnits[key]\n\t\t\t\t\tif l.unitX != 0 {\n\t\t\t\t\t\tvar unit string\n\t\t\t\t\t\tunit, err = getString(p.stringTable, &l.unitX, err)\n\t\t\t\t\t\tunits = padStringArray(units, len(numValues))\n\t\t\t\t\t\tnumUnits[key] = append(units, unit)\n\t\t\t\t\t}\n\t\t\t\t\tnumLabels[key] = append(numLabels[key], l.numX)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(labels) > 0 {\n\t\t\t\ts.Label = labels\n\t\t\t}\n\t\t\tif len(numLabels) > 0 {\n\t\t\t\ts.NumLabel = numLabels\n\t\t\t\tfor key, units := range numUnits {\n\t\t\t\t\tif len(units) > 0 {\n\t\t\t\t\t\tnumUnits[key] = padStringArray(units, len(numLabels[key]))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ts.NumUnit = numUnits\n\t\t\t}\n\t\t}\n\n\t\ts.Location = locBuffer[:len(s.locationIDX)]\n\t\tlocBuffer = locBuffer[len(s.locationIDX):]\n\t\tfor i, lid := range s.locationIDX {\n\t\t\tif lid < uint64(len(locationIds)) {\n\t\t\t\ts.Location[i] = locationIds[lid]\n\t\t\t} else {\n\t\t\t\ts.Location[i] = locations[lid]\n\t\t\t}\n\t\t}\n\t\ts.locationIDX = nil\n\t}\n\n\tp.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)\n\tp.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)\n\n\tif pt := p.PeriodType; pt == nil {\n\t\tp.PeriodType = &ValueType{}\n\t}\n\n\tif pt := p.PeriodType; pt != nil {\n\t\tpt.Type, err = getString(p.stringTable, &pt.typeX, err)\n\t\tpt.Unit, err = getString(p.stringTable, &pt.unitX, err)\n\t}\n\n\tfor _, i := range p.commentX {\n\t\tvar c string\n\t\tc, err = getString(p.stringTable, &i, err)\n\t\tp.Comments = append(p.Comments, c)\n\t}\n\n\tp.commentX = nil\n\tp.DefaultSampleType, err = getString(p.stringTable, &p.defaultSampleTypeX, err)\n\tp.DocURL, err = getString(p.stringTable, &p.docURLX, err)\n\tp.stringTable = nil\n\treturn err\n}\n\n// padStringArray pads arr with enough empty strings to make arr\n// length l when arr's length is less than l.\nfunc padStringArray(arr []string, l int) []string {\n\tif l <= len(arr) {\n\t\treturn arr\n\t}\n\treturn append(arr, make([]string, l-len(arr))...)\n}\n\nfunc (p *ValueType) decoder() []decoder {\n\treturn valueTypeDecoder\n}\n\nfunc (p *ValueType) encode(b *buffer) {\n\tencodeInt64Opt(b, 1, p.typeX)\n\tencodeInt64Opt(b, 2, p.unitX)\n}\n\nvar valueTypeDecoder = []decoder{\n\tnil, // 0\n\t// optional int64 type = 1\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },\n\t// optional int64 unit = 2\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },\n}\n\nfunc (p *Sample) decoder() []decoder {\n\treturn sampleDecoder\n}\n\nfunc (p *Sample) encode(b *buffer) {\n\tencodeUint64s(b, 1, p.locationIDX)\n\tencodeInt64s(b, 2, p.Value)\n\tfor _, x := range p.labelX {\n\t\tencodeMessage(b, 3, x)\n\t}\n}\n\nvar sampleDecoder = []decoder{\n\tnil, // 0\n\t// repeated uint64 location = 1\n\tfunc(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },\n\t// repeated int64 value = 2\n\tfunc(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },\n\t// repeated Label label = 3\n\tfunc(b *buffer, m message) error {\n\t\ts := m.(*Sample)\n\t\tn := len(s.labelX)\n\t\ts.labelX = append(s.labelX, label{})\n\t\treturn decodeMessage(b, &s.labelX[n])\n\t},\n}\n\nfunc (p label) decoder() []decoder {\n\treturn labelDecoder\n}\n\nfunc (p label) encode(b *buffer) {\n\tencodeInt64Opt(b, 1, p.keyX)\n\tencodeInt64Opt(b, 2, p.strX)\n\tencodeInt64Opt(b, 3, p.numX)\n\tencodeInt64Opt(b, 4, p.unitX)\n}\n\nvar labelDecoder = []decoder{\n\tnil, // 0\n\t// optional int64 key = 1\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*label).keyX) },\n\t// optional int64 str = 2\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*label).strX) },\n\t// optional int64 num = 3\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*label).numX) },\n\t// optional int64 num = 4\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*label).unitX) },\n}\n\nfunc (p *Mapping) decoder() []decoder {\n\treturn mappingDecoder\n}\n\nfunc (p *Mapping) encode(b *buffer) {\n\tencodeUint64Opt(b, 1, p.ID)\n\tencodeUint64Opt(b, 2, p.Start)\n\tencodeUint64Opt(b, 3, p.Limit)\n\tencodeUint64Opt(b, 4, p.Offset)\n\tencodeInt64Opt(b, 5, p.fileX)\n\tencodeInt64Opt(b, 6, p.buildIDX)\n\tencodeBoolOpt(b, 7, p.HasFunctions)\n\tencodeBoolOpt(b, 8, p.HasFilenames)\n\tencodeBoolOpt(b, 9, p.HasLineNumbers)\n\tencodeBoolOpt(b, 10, p.HasInlineFrames)\n}\n\nvar mappingDecoder = []decoder{\n\tnil, // 0\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) },            // optional uint64 id = 1\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) },         // optional uint64 memory_offset = 2\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) },         // optional uint64 memory_limit = 3\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) },        // optional uint64 file_offset = 4\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) },          // optional int64 filename = 5\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) },       // optional int64 build_id = 6\n\tfunc(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) },    // optional bool has_functions = 7\n\tfunc(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) },    // optional bool has_filenames = 8\n\tfunc(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) },  // optional bool has_line_numbers = 9\n\tfunc(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10\n}\n\nfunc (p *Location) decoder() []decoder {\n\treturn locationDecoder\n}\n\nfunc (p *Location) encode(b *buffer) {\n\tencodeUint64Opt(b, 1, p.ID)\n\tencodeUint64Opt(b, 2, p.mappingIDX)\n\tencodeUint64Opt(b, 3, p.Address)\n\tfor i := range p.Line {\n\t\tencodeMessage(b, 4, &p.Line[i])\n\t}\n\tencodeBoolOpt(b, 5, p.IsFolded)\n}\n\nvar locationDecoder = []decoder{\n\tnil, // 0\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) },         // optional uint64 id = 1;\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) },    // optional uint64 address = 3;\n\tfunc(b *buffer, m message) error { // repeated Line line = 4\n\t\tpp := m.(*Location)\n\t\tn := len(pp.Line)\n\t\tpp.Line = append(pp.Line, Line{})\n\t\treturn decodeMessage(b, &pp.Line[n])\n\t},\n\tfunc(b *buffer, m message) error { return decodeBool(b, &m.(*Location).IsFolded) }, // optional bool is_folded = 5;\n}\n\nfunc (p *Line) decoder() []decoder {\n\treturn lineDecoder\n}\n\nfunc (p *Line) encode(b *buffer) {\n\tencodeUint64Opt(b, 1, p.functionIDX)\n\tencodeInt64Opt(b, 2, p.Line)\n\tencodeInt64Opt(b, 3, p.Column)\n}\n\nvar lineDecoder = []decoder{\n\tnil, // 0\n\t// optional uint64 function_id = 1\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },\n\t// optional int64 line = 2\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },\n\t// optional int64 column = 3\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Column) },\n}\n\nfunc (p *Function) decoder() []decoder {\n\treturn functionDecoder\n}\n\nfunc (p *Function) encode(b *buffer) {\n\tencodeUint64Opt(b, 1, p.ID)\n\tencodeInt64Opt(b, 2, p.nameX)\n\tencodeInt64Opt(b, 3, p.systemNameX)\n\tencodeInt64Opt(b, 4, p.filenameX)\n\tencodeInt64Opt(b, 5, p.StartLine)\n}\n\nvar functionDecoder = []decoder{\n\tnil, // 0\n\t// optional uint64 id = 1\n\tfunc(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },\n\t// optional int64 function_name = 2\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },\n\t// optional int64 function_system_name = 3\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },\n\t// repeated int64 filename = 4\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },\n\t// optional int64 start_line = 5\n\tfunc(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },\n}\n\nfunc addString(strings map[string]int, s string) int64 {\n\ti, ok := strings[s]\n\tif !ok {\n\t\ti = len(strings)\n\t\tstrings[s] = i\n\t}\n\treturn int64(i)\n}\n\nfunc getString(strings []string, strng *int64, err error) (string, error) {\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ts := int(*strng)\n\tif s < 0 || s >= len(strings) {\n\t\treturn \"\", errMalformed\n\t}\n\t*strng = 0\n\treturn strings[s], nil\n}\n"
  },
  {
    "path": "profile/filter.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\n// Implements methods to filter samples from profiles.\n\nimport \"regexp\"\n\n// FilterSamplesByName filters the samples in a profile and only keeps\n// samples where at least one frame matches focus but none match ignore.\n// Returns true is the corresponding regexp matched at least one sample.\nfunc (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) (fm, im, hm, hnm bool) {\n\tif focus == nil && ignore == nil && hide == nil && show == nil {\n\t\tfm = true // Missing focus implies a match\n\t\treturn\n\t}\n\tfocusOrIgnore := make(map[uint64]bool)\n\thidden := make(map[uint64]bool)\n\tfor _, l := range p.Location {\n\t\tif ignore != nil && l.matchesName(ignore) {\n\t\t\tim = true\n\t\t\tfocusOrIgnore[l.ID] = false\n\t\t} else if focus == nil || l.matchesName(focus) {\n\t\t\tfm = true\n\t\t\tfocusOrIgnore[l.ID] = true\n\t\t}\n\n\t\tif hide != nil && l.matchesName(hide) {\n\t\t\thm = true\n\t\t\tl.Line = l.unmatchedLines(hide)\n\t\t\tif len(l.Line) == 0 {\n\t\t\t\thidden[l.ID] = true\n\t\t\t}\n\t\t}\n\t\tif show != nil {\n\t\t\tl.Line = l.matchedLines(show)\n\t\t\tif len(l.Line) == 0 {\n\t\t\t\thidden[l.ID] = true\n\t\t\t} else {\n\t\t\t\thnm = true\n\t\t\t}\n\t\t}\n\t}\n\n\ts := make([]*Sample, 0, len(p.Sample))\n\tfor _, sample := range p.Sample {\n\t\tif focusedAndNotIgnored(sample.Location, focusOrIgnore) {\n\t\t\tif len(hidden) > 0 {\n\t\t\t\tvar locs []*Location\n\t\t\t\tfor _, loc := range sample.Location {\n\t\t\t\t\tif !hidden[loc.ID] {\n\t\t\t\t\t\tlocs = append(locs, loc)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(locs) == 0 {\n\t\t\t\t\t// Remove sample with no locations (by not adding it to s).\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsample.Location = locs\n\t\t\t}\n\t\t\ts = append(s, sample)\n\t\t}\n\t}\n\tp.Sample = s\n\n\treturn\n}\n\n// ShowFrom drops all stack frames above the highest matching frame and returns\n// whether a match was found. If showFrom is nil it returns false and does not\n// modify the profile.\n//\n// Example: consider a sample with frames [A, B, C, B], where A is the root.\n// ShowFrom(nil) returns false and has frames [A, B, C, B].\n// ShowFrom(A) returns true and has frames [A, B, C, B].\n// ShowFrom(B) returns true and has frames [B, C, B].\n// ShowFrom(C) returns true and has frames [C, B].\n// ShowFrom(D) returns false and drops the sample because no frames remain.\nfunc (p *Profile) ShowFrom(showFrom *regexp.Regexp) (matched bool) {\n\tif showFrom == nil {\n\t\treturn false\n\t}\n\t// showFromLocs stores location IDs that matched ShowFrom.\n\tshowFromLocs := make(map[uint64]bool)\n\t// Apply to locations.\n\tfor _, loc := range p.Location {\n\t\tif filterShowFromLocation(loc, showFrom) {\n\t\t\tshowFromLocs[loc.ID] = true\n\t\t\tmatched = true\n\t\t}\n\t}\n\t// For all samples, strip locations after the highest matching one.\n\ts := make([]*Sample, 0, len(p.Sample))\n\tfor _, sample := range p.Sample {\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tif showFromLocs[sample.Location[i].ID] {\n\t\t\t\tsample.Location = sample.Location[:i+1]\n\t\t\t\ts = append(s, sample)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tp.Sample = s\n\treturn matched\n}\n\n// filterShowFromLocation tests a showFrom regex against a location, removes\n// lines after the last match and returns whether a match was found. If the\n// mapping is matched, then all lines are kept.\nfunc filterShowFromLocation(loc *Location, showFrom *regexp.Regexp) bool {\n\tif m := loc.Mapping; m != nil && showFrom.MatchString(m.File) {\n\t\treturn true\n\t}\n\tif i := loc.lastMatchedLineIndex(showFrom); i >= 0 {\n\t\tloc.Line = loc.Line[:i+1]\n\t\treturn true\n\t}\n\treturn false\n}\n\n// lastMatchedLineIndex returns the index of the last line that matches a regex,\n// or -1 if no match is found.\nfunc (loc *Location) lastMatchedLineIndex(re *regexp.Regexp) int {\n\tfor i := len(loc.Line) - 1; i >= 0; i-- {\n\t\tif fn := loc.Line[i].Function; fn != nil {\n\t\t\tif re.MatchString(fn.Name) || re.MatchString(fn.Filename) {\n\t\t\t\treturn i\n\t\t\t}\n\t\t}\n\t}\n\treturn -1\n}\n\n// FilterTagsByName filters the tags in a profile and only keeps\n// tags that match show and not hide.\nfunc (p *Profile) FilterTagsByName(show, hide *regexp.Regexp) (sm, hm bool) {\n\tmatchRemove := func(name string) bool {\n\t\tmatchShow := show == nil || show.MatchString(name)\n\t\tmatchHide := hide != nil && hide.MatchString(name)\n\n\t\tif matchShow {\n\t\t\tsm = true\n\t\t}\n\t\tif matchHide {\n\t\t\thm = true\n\t\t}\n\t\treturn !matchShow || matchHide\n\t}\n\tfor _, s := range p.Sample {\n\t\tfor lab := range s.Label {\n\t\t\tif matchRemove(lab) {\n\t\t\t\tdelete(s.Label, lab)\n\t\t\t}\n\t\t}\n\t\tfor lab := range s.NumLabel {\n\t\t\tif matchRemove(lab) {\n\t\t\t\tdelete(s.NumLabel, lab)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// matchesName returns whether the location matches the regular\n// expression. It checks any available function names, file names, and\n// mapping object filename.\nfunc (loc *Location) matchesName(re *regexp.Regexp) bool {\n\tfor _, ln := range loc.Line {\n\t\tif fn := ln.Function; fn != nil {\n\t\t\tif re.MatchString(fn.Name) || re.MatchString(fn.Filename) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tif m := loc.Mapping; m != nil && re.MatchString(m.File) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// unmatchedLines returns the lines in the location that do not match\n// the regular expression.\nfunc (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {\n\tif m := loc.Mapping; m != nil && re.MatchString(m.File) {\n\t\treturn nil\n\t}\n\tvar lines []Line\n\tfor _, ln := range loc.Line {\n\t\tif fn := ln.Function; fn != nil {\n\t\t\tif re.MatchString(fn.Name) || re.MatchString(fn.Filename) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tlines = append(lines, ln)\n\t}\n\treturn lines\n}\n\n// matchedLines returns the lines in the location that match\n// the regular expression.\nfunc (loc *Location) matchedLines(re *regexp.Regexp) []Line {\n\tif m := loc.Mapping; m != nil && re.MatchString(m.File) {\n\t\treturn loc.Line\n\t}\n\tvar lines []Line\n\tfor _, ln := range loc.Line {\n\t\tif fn := ln.Function; fn != nil {\n\t\t\tif !re.MatchString(fn.Name) && !re.MatchString(fn.Filename) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tlines = append(lines, ln)\n\t}\n\treturn lines\n}\n\n// focusedAndNotIgnored looks up a slice of ids against a map of\n// focused/ignored locations. The map only contains locations that are\n// explicitly focused or ignored. Returns whether there is at least\n// one focused location but no ignored locations.\nfunc focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {\n\tvar f bool\n\tfor _, loc := range locs {\n\t\tif focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {\n\t\t\tif focus {\n\t\t\t\t// Found focused location. Must keep searching in case there\n\t\t\t\t// is an ignored one as well.\n\t\t\t\tf = true\n\t\t\t} else {\n\t\t\t\t// Found ignored location. Can return false right away.\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn f\n}\n\n// TagMatch selects tags for filtering\ntype TagMatch func(s *Sample) bool\n\n// FilterSamplesByTag removes all samples from the profile, except\n// those that match focus and do not match the ignore regular\n// expression.\nfunc (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {\n\tsamples := make([]*Sample, 0, len(p.Sample))\n\tfor _, s := range p.Sample {\n\t\tfocused, ignored := true, false\n\t\tif focus != nil {\n\t\t\tfocused = focus(s)\n\t\t}\n\t\tif ignore != nil {\n\t\t\tignored = ignore(s)\n\t\t}\n\t\tfm = fm || focused\n\t\tim = im || ignored\n\t\tif focused && !ignored {\n\t\t\tsamples = append(samples, s)\n\t\t}\n\t}\n\tp.Sample = samples\n\treturn\n}\n"
  },
  {
    "path": "profile/filter_test.go",
    "content": "// Copyright 2018 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n)\n\nvar mappings = []*Mapping{\n\t{ID: 1, Start: 0x10000, Limit: 0x40000, File: \"map0\", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},\n\t{ID: 2, Start: 0x50000, Limit: 0x70000, File: \"map1\", HasFunctions: true, HasFilenames: true, HasLineNumbers: true, HasInlineFrames: true},\n}\n\nvar functions = []*Function{\n\t{ID: 1, Name: \"fun0\", SystemName: \"fun0\", Filename: \"file0\"},\n\t{ID: 2, Name: \"fun1\", SystemName: \"fun1\", Filename: \"file1\"},\n\t{ID: 3, Name: \"fun2\", SystemName: \"fun2\", Filename: \"file2\"},\n\t{ID: 4, Name: \"fun3\", SystemName: \"fun3\", Filename: \"file3\"},\n\t{ID: 5, Name: \"fun4\", SystemName: \"fun4\", Filename: \"file4\"},\n\t{ID: 6, Name: \"fun5\", SystemName: \"fun5\", Filename: \"file5\"},\n\t{ID: 7, Name: \"fun6\", SystemName: \"fun6\", Filename: \"file6\"},\n\t{ID: 8, Name: \"fun7\", SystemName: \"fun7\", Filename: \"file7\"},\n\t{ID: 9, Name: \"fun8\", SystemName: \"fun8\", Filename: \"file8\"},\n\t{ID: 10, Name: \"fun9\", SystemName: \"fun9\", Filename: \"file9\"},\n\t{ID: 11, Name: \"fun10\", SystemName: \"fun10\", Filename: \"file10\"},\n}\n\nvar noInlinesLocs = []*Location{\n\t{ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}}},\n\t{ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[1], Line: 1}}},\n\t{ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[2], Line: 1}}},\n\t{ID: 4, Mapping: mappings[0], Address: 0x4000, Line: []Line{{Function: functions[3], Line: 1}}},\n\t{ID: 5, Mapping: mappings[0], Address: 0x5000, Line: []Line{{Function: functions[4], Line: 1}}},\n\t{ID: 6, Mapping: mappings[0], Address: 0x6000, Line: []Line{{Function: functions[5], Line: 1}}},\n\t{ID: 7, Mapping: mappings[0], Address: 0x7000, Line: []Line{{Function: functions[6], Line: 1}}},\n\t{ID: 8, Mapping: mappings[0], Address: 0x8000, Line: []Line{{Function: functions[7], Line: 1}}},\n\t{ID: 9, Mapping: mappings[0], Address: 0x9000, Line: []Line{{Function: functions[8], Line: 1}}},\n\t{ID: 10, Mapping: mappings[0], Address: 0x10000, Line: []Line{{Function: functions[9], Line: 1}}},\n\t{ID: 11, Mapping: mappings[1], Address: 0x11000, Line: []Line{{Function: functions[10], Line: 1}}},\n}\n\nvar noInlinesProfile = &Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType:    []*ValueType{{Type: \"samples\", Unit: \"count\"}},\n\tMapping:       mappings,\n\tFunction:      functions,\n\tLocation:      noInlinesLocs,\n\tSample: []*Sample{\n\t\t{Value: []int64{1}, Location: []*Location{noInlinesLocs[0], noInlinesLocs[1], noInlinesLocs[2], noInlinesLocs[3]}},\n\t\t{Value: []int64{2}, Location: []*Location{noInlinesLocs[4], noInlinesLocs[5], noInlinesLocs[1], noInlinesLocs[6]}},\n\t\t{Value: []int64{3}, Location: []*Location{noInlinesLocs[7], noInlinesLocs[8]}},\n\t\t{Value: []int64{4}, Location: []*Location{noInlinesLocs[9], noInlinesLocs[4], noInlinesLocs[10], noInlinesLocs[7]}},\n\t},\n}\n\nvar allNoInlinesSampleFuncs = []string{\n\t\"fun0 fun1 fun2 fun3: 1\",\n\t\"fun4 fun5 fun1 fun6: 2\",\n\t\"fun7 fun8: 3\",\n\t\"fun9 fun4 fun10 fun7: 4\",\n}\n\nvar inlinesLocs = []*Location{\n\t{ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},\n\t{ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{{Function: functions[2], Line: 1}, {Function: functions[3], Line: 1}}},\n\t{ID: 3, Mapping: mappings[0], Address: 0x3000, Line: []Line{{Function: functions[4], Line: 1}, {Function: functions[5], Line: 1}, {Function: functions[6], Line: 1}}},\n}\n\nvar inlinesProfile = &Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType:    []*ValueType{{Type: \"samples\", Unit: \"count\"}},\n\tMapping:       mappings,\n\tFunction:      functions,\n\tLocation:      inlinesLocs,\n\tSample: []*Sample{\n\t\t{Value: []int64{1}, Location: []*Location{inlinesLocs[0], inlinesLocs[1]}},\n\t\t{Value: []int64{2}, Location: []*Location{inlinesLocs[2]}},\n\t},\n}\n\nvar emptyLinesLocs = []*Location{\n\t{ID: 1, Mapping: mappings[0], Address: 0x1000, Line: []Line{{Function: functions[0], Line: 1}, {Function: functions[1], Line: 1}}},\n\t{ID: 2, Mapping: mappings[0], Address: 0x2000, Line: []Line{}},\n\t{ID: 3, Mapping: mappings[1], Address: 0x2000, Line: []Line{}},\n}\n\nvar emptyLinesProfile = &Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType:    []*ValueType{{Type: \"samples\", Unit: \"count\"}},\n\tMapping:       mappings,\n\tFunction:      functions,\n\tLocation:      emptyLinesLocs,\n\tSample: []*Sample{\n\t\t{Value: []int64{1}, Location: []*Location{emptyLinesLocs[0], emptyLinesLocs[1]}},\n\t\t{Value: []int64{2}, Location: []*Location{emptyLinesLocs[2]}},\n\t\t{Value: []int64{3}, Location: []*Location{}},\n\t},\n}\n\nfunc TestFilterSamplesByName(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\t// name is the name of the test case.\n\t\tname string\n\t\t// profile is the profile that gets filtered.\n\t\tprofile *Profile\n\t\t// These are the inputs to FilterSamplesByName().\n\t\tfocus, ignore, hide, show *regexp.Regexp\n\t\t// want{F,I,S,H}m are expected return values from FilterSamplesByName.\n\t\twantFm, wantIm, wantSm, wantHm bool\n\t\t// wantSampleFuncs contains expected stack functions and sample value after\n\t\t// filtering, in the same order as in the profile. The format is as\n\t\t// returned by sampleFuncs function below, which is \"callee caller: <num>\".\n\t\twantSampleFuncs []string\n\t}{\n\t\t// No Filters\n\t\t{\n\t\t\tname:            \"empty filters keep all frames\",\n\t\t\tprofile:         noInlinesProfile,\n\t\t\twantFm:          true,\n\t\t\twantSampleFuncs: allNoInlinesSampleFuncs,\n\t\t},\n\t\t// Focus\n\t\t{\n\t\t\tname:    \"focus with no matches\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"unknown\"),\n\t\t},\n\t\t{\n\t\t\tname:    \"focus matches function names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"fun1\"),\n\t\t\twantFm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun1 fun6: 2\",\n\t\t\t\t\"fun9 fun4 fun10 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"focus matches file names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"file1\"),\n\t\t\twantFm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun1 fun6: 2\",\n\t\t\t\t\"fun9 fun4 fun10 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"focus matches mapping names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"map1\"),\n\t\t\twantFm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun9 fun4 fun10 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"focus matches inline functions\",\n\t\t\tprofile: inlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"fun5\"),\n\t\t\twantFm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun4 fun5 fun6: 2\",\n\t\t\t},\n\t\t},\n\t\t// Ignore\n\t\t{\n\t\t\tname:            \"ignore with no matches matches all samples\",\n\t\t\tprofile:         noInlinesProfile,\n\t\t\tignore:          regexp.MustCompile(\"unknown\"),\n\t\t\twantFm:          true,\n\t\t\twantSampleFuncs: allNoInlinesSampleFuncs,\n\t\t},\n\t\t{\n\t\t\tname:    \"ignore matches function names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tignore:  regexp.MustCompile(\"fun1\"),\n\t\t\twantFm:  true,\n\t\t\twantIm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"ignore matches file names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tignore:  regexp.MustCompile(\"file1\"),\n\t\t\twantFm:  true,\n\t\t\twantIm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"ignore matches mapping names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tignore:  regexp.MustCompile(\"map1\"),\n\t\t\twantFm:  true,\n\t\t\twantIm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun1 fun6: 2\",\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"ignore matches inline functions\",\n\t\t\tprofile: inlinesProfile,\n\t\t\tignore:  regexp.MustCompile(\"fun5\"),\n\t\t\twantFm:  true,\n\t\t\twantIm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t},\n\t\t},\n\t\t// Show\n\t\t{\n\t\t\tname:    \"show with no matches\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"unknown\"),\n\t\t\twantFm:  true,\n\t\t},\n\t\t{\n\t\t\tname:    \"show matches function names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"fun1|fun2\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun1 fun2: 1\",\n\t\t\t\t\"fun1: 2\",\n\t\t\t\t\"fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"show matches file names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"file1|file3\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun1 fun3: 1\",\n\t\t\t\t\"fun1: 2\",\n\t\t\t\t\"fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"show matches mapping names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"map1\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"show matches inline functions\",\n\t\t\tprofile: inlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"fun[03]\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun3: 1\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"show keeps all lines when matching both mapping and function\",\n\t\t\tprofile: inlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"map0|fun5\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun6: 2\",\n\t\t\t},\n\t\t},\n\t\t// Hide\n\t\t{\n\t\t\tname:            \"hide with no matches\",\n\t\t\tprofile:         noInlinesProfile,\n\t\t\thide:            regexp.MustCompile(\"unknown\"),\n\t\t\twantFm:          true,\n\t\t\twantSampleFuncs: allNoInlinesSampleFuncs,\n\t\t},\n\t\t{\n\t\t\tname:    \"hide matches function names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\thide:    regexp.MustCompile(\"fun1|fun2\"),\n\t\t\twantFm:  true,\n\t\t\twantHm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun6: 2\",\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t\t\"fun9 fun4 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"hide matches file names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\thide:    regexp.MustCompile(\"file1|file3\"),\n\t\t\twantFm:  true,\n\t\t\twantHm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun2: 1\",\n\t\t\t\t\"fun4 fun5 fun6: 2\",\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t\t\"fun9 fun4 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"hide matches mapping names\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\thide:    regexp.MustCompile(\"map1\"),\n\t\t\twantFm:  true,\n\t\t\twantHm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun1 fun6: 2\",\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t\t\"fun9 fun4 fun7: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"hide matches inline functions\",\n\t\t\tprofile: inlinesProfile,\n\t\t\thide:    regexp.MustCompile(\"fun[125]\"),\n\t\t\twantFm:  true,\n\t\t\twantHm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun3: 1\",\n\t\t\t\t\"fun4 fun6: 2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"hide drops all lines when matching both mapping and function\",\n\t\t\tprofile: inlinesProfile,\n\t\t\thide:    regexp.MustCompile(\"map0|fun5\"),\n\t\t\twantFm:  true,\n\t\t\twantHm:  true,\n\t\t},\n\t\t// Compound filters\n\t\t{\n\t\t\tname:    \"hides a stack matched by both focus and ignore\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tfocus:   regexp.MustCompile(\"fun1|fun7\"),\n\t\t\tignore:  regexp.MustCompile(\"fun1\"),\n\t\t\twantFm:  true,\n\t\t\twantIm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun7 fun8: 3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"hides a function if both show and hide match it\",\n\t\t\tprofile: noInlinesProfile,\n\t\t\tshow:    regexp.MustCompile(\"fun1\"),\n\t\t\thide:    regexp.MustCompile(\"fun10\"),\n\t\t\twantFm:  true,\n\t\t\twantSm:  true,\n\t\t\twantHm:  true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun1: 1\",\n\t\t\t\t\"fun1: 2\",\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tp := tc.profile.Copy()\n\t\t\tfm, im, hm, sm := p.FilterSamplesByName(tc.focus, tc.ignore, tc.hide, tc.show)\n\n\t\t\ttype match struct{ fm, im, hm, sm bool }\n\t\t\tif got, want := (match{fm: fm, im: im, hm: hm, sm: sm}), (match{fm: tc.wantFm, im: tc.wantIm, hm: tc.wantHm, sm: tc.wantSm}); got != want {\n\t\t\t\tt.Errorf(\"match got %+v want %+v\", got, want)\n\t\t\t}\n\n\t\t\tif got, want := strings.Join(sampleFuncs(p), \"\\n\")+\"\\n\", strings.Join(tc.wantSampleFuncs, \"\\n\")+\"\\n\"; got != want {\n\t\t\t\tdiff, err := proftest.Diff([]byte(want), []byte(got))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to get diff: %v\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"FilterSamplesByName: got diff(want->got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestShowFrom(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname     string\n\t\tprofile  *Profile\n\t\tshowFrom *regexp.Regexp\n\t\t// wantMatch is the expected return value.\n\t\twantMatch bool\n\t\t// wantSampleFuncs contains expected stack functions and sample value after\n\t\t// filtering, in the same order as in the profile. The format is as\n\t\t// returned by sampleFuncs function below, which is \"callee caller: <num>\".\n\t\twantSampleFuncs []string\n\t}{\n\t\t{\n\t\t\tname:            \"nil showFrom keeps all frames\",\n\t\t\tprofile:         noInlinesProfile,\n\t\t\twantMatch:       false,\n\t\t\twantSampleFuncs: allNoInlinesSampleFuncs,\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom with no matches drops all samples\",\n\t\t\tprofile:   noInlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"unknown\"),\n\t\t\twantMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom matches function names\",\n\t\t\tprofile:   noInlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"fun1\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1: 1\",\n\t\t\t\t\"fun4 fun5 fun1: 2\",\n\t\t\t\t\"fun9 fun4 fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom matches file names\",\n\t\t\tprofile:   noInlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"file1\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1: 1\",\n\t\t\t\t\"fun4 fun5 fun1: 2\",\n\t\t\t\t\"fun9 fun4 fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom matches mapping names\",\n\t\t\tprofile:   noInlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"map1\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun9 fun4 fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom drops frames above highest of multiple matches\",\n\t\t\tprofile:   noInlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"fun[12]\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2: 1\",\n\t\t\t\t\"fun4 fun5 fun1: 2\",\n\t\t\t\t\"fun9 fun4 fun10: 4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom matches inline functions\",\n\t\t\tprofile:   inlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"fun0|fun5\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0: 1\",\n\t\t\t\t\"fun4 fun5: 2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom drops frames above highest of multiple inline matches\",\n\t\t\tprofile:   inlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"fun[1245]\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2: 1\",\n\t\t\t\t\"fun4 fun5: 2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom keeps all lines when matching mapping and function\",\n\t\t\tprofile:   inlinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"map0|fun5\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\"fun0 fun1 fun2 fun3: 1\",\n\t\t\t\t\"fun4 fun5 fun6: 2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"showFrom matches location with empty lines\",\n\t\t\tprofile:   emptyLinesProfile,\n\t\t\tshowFrom:  regexp.MustCompile(\"map1\"),\n\t\t\twantMatch: true,\n\t\t\twantSampleFuncs: []string{\n\t\t\t\t\": 2\",\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tp := tc.profile.Copy()\n\n\t\t\tif gotMatch := p.ShowFrom(tc.showFrom); gotMatch != tc.wantMatch {\n\t\t\t\tt.Errorf(\"match got %+v, want %+v\", gotMatch, tc.wantMatch)\n\t\t\t}\n\n\t\t\tif got, want := strings.Join(sampleFuncs(p), \"\\n\")+\"\\n\", strings.Join(tc.wantSampleFuncs, \"\\n\")+\"\\n\"; got != want {\n\t\t\t\tdiff, err := proftest.Diff([]byte(want), []byte(got))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to get diff: %v\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"profile samples got diff(want->got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// sampleFuncs returns a slice of strings where each string represents one\n// profile sample in the format \"<fun1> <fun2> <fun3>: <value>\". This allows\n// the expected values for test cases to be specified in human-readable\n// strings.\nfunc sampleFuncs(p *Profile) []string {\n\tvar ret []string\n\tfor _, s := range p.Sample {\n\t\tvar funcs []string\n\t\tfor _, loc := range s.Location {\n\t\t\tfor _, line := range loc.Line {\n\t\t\t\tfuncs = append(funcs, line.Function.Name)\n\t\t\t}\n\t\t}\n\t\tret = append(ret, fmt.Sprintf(\"%s: %d\", strings.Join(funcs, \" \"), s.Value[0]))\n\t}\n\treturn ret\n}\n\nfunc TestTagFilter(t *testing.T) {\n\t// Perform several forms of tag filtering on the test profile.\n\n\ttype filterTestcase struct {\n\t\tinclude, exclude *regexp.Regexp\n\t\tim, em           bool\n\t\tcount            int\n\t}\n\n\tcountTags := func(p *Profile) map[string]bool {\n\t\ttags := make(map[string]bool)\n\n\t\tfor _, s := range p.Sample {\n\t\t\tfor l := range s.Label {\n\t\t\t\ttags[l] = true\n\t\t\t}\n\t\t\tfor l := range s.NumLabel {\n\t\t\t\ttags[l] = true\n\t\t\t}\n\t\t}\n\t\treturn tags\n\t}\n\n\tfor tx, tc := range []filterTestcase{\n\t\t{nil, nil, true, false, 3},\n\t\t{regexp.MustCompile(\"notfound\"), nil, false, false, 0},\n\t\t{regexp.MustCompile(\"key1\"), nil, true, false, 1},\n\t\t{nil, regexp.MustCompile(\"key[12]\"), true, true, 1},\n\t} {\n\t\tprof := testProfile1.Copy()\n\t\tgim, gem := prof.FilterTagsByName(tc.include, tc.exclude)\n\t\tif gim != tc.im {\n\t\t\tt.Errorf(\"Filter #%d, got include match=%v, want %v\", tx, gim, tc.im)\n\t\t}\n\t\tif gem != tc.em {\n\t\t\tt.Errorf(\"Filter #%d, got exclude match=%v, want %v\", tx, gem, tc.em)\n\t\t}\n\t\tif tags := countTags(prof); len(tags) != tc.count {\n\t\t\tt.Errorf(\"Filter #%d, got %d tags[%v], want %d\", tx, len(tags), tags, tc.count)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "profile/index.go",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// SampleIndexByName returns the appropriate index for a value of sample index.\n// If numeric, it returns the number, otherwise it looks up the text in the\n// profile sample types.\nfunc (p *Profile) SampleIndexByName(sampleIndex string) (int, error) {\n\tif sampleIndex == \"\" {\n\t\tif dst := p.DefaultSampleType; dst != \"\" {\n\t\t\tfor i, t := range sampleTypes(p) {\n\t\t\t\tif t == dst {\n\t\t\t\t\treturn i, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// By default select the last sample value\n\t\treturn len(p.SampleType) - 1, nil\n\t}\n\tif i, err := strconv.Atoi(sampleIndex); err == nil {\n\t\tif i < 0 || i >= len(p.SampleType) {\n\t\t\treturn 0, fmt.Errorf(\"sample_index %s is outside the range [0..%d]\", sampleIndex, len(p.SampleType)-1)\n\t\t}\n\t\treturn i, nil\n\t}\n\n\t// Remove the inuse_ prefix to support legacy pprof options\n\t// \"inuse_space\" and \"inuse_objects\" for profiles containing types\n\t// \"space\" and \"objects\".\n\tnoInuse := strings.TrimPrefix(sampleIndex, \"inuse_\")\n\tfor i, t := range p.SampleType {\n\t\tif t.Type == sampleIndex || t.Type == noInuse {\n\t\t\treturn i, nil\n\t\t}\n\t}\n\n\treturn 0, fmt.Errorf(\"sample_index %q must be one of: %v\", sampleIndex, sampleTypes(p))\n}\n\nfunc sampleTypes(p *Profile) []string {\n\ttypes := make([]string, len(p.SampleType))\n\tfor i, t := range p.SampleType {\n\t\ttypes[i] = t.Type\n\t}\n\treturn types\n}\n"
  },
  {
    "path": "profile/index_test.go",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSampleIndexByName(t *testing.T) {\n\tfor _, c := range []struct {\n\t\tdesc              string\n\t\tsampleTypes       []string\n\t\tdefaultSampleType string\n\t\tindex             string\n\t\twant              int\n\t\twantError         bool\n\t}{\n\t\t{\n\t\t\tdesc:        \"use last by default\",\n\t\t\tindex:       \"\",\n\t\t\twant:        1,\n\t\t\tsampleTypes: []string{\"zero\", \"default\"},\n\t\t},\n\t\t{\n\t\t\tdesc:              \"honour specified default\",\n\t\t\tindex:             \"\",\n\t\t\twant:              1,\n\t\t\tdefaultSampleType: \"default\",\n\t\t\tsampleTypes:       []string{\"zero\", \"default\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tdesc:              \"invalid default is ignored\",\n\t\t\tindex:             \"\",\n\t\t\twant:              2,\n\t\t\tdefaultSampleType: \"non-existent\",\n\t\t\tsampleTypes:       []string{\"zero\", \"one\", \"default\"},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"index by int\",\n\t\t\tindex:       \"0\",\n\t\t\twant:        0,\n\t\t\tsampleTypes: []string{\"zero\", \"one\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tdesc:              \"index by int ignores default\",\n\t\t\tindex:             \"0\",\n\t\t\twant:              0,\n\t\t\tdefaultSampleType: \"default\",\n\t\t\tsampleTypes:       []string{\"zero\", \"default\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"index by name\",\n\t\t\tindex:       \"two\",\n\t\t\twant:        2,\n\t\t\tsampleTypes: []string{\"zero\", \"one\", \"two\", \"three\"},\n\t\t},\n\t\t{\n\t\t\tdesc:              \"index by name ignores default\",\n\t\t\tindex:             \"zero\",\n\t\t\twant:              0,\n\t\t\tdefaultSampleType: \"default\",\n\t\t\tsampleTypes:       []string{\"zero\", \"default\", \"two\"},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"out of bound int causes error\",\n\t\t\tindex:       \"100\",\n\t\t\twantError:   true,\n\t\t\tsampleTypes: []string{\"zero\", \"default\"},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"unknown name causes error\",\n\t\t\tindex:       \"does not exist\",\n\t\t\twantError:   true,\n\t\t\tsampleTypes: []string{\"zero\", \"default\"},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"'inused_{x}' recognized for legacy '{x}'\",\n\t\t\tindex:       \"inuse_zero\",\n\t\t\twant:        0,\n\t\t\tsampleTypes: []string{\"zero\", \"default\"},\n\t\t},\n\t} {\n\t\tp := &Profile{\n\t\t\tDefaultSampleType: c.defaultSampleType,\n\t\t\tSampleType:        []*ValueType{},\n\t\t}\n\t\tfor _, st := range c.sampleTypes {\n\t\t\tp.SampleType = append(p.SampleType, &ValueType{Type: st, Unit: \"milliseconds\"})\n\t\t}\n\n\t\tgot, err := p.SampleIndexByName(c.index)\n\n\t\tswitch {\n\t\tcase c.wantError && err == nil:\n\t\t\tt.Errorf(\"%s: error should have been returned not index=%d, err=%v\", c.desc, got, err)\n\t\tcase !c.wantError && err != nil:\n\t\t\tt.Errorf(\"%s: unexpected got index=%d, err=%v; wanted index=%d, err=nil\", c.desc, got, err, c.want)\n\t\tcase !c.wantError && got != c.want:\n\t\t\tt.Errorf(\"%s: got index=%d, want index=%d\", c.desc, got, c.want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "profile/legacy_java_profile.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This file implements parsers to convert java legacy profiles into\n// the profile.proto format.\n\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\tattributeRx            = regexp.MustCompile(`([\\w ]+)=([\\w ]+)`)\n\tjavaSampleRx           = regexp.MustCompile(` *(\\d+) +(\\d+) +@ +([ x0-9a-f]*)`)\n\tjavaLocationRx         = regexp.MustCompile(`^\\s*0x([[:xdigit:]]+)\\s+(.*)\\s*$`)\n\tjavaLocationFileLineRx = regexp.MustCompile(`^(.*)\\s+\\((.+):(-?[[:digit:]]+)\\)$`)\n\tjavaLocationPathRx     = regexp.MustCompile(`^(.*)\\s+\\((.*)\\)$`)\n)\n\n// javaCPUProfile returns a new Profile from profilez data.\n// b is the profile bytes after the header, period is the profiling\n// period, and parse is a function to parse 8-byte chunks from the\n// profile in its native endianness.\nfunc javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {\n\tp := &Profile{\n\t\tPeriod:     period * 1000,\n\t\tPeriodType: &ValueType{Type: \"cpu\", Unit: \"nanoseconds\"},\n\t\tSampleType: []*ValueType{{Type: \"samples\", Unit: \"count\"}, {Type: \"cpu\", Unit: \"nanoseconds\"}},\n\t}\n\tvar err error\n\tvar locs map[uint64]*Location\n\tif b, locs, err = parseCPUSamples(b, parse, false, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = parseJavaLocations(b, locs, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Strip out addresses for better merge.\n\tif err = p.Aggregate(true, true, true, true, false, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// parseJavaProfile returns a new profile from heapz or contentionz\n// data. b is the profile bytes after the header.\nfunc parseJavaProfile(b []byte) (*Profile, error) {\n\th := bytes.SplitAfterN(b, []byte(\"\\n\"), 2)\n\tif len(h) < 2 {\n\t\treturn nil, errUnrecognized\n\t}\n\n\tp := &Profile{\n\t\tPeriodType: &ValueType{},\n\t}\n\theader := string(bytes.TrimSpace(h[0]))\n\n\tvar err error\n\tvar pType string\n\tswitch header {\n\tcase \"--- heapz 1 ---\":\n\t\tpType = \"heap\"\n\tcase \"--- contentionz 1 ---\":\n\t\tpType = \"contention\"\n\tdefault:\n\t\treturn nil, errUnrecognized\n\t}\n\n\tif b, err = parseJavaHeader(pType, h[1], p); err != nil {\n\t\treturn nil, err\n\t}\n\tvar locs map[uint64]*Location\n\tif b, locs, err = parseJavaSamples(pType, b, p); err != nil {\n\t\treturn nil, err\n\t}\n\tif err = parseJavaLocations(b, locs, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Strip out addresses for better merge.\n\tif err = p.Aggregate(true, true, true, true, false, false); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// parseJavaHeader parses the attribute section on a java profile and\n// populates a profile. Returns the remainder of the buffer after all\n// attributes.\nfunc parseJavaHeader(pType string, b []byte, p *Profile) ([]byte, error) {\n\tnextNewLine := bytes.IndexByte(b, byte('\\n'))\n\tfor nextNewLine != -1 {\n\t\tline := string(bytes.TrimSpace(b[0:nextNewLine]))\n\t\tif line != \"\" {\n\t\t\th := attributeRx.FindStringSubmatch(line)\n\t\t\tif h == nil {\n\t\t\t\t// Not a valid attribute, exit.\n\t\t\t\treturn b, nil\n\t\t\t}\n\n\t\t\tattribute, value := strings.TrimSpace(h[1]), strings.TrimSpace(h[2])\n\t\t\tvar err error\n\t\t\tswitch pType + \"/\" + attribute {\n\t\t\tcase \"heap/format\", \"cpu/format\", \"contention/format\":\n\t\t\t\tif value != \"java\" {\n\t\t\t\t\treturn nil, errUnrecognized\n\t\t\t\t}\n\t\t\tcase \"heap/resolution\":\n\t\t\t\tp.SampleType = []*ValueType{\n\t\t\t\t\t{Type: \"inuse_objects\", Unit: \"count\"},\n\t\t\t\t\t{Type: \"inuse_space\", Unit: value},\n\t\t\t\t}\n\t\t\tcase \"contention/resolution\":\n\t\t\t\tp.SampleType = []*ValueType{\n\t\t\t\t\t{Type: \"contentions\", Unit: \"count\"},\n\t\t\t\t\t{Type: \"delay\", Unit: value},\n\t\t\t\t}\n\t\t\tcase \"contention/sampling period\":\n\t\t\t\tp.PeriodType = &ValueType{\n\t\t\t\t\tType: \"contentions\", Unit: \"count\",\n\t\t\t\t}\n\t\t\t\tif p.Period, err = strconv.ParseInt(value, 0, 64); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to parse attribute %s: %v\", line, err)\n\t\t\t\t}\n\t\t\tcase \"contention/ms since reset\":\n\t\t\t\tmillis, err := strconv.ParseInt(value, 0, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"failed to parse attribute %s: %v\", line, err)\n\t\t\t\t}\n\t\t\t\tp.DurationNanos = millis * 1000 * 1000\n\t\t\tdefault:\n\t\t\t\treturn nil, errUnrecognized\n\t\t\t}\n\t\t}\n\t\t// Grab next line.\n\t\tb = b[nextNewLine+1:]\n\t\tnextNewLine = bytes.IndexByte(b, byte('\\n'))\n\t}\n\treturn b, nil\n}\n\n// parseJavaSamples parses the samples from a java profile and\n// populates the Samples in a profile. Returns the remainder of the\n// buffer after the samples.\nfunc parseJavaSamples(pType string, b []byte, p *Profile) ([]byte, map[uint64]*Location, error) {\n\tnextNewLine := bytes.IndexByte(b, byte('\\n'))\n\tlocs := make(map[uint64]*Location)\n\tfor nextNewLine != -1 {\n\t\tline := string(bytes.TrimSpace(b[0:nextNewLine]))\n\t\tif line != \"\" {\n\t\t\tsample := javaSampleRx.FindStringSubmatch(line)\n\t\t\tif sample == nil {\n\t\t\t\t// Not a valid sample, exit.\n\t\t\t\treturn b, locs, nil\n\t\t\t}\n\n\t\t\t// Java profiles have data/fields inverted compared to other\n\t\t\t// profile types.\n\t\t\tvar err error\n\t\t\tvalue1, value2, value3 := sample[2], sample[1], sample[3]\n\t\t\taddrs, err := parseHexAddresses(value3)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t\t\t}\n\n\t\t\tvar sloc []*Location\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tloc := locs[addr]\n\t\t\t\tif locs[addr] == nil {\n\t\t\t\t\tloc = &Location{\n\t\t\t\t\t\tAddress: addr,\n\t\t\t\t\t}\n\t\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t\t\tlocs[addr] = loc\n\t\t\t\t}\n\t\t\t\tsloc = append(sloc, loc)\n\t\t\t}\n\t\t\ts := &Sample{\n\t\t\t\tValue:    make([]int64, 2),\n\t\t\t\tLocation: sloc,\n\t\t\t}\n\n\t\t\tif s.Value[0], err = strconv.ParseInt(value1, 0, 64); err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"parsing sample %s: %v\", line, err)\n\t\t\t}\n\t\t\tif s.Value[1], err = strconv.ParseInt(value2, 0, 64); err != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"parsing sample %s: %v\", line, err)\n\t\t\t}\n\n\t\t\tswitch pType {\n\t\t\tcase \"heap\":\n\t\t\t\tconst javaHeapzSamplingRate = 524288 // 512K\n\t\t\t\tif s.Value[0] == 0 {\n\t\t\t\t\treturn nil, nil, fmt.Errorf(\"parsing sample %s: second value must be non-zero\", line)\n\t\t\t\t}\n\t\t\t\ts.NumLabel = map[string][]int64{\"bytes\": {s.Value[1] / s.Value[0]}}\n\t\t\t\ts.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)\n\t\t\tcase \"contention\":\n\t\t\t\tif period := p.Period; period != 0 {\n\t\t\t\t\ts.Value[0] = s.Value[0] * p.Period\n\t\t\t\t\ts.Value[1] = s.Value[1] * p.Period\n\t\t\t\t}\n\t\t\t}\n\t\t\tp.Sample = append(p.Sample, s)\n\t\t}\n\t\t// Grab next line.\n\t\tb = b[nextNewLine+1:]\n\t\tnextNewLine = bytes.IndexByte(b, byte('\\n'))\n\t}\n\treturn b, locs, nil\n}\n\n// parseJavaLocations parses the location information in a java\n// profile and populates the Locations in a profile. It uses the\n// location addresses from the profile as both the ID of each\n// location.\nfunc parseJavaLocations(b []byte, locs map[uint64]*Location, p *Profile) error {\n\tr := bytes.NewBuffer(b)\n\tfns := make(map[string]*Function)\n\tfor {\n\t\tline, err := r.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tif err != io.EOF {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif line == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif line = strings.TrimSpace(line); line == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tjloc := javaLocationRx.FindStringSubmatch(line)\n\t\tif len(jloc) != 3 {\n\t\t\tcontinue\n\t\t}\n\t\taddr, err := strconv.ParseUint(jloc[1], 16, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"parsing sample %s: %v\", line, err)\n\t\t}\n\t\tloc := locs[addr]\n\t\tif loc == nil {\n\t\t\t// Unused/unseen\n\t\t\tcontinue\n\t\t}\n\t\tvar lineFunc, lineFile string\n\t\tvar lineNo int64\n\n\t\tif fileLine := javaLocationFileLineRx.FindStringSubmatch(jloc[2]); len(fileLine) == 4 {\n\t\t\t// Found a line of the form: \"function (file:line)\"\n\t\t\tlineFunc, lineFile = fileLine[1], fileLine[2]\n\t\t\tif n, err := strconv.ParseInt(fileLine[3], 10, 64); err == nil && n > 0 {\n\t\t\t\tlineNo = n\n\t\t\t}\n\t\t} else if filePath := javaLocationPathRx.FindStringSubmatch(jloc[2]); len(filePath) == 3 {\n\t\t\t// If there's not a file:line, it's a shared library path.\n\t\t\t// The path isn't interesting, so just give the .so.\n\t\t\tlineFunc, lineFile = filePath[1], filepath.Base(filePath[2])\n\t\t} else if strings.Contains(jloc[2], \"generated stub/JIT\") {\n\t\t\tlineFunc = \"STUB\"\n\t\t} else {\n\t\t\t// Treat whole line as the function name. This is used by the\n\t\t\t// java agent for internal states such as \"GC\" or \"VM\".\n\t\t\tlineFunc = jloc[2]\n\t\t}\n\t\tfn := fns[lineFunc]\n\n\t\tif fn == nil {\n\t\t\tfn = &Function{\n\t\t\t\tName:       lineFunc,\n\t\t\t\tSystemName: lineFunc,\n\t\t\t\tFilename:   lineFile,\n\t\t\t}\n\t\t\tfns[lineFunc] = fn\n\t\t\tp.Function = append(p.Function, fn)\n\t\t}\n\t\tloc.Line = []Line{\n\t\t\t{\n\t\t\t\tFunction: fn,\n\t\t\t\tLine:     lineNo,\n\t\t\t},\n\t\t}\n\t\tloc.Address = 0\n\t}\n\n\tp.remapLocationIDs()\n\tp.remapFunctionIDs()\n\tp.remapMappingIDs()\n\n\treturn nil\n}\n"
  },
  {
    "path": "profile/legacy_profile.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This file implements parsers to convert legacy profiles into the\n// profile.proto format.\n\npackage profile\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar (\n\tcountStartRE = regexp.MustCompile(`\\A(\\S+) profile: total \\d+\\z`)\n\tcountRE      = regexp.MustCompile(`\\A(\\d+) @(( 0x[0-9a-f]+)+)\\z`)\n\n\theapHeaderRE = regexp.MustCompile(`heap profile: *(\\d+): *(\\d+) *\\[ *(\\d+): *(\\d+) *\\] *@ *(heap[_a-z0-9]*)/?(\\d*)`)\n\theapSampleRE = regexp.MustCompile(`(-?\\d+): *(-?\\d+) *\\[ *(\\d+): *(\\d+) *] @([ x0-9a-f]*)`)\n\n\tcontentionSampleRE = regexp.MustCompile(`(\\d+) *(\\d+) @([ x0-9a-f]*)`)\n\n\thexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`)\n\n\tgrowthHeaderRE = regexp.MustCompile(`heap profile: *(\\d+): *(\\d+) *\\[ *(\\d+): *(\\d+) *\\] @ growthz?`)\n\n\tfragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\\d+): *(\\d+) *\\[ *(\\d+): *(\\d+) *\\] @ fragmentationz?`)\n\n\tthreadzStartRE = regexp.MustCompile(`--- threadz \\d+ ---`)\n\tthreadStartRE  = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \\(name: (.*)/(\\d+)\\) stack: ---`)\n\n\t// Regular expressions to parse process mappings. Support the format used by Linux /proc/.../maps and other tools.\n\t// Recommended format:\n\t// Start   End     object file name     offset(optional)   linker build id\n\t// 0x40000-0x80000 /path/to/binary      (@FF00)            abc123456\n\tspaceDigits = `\\s+[[:digit:]]+`\n\thexPair     = `\\s+[[:xdigit:]]+:[[:xdigit:]]+`\n\toSpace      = `\\s*`\n\t// Capturing expressions.\n\tcHex           = `(?:0x)?([[:xdigit:]]+)`\n\tcHexRange      = `\\s*` + cHex + `[\\s-]?` + oSpace + cHex + `:?`\n\tcSpaceString   = `(?:\\s+(\\S+))?`\n\tcSpaceHex      = `(?:\\s+([[:xdigit:]]+))?`\n\tcSpaceAtOffset = `(?:\\s+\\(@([[:xdigit:]]+)\\))?`\n\tcPerm          = `(?:\\s+([-rwxp]+))?`\n\n\tprocMapsRE  = regexp.MustCompile(`^` + cHexRange + cPerm + cSpaceHex + hexPair + spaceDigits + cSpaceString)\n\tbriefMapsRE = regexp.MustCompile(`^` + cHexRange + cPerm + cSpaceString + cSpaceAtOffset + cSpaceHex)\n\n\t// Regular expression to parse log data, of the form:\n\t// ... file:line] msg...\n\tlogInfoRE = regexp.MustCompile(`^[^\\[\\]]+:[0-9]+]\\s`)\n)\n\nfunc isSpaceOrComment(line string) bool {\n\ttrimmed := strings.TrimSpace(line)\n\treturn len(trimmed) == 0 || trimmed[0] == '#'\n}\n\n// parseGoCount parses a Go count profile (e.g., threadcreate or\n// goroutine) and returns a new Profile.\nfunc parseGoCount(b []byte) (*Profile, error) {\n\ts := bufio.NewScanner(bytes.NewBuffer(b))\n\t// Skip comments at the beginning of the file.\n\tfor s.Scan() && isSpaceOrComment(s.Text()) {\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\tm := countStartRE.FindStringSubmatch(s.Text())\n\tif m == nil {\n\t\treturn nil, errUnrecognized\n\t}\n\tprofileType := m[1]\n\tp := &Profile{\n\t\tPeriodType: &ValueType{Type: profileType, Unit: \"count\"},\n\t\tPeriod:     1,\n\t\tSampleType: []*ValueType{{Type: profileType, Unit: \"count\"}},\n\t}\n\tlocations := make(map[uint64]*Location)\n\tfor s.Scan() {\n\t\tline := s.Text()\n\t\tif isSpaceOrComment(line) {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"---\") {\n\t\t\tbreak\n\t\t}\n\t\tm := countRE.FindStringSubmatch(line)\n\t\tif m == nil {\n\t\t\treturn nil, errMalformed\n\t\t}\n\t\tn, err := strconv.ParseInt(m[1], 0, 64)\n\t\tif err != nil {\n\t\t\treturn nil, errMalformed\n\t\t}\n\t\tfields := strings.Fields(m[2])\n\t\tlocs := make([]*Location, 0, len(fields))\n\t\tfor _, stk := range fields {\n\t\t\taddr, err := strconv.ParseUint(stk, 0, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errMalformed\n\t\t\t}\n\t\t\t// Adjust all frames by -1 to land on top of the call instruction.\n\t\t\taddr--\n\t\t\tloc := locations[addr]\n\t\t\tif loc == nil {\n\t\t\t\tloc = &Location{\n\t\t\t\t\tAddress: addr,\n\t\t\t\t}\n\t\t\t\tlocations[addr] = loc\n\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t}\n\t\t\tlocs = append(locs, loc)\n\t\t}\n\t\tp.Sample = append(p.Sample, &Sample{\n\t\t\tLocation: locs,\n\t\t\tValue:    []int64{n},\n\t\t})\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := parseAdditionalSections(s, p); err != nil {\n\t\treturn nil, err\n\t}\n\treturn p, nil\n}\n\n// remapLocationIDs ensures there is a location for each address\n// referenced by a sample, and remaps the samples to point to the new\n// location ids.\nfunc (p *Profile) remapLocationIDs() {\n\tseen := make(map[*Location]bool, len(p.Location))\n\tvar locs []*Location\n\n\tfor _, s := range p.Sample {\n\t\tfor _, l := range s.Location {\n\t\t\tif seen[l] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tl.ID = uint64(len(locs) + 1)\n\t\t\tlocs = append(locs, l)\n\t\t\tseen[l] = true\n\t\t}\n\t}\n\tp.Location = locs\n}\n\nfunc (p *Profile) remapFunctionIDs() {\n\tseen := make(map[*Function]bool, len(p.Function))\n\tvar fns []*Function\n\n\tfor _, l := range p.Location {\n\t\tfor _, ln := range l.Line {\n\t\t\tfn := ln.Function\n\t\t\tif fn == nil || seen[fn] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfn.ID = uint64(len(fns) + 1)\n\t\t\tfns = append(fns, fn)\n\t\t\tseen[fn] = true\n\t\t}\n\t}\n\tp.Function = fns\n}\n\n// remapMappingIDs matches location addresses with existing mappings\n// and updates them appropriately. This is O(N*M), if this ever shows\n// up as a bottleneck, evaluate sorting the mappings and doing a\n// binary search, which would make it O(N*log(M)).\nfunc (p *Profile) remapMappingIDs() {\n\t// Some profile handlers will incorrectly set regions for the main\n\t// executable if its section is remapped. Fix them through heuristics.\n\n\tif len(p.Mapping) > 0 {\n\t\t// Remove the initial mapping if named '/anon_hugepage' and has a\n\t\t// consecutive adjacent mapping.\n\t\tif m := p.Mapping[0]; strings.HasPrefix(m.File, \"/anon_hugepage\") {\n\t\t\tif len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start {\n\t\t\t\tp.Mapping = p.Mapping[1:]\n\t\t\t}\n\t\t}\n\t}\n\n\t// Subtract the offset from the start of the main mapping if it\n\t// ends up at a recognizable start address.\n\tif len(p.Mapping) > 0 {\n\t\tconst expectedStart = 0x400000\n\t\tif m := p.Mapping[0]; m.Start-m.Offset == expectedStart {\n\t\t\tm.Start = expectedStart\n\t\t\tm.Offset = 0\n\t\t}\n\t}\n\n\t// Associate each location with an address to the corresponding\n\t// mapping. Create fake mapping if a suitable one isn't found.\n\tvar fake *Mapping\nnextLocation:\n\tfor _, l := range p.Location {\n\t\ta := l.Address\n\t\tif l.Mapping != nil || a == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, m := range p.Mapping {\n\t\t\tif m.Start <= a && a < m.Limit {\n\t\t\t\tl.Mapping = m\n\t\t\t\tcontinue nextLocation\n\t\t\t}\n\t\t}\n\t\t// Work around legacy handlers failing to encode the first\n\t\t// part of mappings split into adjacent ranges.\n\t\tfor _, m := range p.Mapping {\n\t\t\tif m.Offset != 0 && m.Start-m.Offset <= a && a < m.Start {\n\t\t\t\tm.Start -= m.Offset\n\t\t\t\tm.Offset = 0\n\t\t\t\tl.Mapping = m\n\t\t\t\tcontinue nextLocation\n\t\t\t}\n\t\t}\n\t\t// If there is still no mapping, create a fake one.\n\t\t// This is important for the Go legacy handler, which produced\n\t\t// no mappings.\n\t\tif fake == nil {\n\t\t\tfake = &Mapping{\n\t\t\t\tID:    1,\n\t\t\t\tLimit: ^uint64(0),\n\t\t\t}\n\t\t\tp.Mapping = append(p.Mapping, fake)\n\t\t}\n\t\tl.Mapping = fake\n\t}\n\n\t// Reset all mapping IDs.\n\tfor i, m := range p.Mapping {\n\t\tm.ID = uint64(i + 1)\n\t}\n}\n\nvar cpuInts = []func([]byte) (uint64, []byte){\n\tget32l,\n\tget32b,\n\tget64l,\n\tget64b,\n}\n\nfunc get32l(b []byte) (uint64, []byte) {\n\tif len(b) < 4 {\n\t\treturn 0, nil\n\t}\n\treturn uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:]\n}\n\nfunc get32b(b []byte) (uint64, []byte) {\n\tif len(b) < 4 {\n\t\treturn 0, nil\n\t}\n\treturn uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:]\n}\n\nfunc get64l(b []byte) (uint64, []byte) {\n\tif len(b) < 8 {\n\t\treturn 0, nil\n\t}\n\treturn uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:]\n}\n\nfunc get64b(b []byte) (uint64, []byte) {\n\tif len(b) < 8 {\n\t\treturn 0, nil\n\t}\n\treturn uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:]\n}\n\n// parseCPU parses a profilez legacy profile and returns a newly\n// populated Profile.\n//\n// The general format for profilez samples is a sequence of words in\n// binary format. The first words are a header with the following data:\n//\n//\t1st word -- 0\n//\t2nd word -- 3\n//\t3rd word -- 0 if a c++ application, 1 if a java application.\n//\t4th word -- Sampling period (in microseconds).\n//\t5th word -- Padding.\nfunc parseCPU(b []byte) (*Profile, error) {\n\tvar parse func([]byte) (uint64, []byte)\n\tvar n1, n2, n3, n4, n5 uint64\n\tfor _, parse = range cpuInts {\n\t\tvar tmp []byte\n\t\tn1, tmp = parse(b)\n\t\tn2, tmp = parse(tmp)\n\t\tn3, tmp = parse(tmp)\n\t\tn4, tmp = parse(tmp)\n\t\tn5, tmp = parse(tmp)\n\n\t\tif tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 {\n\t\t\tb = tmp\n\t\t\treturn cpuProfile(b, int64(n4), parse)\n\t\t}\n\t\tif tmp != nil && n1 == 0 && n2 == 3 && n3 == 1 && n4 > 0 && n5 == 0 {\n\t\t\tb = tmp\n\t\t\treturn javaCPUProfile(b, int64(n4), parse)\n\t\t}\n\t}\n\treturn nil, errUnrecognized\n}\n\n// cpuProfile returns a new Profile from C++ profilez data.\n// b is the profile bytes after the header, period is the profiling\n// period, and parse is a function to parse 8-byte chunks from the\n// profile in its native endianness.\nfunc cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {\n\tp := &Profile{\n\t\tPeriod:     period * 1000,\n\t\tPeriodType: &ValueType{Type: \"cpu\", Unit: \"nanoseconds\"},\n\t\tSampleType: []*ValueType{\n\t\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t\t{Type: \"cpu\", Unit: \"nanoseconds\"},\n\t\t},\n\t}\n\tvar err error\n\tif b, _, err = parseCPUSamples(b, parse, true, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If *most* samples have the same second-to-the-bottom frame, it\n\t// strongly suggests that it is an uninteresting artifact of\n\t// measurement -- a stack frame pushed by the signal handler. The\n\t// bottom frame is always correct as it is picked up from the signal\n\t// structure, not the stack. Check if this is the case and if so,\n\t// remove.\n\n\t// Remove up to two frames.\n\tmaxiter := 2\n\t// Allow one different sample for this many samples with the same\n\t// second-to-last frame.\n\tsimilarSamples := 32\n\tmargin := len(p.Sample) / similarSamples\n\n\tfor iter := 0; iter < maxiter; iter++ {\n\t\taddr1 := make(map[uint64]int)\n\t\tfor _, s := range p.Sample {\n\t\t\tif len(s.Location) > 1 {\n\t\t\t\ta := s.Location[1].Address\n\t\t\t\taddr1[a] = addr1[a] + 1\n\t\t\t}\n\t\t}\n\n\t\tfor id1, count := range addr1 {\n\t\t\tif count >= len(p.Sample)-margin {\n\t\t\t\t// Found uninteresting frame, strip it out from all samples\n\t\t\t\tfor _, s := range p.Sample {\n\t\t\t\t\tif len(s.Location) > 1 && s.Location[1].Address == id1 {\n\t\t\t\t\t\ts.Location = append(s.Location[:1], s.Location[2:]...)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcleanupDuplicateLocations(p)\n\treturn p, nil\n}\n\nfunc cleanupDuplicateLocations(p *Profile) {\n\t// The profile handler may duplicate the leaf frame, because it gets\n\t// its address both from stack unwinding and from the signal\n\t// context. Detect this and delete the duplicate, which has been\n\t// adjusted by -1. The leaf address should not be adjusted as it is\n\t// not a call.\n\tfor _, s := range p.Sample {\n\t\tif len(s.Location) > 1 && s.Location[0].Address == s.Location[1].Address+1 {\n\t\t\ts.Location = append(s.Location[:1], s.Location[2:]...)\n\t\t}\n\t}\n}\n\n// parseCPUSamples parses a collection of profilez samples from a\n// profile.\n//\n// profilez samples are a repeated sequence of stack frames of the\n// form:\n//\n//\t1st word -- The number of times this stack was encountered.\n//\t2nd word -- The size of the stack (StackSize).\n//\t3rd word -- The first address on the stack.\n//\t...\n//\tStackSize + 2 -- The last address on the stack\n//\n// The last stack trace is of the form:\n//\n//\t1st word -- 0\n//\t2nd word -- 1\n//\t3rd word -- 0\n//\n// Addresses from stack traces may point to the next instruction after\n// each call. Optionally adjust by -1 to land somewhere on the actual\n// call (except for the leaf, which is not a call).\nfunc parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) {\n\tlocs := make(map[uint64]*Location)\n\tfor len(b) > 0 {\n\t\tvar count, nstk uint64\n\t\tcount, b = parse(b)\n\t\tnstk, b = parse(b)\n\t\tif b == nil || nstk > uint64(len(b)/4) {\n\t\t\treturn nil, nil, errUnrecognized\n\t\t}\n\t\tvar sloc []*Location\n\t\taddrs := make([]uint64, nstk)\n\t\tfor i := 0; i < int(nstk); i++ {\n\t\t\taddrs[i], b = parse(b)\n\t\t}\n\n\t\tif count == 0 && nstk == 1 && addrs[0] == 0 {\n\t\t\t// End of data marker\n\t\t\tbreak\n\t\t}\n\t\tfor i, addr := range addrs {\n\t\t\tif adjust && i > 0 {\n\t\t\t\taddr--\n\t\t\t}\n\t\t\tloc := locs[addr]\n\t\t\tif loc == nil {\n\t\t\t\tloc = &Location{\n\t\t\t\t\tAddress: addr,\n\t\t\t\t}\n\t\t\t\tlocs[addr] = loc\n\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t}\n\t\t\tsloc = append(sloc, loc)\n\t\t}\n\t\tp.Sample = append(p.Sample,\n\t\t\t&Sample{\n\t\t\t\tValue:    []int64{int64(count), int64(count) * p.Period},\n\t\t\t\tLocation: sloc,\n\t\t\t})\n\t}\n\t// Reached the end without finding the EOD marker.\n\treturn b, locs, nil\n}\n\n// parseHeap parses a heapz legacy or a growthz profile and\n// returns a newly populated Profile.\nfunc parseHeap(b []byte) (p *Profile, err error) {\n\ts := bufio.NewScanner(bytes.NewBuffer(b))\n\tif !s.Scan() {\n\t\tif err := s.Err(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn nil, errUnrecognized\n\t}\n\tp = &Profile{}\n\n\tsampling := \"\"\n\thasAlloc := false\n\n\tline := s.Text()\n\tp.PeriodType = &ValueType{Type: \"space\", Unit: \"bytes\"}\n\tif header := heapHeaderRE.FindStringSubmatch(line); header != nil {\n\t\tsampling, p.Period, hasAlloc, err = parseHeapHeader(line)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if header = growthHeaderRE.FindStringSubmatch(line); header != nil {\n\t\tp.Period = 1\n\t} else if header = fragmentationHeaderRE.FindStringSubmatch(line); header != nil {\n\t\tp.Period = 1\n\t} else {\n\t\treturn nil, errUnrecognized\n\t}\n\n\tif hasAlloc {\n\t\t// Put alloc before inuse so that default pprof selection\n\t\t// will prefer inuse_space.\n\t\tp.SampleType = []*ValueType{\n\t\t\t{Type: \"alloc_objects\", Unit: \"count\"},\n\t\t\t{Type: \"alloc_space\", Unit: \"bytes\"},\n\t\t\t{Type: \"inuse_objects\", Unit: \"count\"},\n\t\t\t{Type: \"inuse_space\", Unit: \"bytes\"},\n\t\t}\n\t} else {\n\t\tp.SampleType = []*ValueType{\n\t\t\t{Type: \"objects\", Unit: \"count\"},\n\t\t\t{Type: \"space\", Unit: \"bytes\"},\n\t\t}\n\t}\n\n\tlocs := make(map[uint64]*Location)\n\tfor s.Scan() {\n\t\tline := strings.TrimSpace(s.Text())\n\n\t\tif isSpaceOrComment(line) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif isMemoryMapSentinel(line) {\n\t\t\tbreak\n\t\t}\n\n\t\tvalue, blocksize, addrs, err := parseHeapSample(line, p.Period, sampling, hasAlloc)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tvar sloc []*Location\n\t\tfor _, addr := range addrs {\n\t\t\t// Addresses from stack traces point to the next instruction after\n\t\t\t// each call. Adjust by -1 to land somewhere on the actual call.\n\t\t\taddr--\n\t\t\tloc := locs[addr]\n\t\t\tif locs[addr] == nil {\n\t\t\t\tloc = &Location{\n\t\t\t\t\tAddress: addr,\n\t\t\t\t}\n\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t\tlocs[addr] = loc\n\t\t\t}\n\t\t\tsloc = append(sloc, loc)\n\t\t}\n\n\t\tp.Sample = append(p.Sample, &Sample{\n\t\t\tValue:    value,\n\t\t\tLocation: sloc,\n\t\t\tNumLabel: map[string][]int64{\"bytes\": {blocksize}},\n\t\t})\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := parseAdditionalSections(s, p); err != nil {\n\t\treturn nil, err\n\t}\n\treturn p, nil\n}\n\nfunc parseHeapHeader(line string) (sampling string, period int64, hasAlloc bool, err error) {\n\theader := heapHeaderRE.FindStringSubmatch(line)\n\tif header == nil {\n\t\treturn \"\", 0, false, errUnrecognized\n\t}\n\n\tif len(header[6]) > 0 {\n\t\tif period, err = strconv.ParseInt(header[6], 10, 64); err != nil {\n\t\t\treturn \"\", 0, false, errUnrecognized\n\t\t}\n\t}\n\n\tif (header[3] != header[1] && header[3] != \"0\") || (header[4] != header[2] && header[4] != \"0\") {\n\t\thasAlloc = true\n\t}\n\n\tswitch header[5] {\n\tcase \"heapz_v2\", \"heap_v2\":\n\t\treturn \"v2\", period, hasAlloc, nil\n\tcase \"heapprofile\":\n\t\treturn \"\", 1, hasAlloc, nil\n\tcase \"heap\":\n\t\treturn \"v2\", period / 2, hasAlloc, nil\n\tdefault:\n\t\treturn \"\", 0, false, errUnrecognized\n\t}\n}\n\n// parseHeapSample parses a single row from a heap profile into a new Sample.\nfunc parseHeapSample(line string, rate int64, sampling string, includeAlloc bool) (value []int64, blocksize int64, addrs []uint64, err error) {\n\tsampleData := heapSampleRE.FindStringSubmatch(line)\n\tif len(sampleData) != 6 {\n\t\treturn nil, 0, nil, fmt.Errorf(\"unexpected number of sample values: got %d, want 6\", len(sampleData))\n\t}\n\n\t// This is a local-scoped helper function to avoid needing to pass\n\t// around rate, sampling and many return parameters.\n\taddValues := func(countString, sizeString string, label string) error {\n\t\tcount, err := strconv.ParseInt(countString, 10, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t\t}\n\t\tsize, err := strconv.ParseInt(sizeString, 10, 64)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t\t}\n\t\tif count == 0 && size != 0 {\n\t\t\treturn fmt.Errorf(\"%s count was 0 but %s bytes was %d\", label, label, size)\n\t\t}\n\t\tif count != 0 {\n\t\t\tblocksize = size / count\n\t\t\tif sampling == \"v2\" {\n\t\t\t\tcount, size = scaleHeapSample(count, size, rate)\n\t\t\t}\n\t\t}\n\t\tvalue = append(value, count, size)\n\t\treturn nil\n\t}\n\n\tif includeAlloc {\n\t\tif err := addValues(sampleData[3], sampleData[4], \"allocation\"); err != nil {\n\t\t\treturn nil, 0, nil, err\n\t\t}\n\t}\n\n\tif err := addValues(sampleData[1], sampleData[2], \"inuse\"); err != nil {\n\t\treturn nil, 0, nil, err\n\t}\n\n\taddrs, err = parseHexAddresses(sampleData[5])\n\tif err != nil {\n\t\treturn nil, 0, nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t}\n\n\treturn value, blocksize, addrs, nil\n}\n\n// parseHexAddresses extracts hex numbers from a string, attempts to convert\n// each to an unsigned 64-bit number and returns the resulting numbers as a\n// slice, or an error if the string contains hex numbers which are too large to\n// handle (which means a malformed profile).\nfunc parseHexAddresses(s string) ([]uint64, error) {\n\thexStrings := hexNumberRE.FindAllString(s, -1)\n\tvar addrs []uint64\n\tfor _, s := range hexStrings {\n\t\tif addr, err := strconv.ParseUint(s, 0, 64); err == nil {\n\t\t\taddrs = append(addrs, addr)\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse as hex 64-bit number: %s\", s)\n\t\t}\n\t}\n\treturn addrs, nil\n}\n\n// scaleHeapSample adjusts the data from a heapz Sample to\n// account for its probability of appearing in the collected\n// data. heapz profiles are a sampling of the memory allocations\n// requests in a program. We estimate the unsampled value by dividing\n// each collected sample by its probability of appearing in the\n// profile. heapz v2 profiles rely on a poisson process to determine\n// which samples to collect, based on the desired average collection\n// rate R. The probability of a sample of size S to appear in that\n// profile is 1-exp(-S/R).\nfunc scaleHeapSample(count, size, rate int64) (int64, int64) {\n\tif count == 0 || size == 0 {\n\t\treturn 0, 0\n\t}\n\n\tif rate <= 1 {\n\t\t// if rate==1 all samples were collected so no adjustment is needed.\n\t\t// if rate<1 treat as unknown and skip scaling.\n\t\treturn count, size\n\t}\n\n\tavgSize := float64(size) / float64(count)\n\tscale := 1 / (1 - math.Exp(-avgSize/float64(rate)))\n\n\treturn int64(float64(count) * scale), int64(float64(size) * scale)\n}\n\n// parseContention parses a mutex or contention profile. There are 2 cases:\n// \"--- contentionz \" for legacy C++ profiles (and backwards compatibility)\n// \"--- mutex:\" or \"--- contention:\" for profiles generated by the Go runtime.\nfunc parseContention(b []byte) (*Profile, error) {\n\ts := bufio.NewScanner(bytes.NewBuffer(b))\n\tif !s.Scan() {\n\t\tif err := s.Err(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn nil, errUnrecognized\n\t}\n\n\tswitch l := s.Text(); {\n\tcase strings.HasPrefix(l, \"--- contentionz \"):\n\tcase strings.HasPrefix(l, \"--- mutex:\"):\n\tcase strings.HasPrefix(l, \"--- contention:\"):\n\tdefault:\n\t\treturn nil, errUnrecognized\n\t}\n\n\tp := &Profile{\n\t\tPeriodType: &ValueType{Type: \"contentions\", Unit: \"count\"},\n\t\tPeriod:     1,\n\t\tSampleType: []*ValueType{\n\t\t\t{Type: \"contentions\", Unit: \"count\"},\n\t\t\t{Type: \"delay\", Unit: \"nanoseconds\"},\n\t\t},\n\t}\n\n\tvar cpuHz int64\n\t// Parse text of the form \"attribute = value\" before the samples.\n\tconst delimiter = \"=\"\n\tfor s.Scan() {\n\t\tline := s.Text()\n\t\tif line = strings.TrimSpace(line); isSpaceOrComment(line) {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(line, \"---\") {\n\t\t\tbreak\n\t\t}\n\t\tattr := strings.SplitN(line, delimiter, 2)\n\t\tif len(attr) != 2 {\n\t\t\tbreak\n\t\t}\n\t\tkey, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])\n\t\tvar err error\n\t\tswitch key {\n\t\tcase \"cycles/second\":\n\t\t\tif cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil {\n\t\t\t\treturn nil, errUnrecognized\n\t\t\t}\n\t\tcase \"sampling period\":\n\t\t\tif p.Period, err = strconv.ParseInt(val, 0, 64); err != nil {\n\t\t\t\treturn nil, errUnrecognized\n\t\t\t}\n\t\tcase \"ms since reset\":\n\t\t\tms, err := strconv.ParseInt(val, 0, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errUnrecognized\n\t\t\t}\n\t\t\tp.DurationNanos = ms * 1000 * 1000\n\t\tcase \"format\":\n\t\t\t// CPP contentionz profiles don't have format.\n\t\t\treturn nil, errUnrecognized\n\t\tcase \"resolution\":\n\t\t\t// CPP contentionz profiles don't have resolution.\n\t\t\treturn nil, errUnrecognized\n\t\tcase \"discarded samples\":\n\t\tdefault:\n\t\t\treturn nil, errUnrecognized\n\t\t}\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tlocs := make(map[uint64]*Location)\n\tfor {\n\t\tline := strings.TrimSpace(s.Text())\n\t\tif strings.HasPrefix(line, \"---\") {\n\t\t\tbreak\n\t\t}\n\t\tif !isSpaceOrComment(line) {\n\t\t\tvalue, addrs, err := parseContentionSample(line, p.Period, cpuHz)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvar sloc []*Location\n\t\t\tfor _, addr := range addrs {\n\t\t\t\t// Addresses from stack traces point to the next instruction after\n\t\t\t\t// each call. Adjust by -1 to land somewhere on the actual call.\n\t\t\t\taddr--\n\t\t\t\tloc := locs[addr]\n\t\t\t\tif locs[addr] == nil {\n\t\t\t\t\tloc = &Location{\n\t\t\t\t\t\tAddress: addr,\n\t\t\t\t\t}\n\t\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t\t\tlocs[addr] = loc\n\t\t\t\t}\n\t\t\t\tsloc = append(sloc, loc)\n\t\t\t}\n\t\t\tp.Sample = append(p.Sample, &Sample{\n\t\t\t\tValue:    value,\n\t\t\t\tLocation: sloc,\n\t\t\t})\n\t\t}\n\t\tif !s.Scan() {\n\t\t\tbreak\n\t\t}\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := parseAdditionalSections(s, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// parseContentionSample parses a single row from a contention profile\n// into a new Sample.\nfunc parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) {\n\tsampleData := contentionSampleRE.FindStringSubmatch(line)\n\tif sampleData == nil {\n\t\treturn nil, nil, errUnrecognized\n\t}\n\n\tv1, err := strconv.ParseInt(sampleData[1], 10, 64)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t}\n\tv2, err := strconv.ParseInt(sampleData[2], 10, 64)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t}\n\n\t// Unsample values if period and cpuHz are available.\n\t// - Delays are scaled to cycles and then to nanoseconds.\n\t// - Contentions are scaled to cycles.\n\tif period > 0 {\n\t\tif cpuHz > 0 {\n\t\t\tcpuGHz := float64(cpuHz) / 1e9\n\t\t\tv1 = int64(float64(v1) * float64(period) / cpuGHz)\n\t\t}\n\t\tv2 = v2 * period\n\t}\n\n\tvalue = []int64{v2, v1}\n\taddrs, err = parseHexAddresses(sampleData[3])\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t}\n\n\treturn value, addrs, nil\n}\n\n// parseThread parses a Threadz profile and returns a new Profile.\nfunc parseThread(b []byte) (*Profile, error) {\n\ts := bufio.NewScanner(bytes.NewBuffer(b))\n\t// Skip past comments and empty lines seeking a real header.\n\tfor s.Scan() && isSpaceOrComment(s.Text()) {\n\t}\n\n\tline := s.Text()\n\tif m := threadzStartRE.FindStringSubmatch(line); m != nil {\n\t\t// Advance over initial comments until first stack trace.\n\t\tfor s.Scan() {\n\t\t\tif line = s.Text(); isMemoryMapSentinel(line) || strings.HasPrefix(line, \"-\") {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {\n\t\treturn nil, errUnrecognized\n\t}\n\n\tp := &Profile{\n\t\tSampleType: []*ValueType{{Type: \"thread\", Unit: \"count\"}},\n\t\tPeriodType: &ValueType{Type: \"thread\", Unit: \"count\"},\n\t\tPeriod:     1,\n\t}\n\n\tlocs := make(map[uint64]*Location)\n\t// Recognize each thread and populate profile samples.\n\tfor !isMemoryMapSentinel(line) {\n\t\tif strings.HasPrefix(line, \"---- no stack trace for\") {\n\t\t\tbreak\n\t\t}\n\t\tif t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {\n\t\t\treturn nil, errUnrecognized\n\t\t}\n\n\t\tvar addrs []uint64\n\t\tvar err error\n\t\tline, addrs, err = parseThreadSample(s)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(addrs) == 0 {\n\t\t\t// We got a --same as previous threads--. Bump counters.\n\t\t\tif len(p.Sample) > 0 {\n\t\t\t\ts := p.Sample[len(p.Sample)-1]\n\t\t\t\ts.Value[0]++\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tvar sloc []*Location\n\t\tfor i, addr := range addrs {\n\t\t\t// Addresses from stack traces point to the next instruction after\n\t\t\t// each call. Adjust by -1 to land somewhere on the actual call\n\t\t\t// (except for the leaf, which is not a call).\n\t\t\tif i > 0 {\n\t\t\t\taddr--\n\t\t\t}\n\t\t\tloc := locs[addr]\n\t\t\tif locs[addr] == nil {\n\t\t\t\tloc = &Location{\n\t\t\t\t\tAddress: addr,\n\t\t\t\t}\n\t\t\t\tp.Location = append(p.Location, loc)\n\t\t\t\tlocs[addr] = loc\n\t\t\t}\n\t\t\tsloc = append(sloc, loc)\n\t\t}\n\n\t\tp.Sample = append(p.Sample, &Sample{\n\t\t\tValue:    []int64{1},\n\t\t\tLocation: sloc,\n\t\t})\n\t}\n\n\tif err := parseAdditionalSections(s, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcleanupDuplicateLocations(p)\n\treturn p, nil\n}\n\n// parseThreadSample parses a symbolized or unsymbolized stack trace.\n// Returns the first line after the traceback, the sample (or nil if\n// it hits a 'same-as-previous' marker) and an error.\nfunc parseThreadSample(s *bufio.Scanner) (nextl string, addrs []uint64, err error) {\n\tvar line string\n\tsameAsPrevious := false\n\tfor s.Scan() {\n\t\tline = strings.TrimSpace(s.Text())\n\t\tif line == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.HasPrefix(line, \"---\") {\n\t\t\tbreak\n\t\t}\n\t\tif strings.Contains(line, \"same as previous thread\") {\n\t\t\tsameAsPrevious = true\n\t\t\tcontinue\n\t\t}\n\n\t\tcurAddrs, err := parseHexAddresses(line)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"malformed sample: %s: %v\", line, err)\n\t\t}\n\t\taddrs = append(addrs, curAddrs...)\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tif sameAsPrevious {\n\t\treturn line, nil, nil\n\t}\n\treturn line, addrs, nil\n}\n\n// parseAdditionalSections parses any additional sections in the\n// profile, ignoring any unrecognized sections.\nfunc parseAdditionalSections(s *bufio.Scanner, p *Profile) error {\n\tfor !isMemoryMapSentinel(s.Text()) && s.Scan() {\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn p.ParseMemoryMapFromScanner(s)\n}\n\n// ParseProcMaps parses a memory map in the format of /proc/self/maps.\n// ParseMemoryMap should be called after setting on a profile to\n// associate locations to the corresponding mapping based on their\n// address.\nfunc ParseProcMaps(rd io.Reader) ([]*Mapping, error) {\n\ts := bufio.NewScanner(rd)\n\treturn parseProcMapsFromScanner(s)\n}\n\nfunc parseProcMapsFromScanner(s *bufio.Scanner) ([]*Mapping, error) {\n\tvar mapping []*Mapping\n\n\tvar attrs []string\n\tconst delimiter = \"=\"\n\tr := strings.NewReplacer()\n\tfor s.Scan() {\n\t\tline := r.Replace(removeLoggingInfo(s.Text()))\n\t\tm, err := parseMappingEntry(line)\n\t\tif err != nil {\n\t\t\tif err == errUnrecognized {\n\t\t\t\t// Recognize assignments of the form: attr=value, and replace\n\t\t\t\t// $attr with value on subsequent mappings.\n\t\t\t\tif attr := strings.SplitN(line, delimiter, 2); len(attr) == 2 {\n\t\t\t\t\tattrs = append(attrs, \"$\"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]))\n\t\t\t\t\tr = strings.NewReplacer(attrs...)\n\t\t\t\t}\n\t\t\t\t// Ignore any unrecognized entries\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\tmapping = append(mapping, m)\n\t}\n\tif err := s.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mapping, nil\n}\n\n// removeLoggingInfo detects and removes log prefix entries generated\n// by the glog package. If no logging prefix is detected, the string\n// is returned unmodified.\nfunc removeLoggingInfo(line string) string {\n\tif match := logInfoRE.FindStringIndex(line); match != nil {\n\t\treturn line[match[1]:]\n\t}\n\treturn line\n}\n\n// ParseMemoryMap parses a memory map in the format of\n// /proc/self/maps, and overrides the mappings in the current profile.\n// It renumbers the samples and locations in the profile correspondingly.\nfunc (p *Profile) ParseMemoryMap(rd io.Reader) error {\n\treturn p.ParseMemoryMapFromScanner(bufio.NewScanner(rd))\n}\n\n// ParseMemoryMapFromScanner parses a memory map in the format of\n// /proc/self/maps or a variety of legacy format, and overrides the\n// mappings in the current profile.  It renumbers the samples and\n// locations in the profile correspondingly.\nfunc (p *Profile) ParseMemoryMapFromScanner(s *bufio.Scanner) error {\n\tmapping, err := parseProcMapsFromScanner(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.Mapping = append(p.Mapping, mapping...)\n\tp.massageMappings()\n\tp.remapLocationIDs()\n\tp.remapFunctionIDs()\n\tp.remapMappingIDs()\n\treturn nil\n}\n\nfunc parseMappingEntry(l string) (*Mapping, error) {\n\tvar start, end, perm, file, offset, buildID string\n\tif me := procMapsRE.FindStringSubmatch(l); len(me) == 6 {\n\t\tstart, end, perm, offset, file = me[1], me[2], me[3], me[4], me[5]\n\t} else if me := briefMapsRE.FindStringSubmatch(l); len(me) == 7 {\n\t\tstart, end, perm, file, offset, buildID = me[1], me[2], me[3], me[4], me[5], me[6]\n\t} else {\n\t\treturn nil, errUnrecognized\n\t}\n\n\tvar err error\n\tmapping := &Mapping{\n\t\tFile:    file,\n\t\tBuildID: buildID,\n\t}\n\tif perm != \"\" && !strings.Contains(perm, \"x\") {\n\t\t// Skip non-executable entries.\n\t\treturn nil, nil\n\t}\n\tif mapping.Start, err = strconv.ParseUint(start, 16, 64); err != nil {\n\t\treturn nil, errUnrecognized\n\t}\n\tif mapping.Limit, err = strconv.ParseUint(end, 16, 64); err != nil {\n\t\treturn nil, errUnrecognized\n\t}\n\tif offset != \"\" {\n\t\tif mapping.Offset, err = strconv.ParseUint(offset, 16, 64); err != nil {\n\t\t\treturn nil, errUnrecognized\n\t\t}\n\t}\n\treturn mapping, nil\n}\n\nvar memoryMapSentinels = []string{\n\t\"--- Memory map: ---\",\n\t\"MAPPED_LIBRARIES:\",\n}\n\n// isMemoryMapSentinel returns true if the string contains one of the\n// known sentinels for memory map information.\nfunc isMemoryMapSentinel(line string) bool {\n\tfor _, s := range memoryMapSentinels {\n\t\tif strings.Contains(line, s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (p *Profile) addLegacyFrameInfo() {\n\tswitch {\n\tcase isProfileType(p, heapzSampleTypes):\n\t\tp.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr\n\tcase isProfileType(p, contentionzSampleTypes):\n\t\tp.DropFrames, p.KeepFrames = lockRxStr, \"\"\n\tdefault:\n\t\tp.DropFrames, p.KeepFrames = cpuProfilerRxStr, \"\"\n\t}\n}\n\nvar heapzSampleTypes = [][]string{\n\t{\"allocations\", \"size\"}, // early Go pprof profiles\n\t{\"objects\", \"space\"},\n\t{\"inuse_objects\", \"inuse_space\"},\n\t{\"alloc_objects\", \"alloc_space\"},\n\t{\"alloc_objects\", \"alloc_space\", \"inuse_objects\", \"inuse_space\"}, // Go pprof legacy profiles\n}\nvar contentionzSampleTypes = [][]string{\n\t{\"contentions\", \"delay\"},\n}\n\nfunc isProfileType(p *Profile, types [][]string) bool {\n\tst := p.SampleType\nnextType:\n\tfor _, t := range types {\n\t\tif len(st) != len(t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor i := range st {\n\t\t\tif st[i].Type != t[i] {\n\t\t\t\tcontinue nextType\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nvar allocRxStr = strings.Join([]string{\n\t// POSIX entry points.\n\t`calloc`,\n\t`cfree`,\n\t`malloc`,\n\t`free`,\n\t`memalign`,\n\t`do_memalign`,\n\t`(__)?posix_memalign`,\n\t`pvalloc`,\n\t`valloc`,\n\t`realloc`,\n\n\t// TC malloc.\n\t`tcmalloc::.*`,\n\t`tc_calloc`,\n\t`tc_cfree`,\n\t`tc_malloc`,\n\t`tc_free`,\n\t`tc_memalign`,\n\t`tc_posix_memalign`,\n\t`tc_pvalloc`,\n\t`tc_valloc`,\n\t`tc_realloc`,\n\t`tc_new`,\n\t`tc_delete`,\n\t`tc_newarray`,\n\t`tc_deletearray`,\n\t`tc_new_nothrow`,\n\t`tc_newarray_nothrow`,\n\n\t// Memory-allocation routines on OS X.\n\t`malloc_zone_malloc`,\n\t`malloc_zone_calloc`,\n\t`malloc_zone_valloc`,\n\t`malloc_zone_realloc`,\n\t`malloc_zone_memalign`,\n\t`malloc_zone_free`,\n\n\t// Go runtime\n\t`runtime\\..*`,\n\n\t// Other misc. memory allocation routines\n\t`BaseArena::.*`,\n\t`(::)?do_malloc_no_errno`,\n\t`(::)?do_malloc_pages`,\n\t`(::)?do_malloc`,\n\t`DoSampledAllocation`,\n\t`MallocedMemBlock::MallocedMemBlock`,\n\t`_M_allocate`,\n\t`__builtin_(vec_)?delete`,\n\t`__builtin_(vec_)?new`,\n\t`__gnu_cxx::new_allocator::allocate`,\n\t`__libc_malloc`,\n\t`__malloc_alloc_template::allocate`,\n\t`allocate`,\n\t`cpp_alloc`,\n\t`operator new(\\[\\])?`,\n\t`simple_alloc::allocate`,\n}, `|`)\n\nvar allocSkipRxStr = strings.Join([]string{\n\t// Preserve Go runtime frames that appear in the middle/bottom of\n\t// the stack.\n\t`runtime\\.panic`,\n\t`runtime\\.reflectcall`,\n\t`runtime\\.call[0-9]*`,\n}, `|`)\n\nvar cpuProfilerRxStr = strings.Join([]string{\n\t`ProfileData::Add`,\n\t`ProfileData::prof_handler`,\n\t`CpuProfiler::prof_handler`,\n\t`__pthread_sighandler`,\n\t`__restore`,\n}, `|`)\n\nvar lockRxStr = strings.Join([]string{\n\t`RecordLockProfileData`,\n\t`(base::)?RecordLockProfileData.*`,\n\t`(base::)?SubmitMutexProfileData.*`,\n\t`(base::)?SubmitSpinLockProfileData.*`,\n\t`(base::Mutex::)?AwaitCommon.*`,\n\t`(base::Mutex::)?Unlock.*`,\n\t`(base::Mutex::)?UnlockSlow.*`,\n\t`(base::Mutex::)?ReaderUnlock.*`,\n\t`(base::MutexLock::)?~MutexLock.*`,\n\t`(Mutex::)?AwaitCommon.*`,\n\t`(Mutex::)?Unlock.*`,\n\t`(Mutex::)?UnlockSlow.*`,\n\t`(Mutex::)?ReaderUnlock.*`,\n\t`(MutexLock::)?~MutexLock.*`,\n\t`(SpinLock::)?Unlock.*`,\n\t`(SpinLock::)?SlowUnlock.*`,\n\t`(SpinLockHolder::)?~SpinLockHolder.*`,\n}, `|`)\n"
  },
  {
    "path": "profile/legacy_profile_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestLegacyProfileType(t *testing.T) {\n\ttype testcase struct {\n\t\tsampleTypes []string\n\t\ttypeSet     [][]string\n\t\twant        bool\n\t\tsetName     string\n\t}\n\n\theap := heapzSampleTypes\n\tcont := contentionzSampleTypes\n\ttestcases := []testcase{\n\t\t// True cases\n\t\t{[]string{\"allocations\", \"size\"}, heap, true, \"heapzSampleTypes\"},\n\t\t{[]string{\"objects\", \"space\"}, heap, true, \"heapzSampleTypes\"},\n\t\t{[]string{\"inuse_objects\", \"inuse_space\"}, heap, true, \"heapzSampleTypes\"},\n\t\t{[]string{\"alloc_objects\", \"alloc_space\"}, heap, true, \"heapzSampleTypes\"},\n\t\t{[]string{\"alloc_objects\", \"alloc_space\", \"inuse_objects\", \"inuse_space\"}, heap, true, \"heapzSampleTypes\"},\n\t\t{[]string{\"contentions\", \"delay\"}, cont, true, \"contentionzSampleTypes\"},\n\t\t// False cases\n\t\t{[]string{\"objects\"}, heap, false, \"heapzSampleTypes\"},\n\t\t{[]string{\"objects\", \"unknown\"}, heap, false, \"heapzSampleTypes\"},\n\t\t{[]string{\"inuse_objects\", \"inuse_space\", \"alloc_objects\", \"alloc_space\"}, heap, false, \"heapzSampleTypes\"},\n\t\t{[]string{\"contentions\", \"delay\"}, heap, false, \"heapzSampleTypes\"},\n\t\t{[]string{\"samples\", \"cpu\"}, heap, false, \"heapzSampleTypes\"},\n\t\t{[]string{\"samples\", \"cpu\"}, cont, false, \"contentionzSampleTypes\"},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tp := profileOfType(tc.sampleTypes)\n\t\tif got := isProfileType(p, tc.typeSet); got != tc.want {\n\t\t\tt.Error(\"isProfileType({\"+strings.Join(tc.sampleTypes, \",\")+\"},\", tc.setName, \"), got\", got, \"want\", tc.want)\n\t\t}\n\t}\n}\n\nfunc TestCpuParse(t *testing.T) {\n\t// profileString is a legacy encoded profile, represented by words separated by \":\"\n\t// Each sample has the form value : N : stack1..stackN\n\t// EOF is represented as \"0:1:0\"\n\tprofileString := \"1:3:100:999:100:\"                                      // sample with bogus 999 and duplicate leaf\n\tprofileString += \"1:5:200:999:200:501:502:\"                              // sample with bogus 999 and duplicate leaf\n\tprofileString += \"1:12:300:999:300:601:602:603:604:605:606:607:608:609:\" // sample with bogus 999 and duplicate leaf\n\tprofileString += \"0:1:0000\"                                              // EOF -- must use 4 bytes for the final zero\n\n\tp, err := cpuProfile([]byte(profileString), 1, parseString)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := checkTestSample(p, []uint64{100}); err != nil {\n\t\tt.Error(err)\n\t}\n\tif err := checkTestSample(p, []uint64{200, 500, 501}); err != nil {\n\t\tt.Error(err)\n\t}\n\tif err := checkTestSample(p, []uint64{300, 600, 601, 602, 603, 604, 605, 606, 607, 608}); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc parseString(b []byte) (uint64, []byte) {\n\tslices := bytes.SplitN(b, []byte(\":\"), 2)\n\tvar value, remainder []byte\n\tif len(slices) > 0 {\n\t\tvalue = slices[0]\n\t}\n\tif len(slices) > 1 {\n\t\tremainder = slices[1]\n\t}\n\tv, _ := strconv.ParseUint(string(value), 10, 64)\n\treturn v, remainder\n}\n\nfunc checkTestSample(p *Profile, want []uint64) error {\n\tfor _, s := range p.Sample {\n\t\tgot := []uint64{}\n\t\tfor _, l := range s.Location {\n\t\t\tgot = append(got, l.Address)\n\t\t}\n\t\tif reflect.DeepEqual(got, want) {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"Could not find sample : %v\", want)\n}\n\n// profileOfType creates an empty profile with only sample types set,\n// for testing purposes only.\nfunc profileOfType(sampleTypes []string) *Profile {\n\tp := new(Profile)\n\tp.SampleType = make([]*ValueType, len(sampleTypes))\n\tfor i, t := range sampleTypes {\n\t\tp.SampleType[i] = new(ValueType)\n\t\tp.SampleType[i].Type = t\n\t}\n\treturn p\n}\n\nfunc TestParseMappingEntry(t *testing.T) {\n\tfor _, test := range []*struct {\n\t\tentry string\n\t\twant  *Mapping\n\t}{\n\t\t{\n\t\t\tentry: \"00400000-02e00000 r-xp 00000000 00:00 0\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x400000,\n\t\t\t\tLimit: 0x2e00000,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"02e00000-02e8a000 r-xp 02a00000 00:00 15953927    /foo/bin\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:  0x2e00000,\n\t\t\t\tLimit:  0x2e8a000,\n\t\t\t\tOffset: 0x2a00000,\n\t\t\t\tFile:   \"/foo/bin\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"02e00000-02e8a000 r-xp 000000 00:00 15953927    [vdso]\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x2e00000,\n\t\t\t\tLimit: 0x2e8a000,\n\t\t\t\tFile:  \"[vdso]\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"  02e00000-02e8a000: /foo/bin (@2a00000)\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:  0x2e00000,\n\t\t\t\tLimit:  0x2e8a000,\n\t\t\t\tOffset: 0x2a00000,\n\t\t\t\tFile:   \"/foo/bin\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"  02e00000-02e8a000: /foo/bin (deleted)\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x2e00000,\n\t\t\t\tLimit: 0x2e8a000,\n\t\t\t\tFile:  \"/foo/bin\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"  02e00000-02e8a000: /foo/bin\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x2e00000,\n\t\t\t\tLimit: 0x2e8a000,\n\t\t\t\tFile:  \"/foo/bin\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tentry: \"  02e00000-02e8a000: [vdso]\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x2e00000,\n\t\t\t\tLimit: 0x2e8a000,\n\t\t\t\tFile:  \"[vdso]\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"0xff6810563000 0xff6810565000 r-xp abc_exe 87c4d547f895cfd6a370e08dc5c5ee7bd4199d5b\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:   0xff6810563000,\n\t\t\t\tLimit:   0xff6810565000,\n\t\t\t\tFile:    \"abc_exe\",\n\t\t\t\tBuildID: \"87c4d547f895cfd6a370e08dc5c5ee7bd4199d5b\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"7f5e5435e000-7f5e5455e000 --xp 00002000 00:00 1531        myprogram\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:  0x7f5e5435e000,\n\t\t\t\tLimit:  0x7f5e5455e000,\n\t\t\t\tOffset: 0x2000,\n\t\t\t\tFile:   \"myprogram\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"7f7472710000-7f7472722000 r-xp 00000000 fc:00 790190      /usr/lib/libfantastic-1.2.so\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x7f7472710000,\n\t\t\t\tLimit: 0x7f7472722000,\n\t\t\t\tFile:  \"/usr/lib/libfantastic-1.2.so\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"7f47a542f000-7f47a5447000: /lib/libpthread-2.15.so\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart: 0x7f47a542f000,\n\t\t\t\tLimit: 0x7f47a5447000,\n\t\t\t\tFile:  \"/lib/libpthread-2.15.so\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"0x40000-0x80000 /path/to/binary      (@FF00)            abc123456\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:   0x40000,\n\t\t\t\tLimit:   0x80000,\n\t\t\t\tFile:    \"/path/to/binary\",\n\t\t\t\tOffset:  0xFF00,\n\t\t\t\tBuildID: \"abc123456\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"W1220 15:07:15.201776    8272 logger.cc:12033] --- Memory map: ---\\n\" +\n\t\t\t\"0x40000-0x80000 /path/to/binary      (@FF00)            abc123456\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:   0x40000,\n\t\t\t\tLimit:   0x80000,\n\t\t\t\tFile:    \"/path/to/binary\",\n\t\t\t\tOffset:  0xFF00,\n\t\t\t\tBuildID: \"abc123456\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"W1220 15:07:15.201776    8272 logger.cc:12033] --- Memory map: ---\\n\" +\n\t\t\t\"W1220 15:07:15.202776    8272 logger.cc:12036]   0x40000-0x80000 /path/to/binary      (@FF00)            abc123456\",\n\t\t\twant: &Mapping{\n\t\t\t\tStart:   0x40000,\n\t\t\t\tLimit:   0x80000,\n\t\t\t\tFile:    \"/path/to/binary\",\n\t\t\t\tOffset:  0xFF00,\n\t\t\t\tBuildID: \"abc123456\",\n\t\t\t},\n\t\t},\n\t\t{entry: \"7f5e5435e000-7f5e5455e000 ---p 00002000 00:00 1531        myprogram\",\n\t\t\twant: nil,\n\t\t},\n\t} {\n\t\tgot, err := ParseProcMaps(strings.NewReader(test.entry))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"%s: %v\", test.entry, err)\n\t\t\tcontinue\n\t\t}\n\t\tif test.want == nil {\n\t\t\tif got, want := len(got), 0; got != want {\n\t\t\t\tt.Errorf(\"%s: got %d mappings, want %d\", test.entry, got, want)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif got, want := len(got), 1; got != want {\n\t\t\tt.Errorf(\"%s: got %d mappings, want %d\", test.entry, got, want)\n\t\t\tcontinue\n\t\t}\n\t\tif !reflect.DeepEqual(test.want, got[0]) {\n\t\t\tt.Errorf(\"%s want=%v got=%v\", test.entry, test.want, got[0])\n\t\t}\n\t}\n}\n\nfunc TestParseThreadProfileWithInvalidAddress(t *testing.T) {\n\tprofile := `\n--- threadz 1 ---\n\n--- Thread 7eff063d9940 (name: main/25376) stack: ---\n  PC: 0x40b688 0x4d5f51 0x40be31 0x473add693e639c6f0\n--- Memory map: ---\n  00400000-00fcb000: /home/rsilvera/cppbench/cppbench_server_main.unstripped\n\t`\n\twantErr := \"failed to parse as hex 64-bit number: 0x473add693e639c6f0\"\n\tif _, gotErr := parseThread([]byte(profile)); !strings.Contains(gotErr.Error(), wantErr) {\n\t\tt.Errorf(\"parseThread(): got error %q, want error containing %q\", gotErr, wantErr)\n\t}\n}\n\nfunc TestParseGoCount(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tin  string\n\t\ttyp string\n\t}{\n\t\t{\n\t\t\tin: `# ignored comment\n\nthreadcreate profile: total 123\n`,\n\t\t\ttyp: \"threadcreate\",\n\t\t},\n\t\t{\n\t\t\tin: `\n# ignored comment\ngoroutine profile: total 123456\n`,\n\t\t\ttyp: \"goroutine\",\n\t\t},\n\t\t{\n\t\t\tin: `\nsub/dir-ect_o.ry profile: total 999\n`,\n\t\t\ttyp: \"sub/dir-ect_o.ry\",\n\t\t},\n\t} {\n\t\tt.Run(test.typ, func(t *testing.T) {\n\t\t\tp, err := parseGoCount([]byte(test.in))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"parseGoCount(%q) = %v\", test.in, err)\n\t\t\t}\n\t\t\tif typ := p.PeriodType.Type; typ != test.typ {\n\t\t\t\tt.Fatalf(\"parseGoCount(%q).PeriodType.Type = %q want %q\", test.in, typ, test.typ)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "profile/merge.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// Compact performs garbage collection on a profile to remove any\n// unreferenced fields. This is useful to reduce the size of a profile\n// after samples or locations have been removed.\nfunc (p *Profile) Compact() *Profile {\n\tp, _ = Merge([]*Profile{p})\n\treturn p\n}\n\n// Merge merges all the profiles in profs into a single Profile.\n// Returns a new profile independent of the input profiles. The merged\n// profile is compacted to eliminate unused samples, locations,\n// functions and mappings. Profiles must have identical profile sample\n// and period types or the merge will fail. profile.Period of the\n// resulting profile will be the maximum of all profiles, and\n// profile.TimeNanos will be the earliest nonzero one. Merges are\n// associative with the caveat of the first profile having some\n// specialization in how headers are combined. There may be other\n// subtleties now or in the future regarding associativity.\nfunc Merge(srcs []*Profile) (*Profile, error) {\n\tif len(srcs) == 0 {\n\t\treturn nil, fmt.Errorf(\"no profiles to merge\")\n\t}\n\tp, err := combineHeaders(srcs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpm := &profileMerger{\n\t\tp:         p,\n\t\tsamples:   make(map[sampleKey]*Sample, len(srcs[0].Sample)),\n\t\tlocations: make(map[locationKey]*Location, len(srcs[0].Location)),\n\t\tfunctions: make(map[functionKey]*Function, len(srcs[0].Function)),\n\t\tmappings:  make(map[mappingKey]*Mapping, len(srcs[0].Mapping)),\n\t}\n\n\tfor _, src := range srcs {\n\t\t// Clear the profile-specific hash tables\n\t\tpm.locationsByID = makeLocationIDMap(len(src.Location))\n\t\tpm.functionsByID = make(map[uint64]*Function, len(src.Function))\n\t\tpm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping))\n\n\t\tif len(pm.mappings) == 0 && len(src.Mapping) > 0 {\n\t\t\t// The Mapping list has the property that the first mapping\n\t\t\t// represents the main binary. Take the first Mapping we see,\n\t\t\t// otherwise the operations below will add mappings in an\n\t\t\t// arbitrary order.\n\t\t\tpm.mapMapping(src.Mapping[0])\n\t\t}\n\n\t\tfor _, s := range src.Sample {\n\t\t\tif !isZeroSample(s) {\n\t\t\t\tpm.mapSample(s)\n\t\t\t}\n\t\t}\n\t}\n\n\tif slices.ContainsFunc(p.Sample, isZeroSample) {\n\t\t// If there are any zero samples, re-merge the profile to GC\n\t\t// them.\n\t\treturn Merge([]*Profile{p})\n\t}\n\n\treturn p, nil\n}\n\n// Normalize normalizes the source profile by multiplying each value in profile by the\n// ratio of the sum of the base profile's values of that sample type to the sum of the\n// source profile's value of that sample type.\nfunc (p *Profile) Normalize(pb *Profile) error {\n\n\tif err := p.compatible(pb); err != nil {\n\t\treturn err\n\t}\n\n\tbaseVals := make([]int64, len(p.SampleType))\n\tfor _, s := range pb.Sample {\n\t\tfor i, v := range s.Value {\n\t\t\tbaseVals[i] += v\n\t\t}\n\t}\n\n\tsrcVals := make([]int64, len(p.SampleType))\n\tfor _, s := range p.Sample {\n\t\tfor i, v := range s.Value {\n\t\t\tsrcVals[i] += v\n\t\t}\n\t}\n\n\tnormScale := make([]float64, len(baseVals))\n\tfor i := range baseVals {\n\t\tif srcVals[i] == 0 {\n\t\t\tnormScale[i] = 0.0\n\t\t} else {\n\t\t\tnormScale[i] = float64(baseVals[i]) / float64(srcVals[i])\n\t\t}\n\t}\n\tp.ScaleN(normScale)\n\treturn nil\n}\n\nfunc isZeroSample(s *Sample) bool {\n\tfor _, v := range s.Value {\n\t\tif v != 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\ntype profileMerger struct {\n\tp *Profile\n\n\t// Memoization tables within a profile.\n\tlocationsByID locationIDMap\n\tfunctionsByID map[uint64]*Function\n\tmappingsByID  map[uint64]mapInfo\n\n\t// Memoization tables for profile entities.\n\tsamples   map[sampleKey]*Sample\n\tlocations map[locationKey]*Location\n\tfunctions map[functionKey]*Function\n\tmappings  map[mappingKey]*Mapping\n}\n\ntype mapInfo struct {\n\tm      *Mapping\n\toffset int64\n}\n\nfunc (pm *profileMerger) mapSample(src *Sample) *Sample {\n\t// Check memoization table\n\tk := pm.sampleKey(src)\n\tif ss, ok := pm.samples[k]; ok {\n\t\tfor i, v := range src.Value {\n\t\t\tss.Value[i] += v\n\t\t}\n\t\treturn ss\n\t}\n\n\t// Make new sample.\n\ts := &Sample{\n\t\tLocation: make([]*Location, len(src.Location)),\n\t\tValue:    make([]int64, len(src.Value)),\n\t\tLabel:    make(map[string][]string, len(src.Label)),\n\t\tNumLabel: make(map[string][]int64, len(src.NumLabel)),\n\t\tNumUnit:  make(map[string][]string, len(src.NumLabel)),\n\t}\n\tfor i, l := range src.Location {\n\t\ts.Location[i] = pm.mapLocation(l)\n\t}\n\tfor k, v := range src.Label {\n\t\tvv := make([]string, len(v))\n\t\tcopy(vv, v)\n\t\ts.Label[k] = vv\n\t}\n\tfor k, v := range src.NumLabel {\n\t\tu := src.NumUnit[k]\n\t\tvv := make([]int64, len(v))\n\t\tuu := make([]string, len(u))\n\t\tcopy(vv, v)\n\t\tcopy(uu, u)\n\t\ts.NumLabel[k] = vv\n\t\ts.NumUnit[k] = uu\n\t}\n\tcopy(s.Value, src.Value)\n\tpm.samples[k] = s\n\tpm.p.Sample = append(pm.p.Sample, s)\n\treturn s\n}\n\nfunc (pm *profileMerger) sampleKey(sample *Sample) sampleKey {\n\t// Accumulate contents into a string.\n\tvar buf strings.Builder\n\tbuf.Grow(64) // Heuristic to avoid extra allocs\n\n\t// encode a number\n\tputNumber := func(v uint64) {\n\t\tvar num [binary.MaxVarintLen64]byte\n\t\tn := binary.PutUvarint(num[:], v)\n\t\tbuf.Write(num[:n])\n\t}\n\n\t// encode a string prefixed with its length.\n\tputDelimitedString := func(s string) {\n\t\tputNumber(uint64(len(s)))\n\t\tbuf.WriteString(s)\n\t}\n\n\tfor _, l := range sample.Location {\n\t\t// Get the location in the merged profile, which may have a different ID.\n\t\tif loc := pm.mapLocation(l); loc != nil {\n\t\t\tputNumber(loc.ID)\n\t\t}\n\t}\n\tputNumber(0) // Delimiter\n\n\tfor _, l := range sortedKeys1(sample.Label) {\n\t\tputDelimitedString(l)\n\t\tvalues := sample.Label[l]\n\t\tputNumber(uint64(len(values)))\n\t\tfor _, v := range values {\n\t\t\tputDelimitedString(v)\n\t\t}\n\t}\n\n\tfor _, l := range sortedKeys2(sample.NumLabel) {\n\t\tputDelimitedString(l)\n\t\tvalues := sample.NumLabel[l]\n\t\tputNumber(uint64(len(values)))\n\t\tfor _, v := range values {\n\t\t\tputNumber(uint64(v))\n\t\t}\n\t\tunits := sample.NumUnit[l]\n\t\tputNumber(uint64(len(units)))\n\t\tfor _, v := range units {\n\t\t\tputDelimitedString(v)\n\t\t}\n\t}\n\n\treturn sampleKey(buf.String())\n}\n\ntype sampleKey string\n\n// sortedKeys1 returns the sorted keys found in a string->[]string map.\n//\n// Note: this is currently non-generic since github pprof runs golint,\n// which does not support generics. When that issue is fixed, it can\n// be merged with sortedKeys2 and made into a generic function.\nfunc sortedKeys1(m map[string][]string) []string {\n\tif len(m) == 0 {\n\t\treturn nil\n\t}\n\tkeys := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n\n// sortedKeys2 returns the sorted keys found in a string->[]int64 map.\n//\n// Note: this is currently non-generic since github pprof runs golint,\n// which does not support generics. When that issue is fixed, it can\n// be merged with sortedKeys1 and made into a generic function.\nfunc sortedKeys2(m map[string][]int64) []string {\n\tif len(m) == 0 {\n\t\treturn nil\n\t}\n\tkeys := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n\nfunc (pm *profileMerger) mapLocation(src *Location) *Location {\n\tif src == nil {\n\t\treturn nil\n\t}\n\n\tif l := pm.locationsByID.get(src.ID); l != nil {\n\t\treturn l\n\t}\n\n\tmi := pm.mapMapping(src.Mapping)\n\tl := &Location{\n\t\tID:       uint64(len(pm.p.Location) + 1),\n\t\tMapping:  mi.m,\n\t\tAddress:  uint64(int64(src.Address) + mi.offset),\n\t\tLine:     make([]Line, len(src.Line)),\n\t\tIsFolded: src.IsFolded,\n\t}\n\tfor i, ln := range src.Line {\n\t\tl.Line[i] = pm.mapLine(ln)\n\t}\n\t// Check memoization table. Must be done on the remapped location to\n\t// account for the remapped mapping ID.\n\tk := l.key()\n\tif ll, ok := pm.locations[k]; ok {\n\t\tpm.locationsByID.set(src.ID, ll)\n\t\treturn ll\n\t}\n\tpm.locationsByID.set(src.ID, l)\n\tpm.locations[k] = l\n\tpm.p.Location = append(pm.p.Location, l)\n\treturn l\n}\n\n// key generates locationKey to be used as a key for maps.\nfunc (l *Location) key() locationKey {\n\tkey := locationKey{\n\t\taddr:     l.Address,\n\t\tisFolded: l.IsFolded,\n\t}\n\tif l.Mapping != nil {\n\t\t// Normalizes address to handle address space randomization.\n\t\tkey.addr -= l.Mapping.Start\n\t\tkey.mappingID = l.Mapping.ID\n\t}\n\tlines := make([]string, len(l.Line)*3)\n\tfor i, line := range l.Line {\n\t\tif line.Function != nil {\n\t\t\tlines[i*2] = strconv.FormatUint(line.Function.ID, 16)\n\t\t}\n\t\tlines[i*2+1] = strconv.FormatInt(line.Line, 16)\n\t\tlines[i*2+2] = strconv.FormatInt(line.Column, 16)\n\t}\n\tkey.lines = strings.Join(lines, \"|\")\n\treturn key\n}\n\ntype locationKey struct {\n\taddr, mappingID uint64\n\tlines           string\n\tisFolded        bool\n}\n\nfunc (pm *profileMerger) mapMapping(src *Mapping) mapInfo {\n\tif src == nil {\n\t\treturn mapInfo{}\n\t}\n\n\tif mi, ok := pm.mappingsByID[src.ID]; ok {\n\t\treturn mi\n\t}\n\n\t// Check memoization tables.\n\tmk := src.key()\n\tif m, ok := pm.mappings[mk]; ok {\n\t\tmi := mapInfo{m, int64(m.Start) - int64(src.Start)}\n\t\tpm.mappingsByID[src.ID] = mi\n\t\treturn mi\n\t}\n\tm := &Mapping{\n\t\tID:                     uint64(len(pm.p.Mapping) + 1),\n\t\tStart:                  src.Start,\n\t\tLimit:                  src.Limit,\n\t\tOffset:                 src.Offset,\n\t\tFile:                   src.File,\n\t\tKernelRelocationSymbol: src.KernelRelocationSymbol,\n\t\tBuildID:                src.BuildID,\n\t\tHasFunctions:           src.HasFunctions,\n\t\tHasFilenames:           src.HasFilenames,\n\t\tHasLineNumbers:         src.HasLineNumbers,\n\t\tHasInlineFrames:        src.HasInlineFrames,\n\t}\n\tpm.p.Mapping = append(pm.p.Mapping, m)\n\n\t// Update memoization tables.\n\tpm.mappings[mk] = m\n\tmi := mapInfo{m, 0}\n\tpm.mappingsByID[src.ID] = mi\n\treturn mi\n}\n\n// key generates encoded strings of Mapping to be used as a key for\n// maps.\nfunc (m *Mapping) key() mappingKey {\n\t// Normalize addresses to handle address space randomization.\n\t// Round up to next 4K boundary to avoid minor discrepancies.\n\tconst mapsizeRounding = 0x1000\n\n\tsize := m.Limit - m.Start\n\tsize = size + mapsizeRounding - 1\n\tsize = size - (size % mapsizeRounding)\n\tkey := mappingKey{\n\t\tsize:   size,\n\t\toffset: m.Offset,\n\t}\n\n\tswitch {\n\tcase m.BuildID != \"\":\n\t\tkey.buildIDOrFile = m.BuildID\n\tcase m.File != \"\":\n\t\tkey.buildIDOrFile = m.File\n\tdefault:\n\t\t// A mapping containing neither build ID nor file name is a fake mapping. A\n\t\t// key with empty buildIDOrFile is used for fake mappings so that they are\n\t\t// treated as the same mapping during merging.\n\t}\n\treturn key\n}\n\ntype mappingKey struct {\n\tsize, offset  uint64\n\tbuildIDOrFile string\n}\n\nfunc (pm *profileMerger) mapLine(src Line) Line {\n\tln := Line{\n\t\tFunction: pm.mapFunction(src.Function),\n\t\tLine:     src.Line,\n\t\tColumn:   src.Column,\n\t}\n\treturn ln\n}\n\nfunc (pm *profileMerger) mapFunction(src *Function) *Function {\n\tif src == nil {\n\t\treturn nil\n\t}\n\tif f, ok := pm.functionsByID[src.ID]; ok {\n\t\treturn f\n\t}\n\tk := src.key()\n\tif f, ok := pm.functions[k]; ok {\n\t\tpm.functionsByID[src.ID] = f\n\t\treturn f\n\t}\n\tf := &Function{\n\t\tID:         uint64(len(pm.p.Function) + 1),\n\t\tName:       src.Name,\n\t\tSystemName: src.SystemName,\n\t\tFilename:   src.Filename,\n\t\tStartLine:  src.StartLine,\n\t}\n\tpm.functions[k] = f\n\tpm.functionsByID[src.ID] = f\n\tpm.p.Function = append(pm.p.Function, f)\n\treturn f\n}\n\n// key generates a struct to be used as a key for maps.\nfunc (f *Function) key() functionKey {\n\treturn functionKey{\n\t\tf.StartLine,\n\t\tf.Name,\n\t\tf.SystemName,\n\t\tf.Filename,\n\t}\n}\n\ntype functionKey struct {\n\tstartLine                  int64\n\tname, systemName, fileName string\n}\n\n// combineHeaders checks that all profiles can be merged and returns\n// their combined profile.\nfunc combineHeaders(srcs []*Profile) (*Profile, error) {\n\tfor _, s := range srcs[1:] {\n\t\tif err := srcs[0].compatible(s); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tvar timeNanos, durationNanos, period int64\n\tvar comments []string\n\tseenComments := map[string]bool{}\n\tvar docURL string\n\tvar defaultSampleType string\n\tfor _, s := range srcs {\n\t\tif timeNanos == 0 || s.TimeNanos < timeNanos {\n\t\t\ttimeNanos = s.TimeNanos\n\t\t}\n\t\tdurationNanos += s.DurationNanos\n\t\tif period == 0 || period < s.Period {\n\t\t\tperiod = s.Period\n\t\t}\n\t\tfor _, c := range s.Comments {\n\t\t\tif seen := seenComments[c]; !seen {\n\t\t\t\tcomments = append(comments, c)\n\t\t\t\tseenComments[c] = true\n\t\t\t}\n\t\t}\n\t\tif defaultSampleType == \"\" {\n\t\t\tdefaultSampleType = s.DefaultSampleType\n\t\t}\n\t\tif docURL == \"\" {\n\t\t\tdocURL = s.DocURL\n\t\t}\n\t}\n\n\tp := &Profile{\n\t\tSampleType: make([]*ValueType, len(srcs[0].SampleType)),\n\n\t\tDropFrames: srcs[0].DropFrames,\n\t\tKeepFrames: srcs[0].KeepFrames,\n\n\t\tTimeNanos:     timeNanos,\n\t\tDurationNanos: durationNanos,\n\t\tPeriodType:    srcs[0].PeriodType,\n\t\tPeriod:        period,\n\n\t\tComments:          comments,\n\t\tDefaultSampleType: defaultSampleType,\n\t\tDocURL:            docURL,\n\t}\n\tcopy(p.SampleType, srcs[0].SampleType)\n\treturn p, nil\n}\n\n// compatible determines if two profiles can be compared/merged.\n// returns nil if the profiles are compatible; otherwise an error with\n// details on the incompatibility.\nfunc (p *Profile) compatible(pb *Profile) error {\n\tif !equalValueType(p.PeriodType, pb.PeriodType) {\n\t\treturn fmt.Errorf(\"incompatible period types %v and %v\", p.PeriodType, pb.PeriodType)\n\t}\n\n\tif len(p.SampleType) != len(pb.SampleType) {\n\t\treturn fmt.Errorf(\"incompatible sample types %v and %v\", p.SampleType, pb.SampleType)\n\t}\n\n\tfor i := range p.SampleType {\n\t\tif !equalValueType(p.SampleType[i], pb.SampleType[i]) {\n\t\t\treturn fmt.Errorf(\"incompatible sample types %v and %v\", p.SampleType, pb.SampleType)\n\t\t}\n\t}\n\treturn nil\n}\n\n// equalValueType returns true if the two value types are semantically\n// equal. It ignores the internal fields used during encode/decode.\nfunc equalValueType(st1, st2 *ValueType) bool {\n\treturn st1.Type == st2.Type && st1.Unit == st2.Unit\n}\n\n// locationIDMap is like a map[uint64]*Location, but provides efficiency for\n// ids that are densely numbered, which is often the case.\ntype locationIDMap struct {\n\tdense  []*Location          // indexed by id for id < len(dense)\n\tsparse map[uint64]*Location // indexed by id for id >= len(dense)\n}\n\nfunc makeLocationIDMap(n int) locationIDMap {\n\treturn locationIDMap{\n\t\tdense:  make([]*Location, n),\n\t\tsparse: map[uint64]*Location{},\n\t}\n}\n\nfunc (lm locationIDMap) get(id uint64) *Location {\n\tif id < uint64(len(lm.dense)) {\n\t\treturn lm.dense[int(id)]\n\t}\n\treturn lm.sparse[id]\n}\n\nfunc (lm locationIDMap) set(id uint64, loc *Location) {\n\tif id < uint64(len(lm.dense)) {\n\t\tlm.dense[id] = loc\n\t\treturn\n\t}\n\tlm.sparse[id] = loc\n}\n\n// CompatibilizeSampleTypes makes profiles compatible to be compared/merged. It\n// keeps sample types that appear in all profiles only and drops/reorders the\n// sample types as necessary.\n//\n// In the case of sample types order is not the same for given profiles the\n// order is derived from the first profile.\n//\n// Profiles are modified in-place.\n//\n// It returns an error if the sample type's intersection is empty.\nfunc CompatibilizeSampleTypes(ps []*Profile) error {\n\tsTypes := commonSampleTypes(ps)\n\tif len(sTypes) == 0 {\n\t\treturn fmt.Errorf(\"profiles have empty common sample type list\")\n\t}\n\tfor _, p := range ps {\n\t\tif err := compatibilizeSampleTypes(p, sTypes); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// commonSampleTypes returns sample types that appear in all profiles in the\n// order how they ordered in the first profile.\nfunc commonSampleTypes(ps []*Profile) []string {\n\tif len(ps) == 0 {\n\t\treturn nil\n\t}\n\tsTypes := map[string]int{}\n\tfor _, p := range ps {\n\t\tfor _, st := range p.SampleType {\n\t\t\tsTypes[st.Type]++\n\t\t}\n\t}\n\tvar res []string\n\tfor _, st := range ps[0].SampleType {\n\t\tif sTypes[st.Type] == len(ps) {\n\t\t\tres = append(res, st.Type)\n\t\t}\n\t}\n\treturn res\n}\n\n// compatibilizeSampleTypes drops sample types that are not present in sTypes\n// list and reorder them if needed.\n//\n// It sets DefaultSampleType to sType[0] if it is not in sType list.\n//\n// It assumes that all sample types from the sTypes list are present in the\n// given profile otherwise it returns an error.\nfunc compatibilizeSampleTypes(p *Profile, sTypes []string) error {\n\tif len(sTypes) == 0 {\n\t\treturn fmt.Errorf(\"sample type list is empty\")\n\t}\n\tdefaultSampleType := sTypes[0]\n\treMap, needToModify := make([]int, len(sTypes)), false\n\tfor i, st := range sTypes {\n\t\tif st == p.DefaultSampleType {\n\t\t\tdefaultSampleType = p.DefaultSampleType\n\t\t}\n\t\tidx := searchValueType(p.SampleType, st)\n\t\tif idx < 0 {\n\t\t\treturn fmt.Errorf(\"%q sample type is not found in profile\", st)\n\t\t}\n\t\treMap[i] = idx\n\t\tif idx != i {\n\t\t\tneedToModify = true\n\t\t}\n\t}\n\tif !needToModify && len(sTypes) == len(p.SampleType) {\n\t\treturn nil\n\t}\n\tp.DefaultSampleType = defaultSampleType\n\toldSampleTypes := p.SampleType\n\tp.SampleType = make([]*ValueType, len(sTypes))\n\tfor i, idx := range reMap {\n\t\tp.SampleType[i] = oldSampleTypes[idx]\n\t}\n\tvalues := make([]int64, len(sTypes))\n\tfor _, s := range p.Sample {\n\t\tfor i, idx := range reMap {\n\t\t\tvalues[i] = s.Value[idx]\n\t\t}\n\t\ts.Value = s.Value[:len(values)]\n\t\tcopy(s.Value, values)\n\t}\n\treturn nil\n}\n\nfunc searchValueType(vts []*ValueType, s string) int {\n\tfor i, vt := range vts {\n\t\tif vt.Type == s {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n"
  },
  {
    "path": "profile/merge_test.go",
    "content": "// Copyright 2018 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n)\n\nfunc TestMapMapping(t *testing.T) {\n\tpm := &profileMerger{\n\t\tp:            &Profile{},\n\t\tmappings:     make(map[mappingKey]*Mapping),\n\t\tmappingsByID: make(map[uint64]mapInfo),\n\t}\n\tfor _, tc := range []struct {\n\t\tdesc       string\n\t\tm1         Mapping\n\t\tm2         Mapping\n\t\twantMerged bool\n\t}{\n\t\t{\n\t\t\tdesc: \"same file name\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:   1,\n\t\t\t\tFile: \"test-file-1\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:   2,\n\t\t\t\tFile: \"test-file-1\",\n\t\t\t},\n\t\t\twantMerged: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"same build ID\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:      3,\n\t\t\t\tBuildID: \"test-build-id-1\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:      4,\n\t\t\t\tBuildID: \"test-build-id-1\",\n\t\t\t},\n\t\t\twantMerged: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"same fake mapping\",\n\t\t\tm1: Mapping{\n\t\t\t\tID: 5,\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID: 6,\n\t\t\t},\n\t\t\twantMerged: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"different start\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:      7,\n\t\t\t\tStart:   0x1000,\n\t\t\t\tLimit:   0x2000,\n\t\t\t\tBuildID: \"test-build-id-2\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:      8,\n\t\t\t\tStart:   0x3000,\n\t\t\t\tLimit:   0x4000,\n\t\t\t\tBuildID: \"test-build-id-2\",\n\t\t\t},\n\t\t\twantMerged: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"different file name\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:   9,\n\t\t\t\tFile: \"test-file-2\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:   10,\n\t\t\t\tFile: \"test-file-3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"different build id\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:      11,\n\t\t\t\tBuildID: \"test-build-id-3\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:      12,\n\t\t\t\tBuildID: \"test-build-id-4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"different size\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:      13,\n\t\t\t\tStart:   0x1000,\n\t\t\t\tLimit:   0x3000,\n\t\t\t\tBuildID: \"test-build-id-5\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:      14,\n\t\t\t\tStart:   0x1000,\n\t\t\t\tLimit:   0x5000,\n\t\t\t\tBuildID: \"test-build-id-5\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"different offset\",\n\t\t\tm1: Mapping{\n\t\t\t\tID:      15,\n\t\t\t\tOffset:  1,\n\t\t\t\tBuildID: \"test-build-id-6\",\n\t\t\t},\n\t\t\tm2: Mapping{\n\t\t\t\tID:      16,\n\t\t\t\tOffset:  2,\n\t\t\t\tBuildID: \"test-build-id-6\",\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tinfo1 := pm.mapMapping(&tc.m1)\n\t\t\tinfo2 := pm.mapMapping(&tc.m2)\n\t\t\tgotM1, gotM2 := *info1.m, *info2.m\n\n\t\t\twantM1 := tc.m1\n\t\t\twantM1.ID = gotM1.ID\n\t\t\tif gotM1 != wantM1 {\n\t\t\t\tt.Errorf(\"first mapping got %v, want %v\", gotM1, wantM1)\n\t\t\t}\n\n\t\t\tif tc.wantMerged {\n\t\t\t\tif gotM1 != gotM2 {\n\t\t\t\t\tt.Errorf(\"first mapping got %v, second mapping got %v, want equal\", gotM1, gotM2)\n\t\t\t\t}\n\t\t\t\tif info1.offset != 0 {\n\t\t\t\t\tt.Errorf(\"first mapping info got offset %d, want 0\", info1.offset)\n\t\t\t\t}\n\t\t\t\tif wantOffset := int64(tc.m1.Start) - int64(tc.m2.Start); wantOffset != info2.offset {\n\t\t\t\t\tt.Errorf(\"second mapping info got offset %d, want %d\", info2.offset, wantOffset)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif gotM1.ID == gotM2.ID {\n\t\t\t\t\tt.Errorf(\"first mapping got %v, second mapping got %v, want different IDs\", gotM1, gotM2)\n\t\t\t\t}\n\t\t\t\twantM2 := tc.m2\n\t\t\t\twantM2.ID = gotM2.ID\n\t\t\t\tif gotM2 != wantM2 {\n\t\t\t\t\tt.Errorf(\"second mapping got %v, want %v\", gotM2, wantM2)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLocationIDMap(t *testing.T) {\n\tids := []uint64{1, 2, 5, 9, 10, 11, 100, 1000, 1000000}\n\tmissing := []uint64{3, 4, 200}\n\n\t// Populate the map,.\n\tidmap := makeLocationIDMap(10)\n\tfor _, id := range ids {\n\t\tloc := &Location{ID: id}\n\t\tidmap.set(id, loc)\n\t}\n\n\t// Check ids that should be present in the map.\n\tfor _, id := range ids {\n\t\tloc := idmap.get(id)\n\t\tif loc == nil {\n\t\t\tt.Errorf(\"No location found for %d\", id)\n\t\t} else if loc.ID != id {\n\t\t\tt.Errorf(\"Wrong location %d found for %d\", loc.ID, id)\n\t\t}\n\t}\n\n\t// Check ids that should not be present in the map.\n\tfor _, id := range missing {\n\t\tloc := idmap.get(id)\n\t\tif loc != nil {\n\t\t\tt.Errorf(\"Unexpected location %d found for %d\", loc.ID, id)\n\t\t}\n\t}\n}\n\nfunc BenchmarkMerge(b *testing.B) {\n\tdata := proftest.LargeProfile(b)\n\tfor n := 1; n <= 2; n++ { // Merge either 1 or 2 instances.\n\t\tb.Run(fmt.Sprintf(\"%d\", n), func(b *testing.B) {\n\t\t\tlist := make([]*Profile, n)\n\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\tprof, err := Parse(bytes.NewBuffer(data))\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t\tlist[i] = prof\n\t\t\t}\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t_, err := Merge(list)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCompatibilizeSampleTypes(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc      string\n\t\tps        []*Profile\n\t\twant      []*Profile\n\t\twantError bool\n\t}{\n\t\t{\n\t\t\tdesc: \"drop first sample types\",\n\t\t\tps: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"delete1\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"delete1\", Unit: \"Unit1\"},\n\t\t\t\t\t\t{Type: \"delete2\", Unit: \"Unit2\"},\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3, 4, 5}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30, 40, 50}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep1\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep1\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{3, 4, 5}},\n\t\t\t\t\t\t{Value: []int64{30, 40, 50}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep1\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"drop last sample types\",\n\t\t\tps: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"delete2\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t\t{Type: \"delete1\", Unit: \"Unit1\"},\n\t\t\t\t\t\t{Type: \"delete2\", Unit: \"Unit2\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3, 4, 5}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30, 40, 50}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep2\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep1\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep2\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"drop sample types and reorder\",\n\t\t\tps: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep3\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"delete1\", Unit: \"Unit1\"},\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"delete2\", Unit: \"Unit2\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3, 4, 5}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30, 40, 50}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep2\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{1, 2, 3}},\n\t\t\t\t\t\t{Value: []int64{10, 20, 30}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep3\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{2, 4, 5}},\n\t\t\t\t\t\t{Value: []int64{20, 40, 50}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDefaultSampleType: \"keep2\",\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit3\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t\tSample: []*Sample{\n\t\t\t\t\t\t{Value: []int64{3, 2, 1}},\n\t\t\t\t\t\t{Value: []int64{30, 20, 10}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"empty common types\",\n\t\t\tps: []*Profile{\n\t\t\t\t{\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep1\", Unit: \"Unit1\"},\n\t\t\t\t\t\t{Type: \"keep2\", Unit: \"Unit2\"},\n\t\t\t\t\t\t{Type: \"keep3\", Unit: \"Unit3\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSampleType: []*ValueType{\n\t\t\t\t\t\t{Type: \"keep4\", Unit: \"Unit4\"},\n\t\t\t\t\t\t{Type: \"keep5\", Unit: \"Unit5\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantError: true,\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\terr := CompatibilizeSampleTypes(tc.ps)\n\t\t\tif (err != nil) != tc.wantError {\n\t\t\t\tt.Fatalf(\"CompatibilizeSampleTypes() returned error: %v, want any error=%t\", err, tc.wantError)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := 0; i < len(tc.want); i++ {\n\t\t\t\tgotStr := tc.ps[i].String()\n\t\t\t\twantStr := tc.want[i].String()\n\t\t\t\tif gotStr != wantStr {\n\t\t\t\t\td, err := proftest.Diff([]byte(wantStr), []byte(gotStr))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to get diff: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"CompatibilizeSampleTypes(): profile[%d] got diff (-want +got)\\n%s\", i, string(d))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDocURLMerge(t *testing.T) {\n\tconst url1 = \"http://example.com/url1\"\n\tconst url2 = \"http://example.com/url2\"\n\ttype testCase struct {\n\t\tname     string\n\t\tprofiles []*Profile\n\t\twant     string\n\t}\n\tprofile := func(url string) *Profile {\n\t\treturn &Profile{\n\t\t\tPeriodType: &ValueType{Type: \"cpu\", Unit: \"seconds\"},\n\t\t\tDocURL:     url,\n\t\t}\n\t}\n\tfor _, test := range []testCase{\n\t\t{\n\t\t\tname: \"nolinks\",\n\t\t\tprofiles: []*Profile{\n\t\t\t\tprofile(\"\"),\n\t\t\t\tprofile(\"\"),\n\t\t\t},\n\t\t\twant: \"\",\n\t\t},\n\t\t{\n\t\t\tname: \"single\",\n\t\t\tprofiles: []*Profile{\n\t\t\t\tprofile(url1),\n\t\t\t},\n\t\t\twant: url1,\n\t\t},\n\t\t{\n\t\t\tname: \"mix\",\n\t\t\tprofiles: []*Profile{\n\t\t\t\tprofile(\"\"),\n\t\t\t\tprofile(url1),\n\t\t\t},\n\t\t\twant: url1,\n\t\t},\n\t\t{\n\t\t\tname: \"different\",\n\t\t\tprofiles: []*Profile{\n\t\t\t\tprofile(url1),\n\t\t\t\tprofile(url2),\n\t\t\t},\n\t\t\twant: url1,\n\t\t},\n\t} {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmerged, err := combineHeaders(test.profiles)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tgot := merged.DocURL\n\t\t\tif !reflect.DeepEqual(test.want, got) {\n\t\t\t\tt.Errorf(\"unexpected links; want: %#v, got: %#v\", test.want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "profile/profile.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package profile provides a representation of profile.proto and\n// methods to encode/decode profiles in this format.\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Profile is an in-memory representation of profile.proto.\ntype Profile struct {\n\tSampleType        []*ValueType\n\tDefaultSampleType string\n\tSample            []*Sample\n\tMapping           []*Mapping\n\tLocation          []*Location\n\tFunction          []*Function\n\tComments          []string\n\tDocURL            string\n\n\tDropFrames string\n\tKeepFrames string\n\n\tTimeNanos     int64\n\tDurationNanos int64\n\tPeriodType    *ValueType\n\tPeriod        int64\n\n\t// The following fields are modified during encoding and copying,\n\t// so are protected by a Mutex.\n\tencodeMu sync.Mutex\n\n\tcommentX           []int64\n\tdocURLX            int64\n\tdropFramesX        int64\n\tkeepFramesX        int64\n\tstringTable        []string\n\tdefaultSampleTypeX int64\n}\n\n// ValueType corresponds to Profile.ValueType\ntype ValueType struct {\n\tType string // cpu, wall, inuse_space, etc\n\tUnit string // seconds, nanoseconds, bytes, etc\n\n\ttypeX int64\n\tunitX int64\n}\n\n// Sample corresponds to Profile.Sample\ntype Sample struct {\n\tLocation []*Location\n\tValue    []int64\n\t// Label is a per-label-key map to values for string labels.\n\t//\n\t// In general, having multiple values for the given label key is strongly\n\t// discouraged - see docs for the sample label field in profile.proto.  The\n\t// main reason this unlikely state is tracked here is to make the\n\t// decoding->encoding roundtrip not lossy. But we expect that the value\n\t// slices present in this map are always of length 1.\n\tLabel map[string][]string\n\t// NumLabel is a per-label-key map to values for numeric labels. See a note\n\t// above on handling multiple values for a label.\n\tNumLabel map[string][]int64\n\t// NumUnit is a per-label-key map to the unit names of corresponding numeric\n\t// label values. The unit info may be missing even if the label is in\n\t// NumLabel, see the docs in profile.proto for details. When the value is\n\t// slice is present and not nil, its length must be equal to the length of\n\t// the corresponding value slice in NumLabel.\n\tNumUnit map[string][]string\n\n\tlocationIDX []uint64\n\tlabelX      []label\n}\n\n// label corresponds to Profile.Label\ntype label struct {\n\tkeyX int64\n\t// Exactly one of the two following values must be set\n\tstrX int64\n\tnumX int64 // Integer value for this label\n\t// can be set if numX has value\n\tunitX int64\n}\n\n// Mapping corresponds to Profile.Mapping\ntype Mapping struct {\n\tID              uint64\n\tStart           uint64\n\tLimit           uint64\n\tOffset          uint64\n\tFile            string\n\tBuildID         string\n\tHasFunctions    bool\n\tHasFilenames    bool\n\tHasLineNumbers  bool\n\tHasInlineFrames bool\n\n\tfileX    int64\n\tbuildIDX int64\n\n\t// Name of the kernel relocation symbol (\"_text\" or \"_stext\"), extracted from File.\n\t// For linux kernel mappings generated by some tools, correct symbolization depends\n\t// on knowing which of the two possible relocation symbols was used for `Start`.\n\t// This is given to us as a suffix in `File` (e.g. \"[kernel.kallsyms]_stext\").\n\t//\n\t// Note, this public field is not persisted in the proto. For the purposes of\n\t// copying / merging / hashing profiles, it is considered subsumed by `File`.\n\tKernelRelocationSymbol string\n}\n\n// Location corresponds to Profile.Location\ntype Location struct {\n\tID       uint64\n\tMapping  *Mapping\n\tAddress  uint64\n\tLine     []Line\n\tIsFolded bool\n\n\tmappingIDX uint64\n}\n\n// Line corresponds to Profile.Line\ntype Line struct {\n\tFunction *Function\n\tLine     int64\n\tColumn   int64\n\n\tfunctionIDX uint64\n}\n\n// Function corresponds to Profile.Function\ntype Function struct {\n\tID         uint64\n\tName       string\n\tSystemName string\n\tFilename   string\n\tStartLine  int64\n\n\tnameX       int64\n\tsystemNameX int64\n\tfilenameX   int64\n}\n\n// Parse parses a profile and checks for its validity. The input\n// may be a gzip-compressed encoded protobuf or one of many legacy\n// profile formats which may be unsupported in the future.\nfunc Parse(r io.Reader) (*Profile, error) {\n\tdata, err := io.ReadAll(r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ParseData(data)\n}\n\n// ParseData parses a profile from a buffer and checks for its\n// validity.\nfunc ParseData(data []byte) (*Profile, error) {\n\tvar p *Profile\n\tvar err error\n\tif len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {\n\t\tgz, err := gzip.NewReader(bytes.NewBuffer(data))\n\t\tif err == nil {\n\t\t\tdata, err = io.ReadAll(gz)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"decompressing profile: %v\", err)\n\t\t}\n\t}\n\tif p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {\n\t\tp, err = parseLegacy(data)\n\t}\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing profile: %v\", err)\n\t}\n\n\tif err := p.CheckValid(); err != nil {\n\t\treturn nil, fmt.Errorf(\"malformed profile: %v\", err)\n\t}\n\treturn p, nil\n}\n\nvar errUnrecognized = fmt.Errorf(\"unrecognized profile format\")\nvar errMalformed = fmt.Errorf(\"malformed profile format\")\nvar errNoData = fmt.Errorf(\"empty input file\")\nvar errConcatProfile = fmt.Errorf(\"concatenated profiles detected\")\n\nfunc parseLegacy(data []byte) (*Profile, error) {\n\tparsers := []func([]byte) (*Profile, error){\n\t\tparseCPU,\n\t\tparseHeap,\n\t\tparseGoCount, // goroutine, threadcreate\n\t\tparseThread,\n\t\tparseContention,\n\t\tparseJavaProfile,\n\t}\n\n\tfor _, parser := range parsers {\n\t\tp, err := parser(data)\n\t\tif err == nil {\n\t\t\tp.addLegacyFrameInfo()\n\t\t\treturn p, nil\n\t\t}\n\t\tif err != errUnrecognized {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nil, errUnrecognized\n}\n\n// ParseUncompressed parses an uncompressed protobuf into a profile.\nfunc ParseUncompressed(data []byte) (*Profile, error) {\n\tif len(data) == 0 {\n\t\treturn nil, errNoData\n\t}\n\tp := &Profile{}\n\tif err := unmarshal(data, p); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := p.postDecode(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\nvar libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)\n\n// massageMappings applies heuristic-based changes to the profile\n// mappings to account for quirks of some environments.\nfunc (p *Profile) massageMappings() {\n\t// Merge adjacent regions with matching names, checking that the offsets match\n\tif len(p.Mapping) > 1 {\n\t\tmappings := []*Mapping{p.Mapping[0]}\n\t\tfor _, m := range p.Mapping[1:] {\n\t\t\tlm := mappings[len(mappings)-1]\n\t\t\tif adjacent(lm, m) {\n\t\t\t\tlm.Limit = m.Limit\n\t\t\t\tif m.File != \"\" {\n\t\t\t\t\tlm.File = m.File\n\t\t\t\t}\n\t\t\t\tif m.BuildID != \"\" {\n\t\t\t\t\tlm.BuildID = m.BuildID\n\t\t\t\t}\n\t\t\t\tp.updateLocationMapping(m, lm)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmappings = append(mappings, m)\n\t\t}\n\t\tp.Mapping = mappings\n\t}\n\n\t// Use heuristics to identify main binary and move it to the top of the list of mappings\n\tfor i, m := range p.Mapping {\n\t\tfile := strings.TrimSpace(strings.ReplaceAll(m.File, \"(deleted)\", \"\"))\n\t\tif len(file) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif len(libRx.FindStringSubmatch(file)) > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif file[0] == '[' {\n\t\t\tcontinue\n\t\t}\n\t\t// Swap what we guess is main to position 0.\n\t\tp.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]\n\t\tbreak\n\t}\n\n\t// Keep the mapping IDs neatly sorted\n\tfor i, m := range p.Mapping {\n\t\tm.ID = uint64(i + 1)\n\t}\n}\n\n// adjacent returns whether two mapping entries represent the same\n// mapping that has been split into two. Check that their addresses are adjacent,\n// and if the offsets match, if they are available.\nfunc adjacent(m1, m2 *Mapping) bool {\n\tif m1.File != \"\" && m2.File != \"\" {\n\t\tif m1.File != m2.File {\n\t\t\treturn false\n\t\t}\n\t}\n\tif m1.BuildID != \"\" && m2.BuildID != \"\" {\n\t\tif m1.BuildID != m2.BuildID {\n\t\t\treturn false\n\t\t}\n\t}\n\tif m1.Limit != m2.Start {\n\t\treturn false\n\t}\n\tif m1.Offset != 0 && m2.Offset != 0 {\n\t\toffset := m1.Offset + (m1.Limit - m1.Start)\n\t\tif offset != m2.Offset {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (p *Profile) updateLocationMapping(from, to *Mapping) {\n\tfor _, l := range p.Location {\n\t\tif l.Mapping == from {\n\t\t\tl.Mapping = to\n\t\t}\n\t}\n}\n\nfunc serialize(p *Profile) []byte {\n\tp.encodeMu.Lock()\n\tp.preEncode()\n\tb := marshal(p)\n\tp.encodeMu.Unlock()\n\treturn b\n}\n\n// Write writes the profile as a gzip-compressed marshaled protobuf.\nfunc (p *Profile) Write(w io.Writer) error {\n\tzw := gzip.NewWriter(w)\n\tdefer zw.Close()\n\t_, err := zw.Write(serialize(p))\n\treturn err\n}\n\n// WriteUncompressed writes the profile as a marshaled protobuf.\nfunc (p *Profile) WriteUncompressed(w io.Writer) error {\n\t_, err := w.Write(serialize(p))\n\treturn err\n}\n\n// CheckValid tests whether the profile is valid. Checks include, but are\n// not limited to:\n//   - len(Profile.Sample[n].value) == len(Profile.value_unit)\n//   - Sample.id has a corresponding Profile.Location\nfunc (p *Profile) CheckValid() error {\n\t// Check that sample values are consistent\n\tsampleLen := len(p.SampleType)\n\tif sampleLen == 0 && len(p.Sample) != 0 {\n\t\treturn fmt.Errorf(\"missing sample type information\")\n\t}\n\tfor _, s := range p.Sample {\n\t\tif s == nil {\n\t\t\treturn fmt.Errorf(\"profile has nil sample\")\n\t\t}\n\t\tif len(s.Value) != sampleLen {\n\t\t\treturn fmt.Errorf(\"mismatch: sample has %d values vs. %d types\", len(s.Value), len(p.SampleType))\n\t\t}\n\t\tfor _, l := range s.Location {\n\t\t\tif l == nil {\n\t\t\t\treturn fmt.Errorf(\"sample has nil location\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check that all mappings/locations/functions are in the tables\n\t// Check that there are no duplicate ids\n\tmappings := make(map[uint64]*Mapping, len(p.Mapping))\n\tfor _, m := range p.Mapping {\n\t\tif m == nil {\n\t\t\treturn fmt.Errorf(\"profile has nil mapping\")\n\t\t}\n\t\tif m.ID == 0 {\n\t\t\treturn fmt.Errorf(\"found mapping with reserved ID=0\")\n\t\t}\n\t\tif mappings[m.ID] != nil {\n\t\t\treturn fmt.Errorf(\"multiple mappings with same id: %d\", m.ID)\n\t\t}\n\t\tmappings[m.ID] = m\n\t}\n\tfunctions := make(map[uint64]*Function, len(p.Function))\n\tfor _, f := range p.Function {\n\t\tif f == nil {\n\t\t\treturn fmt.Errorf(\"profile has nil function\")\n\t\t}\n\t\tif f.ID == 0 {\n\t\t\treturn fmt.Errorf(\"found function with reserved ID=0\")\n\t\t}\n\t\tif functions[f.ID] != nil {\n\t\t\treturn fmt.Errorf(\"multiple functions with same id: %d\", f.ID)\n\t\t}\n\t\tfunctions[f.ID] = f\n\t}\n\tlocations := make(map[uint64]*Location, len(p.Location))\n\tfor _, l := range p.Location {\n\t\tif l == nil {\n\t\t\treturn fmt.Errorf(\"profile has nil location\")\n\t\t}\n\t\tif l.ID == 0 {\n\t\t\treturn fmt.Errorf(\"found location with reserved id=0\")\n\t\t}\n\t\tif locations[l.ID] != nil {\n\t\t\treturn fmt.Errorf(\"multiple locations with same id: %d\", l.ID)\n\t\t}\n\t\tlocations[l.ID] = l\n\t\tif m := l.Mapping; m != nil {\n\t\t\tif m.ID == 0 || mappings[m.ID] != m {\n\t\t\t\treturn fmt.Errorf(\"inconsistent mapping %p: %d\", m, m.ID)\n\t\t\t}\n\t\t}\n\t\tfor _, ln := range l.Line {\n\t\t\tf := ln.Function\n\t\t\tif f == nil {\n\t\t\t\treturn fmt.Errorf(\"location id: %d has a line with nil function\", l.ID)\n\t\t\t}\n\t\t\tif f.ID == 0 || functions[f.ID] != f {\n\t\t\t\treturn fmt.Errorf(\"inconsistent function %p: %d\", f, f.ID)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Aggregate merges the locations in the profile into equivalence\n// classes preserving the request attributes. It also updates the\n// samples to point to the merged locations.\nfunc (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, columnnumber, address bool) error {\n\tfor _, m := range p.Mapping {\n\t\tm.HasInlineFrames = m.HasInlineFrames && inlineFrame\n\t\tm.HasFunctions = m.HasFunctions && function\n\t\tm.HasFilenames = m.HasFilenames && filename\n\t\tm.HasLineNumbers = m.HasLineNumbers && linenumber\n\t}\n\n\t// Aggregate functions\n\tif !function || !filename {\n\t\tfor _, f := range p.Function {\n\t\t\tif !function {\n\t\t\t\tf.Name = \"\"\n\t\t\t\tf.SystemName = \"\"\n\t\t\t}\n\t\t\tif !filename {\n\t\t\t\tf.Filename = \"\"\n\t\t\t}\n\t\t}\n\t}\n\n\t// Aggregate locations\n\tif !inlineFrame || !address || !linenumber || !columnnumber {\n\t\tfor _, l := range p.Location {\n\t\t\tif !inlineFrame && len(l.Line) > 1 {\n\t\t\t\tl.Line = l.Line[len(l.Line)-1:]\n\t\t\t}\n\t\t\tif !linenumber {\n\t\t\t\tfor i := range l.Line {\n\t\t\t\t\tl.Line[i].Line = 0\n\t\t\t\t\tl.Line[i].Column = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !columnnumber {\n\t\t\t\tfor i := range l.Line {\n\t\t\t\t\tl.Line[i].Column = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !address {\n\t\t\t\tl.Address = 0\n\t\t\t}\n\t\t}\n\t}\n\n\treturn p.CheckValid()\n}\n\n// NumLabelUnits returns a map of numeric label keys to the units\n// associated with those keys and a map of those keys to any units\n// that were encountered but not used.\n// Unit for a given key is the first encountered unit for that key. If multiple\n// units are encountered for values paired with a particular key, then the first\n// unit encountered is used and all other units are returned in sorted order\n// in map of ignored units.\n// If no units are encountered for a particular key, the unit is then inferred\n// based on the key.\nfunc (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {\n\tnumLabelUnits := map[string]string{}\n\tignoredUnits := map[string]map[string]bool{}\n\tencounteredKeys := map[string]bool{}\n\n\t// Determine units based on numeric tags for each sample.\n\tfor _, s := range p.Sample {\n\t\tfor k := range s.NumLabel {\n\t\t\tencounteredKeys[k] = true\n\t\t\tfor _, unit := range s.NumUnit[k] {\n\t\t\t\tif unit == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif wantUnit, ok := numLabelUnits[k]; !ok {\n\t\t\t\t\tnumLabelUnits[k] = unit\n\t\t\t\t} else if wantUnit != unit {\n\t\t\t\t\tif v, ok := ignoredUnits[k]; ok {\n\t\t\t\t\t\tv[unit] = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tignoredUnits[k] = map[string]bool{unit: true}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Infer units for keys without any units associated with\n\t// numeric tag values.\n\tfor key := range encounteredKeys {\n\t\tunit := numLabelUnits[key]\n\t\tif unit == \"\" {\n\t\t\tswitch key {\n\t\t\tcase \"alignment\", \"request\":\n\t\t\t\tnumLabelUnits[key] = \"bytes\"\n\t\t\tdefault:\n\t\t\t\tnumLabelUnits[key] = key\n\t\t\t}\n\t\t}\n\t}\n\n\t// Copy ignored units into more readable format\n\tunitsIgnored := make(map[string][]string, len(ignoredUnits))\n\tfor key, values := range ignoredUnits {\n\t\tunits := make([]string, len(values))\n\t\ti := 0\n\t\tfor unit := range values {\n\t\t\tunits[i] = unit\n\t\t\ti++\n\t\t}\n\t\tsort.Strings(units)\n\t\tunitsIgnored[key] = units\n\t}\n\n\treturn numLabelUnits, unitsIgnored\n}\n\n// String dumps a text representation of a profile. Intended mainly\n// for debugging purposes.\nfunc (p *Profile) String() string {\n\tss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))\n\tfor _, c := range p.Comments {\n\t\tss = append(ss, \"Comment: \"+c)\n\t}\n\tif url := p.DocURL; url != \"\" {\n\t\tss = append(ss, fmt.Sprintf(\"Doc: %s\", url))\n\t}\n\tif pt := p.PeriodType; pt != nil {\n\t\tss = append(ss, fmt.Sprintf(\"PeriodType: %s %s\", pt.Type, pt.Unit))\n\t}\n\tss = append(ss, fmt.Sprintf(\"Period: %d\", p.Period))\n\tif p.TimeNanos != 0 {\n\t\tss = append(ss, fmt.Sprintf(\"Time: %v\", time.Unix(0, p.TimeNanos)))\n\t}\n\tif p.DurationNanos != 0 {\n\t\tss = append(ss, fmt.Sprintf(\"Duration: %.4v\", time.Duration(p.DurationNanos)))\n\t}\n\n\tss = append(ss, \"Samples:\")\n\tvar sh1 string\n\tfor _, s := range p.SampleType {\n\t\tdflt := \"\"\n\t\tif s.Type == p.DefaultSampleType {\n\t\t\tdflt = \"[dflt]\"\n\t\t}\n\t\tsh1 = sh1 + fmt.Sprintf(\"%s/%s%s \", s.Type, s.Unit, dflt)\n\t}\n\tss = append(ss, strings.TrimSpace(sh1))\n\tfor _, s := range p.Sample {\n\t\tss = append(ss, s.string())\n\t}\n\n\tss = append(ss, \"Locations\")\n\tfor _, l := range p.Location {\n\t\tss = append(ss, l.string())\n\t}\n\n\tss = append(ss, \"Mappings\")\n\tfor _, m := range p.Mapping {\n\t\tss = append(ss, m.string())\n\t}\n\n\treturn strings.Join(ss, \"\\n\") + \"\\n\"\n}\n\n// string dumps a text representation of a mapping. Intended mainly\n// for debugging purposes.\nfunc (m *Mapping) string() string {\n\tbits := \"\"\n\tif m.HasFunctions {\n\t\tbits = bits + \"[FN]\"\n\t}\n\tif m.HasFilenames {\n\t\tbits = bits + \"[FL]\"\n\t}\n\tif m.HasLineNumbers {\n\t\tbits = bits + \"[LN]\"\n\t}\n\tif m.HasInlineFrames {\n\t\tbits = bits + \"[IN]\"\n\t}\n\treturn fmt.Sprintf(\"%d: %#x/%#x/%#x %s %s %s\",\n\t\tm.ID,\n\t\tm.Start, m.Limit, m.Offset,\n\t\tm.File,\n\t\tm.BuildID,\n\t\tbits)\n}\n\n// string dumps a text representation of a location. Intended mainly\n// for debugging purposes.\nfunc (l *Location) string() string {\n\tss := []string{}\n\tlocStr := fmt.Sprintf(\"%6d: %#x \", l.ID, l.Address)\n\tif m := l.Mapping; m != nil {\n\t\tlocStr = locStr + fmt.Sprintf(\"M=%d \", m.ID)\n\t}\n\tif l.IsFolded {\n\t\tlocStr = locStr + \"[F] \"\n\t}\n\tif len(l.Line) == 0 {\n\t\tss = append(ss, locStr)\n\t}\n\tfor li := range l.Line {\n\t\tlnStr := \"??\"\n\t\tif fn := l.Line[li].Function; fn != nil {\n\t\t\tlnStr = fmt.Sprintf(\"%s %s:%d:%d s=%d\",\n\t\t\t\tfn.Name,\n\t\t\t\tfn.Filename,\n\t\t\t\tl.Line[li].Line,\n\t\t\t\tl.Line[li].Column,\n\t\t\t\tfn.StartLine)\n\t\t\tif fn.Name != fn.SystemName {\n\t\t\t\tlnStr = lnStr + \"(\" + fn.SystemName + \")\"\n\t\t\t}\n\t\t}\n\t\tss = append(ss, locStr+lnStr)\n\t\t// Do not print location details past the first line\n\t\tlocStr = \"             \"\n\t}\n\treturn strings.Join(ss, \"\\n\")\n}\n\n// string dumps a text representation of a sample. Intended mainly\n// for debugging purposes.\nfunc (s *Sample) string() string {\n\tss := []string{}\n\tvar sv string\n\tfor _, v := range s.Value {\n\t\tsv = fmt.Sprintf(\"%s %10d\", sv, v)\n\t}\n\tsv = sv + \": \"\n\tfor _, l := range s.Location {\n\t\tsv = sv + fmt.Sprintf(\"%d \", l.ID)\n\t}\n\tss = append(ss, sv)\n\tconst labelHeader = \"                \"\n\tif len(s.Label) > 0 {\n\t\tss = append(ss, labelHeader+labelsToString(s.Label))\n\t}\n\tif len(s.NumLabel) > 0 {\n\t\tss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))\n\t}\n\treturn strings.Join(ss, \"\\n\")\n}\n\n// labelsToString returns a string representation of a\n// map representing labels.\nfunc labelsToString(labels map[string][]string) string {\n\tls := []string{}\n\tfor k, v := range labels {\n\t\tls = append(ls, fmt.Sprintf(\"%s:%v\", k, v))\n\t}\n\tsort.Strings(ls)\n\treturn strings.Join(ls, \" \")\n}\n\n// numLabelsToString returns a string representation of a map\n// representing numeric labels.\nfunc numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {\n\tls := []string{}\n\tfor k, v := range numLabels {\n\t\tunits := numUnits[k]\n\t\tvar labelString string\n\t\tif len(units) == len(v) {\n\t\t\tvalues := make([]string, len(v))\n\t\t\tfor i, vv := range v {\n\t\t\t\tvalues[i] = fmt.Sprintf(\"%d %s\", vv, units[i])\n\t\t\t}\n\t\t\tlabelString = fmt.Sprintf(\"%s:%v\", k, values)\n\t\t} else {\n\t\t\tlabelString = fmt.Sprintf(\"%s:%v\", k, v)\n\t\t}\n\t\tls = append(ls, labelString)\n\t}\n\tsort.Strings(ls)\n\treturn strings.Join(ls, \" \")\n}\n\n// SetLabel sets the specified key to the specified value for all samples in the\n// profile.\nfunc (p *Profile) SetLabel(key string, value []string) {\n\tfor _, sample := range p.Sample {\n\t\tif sample.Label == nil {\n\t\t\tsample.Label = map[string][]string{key: value}\n\t\t} else {\n\t\t\tsample.Label[key] = value\n\t\t}\n\t}\n}\n\n// RemoveLabel removes all labels associated with the specified key for all\n// samples in the profile.\nfunc (p *Profile) RemoveLabel(key string) {\n\tfor _, sample := range p.Sample {\n\t\tdelete(sample.Label, key)\n\t}\n}\n\n// HasLabel returns true if a sample has a label with indicated key and value.\nfunc (s *Sample) HasLabel(key, value string) bool {\n\treturn slices.Contains(s.Label[key], value)\n}\n\n// SetNumLabel sets the specified key to the specified value for all samples in the\n// profile. \"unit\" is a slice that describes the units that each corresponding member\n// of \"values\" is measured in (e.g. bytes or seconds).  If there is no relevant\n// unit for a given value, that member of \"unit\" should be the empty string.\n// \"unit\" must either have the same length as \"value\", or be nil.\nfunc (p *Profile) SetNumLabel(key string, value []int64, unit []string) {\n\tfor _, sample := range p.Sample {\n\t\tif sample.NumLabel == nil {\n\t\t\tsample.NumLabel = map[string][]int64{key: value}\n\t\t} else {\n\t\t\tsample.NumLabel[key] = value\n\t\t}\n\t\tif sample.NumUnit == nil {\n\t\t\tsample.NumUnit = map[string][]string{key: unit}\n\t\t} else {\n\t\t\tsample.NumUnit[key] = unit\n\t\t}\n\t}\n}\n\n// RemoveNumLabel removes all numerical labels associated with the specified key for all\n// samples in the profile.\nfunc (p *Profile) RemoveNumLabel(key string) {\n\tfor _, sample := range p.Sample {\n\t\tdelete(sample.NumLabel, key)\n\t\tdelete(sample.NumUnit, key)\n\t}\n}\n\n// DiffBaseSample returns true if a sample belongs to the diff base and false\n// otherwise.\nfunc (s *Sample) DiffBaseSample() bool {\n\treturn s.HasLabel(\"pprof::base\", \"true\")\n}\n\n// Scale multiplies all sample values in a profile by a constant and keeps\n// only samples that have at least one non-zero value.\nfunc (p *Profile) Scale(ratio float64) {\n\tif ratio == 1 {\n\t\treturn\n\t}\n\tratios := make([]float64, len(p.SampleType))\n\tfor i := range p.SampleType {\n\t\tratios[i] = ratio\n\t}\n\tp.ScaleN(ratios)\n}\n\n// ScaleN multiplies each sample values in a sample by a different amount\n// and keeps only samples that have at least one non-zero value.\nfunc (p *Profile) ScaleN(ratios []float64) error {\n\tif len(p.SampleType) != len(ratios) {\n\t\treturn fmt.Errorf(\"mismatched scale ratios, got %d, want %d\", len(ratios), len(p.SampleType))\n\t}\n\tallOnes := true\n\tfor _, r := range ratios {\n\t\tif r != 1 {\n\t\t\tallOnes = false\n\t\t\tbreak\n\t\t}\n\t}\n\tif allOnes {\n\t\treturn nil\n\t}\n\tfillIdx := 0\n\tfor _, s := range p.Sample {\n\t\tkeepSample := false\n\t\tfor i, v := range s.Value {\n\t\t\tif ratios[i] != 1 {\n\t\t\t\tval := int64(math.Round(float64(v) * ratios[i]))\n\t\t\t\ts.Value[i] = val\n\t\t\t\tkeepSample = keepSample || val != 0\n\t\t\t}\n\t\t}\n\t\tif keepSample {\n\t\t\tp.Sample[fillIdx] = s\n\t\t\tfillIdx++\n\t\t}\n\t}\n\tp.Sample = p.Sample[:fillIdx]\n\treturn nil\n}\n\n// HasFunctions determines if all locations in this profile have\n// symbolized function information.\nfunc (p *Profile) HasFunctions() bool {\n\tfor _, l := range p.Location {\n\t\tif l.Mapping != nil && !l.Mapping.HasFunctions {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// HasFileLines determines if all locations in this profile have\n// symbolized file and line number information.\nfunc (p *Profile) HasFileLines() bool {\n\tfor _, l := range p.Location {\n\t\tif l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Unsymbolizable returns true if a mapping points to a binary for which\n// locations can't be symbolized in principle, at least now. Examples are\n// \"[vdso]\", \"[vsyscall]\" and some others, see the code.\nfunc (m *Mapping) Unsymbolizable() bool {\n\tname := filepath.Base(m.File)\n\tswitch {\n\tcase strings.HasPrefix(name, \"[\"):\n\tcase strings.HasPrefix(name, \"linux-vdso\"):\n\tcase strings.HasPrefix(m.File, \"/dev/dri/\"):\n\tcase m.File == \"//anon\":\n\tcase m.File == \"\":\n\tcase strings.HasPrefix(m.File, \"/memfd:\"):\n\tdefault:\n\t\treturn false\n\t}\n\treturn true\n}\n\n// Copy makes a fully independent copy of a profile.\nfunc (p *Profile) Copy() *Profile {\n\tpp := &Profile{}\n\tif err := unmarshal(serialize(p), pp); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := pp.postDecode(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn pp\n}\n"
  },
  {
    "path": "profile/profile_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n)\n\nvar update = flag.Bool(\"update\", false, \"Update the golden files\")\n\nfunc TestParse(t *testing.T) {\n\tconst path = \"testdata/\"\n\n\tfor _, source := range []string{\n\t\t\"go.crc32.cpu\",\n\t\t\"go.godoc.thread\",\n\t\t\"gobench.cpu\",\n\t\t\"gobench.heap\",\n\t\t\"cppbench.cpu\",\n\t\t\"cppbench.heap\",\n\t\t\"cppbench.contention\",\n\t\t\"cppbench.growth\",\n\t\t\"cppbench.thread\",\n\t\t\"cppbench.thread.all\",\n\t\t\"cppbench.thread.none\",\n\t\t\"java.cpu\",\n\t\t\"java.heap\",\n\t\t\"java.contention\",\n\t} {\n\t\tinbytes, err := os.ReadFile(filepath.Join(path, source))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tp, err := Parse(bytes.NewBuffer(inbytes))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s: %s\", source, err)\n\t\t}\n\n\t\tjs := p.String()\n\t\tgoldFilename := path + source + \".string\"\n\t\tif *update {\n\t\t\terr := os.WriteFile(goldFilename, []byte(js), 0644)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"failed to update the golden file file %q: %v\", goldFilename, err)\n\t\t\t}\n\t\t}\n\t\tgold, err := os.ReadFile(goldFilename)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s: %v\", source, err)\n\t\t}\n\n\t\tif js != string(gold) {\n\t\t\tt.Errorf(\"diff %s %s\", source, goldFilename)\n\t\t\td, err := proftest.Diff(gold, []byte(js))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"%s: %v\", source, err)\n\t\t\t}\n\t\t\tt.Error(source + \"\\n\" + string(d) + \"\\n\" + \"new profile at:\\n\" + leaveTempfile([]byte(js)))\n\t\t}\n\n\t\t// Reencode and decode.\n\t\tvar bw bytes.Buffer\n\t\tif err := p.Write(&bw); err != nil {\n\t\t\tt.Fatalf(\"%s: %v\", source, err)\n\t\t}\n\t\tif p, err = Parse(&bw); err != nil {\n\t\t\tt.Fatalf(\"%s: %v\", source, err)\n\t\t}\n\t\tjs2 := p.String()\n\t\tif js2 != string(gold) {\n\t\t\td, err := proftest.Diff(gold, []byte(js2))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"%s: %v\", source, err)\n\t\t\t}\n\t\t\tt.Error(source + \"\\n\" + string(d) + \"\\n\" + \"gold:\\n\" + goldFilename +\n\t\t\t\t\"\\nnew profile at:\\n\" + leaveTempfile([]byte(js)))\n\t\t}\n\t}\n}\n\nfunc TestParseError(t *testing.T) {\n\ttestcases := []string{\n\t\t\"\",\n\t\t\"garbage text\",\n\t\t\"\\x1f\\x8b\", // truncated gzip header\n\t\t\"\\x1f\\x8b\\x08\\x08\\xbe\\xe9\\x20\\x58\\x00\\x03\\x65\\x6d\\x70\\x74\\x79\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\", // empty gzipped file\n\t}\n\n\tfor i, input := range testcases {\n\t\t_, err := Parse(strings.NewReader(input))\n\t\tif err == nil {\n\t\t\tt.Errorf(\"got nil, want error for input #%d\", i)\n\t\t}\n\t}\n}\n\nfunc TestParseConcatentated(t *testing.T) {\n\tprof := testProfile1.Copy()\n\t// Write the profile twice to buffer to create concatenated profile.\n\tvar buf bytes.Buffer\n\tprof.Write(&buf)\n\tprof.Write(&buf)\n\t_, err := Parse(&buf)\n\tif err == nil {\n\t\tt.Fatalf(\"got nil, want error\")\n\t}\n\tif got, want := err.Error(), \"parsing profile: concatenated profiles detected\"; want != got {\n\t\tt.Fatalf(\"got error %q, want error %q\", got, want)\n\t}\n}\n\nfunc TestCheckValid(t *testing.T) {\n\tconst path = \"testdata/java.cpu\"\n\n\tinbytes, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read profile file %q: %v\", path, err)\n\t}\n\tp, err := Parse(bytes.NewBuffer(inbytes))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to parse profile %q: %s\", path, err)\n\t}\n\n\tfor _, tc := range []struct {\n\t\tmutateFn func(*Profile)\n\t\twantErr  string\n\t}{\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.SampleType = nil },\n\t\t\twantErr:  \"missing sample type information\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Sample[0] = nil },\n\t\t\twantErr:  \"profile has nil sample\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Sample[0].Value = append(p.Sample[0].Value, 0) },\n\t\t\twantErr:  \"sample has 3 values vs. 2 types\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Sample[0].Location[0] = nil },\n\t\t\twantErr:  \"sample has nil location\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Location[0] = nil },\n\t\t\twantErr:  \"profile has nil location\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Mapping = append(p.Mapping, nil) },\n\t\t\twantErr:  \"profile has nil mapping\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Function[0] = nil },\n\t\t\twantErr:  \"profile has nil function\",\n\t\t},\n\t\t{\n\t\t\tmutateFn: func(p *Profile) { p.Location[0].Line = append(p.Location[0].Line, Line{}) },\n\t\t\twantErr:  \"has a line with nil function\",\n\t\t},\n\t} {\n\t\tt.Run(tc.wantErr, func(t *testing.T) {\n\t\t\tp := p.Copy()\n\t\t\ttc.mutateFn(p)\n\t\t\tif err := p.CheckValid(); err == nil {\n\t\t\t\tt.Errorf(\"CheckValid(): got no error, want error %q\", tc.wantErr)\n\t\t\t} else if !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\tt.Errorf(\"CheckValid(): got error %v, want error %q\", err, tc.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// leaveTempfile leaves |b| in a temporary file on disk and returns the\n// temp filename. This is useful to recover a profile when the test\n// fails.\nfunc leaveTempfile(b []byte) string {\n\tf1, err := os.CreateTemp(\"\", \"profile_test\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif _, err := f1.Write(b); err != nil {\n\t\tpanic(err)\n\t}\n\treturn f1.Name()\n}\n\nconst mainBinary = \"/bin/main\"\n\nvar cpuM = []*Mapping{\n\t{\n\t\tID:              1,\n\t\tStart:           0x10000,\n\t\tLimit:           0x40000,\n\t\tFile:            mainBinary,\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              2,\n\t\tStart:           0x1000,\n\t\tLimit:           0x4000,\n\t\tFile:            \"/lib/lib.so\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              3,\n\t\tStart:           0x4000,\n\t\tLimit:           0x5000,\n\t\tFile:            \"/lib/lib2_c.so.6\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              4,\n\t\tStart:           0x5000,\n\t\tLimit:           0x9000,\n\t\tFile:            \"/lib/lib.so_6 (deleted)\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              5,\n\t\tStart:           0xffff000010080000,\n\t\tLimit:           0xffffffffffffffff,\n\t\tFile:            \"[kernel.kallsyms]_text\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n}\n\nvar cpuF = []*Function{\n\t{ID: 1, Name: \"main\", SystemName: \"main\", Filename: \"main.c\"},\n\t{ID: 2, Name: \"foo\", SystemName: \"foo\", Filename: \"foo.c\"},\n\t{ID: 3, Name: \"foo_caller\", SystemName: \"foo_caller\", Filename: \"foo.c\"},\n}\n\nvar cpuL = []*Location{\n\t{\n\t\tID:      1000,\n\t\tMapping: cpuM[1],\n\t\tAddress: 0x1000,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[0], Line: 1, Column: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      2000,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x2000,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[1], Line: 2, Column: 2},\n\t\t\t{Function: cpuF[2], Line: 1, Column: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      3000,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3000,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[1], Line: 2, Column: 2},\n\t\t\t{Function: cpuF[2], Line: 1, Column: 1},\n\t\t},\n\t},\n\t{\n\t\tID:      3001,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3001,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[2], Line: 2, Column: 2},\n\t\t},\n\t},\n\t{\n\t\tID:      3002,\n\t\tMapping: cpuM[0],\n\t\tAddress: 0x3002,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[2], Line: 3, Column: 3},\n\t\t},\n\t},\n\t// Differs from 1000 due to address and column number.\n\t{\n\t\tID:      1001,\n\t\tMapping: cpuM[1],\n\t\tAddress: 0x1001,\n\t\tLine: []Line{\n\t\t\t{Function: cpuF[0], Line: 1, Column: 2},\n\t\t},\n\t},\n}\n\nvar testProfile1 = &Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000, 1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[1], cpuL[0]},\n\t\t\tValue:    []int64{100, 100},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[2], cpuL[0]},\n\t\t\tValue:    []int64{10, 10},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[3], cpuL[0]},\n\t\t\tValue:    []int64{10000, 10000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[4], cpuL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar testProfile1NoMapping = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000, 1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[1], cpuL[0]},\n\t\t\tValue:    []int64{100, 100},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[2], cpuL[0]},\n\t\t\tValue:    []int64{10, 10},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[3], cpuL[0]},\n\t\t\tValue:    []int64{10000, 10000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[4], cpuL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n}\n\nvar testProfile2 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{70, 1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[1], cpuL[0]},\n\t\t\tValue:    []int64{60, 100},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[2], cpuL[0]},\n\t\t\tValue:    []int64{50, 10},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[3], cpuL[0]},\n\t\t\tValue:    []int64{40, 10000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[4], cpuL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar testProfile3 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar testProfile4 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"key1\": {10},\n\t\t\t\t\"key2\": {30},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"key1\": {\"bytes\"},\n\t\t\t\t\"key2\": {\"bytes\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar testProfile5 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"key1\": {10},\n\t\t\t\t\"key2\": {30},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"key1\": {\"bytes\"},\n\t\t\t\t\"key2\": {\"bytes\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"key1\": {10},\n\t\t\t\t\"key2\": {30},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"key1\": {\"kilobytes\"},\n\t\t\t\t\"key2\": {\"kilobytes\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar testProfile6 = &Profile{\n\tTimeNanos:     10000,\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\tValue:    []int64{1000, 1000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag1\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[1], cpuL[0]},\n\t\t\tValue:    []int64{100, 100},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag2\"},\n\t\t\t\t\"key3\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[2], cpuL[0]},\n\t\t\tValue:    []int64{10, 10},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag3\"},\n\t\t\t\t\"key2\": {\"tag2\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[3], cpuL[0]},\n\t\t\tValue:    []int64{10000, 10000},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[4], cpuL[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag4\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{cpuL[5]},\n\t\t\tValue:    []int64{1, 1},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"tag5\"},\n\t\t\t\t\"key2\": {\"tag1\"},\n\t\t\t},\n\t\t},\n\t},\n\tLocation: cpuL,\n\tFunction: cpuF,\n\tMapping:  cpuM,\n}\n\nvar aggTests = map[string]aggTest{\n\t\"precise\":         {true, true, true, true, true, 6},\n\t\"columns\":         {false, true, true, true, true, 5},\n\t\"fileline\":        {false, true, true, false, true, 4},\n\t\"inline_function\": {false, true, false, false, true, 3},\n\t\"function\":        {false, true, false, false, false, 2},\n}\n\ntype aggTest struct {\n\tprecise, function, fileline, column, inlineFrame bool\n\trows                                             int\n}\n\n// totalSamples is the sum of sample.Value[0] for testProfile6.\nconst totalSamples = int64(11112)\n\nfunc TestAggregation(t *testing.T) {\n\tprof := testProfile6.Copy()\n\tfor _, resolution := range []string{\"precise\", \"columns\", \"fileline\", \"inline_function\", \"function\"} {\n\t\ta := aggTests[resolution]\n\t\tif !a.precise {\n\t\t\tif err := prof.Aggregate(a.inlineFrame, a.function, a.fileline, a.fileline, a.column, false); err != nil {\n\t\t\t\tt.Error(\"aggregating to \" + resolution + \":\" + err.Error())\n\t\t\t}\n\t\t}\n\t\tif err := checkAggregation(prof, &a); err != nil {\n\t\t\tt.Error(\"failed aggregation to \" + resolution + \": \" + err.Error())\n\t\t}\n\t}\n}\n\n// checkAggregation verifies that the profile remained consistent\n// with its aggregation.\nfunc checkAggregation(prof *Profile, a *aggTest) error {\n\t// Check that the total number of samples for the rows was preserved.\n\ttotal := int64(0)\n\n\tsamples := make(map[string]bool)\n\tfor _, sample := range prof.Sample {\n\t\ttb := locationHash(sample)\n\t\tsamples[tb] = true\n\t\ttotal += sample.Value[0]\n\t}\n\n\tif total != totalSamples {\n\t\treturn fmt.Errorf(\"sample total %d, want %d\", total, totalSamples)\n\t}\n\n\t// Check the number of unique sample locations\n\tif a.rows != len(samples) {\n\t\treturn fmt.Errorf(\"number of samples %d, want %d\", len(samples), a.rows)\n\t}\n\n\t// Check that all mappings have the right detail flags.\n\tfor _, m := range prof.Mapping {\n\t\tif m.HasFunctions != a.function {\n\t\t\treturn fmt.Errorf(\"unexpected mapping.HasFunctions %v, want %v\", m.HasFunctions, a.function)\n\t\t}\n\t\tif m.HasFilenames != a.fileline {\n\t\t\treturn fmt.Errorf(\"unexpected mapping.HasFilenames %v, want %v\", m.HasFilenames, a.fileline)\n\t\t}\n\t\tif m.HasLineNumbers != a.fileline {\n\t\t\treturn fmt.Errorf(\"unexpected mapping.HasLineNumbers %v, want %v\", m.HasLineNumbers, a.fileline)\n\t\t}\n\t\tif m.HasInlineFrames != a.inlineFrame {\n\t\t\treturn fmt.Errorf(\"unexpected mapping.HasInlineFrames %v, want %v\", m.HasInlineFrames, a.inlineFrame)\n\t\t}\n\t}\n\n\t// Check that aggregation has removed finer resolution data.\n\tfor _, l := range prof.Location {\n\t\tif !a.inlineFrame && len(l.Line) > 1 {\n\t\t\treturn fmt.Errorf(\"found %d lines on location %d, want 1\", len(l.Line), l.ID)\n\t\t}\n\n\t\tfor _, ln := range l.Line {\n\t\t\tif !a.column && ln.Column != 0 {\n\t\t\t\treturn fmt.Errorf(\"found column %d on location %d, want:0\", ln.Column, l.ID)\n\t\t\t}\n\t\t\tif !a.fileline && (ln.Function.Filename != \"\" || ln.Line != 0) {\n\t\t\t\treturn fmt.Errorf(\"found line %s:%d on location %d, want :0\",\n\t\t\t\t\tln.Function.Filename, ln.Line, l.ID)\n\t\t\t}\n\t\t\tif !a.function && (ln.Function.Name != \"\") {\n\t\t\t\treturn fmt.Errorf(`found file %s location %d, want \"\"`,\n\t\t\t\t\tln.Function.Name, l.ID)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// TestScale tests that Scale() rounds values and drops samples\n// as expected.\nfunc TestScale(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tdesc        string\n\t\tratio       float64\n\t\tp           *Profile\n\t\twantSamples [][]int64\n\t}{\n\t\t{\n\t\t\tdesc:  \"scale by 1\",\n\t\t\tratio: 1.0,\n\t\t\tp:     testProfile1.Copy(),\n\t\t\twantSamples: [][]int64{\n\t\t\t\t{1000, 1000},\n\t\t\t\t{100, 100},\n\t\t\t\t{10, 10},\n\t\t\t\t{10000, 10000},\n\t\t\t\t{1, 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"sample values will be rounded up\",\n\t\t\tratio: .66666,\n\t\t\tp:     testProfile1.Copy(),\n\t\t\twantSamples: [][]int64{\n\t\t\t\t{667, 667},\n\t\t\t\t{67, 67},\n\t\t\t\t{7, 7},\n\t\t\t\t{6667, 6667},\n\t\t\t\t{1, 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:  \"sample values will be rounded down\",\n\t\t\tratio: .33333,\n\t\t\tp:     testProfile1.Copy(),\n\t\t\twantSamples: [][]int64{\n\t\t\t\t{333, 333},\n\t\t\t\t{33, 33},\n\t\t\t\t{3, 3},\n\t\t\t\t{3333, 3333},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc:        \"all sample values will be dropped\",\n\t\t\tratio:       0.00001,\n\t\t\tp:           testProfile1.Copy(),\n\t\t\twantSamples: [][]int64{},\n\t\t},\n\t} {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\ttc.p.Scale(tc.ratio)\n\t\t\tif got, want := len(tc.p.Sample), len(tc.wantSamples); got != want {\n\t\t\t\tt.Fatalf(\"got %d samples, want %d\", got, want)\n\t\t\t}\n\t\t\tfor i, s := range tc.p.Sample {\n\t\t\t\tfor j, got := range s.Value {\n\t\t\t\t\twant := tc.wantSamples[i][j]\n\t\t\t\t\tif want != got {\n\t\t\t\t\t\tt.Errorf(\"For value %d of sample %d, got %d want %d\", j, i, got, want)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestMergeMain tests merge leaves the main binary in place.\nfunc TestMergeMain(t *testing.T) {\n\tprof := testProfile1.Copy()\n\tp1, err := Merge([]*Profile{prof})\n\tif err != nil {\n\t\tt.Fatalf(\"merge error: %v\", err)\n\t}\n\tif cpuM[0].File != p1.Mapping[0].File {\n\t\tt.Errorf(\"want Mapping[0]=%s got %s\", cpuM[0].File, p1.Mapping[0].File)\n\t}\n}\n\nfunc TestMerge(t *testing.T) {\n\t// Aggregate a profile with itself and once again with a factor of\n\t// -2. Should end up with an empty profile (all samples for a\n\t// location should add up to 0).\n\n\tprof := testProfile1.Copy()\n\tprof.Comments = []string{\"comment1\"}\n\tp1, err := Merge([]*Profile{prof, prof})\n\tif err != nil {\n\t\tt.Errorf(\"merge error: %v\", err)\n\t}\n\tprof.Scale(-2)\n\tprof, err = Merge([]*Profile{p1, prof})\n\tif err != nil {\n\t\tt.Errorf(\"merge error: %v\", err)\n\t}\n\tif got, want := len(prof.Comments), 1; got != want {\n\t\tt.Errorf(\"len(prof.Comments) = %d, want %d\", got, want)\n\t}\n\n\t// Use aggregation to merge locations at function granularity.\n\tif err := prof.Aggregate(false, true, false, false, false, false); err != nil {\n\t\tt.Errorf(\"aggregating after merge: %v\", err)\n\t}\n\n\tsamples := make(map[string]int64)\n\tfor _, s := range prof.Sample {\n\t\ttb := locationHash(s)\n\t\tsamples[tb] = samples[tb] + s.Value[0]\n\t}\n\tfor s, v := range samples {\n\t\tif v != 0 {\n\t\t\tt.Errorf(\"nonzero value for sample %s: %d\", s, v)\n\t\t}\n\t}\n}\n\nfunc TestMergeAll(t *testing.T) {\n\t// Aggregate 10 copies of the profile.\n\tprofs := make([]*Profile, 10)\n\tfor i := 0; i < 10; i++ {\n\t\tprofs[i] = testProfile1.Copy()\n\t}\n\tprof, err := Merge(profs)\n\tif err != nil {\n\t\tt.Errorf(\"merge error: %v\", err)\n\t}\n\tsamples := make(map[string]int64)\n\tfor _, s := range prof.Sample {\n\t\ttb := locationHash(s)\n\t\tsamples[tb] = samples[tb] + s.Value[0]\n\t}\n\tfor _, s := range testProfile1.Sample {\n\t\ttb := locationHash(s)\n\t\tif samples[tb] != s.Value[0]*10 {\n\t\t\tt.Errorf(\"merge got wrong value at %s : %d instead of %d\", tb, samples[tb], s.Value[0]*10)\n\t\t}\n\t}\n}\n\nfunc TestIsFoldedMerge(t *testing.T) {\n\ttestProfile1Folded := testProfile1.Copy()\n\ttestProfile1Folded.Location[0].IsFolded = true\n\ttestProfile1Folded.Location[1].IsFolded = true\n\n\tfor _, tc := range []struct {\n\t\tname            string\n\t\tprofs           []*Profile\n\t\twantLocationLen int\n\t}{\n\t\t{\n\t\t\tname:            \"folded and non-folded locations not merged\",\n\t\t\tprofs:           []*Profile{testProfile1.Copy(), testProfile1Folded.Copy()},\n\t\t\twantLocationLen: 7,\n\t\t},\n\t\t{\n\t\t\tname:            \"identical folded locations are merged\",\n\t\t\tprofs:           []*Profile{testProfile1Folded.Copy(), testProfile1Folded.Copy()},\n\t\t\twantLocationLen: 5,\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tprof, err := Merge(tc.profs)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"merge error: %v\", err)\n\t\t\t}\n\t\t\tif got, want := len(prof.Location), tc.wantLocationLen; got != want {\n\t\t\t\tt.Fatalf(\"got %d locations, want %d locations\", got, want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNumLabelMerge(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname          string\n\t\tprofs         []*Profile\n\t\twantNumLabels []map[string][]int64\n\t\twantNumUnits  []map[string][]string\n\t}{\n\t\t{\n\t\t\tname:  \"different label units not merged\",\n\t\t\tprofs: []*Profile{testProfile4.Copy(), testProfile5.Copy()},\n\t\t\twantNumLabels: []map[string][]int64{\n\t\t\t\t{\n\t\t\t\t\t\"key1\": {10},\n\t\t\t\t\t\"key2\": {30},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key1\": {10},\n\t\t\t\t\t\"key2\": {30},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumUnits: []map[string][]string{\n\t\t\t\t{\n\t\t\t\t\t\"key1\": {\"bytes\"},\n\t\t\t\t\t\"key2\": {\"bytes\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"key1\": {\"kilobytes\"},\n\t\t\t\t\t\"key2\": {\"kilobytes\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tprof, err := Merge(tc.profs)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"merge error: %v\", err)\n\t\t\t}\n\n\t\t\tif want, got := len(tc.wantNumLabels), len(prof.Sample); want != got {\n\t\t\t\tt.Fatalf(\"got %d samples, want %d samples\", got, want)\n\t\t\t}\n\t\t\tfor i, wantLabels := range tc.wantNumLabels {\n\t\t\t\tnumLabels := prof.Sample[i].NumLabel\n\t\t\t\tif !reflect.DeepEqual(wantLabels, numLabels) {\n\t\t\t\t\tt.Errorf(\"got numeric labels %v, want %v\", numLabels, wantLabels)\n\t\t\t\t}\n\n\t\t\t\twantUnits := tc.wantNumUnits[i]\n\t\t\t\tnumUnits := prof.Sample[i].NumUnit\n\t\t\t\tif !reflect.DeepEqual(wantUnits, numUnits) {\n\t\t\t\t\tt.Errorf(\"got numeric labels %v, want %v\", numUnits, wantUnits)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEmptyMappingMerge(t *testing.T) {\n\t// Aggregate a profile with itself and once again with a factor of\n\t// -2. Should end up with an empty profile (all samples for a\n\t// location should add up to 0).\n\n\tprof1 := testProfile1.Copy()\n\tprof2 := testProfile1NoMapping.Copy()\n\tp1, err := Merge([]*Profile{prof2, prof1})\n\tif err != nil {\n\t\tt.Errorf(\"merge error: %v\", err)\n\t}\n\tprof2.Scale(-2)\n\tprof, err := Merge([]*Profile{p1, prof2})\n\tif err != nil {\n\t\tt.Errorf(\"merge error: %v\", err)\n\t}\n\n\t// Use aggregation to merge locations at function granularity.\n\tif err := prof.Aggregate(false, true, false, false, false, false); err != nil {\n\t\tt.Errorf(\"aggregating after merge: %v\", err)\n\t}\n\n\tsamples := make(map[string]int64)\n\tfor _, s := range prof.Sample {\n\t\ttb := locationHash(s)\n\t\tsamples[tb] = samples[tb] + s.Value[0]\n\t}\n\tfor s, v := range samples {\n\t\tif v != 0 {\n\t\t\tt.Errorf(\"nonzero value for sample %s: %d\", s, v)\n\t\t}\n\t}\n}\n\nfunc TestNormalizeBySameProfile(t *testing.T) {\n\tpb := testProfile1.Copy()\n\tp := testProfile1.Copy()\n\n\tif err := p.Normalize(pb); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i, s := range p.Sample {\n\t\tfor j, v := range s.Value {\n\t\t\texpectedSampleValue := testProfile1.Sample[i].Value[j]\n\t\t\tif v != expectedSampleValue {\n\t\t\t\tt.Errorf(\"For sample %d, value %d want %d got %d\", i, j, expectedSampleValue, v)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNormalizeByDifferentProfile(t *testing.T) {\n\tp := testProfile1.Copy()\n\tpb := testProfile2.Copy()\n\n\tif err := p.Normalize(pb); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpectedSampleValues := [][]int64{\n\t\t{20, 1000},\n\t\t{2, 100},\n\t\t{199, 10000},\n\t\t{0, 1},\n\t}\n\n\tfor i, s := range p.Sample {\n\t\tfor j, v := range s.Value {\n\t\t\tif v != expectedSampleValues[i][j] {\n\t\t\t\tt.Errorf(\"For sample %d, value %d want %d got %d\", i, j, expectedSampleValues[i][j], v)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNormalizeByMultipleOfSameProfile(t *testing.T) {\n\tpb := testProfile1.Copy()\n\tfor i, s := range pb.Sample {\n\t\tfor j, v := range s.Value {\n\t\t\tpb.Sample[i].Value[j] = 10 * v\n\t\t}\n\t}\n\n\tp := testProfile1.Copy()\n\n\terr := p.Normalize(pb)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i, s := range p.Sample {\n\t\tfor j, v := range s.Value {\n\t\t\texpectedSampleValue := 10 * testProfile1.Sample[i].Value[j]\n\t\t\tif v != expectedSampleValue {\n\t\t\t\tt.Errorf(\"For sample %d, value %d, want %d got %d\", i, j, expectedSampleValue, v)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestNormalizeIncompatibleProfiles(t *testing.T) {\n\tp := testProfile1.Copy()\n\tpb := testProfile3.Copy()\n\n\tif err := p.Normalize(pb); err == nil {\n\t\tt.Errorf(\"Expected an error\")\n\t}\n}\n\n// locationHash constructs a string to use as a hashkey for a sample, based on its locations\nfunc locationHash(s *Sample) string {\n\tvar tb string\n\tfor _, l := range s.Location {\n\t\tfor _, ln := range l.Line {\n\t\t\ttb = tb + fmt.Sprintf(\"%s:%d:%d@%d \", ln.Function.Name, ln.Line, ln.Column, l.Address)\n\t\t}\n\t}\n\treturn tb\n}\n\nfunc TestHasLabel(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc         string\n\t\tlabels       map[string][]string\n\t\tkey          string\n\t\tvalue        string\n\t\twantHasLabel bool\n\t}{\n\t\t{\n\t\t\tdesc:         \"empty label does not have label\",\n\t\t\tlabels:       map[string][]string{},\n\t\t\tkey:          \"key\",\n\t\t\tvalue:        \"value\",\n\t\t\twantHasLabel: false,\n\t\t},\n\t\t{\n\t\t\tdesc:         \"label with one key and value has label\",\n\t\t\tlabels:       map[string][]string{\"key\": {\"value\"}},\n\t\t\tkey:          \"key\",\n\t\t\tvalue:        \"value\",\n\t\t\twantHasLabel: true,\n\t\t},\n\t\t{\n\t\t\tdesc:         \"label with one key and value does not have label\",\n\t\t\tlabels:       map[string][]string{\"key\": {\"value\"}},\n\t\t\tkey:          \"key1\",\n\t\t\tvalue:        \"value1\",\n\t\t\twantHasLabel: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"label with many keys and values has label\",\n\t\t\tlabels: map[string][]string{\n\t\t\t\t\"key1\": {\"value2\", \"value1\"},\n\t\t\t\t\"key2\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t\t\"key3\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t},\n\t\t\tkey:          \"key1\",\n\t\t\tvalue:        \"value1\",\n\t\t\twantHasLabel: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"label with many keys and values does not have label\",\n\t\t\tlabels: map[string][]string{\n\t\t\t\t\"key1\": {\"value2\", \"value1\"},\n\t\t\t\t\"key2\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t\t\"key3\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t},\n\t\t\tkey:          \"key5\",\n\t\t\tvalue:        \"value5\",\n\t\t\twantHasLabel: false,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tsample := &Sample{\n\t\t\t\tLabel: tc.labels,\n\t\t\t}\n\t\t\tif gotHasLabel := sample.HasLabel(tc.key, tc.value); gotHasLabel != tc.wantHasLabel {\n\t\t\t\tt.Errorf(\"sample.HasLabel(%q, %q) got %v, want %v\", tc.key, tc.value, gotHasLabel, tc.wantHasLabel)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDiffBaseSample(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc               string\n\t\tlabels             map[string][]string\n\t\twantDiffBaseSample bool\n\t}{\n\t\t{\n\t\t\tdesc:               \"empty label does not have label\",\n\t\t\tlabels:             map[string][]string{},\n\t\t\twantDiffBaseSample: false,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"label with one key and value, including diff base label\",\n\t\t\tlabels:             map[string][]string{\"pprof::base\": {\"true\"}},\n\t\t\twantDiffBaseSample: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"label with one key and value, not including diff base label\",\n\t\t\tlabels:             map[string][]string{\"key\": {\"value\"}},\n\t\t\twantDiffBaseSample: false,\n\t\t},\n\t\t{\n\t\t\tdesc: \"label with many keys and values, including diff base label\",\n\t\t\tlabels: map[string][]string{\n\t\t\t\t\"pprof::base\": {\"value2\", \"true\"},\n\t\t\t\t\"key2\":        {\"true\", \"value2\", \"value2\"},\n\t\t\t\t\"key3\":        {\"true\", \"value2\", \"value2\"},\n\t\t\t},\n\t\t\twantDiffBaseSample: true,\n\t\t},\n\t\t{\n\t\t\tdesc: \"label with many keys and values, not including diff base label\",\n\t\t\tlabels: map[string][]string{\n\t\t\t\t\"key1\": {\"value2\", \"value1\"},\n\t\t\t\t\"key2\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t\t\"key3\": {\"value1\", \"value2\", \"value2\"},\n\t\t\t},\n\t\t\twantDiffBaseSample: false,\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tsample := &Sample{\n\t\t\t\tLabel: tc.labels,\n\t\t\t}\n\t\t\tif gotHasLabel := sample.DiffBaseSample(); gotHasLabel != tc.wantDiffBaseSample {\n\t\t\t\tt.Errorf(\"sample.DiffBaseSample() got %v, want %v\", gotHasLabel, tc.wantDiffBaseSample)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRemove(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc       string\n\t\tsamples    []*Sample\n\t\tremoveKey  string\n\t\twantLabels []map[string][]string\n\t}{\n\t\t{\n\t\t\tdesc: \"some samples have label already\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value1\", \"value2\", \"value3\"},\n\t\t\t\t\t\t\"key2\": {\"value1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tremoveKey: \"key1\",\n\t\t\twantLabels: []map[string][]string{\n\t\t\t\t{},\n\t\t\t\t{\"key2\": {\"value1\"}},\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tprofile := testProfile1.Copy()\n\t\t\tprofile.Sample = tc.samples\n\t\t\tprofile.RemoveLabel(tc.removeKey)\n\t\t\tif got, want := len(profile.Sample), len(tc.wantLabels); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v samples\", got, want)\n\t\t\t}\n\t\t\tfor i, sample := range profile.Sample {\n\t\t\t\twantLabels := tc.wantLabels[i]\n\t\t\t\tif got, want := len(sample.Label), len(wantLabels); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label keys for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor wantKey, wantValues := range wantLabels {\n\t\t\t\t\tif gotValues, ok := sample.Label[wantKey]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotValues, wantValues) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got values %v, want values %v\", wantKey, gotValues, wantValues)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no values, want %v\", wantKey, wantValues)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetLabel(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc       string\n\t\tsamples    []*Sample\n\t\tsetKey     string\n\t\tsetVal     []string\n\t\twantLabels []map[string][]string\n\t}{\n\t\t{\n\t\t\tdesc: \"some samples have label already\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value1\", \"value2\", \"value3\"},\n\t\t\t\t\t\t\"key2\": {\"value1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey: \"key1\",\n\t\t\tsetVal: []string{\"value1\"},\n\t\t\twantLabels: []map[string][]string{\n\t\t\t\t{\"key1\": {\"value1\"}},\n\t\t\t\t{\"key1\": {\"value1\"}, \"key2\": {\"value1\"}},\n\t\t\t\t{\"key1\": {\"value1\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"no samples have labels\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey: \"key1\",\n\t\t\tsetVal: []string{\"value1\"},\n\t\t\twantLabels: []map[string][]string{\n\t\t\t\t{\"key1\": {\"value1\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"all samples have some labels, but not key being added\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key2\": {\"value2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key3\": {\"value3\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey: \"key1\",\n\t\t\tsetVal: []string{\"value1\"},\n\t\t\twantLabels: []map[string][]string{\n\t\t\t\t{\"key1\": {\"value1\"}, \"key2\": {\"value2\"}},\n\t\t\t\t{\"key1\": {\"value1\"}, \"key3\": {\"value3\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"all samples have key being added\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tLabel: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"value1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey: \"key1\",\n\t\t\tsetVal: []string{\"value1\"},\n\t\t\twantLabels: []map[string][]string{\n\t\t\t\t{\"key1\": {\"value1\"}},\n\t\t\t\t{\"key1\": {\"value1\"}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tprofile := testProfile1.Copy()\n\t\t\tprofile.Sample = tc.samples\n\t\t\tprofile.SetLabel(tc.setKey, tc.setVal)\n\t\t\tif got, want := len(profile.Sample), len(tc.wantLabels); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v samples\", got, want)\n\t\t\t}\n\t\t\tfor i, sample := range profile.Sample {\n\t\t\t\twantLabels := tc.wantLabels[i]\n\t\t\t\tif got, want := len(sample.Label), len(wantLabels); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label keys for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor wantKey, wantValues := range wantLabels {\n\t\t\t\t\tif gotValues, ok := sample.Label[wantKey]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotValues, wantValues) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got values %v, want values %v\", wantKey, gotValues, wantValues)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no values, want %v\", wantKey, wantValues)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetNumLabel(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc       string\n\t\tsamples    []*Sample\n\t\tsetKey     string\n\t\tsetVal     []int64\n\t\tsetUnit    []string\n\t\twantValues []map[string][]int64\n\t\twantUnits  []map[string][]string\n\t}{\n\t\t{\n\t\t\tdesc: \"some samples have label already\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {1, 2, 3},\n\t\t\t\t\t\t\"key2\": {1},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"bytes\", \"bytes\", \"bytes\"},\n\t\t\t\t\t\t\"key2\": {\"gallons\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {2},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"volts\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey:  \"key1\",\n\t\t\tsetVal:  []int64{1},\n\t\t\tsetUnit: []string{\"bytes\"},\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{\"key1\": {1}},\n\t\t\t\t{\"key1\": {1}, \"key2\": {1}},\n\t\t\t\t{\"key1\": {1}},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{\"key1\": {\"bytes\"}},\n\t\t\t\t{\"key1\": {\"bytes\"}, \"key2\": {\"gallons\"}},\n\t\t\t\t{\"key1\": {\"bytes\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"no samples have labels\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey:  \"key1\",\n\t\t\tsetVal:  []int64{1},\n\t\t\tsetUnit: []string{\"bytes\"},\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{\"key1\": {1}},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{\"key1\": {\"bytes\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"all samples have some labels, but not key being added\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key2\": {2},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key2\": {\"joules\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key3\": {3},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key3\": {\"meters\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey:  \"key1\",\n\t\t\tsetVal:  []int64{1},\n\t\t\tsetUnit: []string{\"seconds\"},\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{\"key1\": {1}, \"key2\": {2}},\n\t\t\t\t{\"key1\": {1}, \"key3\": {3}},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{\"key1\": {\"seconds\"}, \"key2\": {\"joules\"}},\n\t\t\t\t{\"key1\": {\"seconds\"}, \"key3\": {\"meters\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"all samples have key being added\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {1},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"exabytes\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {1},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"petabytes\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsetKey:  \"key1\",\n\t\t\tsetVal:  []int64{1, 2},\n\t\t\tsetUnit: []string{\"daltons\", \"\"},\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{\"key1\": {1, 2}},\n\t\t\t\t{\"key1\": {1, 2}},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{\"key1\": {\"daltons\", \"\"}},\n\t\t\t\t{\"key1\": {\"daltons\", \"\"}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tprofile := testProfile1.Copy()\n\t\t\tprofile.Sample = tc.samples\n\t\t\tprofile.SetNumLabel(tc.setKey, tc.setVal, tc.setUnit)\n\t\t\tif got, want := len(profile.Sample), len(tc.wantValues); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v samples\", got, want)\n\t\t\t}\n\t\t\tif got, want := len(profile.Sample), len(tc.wantUnits); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v samples\", got, want)\n\t\t\t}\n\t\t\tfor i, sample := range profile.Sample {\n\t\t\t\twantValues := tc.wantValues[i]\n\t\t\t\tif got, want := len(sample.NumLabel), len(wantValues); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label values for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor key, values := range wantValues {\n\t\t\t\t\tif gotValues, ok := sample.NumLabel[key]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotValues, values) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got values %v, want values %v\", key, gotValues, values)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no values, want %v\", key, values)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\twantUnits := tc.wantUnits[i]\n\t\t\t\tif got, want := len(sample.NumUnit), len(wantUnits); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label units for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor key, units := range wantUnits {\n\t\t\t\t\tif gotUnits, ok := sample.NumUnit[key]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotUnits, units) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got units %v, want units %v\", key, gotUnits, units)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no units, want %v\", key, units)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRemoveNumLabel(t *testing.T) {\n\tvar testcases = []struct {\n\t\tdesc       string\n\t\tsamples    []*Sample\n\t\tremoveKey  string\n\t\twantValues []map[string][]int64\n\t\twantUnits  []map[string][]string\n\t}{\n\t\t{\n\t\t\tdesc: \"some samples have label already\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {1, 2, 3},\n\t\t\t\t\t\t\"key2\": {1},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"foo\", \"bar\", \"baz\"},\n\t\t\t\t\t\t\"key2\": {\"seconds\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key1\": {2},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key1\": {\"seconds\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tremoveKey: \"key1\",\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{},\n\t\t\t\t{\"key2\": {1}},\n\t\t\t\t{},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{},\n\t\t\t\t{\"key2\": {\"seconds\"}},\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"no samples have label\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t},\n\t\t\t},\n\t\t\tremoveKey: \"key1\",\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tdesc: \"all samples have some labels, but not key being removed\",\n\t\t\tsamples: []*Sample{\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key2\": {2},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key2\": {\"terabytes\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tLocation: []*Location{cpuL[0]},\n\t\t\t\t\tValue:    []int64{1000},\n\t\t\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\t\t\"key3\": {3},\n\t\t\t\t\t},\n\t\t\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\t\t\"key3\": {\"\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tremoveKey: \"key1\",\n\t\t\twantValues: []map[string][]int64{\n\t\t\t\t{\"key2\": {2}},\n\t\t\t\t{\"key3\": {3}},\n\t\t\t},\n\t\t\twantUnits: []map[string][]string{\n\t\t\t\t{\"key2\": {\"terabytes\"}},\n\t\t\t\t{\"key3\": {\"\"}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tprofile := testProfile1.Copy()\n\t\t\tprofile.Sample = tc.samples\n\t\t\tprofile.RemoveNumLabel(tc.removeKey)\n\t\t\tif got, want := len(profile.Sample), len(tc.wantValues); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v values\", got, want)\n\t\t\t}\n\t\t\tif got, want := len(profile.Sample), len(tc.wantUnits); got != want {\n\t\t\t\tt.Fatalf(\"got %v samples, want %v units\", got, want)\n\t\t\t}\n\t\t\tfor i, sample := range profile.Sample {\n\t\t\t\twantValues := tc.wantValues[i]\n\t\t\t\tif got, want := len(sample.NumLabel), len(wantValues); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label values for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor key, values := range wantValues {\n\t\t\t\t\tif gotValues, ok := sample.NumLabel[key]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotValues, values) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got values %v, want values %v\", key, gotValues, values)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no values, want %v\", key, values)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twantUnits := tc.wantUnits[i]\n\t\t\t\tif got, want := len(sample.NumLabel), len(wantUnits); got != want {\n\t\t\t\t\tt.Errorf(\"got %v label values for sample %v, want %v\", got, i, want)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor key, units := range wantUnits {\n\t\t\t\t\tif gotUnits, ok := sample.NumUnit[key]; ok {\n\t\t\t\t\t\tif !reflect.DeepEqual(gotUnits, units) {\n\t\t\t\t\t\t\tt.Errorf(\"for key %s, got units %v, want units %v\", key, gotUnits, units)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"for key %s got no units, want %v\", key, units)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNumLabelUnits(t *testing.T) {\n\tvar tagFilterTests = []struct {\n\t\tdesc             string\n\t\ttagVals          []map[string][]int64\n\t\ttagUnits         []map[string][]string\n\t\twantUnits        map[string]string\n\t\twantIgnoredUnits map[string][]string\n\t}{\n\t\t{\n\t\t\t\"One sample, multiple keys, different specified units\",\n\t\t\t[]map[string][]int64{{\"key1\": {131072}, \"key2\": {128}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\"}, \"key2\": {\"kilobytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\", \"key2\": \"kilobytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"One sample, one key with one value, unit specified\",\n\t\t\t[]map[string][]int64{{\"key1\": {8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"One sample, one key with one value, empty unit specified\",\n\t\t\t[]map[string][]int64{{\"key1\": {8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"\"}}},\n\t\t\tmap[string]string{\"key1\": \"key1\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"Key bytes, unit not specified\",\n\t\t\t[]map[string][]int64{{\"bytes\": {8}}},\n\t\t\t[]map[string][]string{nil},\n\t\t\tmap[string]string{\"bytes\": \"bytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"One sample, one key with one value, unit not specified\",\n\t\t\t[]map[string][]int64{{\"kilobytes\": {8}}},\n\t\t\t[]map[string][]string{nil},\n\t\t\tmap[string]string{\"kilobytes\": \"kilobytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"Key request, unit not specified\",\n\t\t\t[]map[string][]int64{{\"request\": {8}}},\n\t\t\t[]map[string][]string{nil},\n\t\t\tmap[string]string{\"request\": \"bytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"Key alignment, unit not specified\",\n\t\t\t[]map[string][]int64{{\"alignment\": {8}}},\n\t\t\t[]map[string][]string{nil},\n\t\t\tmap[string]string{\"alignment\": \"bytes\"},\n\t\t\tmap[string][]string{},\n\t\t},\n\t\t{\n\t\t\t\"One sample, one key with multiple values and two different units\",\n\t\t\t[]map[string][]int64{{\"key1\": {8, 8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\", \"kilobytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tmap[string][]string{\"key1\": {\"kilobytes\"}},\n\t\t},\n\t\t{\n\t\t\t\"One sample, one key with multiple values and three different units\",\n\t\t\t[]map[string][]int64{{\"key1\": {8, 8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\", \"megabytes\", \"kilobytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tmap[string][]string{\"key1\": {\"kilobytes\", \"megabytes\"}},\n\t\t},\n\t\t{\n\t\t\t\"Two samples, one key, different units specified\",\n\t\t\t[]map[string][]int64{{\"key1\": {8}}, {\"key1\": {8}}},\n\t\t\t[]map[string][]string{{\"key1\": {\"bytes\"}}, {\"key1\": {\"kilobytes\"}}},\n\t\t\tmap[string]string{\"key1\": \"bytes\"},\n\t\t\tmap[string][]string{\"key1\": {\"kilobytes\"}},\n\t\t},\n\t\t{\n\t\t\t\"Keys alignment, request, and bytes have units specified\",\n\t\t\t[]map[string][]int64{{\n\t\t\t\t\"alignment\": {8},\n\t\t\t\t\"request\":   {8},\n\t\t\t\t\"bytes\":     {8},\n\t\t\t}},\n\t\t\t[]map[string][]string{{\n\t\t\t\t\"alignment\": {\"seconds\"},\n\t\t\t\t\"request\":   {\"minutes\"},\n\t\t\t\t\"bytes\":     {\"hours\"},\n\t\t\t}},\n\t\t\tmap[string]string{\n\t\t\t\t\"alignment\": \"seconds\",\n\t\t\t\t\"request\":   \"minutes\",\n\t\t\t\t\"bytes\":     \"hours\",\n\t\t\t},\n\t\t\tmap[string][]string{},\n\t\t},\n\t}\n\tfor _, test := range tagFilterTests {\n\t\tp := &Profile{Sample: make([]*Sample, len(test.tagVals))}\n\t\tfor i, numLabel := range test.tagVals {\n\t\t\ts := Sample{\n\t\t\t\tNumLabel: numLabel,\n\t\t\t\tNumUnit:  test.tagUnits[i],\n\t\t\t}\n\t\t\tp.Sample[i] = &s\n\t\t}\n\t\tunits, ignoredUnits := p.NumLabelUnits()\n\t\tif !reflect.DeepEqual(test.wantUnits, units) {\n\t\t\tt.Errorf(\"%s: got %v units, want %v\", test.desc, units, test.wantUnits)\n\t\t}\n\t\tif !reflect.DeepEqual(test.wantIgnoredUnits, ignoredUnits) {\n\t\t\tt.Errorf(\"%s: got %v ignored units, want %v\", test.desc, ignoredUnits, test.wantIgnoredUnits)\n\t\t}\n\t}\n}\n\nfunc TestSetMain(t *testing.T) {\n\ttestProfile1.massageMappings()\n\tif testProfile1.Mapping[0].File != mainBinary {\n\t\tt.Errorf(\"got %s for main\", testProfile1.Mapping[0].File)\n\t}\n}\n\nfunc TestParseKernelRelocation(t *testing.T) {\n\tsrc := testProfile1.Copy()\n\tif src.Mapping[len(src.Mapping)-1].KernelRelocationSymbol != \"_text\" {\n\t\tt.Errorf(\"got %s for Mapping.KernelRelocationSymbol\", src.Mapping[0].KernelRelocationSymbol)\n\t}\n}\n\nfunc TestEncodeDecodeDocURL(t *testing.T) {\n\tinput := testProfile1.Copy()\n\tinput.DocURL = \"http://example.comp/url\"\n\n\t// Encode/decode.\n\tvar buf bytes.Buffer\n\tif err := input.Write(&buf); err != nil {\n\t\tt.Fatal(\"encode: \", err)\n\t}\n\toutput, err := Parse(&buf)\n\tif err != nil {\n\t\tt.Fatal(\"decode: \", err)\n\t}\n\tif want, got := input.String(), output.String(); want != got {\n\t\td, err := proftest.Diff([]byte(want), []byte(got))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Errorf(\"wrong result of encode/decode (-want,+got):\\n%s\\n\", string(d))\n\t}\n}\n\n// parallel runs n copies of fn in parallel.\nfunc parallel(n int, fn func()) {\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\tfor i := 0; i < n; i++ {\n\t\tgo func() {\n\t\t\tfn()\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc TestThreadSafety(t *testing.T) {\n\tsrc := testProfile1.Copy()\n\tparallel(4, func() { src.Copy() })\n\tparallel(4, func() {\n\t\tvar b bytes.Buffer\n\t\tsrc.WriteUncompressed(&b)\n\t})\n\tparallel(4, func() {\n\t\tvar b bytes.Buffer\n\t\tsrc.Write(&b)\n\t})\n}\n\nfunc BenchmarkParse(b *testing.B) {\n\tdata := proftest.LargeProfile(b)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := Parse(bytes.NewBuffer(data))\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkWrite(b *testing.B) {\n\tp, err := Parse(bytes.NewBuffer(proftest.LargeProfile(b)))\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tif err := p.WriteUncompressed(io.Discard); err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestMappingUnsymbolizable(t *testing.T) {\n\ttestcases := []struct {\n\t\tdesc               string\n\t\tfile               string\n\t\twantUnsymbolizable bool\n\t}{\n\t\t{\n\t\t\tdesc:               \"regular file is symbolizable\",\n\t\t\tfile:               \"/usr/bin/program\",\n\t\t\twantUnsymbolizable: false,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"vdso mapping is unsymbolizable\",\n\t\t\tfile:               \"[vdso]\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"vsyscall mapping is unsymbolizable\",\n\t\t\tfile:               \"[vsyscall]\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"kernel mapping is unsymbolizable\",\n\t\t\tfile:               \"[kernel.kallsyms]_text\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"any bracket-prefixed file is unsymbolizable\",\n\t\t\tfile:               \"[some_other_mapping]\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"linux-vdso module is unsymbolizable\",\n\t\t\tfile:               \"/lib/linux-vdso.so.1\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"dri device file is unsymbolizable\",\n\t\t\tfile:               \"/dev/dri/by-id/pci-0000_01_00_0-card0\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"anon mapping is unsymbolizable\",\n\t\t\tfile:               \"//anon\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"empty file is unsymbolizable\",\n\t\t\tfile:               \"\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t\t{\n\t\t\tdesc:               \"memfd file without deleted suffix is unsymbolizable\",\n\t\t\tfile:               \"/memfd:some-memory-file\",\n\t\t\twantUnsymbolizable: true,\n\t\t},\n\t}\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tm := &Mapping{File: tc.file}\n\t\t\tgot := m.Unsymbolizable()\n\t\t\tif got != tc.wantUnsymbolizable {\n\t\t\t\tt.Errorf(\"Mapping.Unsymbolizable() for file %q = %v, want %v\", tc.file, got, tc.wantUnsymbolizable)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "profile/proto.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// This file is a simple protocol buffer encoder and decoder.\n// The format is described at\n// https://developers.google.com/protocol-buffers/docs/encoding\n//\n// A protocol message must implement the message interface:\n//   decoder() []decoder\n//   encode(*buffer)\n//\n// The decode method returns a slice indexed by field number that gives the\n// function to decode that field.\n// The encode method encodes its receiver into the given buffer.\n//\n// The two methods are simple enough to be implemented by hand rather than\n// by using a protocol compiler.\n//\n// See profile.go for examples of messages implementing this interface.\n//\n// There is no support for groups, message sets, or \"has\" bits.\n\npackage profile\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n)\n\ntype buffer struct {\n\tfield    int // field tag\n\ttyp      int // proto wire type code for field\n\tu64      uint64\n\tdata     []byte\n\ttmp      [16]byte\n\ttmpLines []Line // temporary storage used while decoding \"repeated Line\".\n}\n\ntype decoder func(*buffer, message) error\n\ntype message interface {\n\tdecoder() []decoder\n\tencode(*buffer)\n}\n\nfunc marshal(m message) []byte {\n\tvar b buffer\n\tm.encode(&b)\n\treturn b.data\n}\n\nfunc encodeVarint(b *buffer, x uint64) {\n\tfor x >= 128 {\n\t\tb.data = append(b.data, byte(x)|0x80)\n\t\tx >>= 7\n\t}\n\tb.data = append(b.data, byte(x))\n}\n\nfunc encodeLength(b *buffer, tag int, len int) {\n\tencodeVarint(b, uint64(tag)<<3|2)\n\tencodeVarint(b, uint64(len))\n}\n\nfunc encodeUint64(b *buffer, tag int, x uint64) {\n\t// append varint to b.data\n\tencodeVarint(b, uint64(tag)<<3)\n\tencodeVarint(b, x)\n}\n\nfunc encodeUint64s(b *buffer, tag int, x []uint64) {\n\tif len(x) > 2 {\n\t\t// Use packed encoding\n\t\tn1 := len(b.data)\n\t\tfor _, u := range x {\n\t\t\tencodeVarint(b, u)\n\t\t}\n\t\tn2 := len(b.data)\n\t\tencodeLength(b, tag, n2-n1)\n\t\tn3 := len(b.data)\n\t\tcopy(b.tmp[:], b.data[n2:n3])\n\t\tcopy(b.data[n1+(n3-n2):], b.data[n1:n2])\n\t\tcopy(b.data[n1:], b.tmp[:n3-n2])\n\t\treturn\n\t}\n\tfor _, u := range x {\n\t\tencodeUint64(b, tag, u)\n\t}\n}\n\nfunc encodeUint64Opt(b *buffer, tag int, x uint64) {\n\tif x == 0 {\n\t\treturn\n\t}\n\tencodeUint64(b, tag, x)\n}\n\nfunc encodeInt64(b *buffer, tag int, x int64) {\n\tu := uint64(x)\n\tencodeUint64(b, tag, u)\n}\n\nfunc encodeInt64s(b *buffer, tag int, x []int64) {\n\tif len(x) > 2 {\n\t\t// Use packed encoding\n\t\tn1 := len(b.data)\n\t\tfor _, u := range x {\n\t\t\tencodeVarint(b, uint64(u))\n\t\t}\n\t\tn2 := len(b.data)\n\t\tencodeLength(b, tag, n2-n1)\n\t\tn3 := len(b.data)\n\t\tcopy(b.tmp[:], b.data[n2:n3])\n\t\tcopy(b.data[n1+(n3-n2):], b.data[n1:n2])\n\t\tcopy(b.data[n1:], b.tmp[:n3-n2])\n\t\treturn\n\t}\n\tfor _, u := range x {\n\t\tencodeInt64(b, tag, u)\n\t}\n}\n\nfunc encodeInt64Opt(b *buffer, tag int, x int64) {\n\tif x == 0 {\n\t\treturn\n\t}\n\tencodeInt64(b, tag, x)\n}\n\nfunc encodeString(b *buffer, tag int, x string) {\n\tencodeLength(b, tag, len(x))\n\tb.data = append(b.data, x...)\n}\n\nfunc encodeStrings(b *buffer, tag int, x []string) {\n\tfor _, s := range x {\n\t\tencodeString(b, tag, s)\n\t}\n}\n\nfunc encodeBool(b *buffer, tag int, x bool) {\n\tif x {\n\t\tencodeUint64(b, tag, 1)\n\t} else {\n\t\tencodeUint64(b, tag, 0)\n\t}\n}\n\nfunc encodeBoolOpt(b *buffer, tag int, x bool) {\n\tif x {\n\t\tencodeBool(b, tag, x)\n\t}\n}\n\nfunc encodeMessage(b *buffer, tag int, m message) {\n\tn1 := len(b.data)\n\tm.encode(b)\n\tn2 := len(b.data)\n\tencodeLength(b, tag, n2-n1)\n\tn3 := len(b.data)\n\tcopy(b.tmp[:], b.data[n2:n3])\n\tcopy(b.data[n1+(n3-n2):], b.data[n1:n2])\n\tcopy(b.data[n1:], b.tmp[:n3-n2])\n}\n\nfunc unmarshal(data []byte, m message) (err error) {\n\tb := buffer{data: data, typ: 2}\n\treturn decodeMessage(&b, m)\n}\n\nfunc le64(p []byte) uint64 {\n\treturn uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56\n}\n\nfunc le32(p []byte) uint32 {\n\treturn uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24\n}\n\nfunc peekNumVarints(data []byte) (numVarints int) {\n\tfor ; len(data) > 0; numVarints++ {\n\t\tvar err error\n\t\tif _, data, err = decodeVarint(data); err != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn numVarints\n}\n\nfunc decodeVarint(data []byte) (uint64, []byte, error) {\n\tvar u uint64\n\tfor i := 0; ; i++ {\n\t\tif i >= 10 || i >= len(data) {\n\t\t\treturn 0, nil, errors.New(\"bad varint\")\n\t\t}\n\t\tu |= uint64(data[i]&0x7F) << uint(7*i)\n\t\tif data[i]&0x80 == 0 {\n\t\t\treturn u, data[i+1:], nil\n\t\t}\n\t}\n}\n\nfunc decodeField(b *buffer, data []byte) ([]byte, error) {\n\tx, data, err := decodeVarint(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tb.field = int(x >> 3)\n\tb.typ = int(x & 7)\n\tb.data = nil\n\tb.u64 = 0\n\tswitch b.typ {\n\tcase 0:\n\t\tb.u64, data, err = decodeVarint(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase 1:\n\t\tif len(data) < 8 {\n\t\t\treturn nil, errors.New(\"not enough data\")\n\t\t}\n\t\tb.u64 = le64(data[:8])\n\t\tdata = data[8:]\n\tcase 2:\n\t\tvar n uint64\n\t\tn, data, err = decodeVarint(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif n > uint64(len(data)) {\n\t\t\treturn nil, errors.New(\"too much data\")\n\t\t}\n\t\tb.data = data[:n]\n\t\tdata = data[n:]\n\tcase 5:\n\t\tif len(data) < 4 {\n\t\t\treturn nil, errors.New(\"not enough data\")\n\t\t}\n\t\tb.u64 = uint64(le32(data[:4]))\n\t\tdata = data[4:]\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown wire type: %d\", b.typ)\n\t}\n\n\treturn data, nil\n}\n\nfunc checkType(b *buffer, typ int) error {\n\tif b.typ != typ {\n\t\treturn errors.New(\"type mismatch\")\n\t}\n\treturn nil\n}\n\nfunc decodeMessage(b *buffer, m message) error {\n\tif err := checkType(b, 2); err != nil {\n\t\treturn err\n\t}\n\tdec := m.decoder()\n\tdata := b.data\n\tfor len(data) > 0 {\n\t\t// pull varint field# + type\n\t\tvar err error\n\t\tdata, err = decodeField(b, data)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif b.field >= len(dec) || dec[b.field] == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif err := dec[b.field](b, m); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc decodeInt64(b *buffer, x *int64) error {\n\tif err := checkType(b, 0); err != nil {\n\t\treturn err\n\t}\n\t*x = int64(b.u64)\n\treturn nil\n}\n\nfunc decodeInt64s(b *buffer, x *[]int64) error {\n\tif b.typ == 2 {\n\t\t// Packed encoding\n\t\tdataLen := peekNumVarints(b.data)\n\t\t*x = slices.Grow(*x, dataLen)\n\n\t\tdata := b.data\n\t\tfor len(data) > 0 {\n\t\t\tvar u uint64\n\t\t\tvar err error\n\n\t\t\tif u, data, err = decodeVarint(data); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t*x = append(*x, int64(u))\n\t\t}\n\t\treturn nil\n\t}\n\tvar i int64\n\tif err := decodeInt64(b, &i); err != nil {\n\t\treturn err\n\t}\n\t*x = append(*x, i)\n\treturn nil\n}\n\nfunc decodeUint64(b *buffer, x *uint64) error {\n\tif err := checkType(b, 0); err != nil {\n\t\treturn err\n\t}\n\t*x = b.u64\n\treturn nil\n}\n\nfunc decodeUint64s(b *buffer, x *[]uint64) error {\n\tif b.typ == 2 {\n\t\t// Packed encoding\n\t\tdataLen := peekNumVarints(b.data)\n\t\t*x = slices.Grow(*x, dataLen)\n\n\t\tdata := b.data\n\t\tfor len(data) > 0 {\n\t\t\tvar u uint64\n\t\t\tvar err error\n\n\t\t\tif u, data, err = decodeVarint(data); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t*x = append(*x, u)\n\t\t}\n\t\treturn nil\n\t}\n\tvar u uint64\n\tif err := decodeUint64(b, &u); err != nil {\n\t\treturn err\n\t}\n\t*x = append(*x, u)\n\treturn nil\n}\n\nfunc decodeString(b *buffer, x *string) error {\n\tif err := checkType(b, 2); err != nil {\n\t\treturn err\n\t}\n\t*x = string(b.data)\n\treturn nil\n}\n\nfunc decodeStrings(b *buffer, x *[]string) error {\n\tvar s string\n\tif err := decodeString(b, &s); err != nil {\n\t\treturn err\n\t}\n\t*x = append(*x, s)\n\treturn nil\n}\n\nfunc decodeBool(b *buffer, x *bool) error {\n\tif err := checkType(b, 0); err != nil {\n\t\treturn err\n\t}\n\tif int64(b.u64) == 0 {\n\t\t*x = false\n\t} else {\n\t\t*x = true\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "profile/proto_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/google/pprof/internal/proftest\"\n)\n\nvar testM = []*Mapping{\n\t{\n\t\tID:              1,\n\t\tStart:           1,\n\t\tLimit:           10,\n\t\tOffset:          0,\n\t\tFile:            \"file1\",\n\t\tBuildID:         \"buildid1\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n\t{\n\t\tID:              2,\n\t\tStart:           10,\n\t\tLimit:           30,\n\t\tOffset:          9,\n\t\tFile:            \"file1\",\n\t\tBuildID:         \"buildid2\",\n\t\tHasFunctions:    true,\n\t\tHasFilenames:    true,\n\t\tHasLineNumbers:  true,\n\t\tHasInlineFrames: true,\n\t},\n}\n\nvar testF = []*Function{\n\t{ID: 1, Name: \"func1\", SystemName: \"func1\", Filename: \"file1\"},\n\t{ID: 2, Name: \"func2\", SystemName: \"func2\", Filename: \"file1\"},\n\t{ID: 3, Name: \"func3\", SystemName: \"func3\", Filename: \"file2\"},\n\t{ID: 4, Name: \"func4\", SystemName: \"func4\", Filename: \"file3\"},\n\t{ID: 5, Name: \"func5\", SystemName: \"func5\", Filename: \"file4\"},\n}\n\nvar testL = []*Location{\n\t{\n\t\tID:      1,\n\t\tAddress: 1,\n\t\tMapping: testM[0],\n\t\tLine: []Line{\n\t\t\t{\n\t\t\t\tFunction: testF[0],\n\t\t\t\tLine:     2,\n\t\t\t},\n\t\t\t{\n\t\t\t\tFunction: testF[1],\n\t\t\t\tLine:     2222222,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      2,\n\t\tMapping: testM[1],\n\t\tAddress: 11,\n\t\tLine: []Line{\n\t\t\t{\n\t\t\t\tFunction: testF[2],\n\t\t\t\tLine:     2,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tID:      3,\n\t\tMapping: testM[1],\n\t\tAddress: 12,\n\t},\n\t{\n\t\tID:      4,\n\t\tMapping: testM[1],\n\t\tAddress: 12,\n\t\tLine: []Line{\n\t\t\t{\n\t\t\t\tFunction: testF[4],\n\t\t\t\tLine:     6,\n\t\t\t},\n\t\t\t{\n\t\t\t\tFunction: testF[4],\n\t\t\t\tLine:     6,\n\t\t\t},\n\t\t},\n\t\tIsFolded: true,\n\t},\n}\n\nvar all = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        10,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"cpu\", Unit: \"cycles\"},\n\t\t{Type: \"object\", Unit: \"count\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{testL[0], testL[1], testL[2], testL[1], testL[1]},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"value1\"},\n\t\t\t\t\"key2\": {\"value2\"},\n\t\t\t},\n\t\t\tValue: []int64{10, 20},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{testL[1], testL[2], testL[0], testL[1]},\n\t\t\tValue:    []int64{30, 40},\n\t\t\tLabel: map[string][]string{\n\t\t\t\t\"key1\": {\"value1\"},\n\t\t\t\t\"key2\": {\"value2\"},\n\t\t\t},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"key1\":      {1, 2},\n\t\t\t\t\"key2\":      {3, 4},\n\t\t\t\t\"bytes\":     {3, 4},\n\t\t\t\t\"requests\":  {1, 1, 3, 4, 5},\n\t\t\t\t\"alignment\": {3, 4},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"requests\":  {\"\", \"\", \"seconds\", \"\", \"s\"},\n\t\t\t\t\"alignment\": {\"kilobytes\", \"kilobytes\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{testL[1], testL[2], testL[0], testL[1]},\n\t\t\tValue:    []int64{30, 40},\n\t\t\tNumLabel: map[string][]int64{\n\t\t\t\t\"size\": {0},\n\t\t\t},\n\t\t\tNumUnit: map[string][]string{\n\t\t\t\t\"size\": {\"bytes\"},\n\t\t\t},\n\t\t},\n\t},\n\tFunction: testF,\n\tMapping:  testM,\n\tLocation: testL,\n\tComments: []string{\"Comment 1\", \"Comment 2\"},\n}\n\nfunc TestMarshalUnmarshal(t *testing.T) {\n\t// Write the profile, parse it, and ensure they're equal.\n\tvar buf bytes.Buffer\n\tall.Write(&buf)\n\tall2, err := Parse(&buf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tjs1 := proftest.EncodeJSON(&all)\n\tjs2 := proftest.EncodeJSON(&all2)\n\tif string(js1) != string(js2) {\n\t\tt.Errorf(\"profiles differ\")\n\t\td, err := proftest.Diff(js1, js2)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Error(\"\\n\" + string(d))\n\t}\n}\n"
  },
  {
    "path": "profile/prune.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Implements methods to remove frames from profiles.\n\npackage profile\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n)\n\nvar (\n\treservedNames = []string{\"(anonymous namespace)\", \"operator()\"}\n\tbracketRx     = func() *regexp.Regexp {\n\t\tvar quotedNames []string\n\t\tfor _, name := range append(reservedNames, \"(\") {\n\t\t\tquotedNames = append(quotedNames, regexp.QuoteMeta(name))\n\t\t}\n\t\treturn regexp.MustCompile(strings.Join(quotedNames, \"|\"))\n\t}()\n)\n\n// simplifyFunc does some primitive simplification of function names.\nfunc simplifyFunc(f string) string {\n\t// Account for leading '.' on the PPC ELF v1 ABI.\n\tfuncName := strings.TrimPrefix(f, \".\")\n\t// Account for unsimplified names -- try  to remove the argument list by trimming\n\t// starting from the first '(', but skipping reserved names that have '('.\n\tfor _, ind := range bracketRx.FindAllStringSubmatchIndex(funcName, -1) {\n\t\tfoundReserved := slices.Contains(reservedNames, funcName[ind[0]:ind[1]])\n\t\tif !foundReserved {\n\t\t\tfuncName = funcName[:ind[0]]\n\t\t\tbreak\n\t\t}\n\t}\n\treturn funcName\n}\n\n// Prune removes all nodes beneath a node matching dropRx, and not\n// matching keepRx. If the root node of a Sample matches, the sample\n// will have an empty stack.\nfunc (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) {\n\tprune := make(map[uint64]bool)\n\tpruneBeneath := make(map[uint64]bool)\n\n\t// simplifyFunc can be expensive, so cache results.\n\t// Note that the same function name can be encountered many times due\n\t// different lines and addresses in the same function.\n\tpruneCache := map[string]bool{} // Map from function to whether or not to prune\n\tpruneFromHere := func(s string) bool {\n\t\tif r, ok := pruneCache[s]; ok {\n\t\t\treturn r\n\t\t}\n\t\tfuncName := simplifyFunc(s)\n\t\tif dropRx.MatchString(funcName) {\n\t\t\tif keepRx == nil || !keepRx.MatchString(funcName) {\n\t\t\t\tpruneCache[s] = true\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tpruneCache[s] = false\n\t\treturn false\n\t}\n\n\tfor _, loc := range p.Location {\n\t\tvar i int\n\t\tfor i = len(loc.Line) - 1; i >= 0; i-- {\n\t\t\tif fn := loc.Line[i].Function; fn != nil && fn.Name != \"\" {\n\t\t\t\tif pruneFromHere(fn.Name) {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif i >= 0 {\n\t\t\t// Found matching entry to prune.\n\t\t\tpruneBeneath[loc.ID] = true\n\n\t\t\t// Remove the matching location.\n\t\t\tif i == len(loc.Line)-1 {\n\t\t\t\t// Matched the top entry: prune the whole location.\n\t\t\t\tprune[loc.ID] = true\n\t\t\t} else {\n\t\t\t\tloc.Line = loc.Line[i+1:]\n\t\t\t}\n\t\t}\n\t}\n\n\t// Prune locs from each Sample\n\tfor _, sample := range p.Sample {\n\t\t// Scan from the root to the leaves to find the prune location.\n\t\t// Do not prune frames before the first user frame, to avoid\n\t\t// pruning everything.\n\t\tfoundUser := false\n\t\tfor i := len(sample.Location) - 1; i >= 0; i-- {\n\t\t\tid := sample.Location[i].ID\n\t\t\tif !prune[id] && !pruneBeneath[id] {\n\t\t\t\tfoundUser = true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !foundUser {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif prune[id] {\n\t\t\t\tsample.Location = sample.Location[i+1:]\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif pruneBeneath[id] {\n\t\t\t\tsample.Location = sample.Location[i:]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// RemoveUninteresting prunes and elides profiles using built-in\n// tables of uninteresting function names.\nfunc (p *Profile) RemoveUninteresting() error {\n\tvar keep, drop *regexp.Regexp\n\tvar err error\n\n\tif p.DropFrames != \"\" {\n\t\tif drop, err = regexp.Compile(\"^(\" + p.DropFrames + \")$\"); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to compile regexp %s: %v\", p.DropFrames, err)\n\t\t}\n\t\tif p.KeepFrames != \"\" {\n\t\t\tif keep, err = regexp.Compile(\"^(\" + p.KeepFrames + \")$\"); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to compile regexp %s: %v\", p.KeepFrames, err)\n\t\t\t}\n\t\t}\n\t\tp.Prune(drop, keep)\n\t}\n\treturn nil\n}\n\n// PruneFrom removes all nodes beneath the lowest node matching dropRx, not including itself.\n//\n// Please see the example below to understand this method as well as\n// the difference from Prune method.\n//\n// A sample contains Location of [A,B,C,B,D] where D is the top frame and there's no inline.\n//\n// PruneFrom(A) returns [A,B,C,B,D] because there's no node beneath A.\n// Prune(A, nil) returns [B,C,B,D] by removing A itself.\n//\n// PruneFrom(B) returns [B,C,B,D] by removing all nodes beneath the first B when scanning from the bottom.\n// Prune(B, nil) returns [D] because a matching node is found by scanning from the root.\nfunc (p *Profile) PruneFrom(dropRx *regexp.Regexp) {\n\tpruneBeneath := make(map[uint64]bool)\n\n\tfor _, loc := range p.Location {\n\t\tfor i := 0; i < len(loc.Line); i++ {\n\t\t\tif fn := loc.Line[i].Function; fn != nil && fn.Name != \"\" {\n\t\t\t\tfuncName := simplifyFunc(fn.Name)\n\t\t\t\tif dropRx.MatchString(funcName) {\n\t\t\t\t\t// Found matching entry to prune.\n\t\t\t\t\tpruneBeneath[loc.ID] = true\n\t\t\t\t\tloc.Line = loc.Line[i:]\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Prune locs from each Sample\n\tfor _, sample := range p.Sample {\n\t\t// Scan from the bottom leaf to the root to find the prune location.\n\t\tfor i, loc := range sample.Location {\n\t\t\tif pruneBeneath[loc.ID] {\n\t\t\t\tsample.Location = sample.Location[i:]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "profile/prune_test.go",
    "content": "// Copyright 2014 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage profile\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestPrune(t *testing.T) {\n\tfor _, test := range []struct {\n\t\tin   *Profile\n\t\twant string\n\t}{\n\t\t{in1, out1},\n\t\t{in2, out2},\n\t} {\n\t\tin := test.in.Copy()\n\t\tin.RemoveUninteresting()\n\t\tif err := in.CheckValid(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tw := strings.Split(test.want, \"\\n\")\n\t\tfor i, g := range strings.Split(in.String(), \"\\n\") {\n\t\t\tif i >= len(w) {\n\t\t\t\tt.Fatalf(\"got trailing %s\", g)\n\t\t\t}\n\t\t\tif strings.TrimSpace(g) != strings.TrimSpace(w[i]) {\n\t\t\t\tt.Fatalf(`%d: got: \"%s\"  want:\"%s\"`, i, g, w[i])\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar funs = []*Function{\n\t{ID: 1, Name: \"main\", SystemName: \"main\", Filename: \"main.c\"},\n\t{ID: 2, Name: \"fun1\", SystemName: \"fun1\", Filename: \"fun.c\"},\n\t{ID: 3, Name: \"fun2\", SystemName: \"fun2\", Filename: \"fun.c\"},\n\t{ID: 4, Name: \"fun3\", SystemName: \"fun3\", Filename: \"fun.c\"},\n\t{ID: 5, Name: \"fun4\", SystemName: \"fun4\", Filename: \"fun.c\"},\n\t{ID: 6, Name: \"fun5\", SystemName: \"fun5\", Filename: \"fun.c\"},\n\t{ID: 7, Name: \"unsimplified_fun(int)\", SystemName: \"unsimplified_fun(int)\", Filename: \"fun.c\"},\n\t{ID: 8, Name: \"Foo::(anonymous namespace)::Test::Bar\", SystemName: \"Foo::(anonymous namespace)::Test::Bar\", Filename: \"fun.c\"},\n\t{ID: 9, Name: \"Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar)\", SystemName: \"Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar)\", Filename: \"fun.c\"},\n\t{ID: 10, Name: \"Foo::operator()(::Bar)\", SystemName: \"Foo::operator()(::Bar)\", Filename: \"fun.c\"},\n}\n\nvar locs1 = []*Location{\n\t{\n\t\tID: 1,\n\t\tLine: []Line{\n\t\t\t{Function: funs[0], Line: 1, Column: 7},\n\t\t},\n\t},\n\t{\n\t\tID: 2,\n\t\tLine: []Line{\n\t\t\t{Function: funs[1], Line: 2},\n\t\t\t{Function: funs[2], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID: 3,\n\t\tLine: []Line{\n\t\t\t{Function: funs[3], Line: 2},\n\t\t\t{Function: funs[1], Line: 1, Column: 7},\n\t\t},\n\t},\n\t{\n\t\tID: 4,\n\t\tLine: []Line{\n\t\t\t{Function: funs[3], Line: 2},\n\t\t\t{Function: funs[1], Line: 2},\n\t\t\t{Function: funs[5], Line: 2},\n\t\t},\n\t},\n}\n\nvar in1 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t{\n\t\t\tLocation: []*Location{locs1[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{locs1[1], locs1[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{locs1[2], locs1[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{locs1[3], locs1[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t{\n\t\t\tLocation: []*Location{locs1[3], locs1[2], locs1[1], locs1[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t},\n\tLocation:   locs1,\n\tFunction:   funs,\n\tDropFrames: \"fu.*[12]|banana\",\n\tKeepFrames: \".*[n2][n2]\",\n}\n\nconst out1 = `PeriodType: cpu milliseconds\nPeriod: 1\nDuration: 10s\nSamples:\nsamples/count cpu/milliseconds\n          1          1: 1\n          1          1: 2 1\n          1          1: 1\n          1          1: 4 1\n          1          1: 2 1\nLocations\n     1: 0x0 main main.c:1:7 s=0\n     2: 0x0 fun2 fun.c:1:0 s=0\n     3: 0x0 fun3 fun.c:2:0 s=0\n             fun1 fun.c:1:7 s=0\n     4: 0x0 fun5 fun.c:2:0 s=0\nMappings\n`\n\nvar locs2 = []*Location{\n\t{\n\t\tID: 1,\n\t\tLine: []Line{\n\t\t\t{Function: funs[0], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID: 2,\n\t\tLine: []Line{\n\t\t\t{Function: funs[6], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID: 3,\n\t\tLine: []Line{\n\t\t\t{Function: funs[7], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID: 4,\n\t\tLine: []Line{\n\t\t\t{Function: funs[8], Line: 1},\n\t\t},\n\t},\n\t{\n\t\tID: 5,\n\t\tLine: []Line{\n\t\t\t{Function: funs[9], Line: 1},\n\t\t},\n\t},\n}\n\nvar in2 = &Profile{\n\tPeriodType:    &ValueType{Type: \"cpu\", Unit: \"milliseconds\"},\n\tPeriod:        1,\n\tDurationNanos: 10e9,\n\tSampleType: []*ValueType{\n\t\t{Type: \"samples\", Unit: \"count\"},\n\t\t{Type: \"cpu\", Unit: \"milliseconds\"},\n\t},\n\tSample: []*Sample{\n\t\t// Unsimplified name with parameters shouldn't match.\n\t\t{\n\t\t\tLocation: []*Location{locs2[1], locs2[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t// .*Foo::.*::Bar.* should (and will be dropped) regardless of the anonymous namespace.\n\t\t{\n\t\t\tLocation: []*Location{locs2[2], locs2[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t// .*Foo::.*::Bar.* shouldn't match inside the parameter list.\n\t\t{\n\t\t\tLocation: []*Location{locs2[3], locs2[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t\t// .*operator\\(\\) should match, regardless of parameters.\n\t\t{\n\t\t\tLocation: []*Location{locs2[4], locs2[0]},\n\t\t\tValue:    []int64{1, 1},\n\t\t},\n\t},\n\tLocation:   locs2,\n\tFunction:   funs,\n\tDropFrames: `unsimplified_fun\\(int\\)|.*Foo::.*::Bar.*|.*operator\\(\\)`,\n}\n\nconst out2 = `PeriodType: cpu milliseconds\nPeriod: 1\nDuration: 10s\nSamples:\nsamples/count cpu/milliseconds\n          1          1: 2 1\n          1          1: 1\n          1          1: 4 1\n          1          1: 1\nLocations\n     1: 0x0 main main.c:1:0 s=0\n     2: 0x0 unsimplified_fun(int) fun.c:1:0 s=0\n     3: 0x0 Foo::(anonymous namespace)::Test::Bar fun.c:1:0 s=0\n     4: 0x0 Hello::(anonymous namespace)::World(const Foo::(anonymous namespace)::Test::Bar) fun.c:1:0 s=0\n     5: 0x0 Foo::operator()(::Bar) fun.c:1:0 s=0\nMappings\n`\n"
  },
  {
    "path": "profile/testdata/cppbench.contention",
    "content": "--- contentionz 1 ---\ncycles/second = 3201000000\nsampling period = 100\nms since reset = 16502830\ndiscarded samples = 0\n  19490304       27 @ 0xbccc97 0xc61202 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n       768        1 @ 0xbccc97 0xa42dc7 0xa456e4 0x7fcdc2ff214e\n      5760        2 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x4e969d 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n    569088        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87f08 0xb8814c 0x42ed5f 0x42edc1 0x42e15a 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n      2432        1 @ 0xbccc97 0xb82b73 0xb82bcb 0xb87eab 0xb8814c 0x7aa74c 0x7ab844 0x7ab914 0x79e9e9 0x79e326 0x4d299e 0x4d4b7b 0x4b7be8 0x4b7ff1 0x4d2dae 0x79e80a\n   2034816        3 @ 0xbccc97 0xb82f0f 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n--- Memory map: ---\n  00400000-00fcb000: cppbench_server_main\n  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so\n  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so\n  7fcdc272f000-7fcdc28dd000: /libc-2.15.so\n  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so\n  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so\n  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so\n  7fcdc3208000-7fcdc320a000: /libdl-2.15.so\n  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so\n  7fcdc3645000-7fcdc3669000: /ld-2.15.so\n  7fff86bff000-7fff86c00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "profile/testdata/cppbench.contention.string",
    "content": "PeriodType: contentions count\nPeriod: 100\nDuration: 4h35\nSamples:\ncontentions/count delay/nanoseconds\n       2700  608881724: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n        100      23992: 1 14 12 13 \n        200     179943: 1 15 16 17 18 19 20 21 22 23 9 10 11 12 13 \n        100   17778444: 1 15 16 24 18 3 4 5 6 7 8 9 10 11 12 13 \n        100      75976: 1 15 16 17 18 25 26 27 28 29 30 31 32 33 34 9 \n        300   63568134: 1 35 36 37 38 39 40 6 7 8 9 10 11 12 13 \nLocations\n     1: 0xbccc96 M=1 \n     2: 0xc61201 M=1 \n     3: 0x42ed5e M=1 \n     4: 0x42edc0 M=1 \n     5: 0x42e159 M=1 \n     6: 0x5261ae M=1 \n     7: 0x526ede M=1 \n     8: 0x5280aa M=1 \n     9: 0x79e809 M=1 \n    10: 0x7a251a M=1 \n    11: 0x7a296c M=1 \n    12: 0xa456e3 M=1 \n    13: 0x7fcdc2ff214d M=7 \n    14: 0xa42dc6 M=1 \n    15: 0xb82b72 M=1 \n    16: 0xb82bca M=1 \n    17: 0xb87eaa M=1 \n    18: 0xb8814b M=1 \n    19: 0x4e969c M=1 \n    20: 0x4faa16 M=1 \n    21: 0x4fc5f5 M=1 \n    22: 0x4fd027 M=1 \n    23: 0x4fd22f M=1 \n    24: 0xb87f07 M=1 \n    25: 0x7aa74b M=1 \n    26: 0x7ab843 M=1 \n    27: 0x7ab913 M=1 \n    28: 0x79e9e8 M=1 \n    29: 0x79e325 M=1 \n    30: 0x4d299d M=1 \n    31: 0x4d4b7a M=1 \n    32: 0x4b7be7 M=1 \n    33: 0x4b7ff0 M=1 \n    34: 0x4d2dad M=1 \n    35: 0xb82f0e M=1 \n    36: 0xb83002 M=1 \n    37: 0xb87d4f M=1 \n    38: 0xc635ef M=1 \n    39: 0x42ecc2 M=1 \n    40: 0x42e14b M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 cppbench_server_main  \n2: 0x7fcdc231e000/0x7fcdc2321000/0x0 /libnss_cache-2.15.so  \n3: 0x7fcdc2522000/0x7fcdc252e000/0x0 /libnss_files-2.15.so  \n4: 0x7fcdc272f000/0x7fcdc28dd000/0x0 /libc-2.15.so  \n5: 0x7fcdc2ae7000/0x7fcdc2be2000/0x0 /libm-2.15.so  \n6: 0x7fcdc2de3000/0x7fcdc2dea000/0x0 /librt-2.15.so  \n7: 0x7fcdc2feb000/0x7fcdc3003000/0x0 /libpthread-2.15.so  \n8: 0x7fcdc3208000/0x7fcdc320a000/0x0 /libdl-2.15.so  \n9: 0x7fcdc340c000/0x7fcdc3415000/0x0 /libcrypt-2.15.so  \n10: 0x7fcdc3645000/0x7fcdc3669000/0x0 /ld-2.15.so  \n11: 0x7fff86bff000/0x7fff86c00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.cpu.string",
    "content": "PeriodType: cpu nanoseconds\nPeriod: 10000000\nSamples:\nsamples/count cpu/nanoseconds\n          1   10000000: 1 2 3 4 5 6 7 8 9 10 \n          1   10000000: 11 2 3 4 5 6 7 8 9 10 \n          1   10000000: 1 2 3 4 5 6 7 8 9 10 \n          1   10000000: 12 13 14 15 16 17 18 3 4 5 6 7 8 9 10 \n        542 5420000000: 19 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 20 17 18 3 4 5 6 7 8 9 10 \n         10  100000000: 21 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 22 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 23 24 25 2 3 4 5 6 7 8 9 10 \n          3   30000000: 26 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 27 16 17 18 3 4 5 6 7 8 9 10 \n          2   20000000: 28 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 29 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 30 31 32 33 34 35 36 37 38 9 10 \n          3   30000000: 39 40 41 24 25 2 3 4 5 6 7 8 9 10 \n          2   20000000: 42 40 41 24 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 43 40 41 24 25 2 3 4 5 6 7 8 9 10 \n          2   20000000: 44 45 41 24 25 2 3 4 5 6 7 8 9 10 \n         67  670000000: 46 2 3 4 5 6 7 8 9 10 \n         20  200000000: 47 2 3 4 5 6 7 8 9 10 \n         12  120000000: 48 2 3 4 5 6 7 8 9 10 \n          5   50000000: 11 2 3 4 5 6 7 8 9 10 \n          1   10000000: 49 10 \n          1   10000000: 50 51 52 13 14 15 16 17 18 3 4 5 6 7 8 9 10 \n          2   20000000: 53 51 52 13 14 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 54 14 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 55 56 57 58 4 5 6 7 8 9 10 \n          1   10000000: 59 41 24 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 60 41 24 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 61 62 63 64 40 41 24 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 65 66 67 68 69 70 71 72 73 74 75 37 38 9 10 \n          1   10000000: 76 13 77 15 16 17 18 3 4 5 6 7 8 9 10 \n          2   20000000: 78 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 79 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 80 13 77 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 81 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 82 13 14 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 83 13 77 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 83 13 14 15 16 17 18 3 4 5 6 7 8 9 10 \n          1   10000000: 30 84 85 86 9 10 \n          1   10000000: 87 88 40 41 24 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 89 90 91 92 8 9 10 \n          1   10000000: 30 93 8 9 10 \n          1   10000000: 30 84 94 9 10 \n          1   10000000: 95 3 4 5 6 7 8 9 10 \n          1   10000000: 96 97 3 4 5 6 7 8 9 10 \n          1   10000000: 98 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 99 25 2 3 4 5 6 7 8 9 10 \n          1   10000000: 100 101 102 41 24 25 2 3 4 5 6 7 8 9 10 \n          2   20000000: 103 104 91 92 8 9 10 \n          1   10000000: 105 104 91 92 8 9 10 \n          1   10000000: 106 107 108 109 97 3 4 5 6 7 8 9 10 \nLocations\n     1: 0x42ef04 M=1 \n     2: 0x42e14b M=1 \n     3: 0x5261ae M=1 \n     4: 0x526ede M=1 \n     5: 0x5280aa M=1 \n     6: 0x79e809 M=1 \n     7: 0x7a251a M=1 \n     8: 0x7a296c M=1 \n     9: 0xa456e3 M=1 \n    10: 0x7f5e541460fd M=7 \n    11: 0x42ef17 M=1 \n    12: 0xb867c0 M=1 \n    13: 0xb82bca M=1 \n    14: 0xb87eaa M=1 \n    15: 0xb8814b M=1 \n    16: 0x42ed5e M=1 \n    17: 0x42edc0 M=1 \n    18: 0x42e159 M=1 \n    19: 0x42ed43 M=1 \n    20: 0xc60ea0 M=1 \n    21: 0x42ed40 M=1 \n    22: 0xbf42fe M=1 \n    23: 0xb87d6f M=1 \n    24: 0xc635ef M=1 \n    25: 0x42ecc2 M=1 \n    26: 0xc60f0f M=1 \n    27: 0xc610d7 M=1 \n    28: 0xc61108 M=1 \n    29: 0xb8816e M=1 \n    30: 0xbc8f1c M=1 \n    31: 0xbcae54 M=1 \n    32: 0xbcb5f4 M=1 \n    33: 0x40b687 M=1 \n    34: 0x535244 M=1 \n    35: 0x536bf4 M=1 \n    36: 0x42eb0f M=1 \n    37: 0x42de64 M=1 \n    38: 0xa41281 M=1 \n    39: 0xb82dea M=1 \n    40: 0xb83002 M=1 \n    41: 0xb87d4f M=1 \n    42: 0xb82df1 M=1 \n    43: 0xb82dd3 M=1 \n    44: 0xb82c23 M=1 \n    45: 0xb82fd1 M=1 \n    46: 0x42ef13 M=1 \n    47: 0x42ef0b M=1 \n    48: 0x42ef0f M=1 \n    49: 0x7f5e53999f13 M=4 \n    50: 0xb8591b M=1 \n    51: 0xb85e48 M=1 \n    52: 0xb82ae3 M=1 \n    53: 0xb85893 M=1 \n    54: 0xb88cdc M=1 \n    55: 0x698000 M=1 \n    56: 0x653f4b M=1 \n    57: 0x54dc65 M=1 \n    58: 0x525120 M=1 \n    59: 0xb88d84 M=1 \n    60: 0xb88d98 M=1 \n    61: 0xb86591 M=1 \n    62: 0xb859de M=1 \n    63: 0xb862de M=1 \n    64: 0xb82d5e M=1 \n    65: 0x967171 M=1 \n    66: 0x964990 M=1 \n    67: 0x448584 M=1 \n    68: 0x5476d7 M=1 \n    69: 0x4f1be0 M=1 \n    70: 0x4f34db M=1 \n    71: 0x4f8a9a M=1 \n    72: 0x5388df M=1 \n    73: 0x573c5a M=1 \n    74: 0x4a4168 M=1 \n    75: 0x42eb03 M=1 \n    76: 0xb82a31 M=1 \n    77: 0xb87f07 M=1 \n    78: 0xb87e76 M=1 \n    79: 0xb87e7e M=1 \n    80: 0xb82a36 M=1 \n    81: 0xb87ede M=1 \n    82: 0xb82a55 M=1 \n    83: 0xb82b08 M=1 \n    84: 0xbcbcff M=1 \n    85: 0xbcbea4 M=1 \n    86: 0xa40112 M=1 \n    87: 0xb85e87 M=1 \n    88: 0xb82d77 M=1 \n    89: 0x79eb32 M=1 \n    90: 0x7a18e8 M=1 \n    91: 0x7a1c44 M=1 \n    92: 0x7a2726 M=1 \n    93: 0x7a2690 M=1 \n    94: 0x89f186 M=1 \n    95: 0xc60eb7 M=1 \n    96: 0x521c7f M=1 \n    97: 0x5194c8 M=1 \n    98: 0xc634f0 M=1 \n    99: 0xc63245 M=1 \n   100: 0xb867d8 M=1 \n   101: 0xb82cf2 M=1 \n   102: 0xb82f82 M=1 \n   103: 0x7f5e538b9a93 M=4 \n   104: 0x7a1955 M=1 \n   105: 0x7f5e538b9a97 M=4 \n   106: 0x7e0f10 M=1 \n   107: 0x7e0b5d M=1 \n   108: 0x6ab44f M=1 \n   109: 0x521d51 M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 cppbench_server_main  \n2: 0x7f5e53061000/0x7f5e53062000/0x0 /lib/libnss_borg-2.15.so  \n3: 0x7f5e53264000/0x7f5e53270000/0x0 /lib/libnss_files-2.15.so  \n4: 0x7f5e53883000/0x7f5e53a31000/0x0 /lib/libc-2.15.so  \n5: 0x7f5e53c3b000/0x7f5e53d36000/0x0 /lib/libm-2.15.so  \n6: 0x7f5e53f37000/0x7f5e53f3e000/0x0 /lib/librt-2.15.so  \n7: 0x7f5e5413f000/0x7f5e54157000/0x0 /lib/libpthread-2.15.so  \n8: 0x7f5e5435c000/0x7f5e5435e000/0x0 /lib/libdl-2.15.so  \n9: 0x7f5e54560000/0x7f5e54569000/0x0 /lib/libcrypt-2.15.so  \n10: 0x7f5e54799000/0x7f5e547bd000/0x0 /lib/ld-2.15.so  \n11: 0x7ffffb56b000/0x7ffffb56d000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.growth",
    "content": "heap profile:     85: 178257920 [    85: 178257920] @ growthz\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0xafc0eb 0xb087b1 0xb0aa7d 0xb0b374 0xb12f10 0xb13a92 0xb0c443 0xb145f3 0xb147ca 0xa5dddd 0xbbffe6 0xa5e837 0xa65f94 0x5aac9e 0x535526 0x535144 0x5aa468 0x7e3ce7 0x7d13a2 0x7e0d28 0x6ab450 0x538d27 0x5390e8 0x5391e3 0x4e9603 0x4faa17 0x4fc5f6\n     1:  2097152 [     1:  2097152] @ 0xc635c8 0x816900 0x8149fd 0x813aa0 0xbbff77 0x81421c 0x4ed414 0x4fd707 0x4de2a2 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7fcdc2ff214e\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0xbb5783 0x40acd8 0x61192e 0x4b9522 0x4b9f62 0x4ba025 0x40bd86 0x7fcdc276711d\n     1:  2097152 [     1:  2097152] @ 0xb83003 0xb87d50 0xc635f0 0x42d576 0xc25cc6 0x40651b\n--- Memory map: ---\n  00400000-00fcb000: cppbench_server_main\n  7fcdc231e000-7fcdc2321000: /libnss_cache-2.15.so\n  7fcdc2522000-7fcdc252e000: /libnss_files-2.15.so\n  7fcdc272f000-7fcdc28dd000: /libc-2.15.so\n  7fcdc2ae7000-7fcdc2be2000: /libm-2.15.so\n  7fcdc2de3000-7fcdc2dea000: /librt-2.15.so\n  7fcdc2feb000-7fcdc3003000: /libpthread-2.15.so\n  7fcdc3208000-7fcdc320a000: /libdl-2.15.so\n  7fcdc340c000-7fcdc3415000: /libcrypt-2.15.so\n  7fcdc3645000-7fcdc3669000: /ld-2.15.so\n  7fff86bff000-7fff86c00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "profile/testdata/cppbench.growth.string",
    "content": "PeriodType: space bytes\nPeriod: 1\nSamples:\nobjects/count space/bytes\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 14 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 4 5 6 7 8 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 \n                bytes:[2097152]\n          1    2097152: 14 42 43 44 45 46 47 48 49 9 10 11 12 13 \n                bytes:[2097152]\n          1    2097152: 1 2 3 50 51 52 53 54 55 56 57 \n                bytes:[2097152]\n          1    2097152: 1 2 3 58 59 60 \n                bytes:[2097152]\nLocations\n     1: 0xb83002 M=1 \n     2: 0xb87d4f M=1 \n     3: 0xc635ef M=1 \n     4: 0x42ecc2 M=1 \n     5: 0x42e14b M=1 \n     6: 0x5261ae M=1 \n     7: 0x526ede M=1 \n     8: 0x5280aa M=1 \n     9: 0x79e809 M=1 \n    10: 0x7a251a M=1 \n    11: 0x7a296c M=1 \n    12: 0xa456e3 M=1 \n    13: 0x7fcdc2ff214d M=7 \n    14: 0xc635c7 M=1 \n    15: 0xafc0ea M=1 \n    16: 0xb087b0 M=1 \n    17: 0xb0aa7c M=1 \n    18: 0xb0b373 M=1 \n    19: 0xb12f0f M=1 \n    20: 0xb13a91 M=1 \n    21: 0xb0c442 M=1 \n    22: 0xb145f2 M=1 \n    23: 0xb147c9 M=1 \n    24: 0xa5dddc M=1 \n    25: 0xbbffe5 M=1 \n    26: 0xa5e836 M=1 \n    27: 0xa65f93 M=1 \n    28: 0x5aac9d M=1 \n    29: 0x535525 M=1 \n    30: 0x535143 M=1 \n    31: 0x5aa467 M=1 \n    32: 0x7e3ce6 M=1 \n    33: 0x7d13a1 M=1 \n    34: 0x7e0d27 M=1 \n    35: 0x6ab44f M=1 \n    36: 0x538d26 M=1 \n    37: 0x5390e7 M=1 \n    38: 0x5391e2 M=1 \n    39: 0x4e9602 M=1 \n    40: 0x4faa16 M=1 \n    41: 0x4fc5f5 M=1 \n    42: 0x8168ff M=1 \n    43: 0x8149fc M=1 \n    44: 0x813a9f M=1 \n    45: 0xbbff76 M=1 \n    46: 0x81421b M=1 \n    47: 0x4ed413 M=1 \n    48: 0x4fd706 M=1 \n    49: 0x4de2a1 M=1 \n    50: 0xbb5782 M=1 \n    51: 0x40acd7 M=1 \n    52: 0x61192d M=1 \n    53: 0x4b9521 M=1 \n    54: 0x4b9f61 M=1 \n    55: 0x4ba024 M=1 \n    56: 0x40bd85 M=1 \n    57: 0x7fcdc276711c M=4 \n    58: 0x42d575 M=1 \n    59: 0xc25cc5 M=1 \n    60: 0x40651a M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 cppbench_server_main  \n2: 0x7fcdc231e000/0x7fcdc2321000/0x0 /libnss_cache-2.15.so  \n3: 0x7fcdc2522000/0x7fcdc252e000/0x0 /libnss_files-2.15.so  \n4: 0x7fcdc272f000/0x7fcdc28dd000/0x0 /libc-2.15.so  \n5: 0x7fcdc2ae7000/0x7fcdc2be2000/0x0 /libm-2.15.so  \n6: 0x7fcdc2de3000/0x7fcdc2dea000/0x0 /librt-2.15.so  \n7: 0x7fcdc2feb000/0x7fcdc3003000/0x0 /libpthread-2.15.so  \n8: 0x7fcdc3208000/0x7fcdc320a000/0x0 /libdl-2.15.so  \n9: 0x7fcdc340c000/0x7fcdc3415000/0x0 /libcrypt-2.15.so  \n10: 0x7fcdc3645000/0x7fcdc3669000/0x0 /ld-2.15.so  \n11: 0x7fff86bff000/0x7fff86c00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.heap",
    "content": "heap profile:    144:  8498176 [   144:  8498176] @ heapz_v2/524288\n     1:     9216 [     1:     9216] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:      144 [     1:      144] @ 0xc635c8 0xa7479b 0xb65e6b 0xb65f80 0xa6d069 0xa6dc80 0xbbffe6 0xa5dd84 0xa7b7c6 0xaa88da 0xaa9db2 0xb59bae 0xb0c39c 0xb145f3 0xb147ca 0xa5dddd 0xbbffe6 0xa5e837 0xa65f94 0x5aac9e 0x535526 0x535144 0x5aa468 0x7e3ce7 0x7d13a2 0x7e0d28 0x6ab450 0x538d27 0x5390e8 0x5391e3 0x4e9603\n     7:   114688 [     7:   114688] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     1792 [     1:     1792] @ 0xc635c8 0x51a272 0x524997 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n    13:   319488 [    13:   319488] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     1792 [     1:     1792] @ 0xc635c8 0xac95a0 0xacdc7c 0xace07b 0xace1ac 0xabd100 0xabe2a9 0x72f52e 0x655376 0x6558d3 0x41c711 0xc25cc6 0x40651b\n     1:  2162688 [     1:  2162688] @ 0xc63568 0xbc462e 0xbc4bb5 0xbc4eda 0x4a57b8 0x4b152c 0x4ae04c 0x4ad225 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:       48 [     1:       48] @ 0xc635c8 0x7be14a 0x7be675 0x6b312d 0xbaa17f 0xbaa142 0xbaabc6 0xbb092c 0x40bce4 0x7f47a4bab11d\n     1:   262144 [     1:   262144] @ 0xc635c8 0x816900 0x8149fd 0x8139f4 0xbbff77 0x81421c 0x4ed414 0x4fd707 0x4de2a2 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:      320 [     1:      320] @ 0xc635c8 0x721a59 0x43005e 0x7382a4 0x430590 0x435425 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     1792 [     1:     1792] @ 0xc635c8 0x5413b0 0x541ab2 0xbaa17f 0xbaabc6 0x53507c 0xbaa17f 0xbaa9f9 0xbb0d21 0x40bce4 0x7f47a4bab11d\n     1:    10240 [     1:    10240] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n    16:   327680 [    16:   327680] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:      160 [     1:      160] @ 0xc635c8 0x578705 0x586247 0x592615 0x592745 0x592cb9 0xa456e4 0x7f47a54360fe\n     1:     8192 [     1:     8192] @ 0xc635c8 0xaaf469 0x52cad7 0x52e89b 0x527f32 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     2:    24576 [     2:    24576] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:  2097152 [     1:  2097152] @ 0xc63568 0xbc463b 0xbc4bb5 0xbc4eda 0x4a57b8 0x4b152c 0x4ae04c 0x4ad225 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:      448 [     1:      448] @ 0xc635c8 0xafca3b 0xb09ba0 0xb09ec0 0xb12fec 0xb13a92 0xb13c93 0xb13d9d 0xa02777 0xbbff77 0xa026ec 0x5701e2 0x53541a 0x535144 0x5aa468 0x7e3ce7 0x7d13a2 0x7e0d28 0x6ab450 0x538d27 0x5390e8 0x5391e3 0x4e9603 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d 0xa456e4\n    47:  1925120 [    47:  1925120] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     6656 [     1:     6656] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n    11:   292864 [    11:   292864] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     4096 [     1:     4096] @ 0xc635c8 0x75373b 0x7eb2d3 0x7ecc87 0x7ece56 0x7ed1ce 0x7ed360 0x7edb1a 0x7edbb5 0x7d50b0 0x4b9ba6 0x4b9f62 0x4ba025 0x40bd86 0x7f47a4bab11d\n     1:      112 [     1:      112] @ 0xc635c8 0x430498 0x435425 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:    20480 [     1:    20480] @ 0xc635c8 0x5a8b92 0x526bff 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:       48 [     1:       48] @ 0xc635c8 0x720c2e 0x5d35f0 0xbaa17f 0xbaabc6 0x42f03d 0xbaa17f 0xbaa9f9 0xbb0d21 0x40bce4 0x7f47a4bab11d\n     1:     8192 [     1:     8192] @ 0xc635c8 0xaaf3e6 0xab0ba0 0xab11be 0xab1639 0x52ebdc 0x527f32 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     2:   131072 [     2:   131072] @ 0xc635c8 0xaaf469 0xaad4ce 0xb66bcd 0xb670f2 0xb659b5 0x63689b 0x548172 0x520cdc 0x521b82 0x5194c9 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:     8192 [     1:     8192] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n     1:      512 [     1:      512] @ 0xc635c8 0xaff12a 0xb0b331 0xb12f10 0xb13a92 0xb0c443 0xb145f3 0xb147ca 0xa5dddd 0xbbffe6 0xa5e837 0xa65f94 0x5aac9e 0x535526 0x535144 0x5aa468 0x7e3ce7 0x7d13a2 0x7e0d28 0x6ab450 0x538d27 0x5390e8 0x5391e3 0x4e9603 0x4faa17 0x4fc5f6 0x4fd028 0x4fd230 0x79e80a 0x7a251b 0x7a296d\n     1:     4608 [     1:     4608] @ 0xc635c8 0x464379 0xa6318d 0x7feee9 0x5ab69c 0x7b0b26 0x79e81a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n    23:   753664 [    23:   753664] @ 0xc635c8 0x42ecc3 0x42e14c 0x5261af 0x526edf 0x5280ab 0x79e80a 0x7a251b 0x7a296d 0xa456e4 0x7f47a54360fe\n--- Memory map: ---\n\tsource=/home\n  00400000-00fcb000: $source/cppbench_server_main\n  7f47a4351000-7f47a4352000: /lib/libnss_borg-2.15.so\n  7f47a4554000-7f47a4560000: /lib/libnss_files-2.15.so\n  7f47a4b73000-7f47a4d21000: /lib/libc-2.15.so\n  7f47a4f2b000-7f47a5026000: /lib/libm-2.15.so\n  7f47a5227000-7f47a522e000: /lib/librt-2.15.so\n  7f47a542f000-7f47a5447000: /lib/libpthread-2.15.so\n  7f47a564c000-7f47a564e000: /lib/libdl-2.15.so\n  7f47a5850000-7f47a5859000: /lib/libcrypt-2.15.so\n  7f47a5a89000-7f47a5aad000: /lib/ld-2.15.so\n  7fff63dfe000-7fff63e00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n\n"
  },
  {
    "path": "profile/testdata/cppbench.heap.string",
    "content": "PeriodType: space bytes\nPeriod: 524288\nSamples:\nobjects/count space/bytes\n         57     528909: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[9216]\n       3641     524360: 1 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 17 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \n                bytes:[144]\n        227    3727658: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[16384]\n        293     525184: 1 41 42 5 6 7 8 9 10 11 \n                bytes:[1792]\n        283    6976735: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[24576]\n        293     525184: 1 43 44 45 46 47 48 49 50 51 52 53 54 \n                bytes:[1792]\n          1    2198218: 55 56 57 58 59 60 61 62 7 8 9 10 11 \n                bytes:[2162688]\n      10923     524312: 1 63 64 65 66 67 68 69 70 71 \n                bytes:[48]\n          2     666237: 1 72 73 74 75 76 77 78 79 7 8 9 10 11 \n                bytes:[262144]\n       1638     524448: 1 80 81 82 83 84 4 5 6 7 8 9 10 11 \n                bytes:[320]\n        293     525184: 1 85 86 66 68 87 66 88 89 70 71 \n                bytes:[1792]\n         51     529424: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[10240]\n        417    8553514: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[20480]\n       3277     524368: 1 90 91 92 93 94 10 11 \n                bytes:[160]\n         64     528394: 1 95 96 97 98 7 8 9 10 11 \n                bytes:[8192]\n         86    1060911: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[12288]\n          1    2136279: 55 99 57 58 59 60 61 62 7 8 9 10 11 \n                bytes:[2097152]\n       1170     524512: 1 100 101 102 103 104 105 106 107 75 108 109 110 31 32 33 34 35 36 37 38 39 40 111 112 113 114 7 8 9 10 \n                bytes:[448]\n        625   25616628: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[40960]\n         79     527623: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[6656]\n        222    5914839: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[26624]\n        128     526338: 1 115 116 117 118 119 120 121 122 123 124 125 126 127 71 \n                bytes:[4096]\n       4681     524344: 1 128 84 4 5 6 7 8 9 10 11 \n                bytes:[112]\n         26     534594: 1 129 130 6 7 8 9 10 11 \n                bytes:[20480]\n      10923     524312: 1 131 132 66 68 133 66 88 89 70 71 \n                bytes:[48]\n         64     528394: 1 134 135 136 137 138 98 7 8 9 10 11 \n                bytes:[8192]\n         17    1115476: 1 95 139 140 141 142 143 144 145 146 147 4 5 6 7 8 9 10 11 \n                bytes:[65536]\n         64     528394: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[8192]\n       1024     524544: 1 148 149 150 104 151 24 25 26 17 27 28 29 30 31 32 33 34 35 36 37 38 39 40 111 112 113 114 7 8 9 \n                bytes:[512]\n        114     526595: 1 152 153 154 155 156 157 8 9 10 11 \n                bytes:[4608]\n        379   12439381: 1 2 3 4 5 6 7 8 9 10 11 \n                bytes:[32768]\nLocations\n     1: 0xc635c7 M=1 \n     2: 0x42ecc2 M=1 \n     3: 0x42e14b M=1 \n     4: 0x5261ae M=1 \n     5: 0x526ede M=1 \n     6: 0x5280aa M=1 \n     7: 0x79e809 M=1 \n     8: 0x7a251a M=1 \n     9: 0x7a296c M=1 \n    10: 0xa456e3 M=1 \n    11: 0x7f47a54360fd M=7 \n    12: 0xa7479a M=1 \n    13: 0xb65e6a M=1 \n    14: 0xb65f7f M=1 \n    15: 0xa6d068 M=1 \n    16: 0xa6dc7f M=1 \n    17: 0xbbffe5 M=1 \n    18: 0xa5dd83 M=1 \n    19: 0xa7b7c5 M=1 \n    20: 0xaa88d9 M=1 \n    21: 0xaa9db1 M=1 \n    22: 0xb59bad M=1 \n    23: 0xb0c39b M=1 \n    24: 0xb145f2 M=1 \n    25: 0xb147c9 M=1 \n    26: 0xa5dddc M=1 \n    27: 0xa5e836 M=1 \n    28: 0xa65f93 M=1 \n    29: 0x5aac9d M=1 \n    30: 0x535525 M=1 \n    31: 0x535143 M=1 \n    32: 0x5aa467 M=1 \n    33: 0x7e3ce6 M=1 \n    34: 0x7d13a1 M=1 \n    35: 0x7e0d27 M=1 \n    36: 0x6ab44f M=1 \n    37: 0x538d26 M=1 \n    38: 0x5390e7 M=1 \n    39: 0x5391e2 M=1 \n    40: 0x4e9602 M=1 \n    41: 0x51a271 M=1 \n    42: 0x524996 M=1 \n    43: 0xac959f M=1 \n    44: 0xacdc7b M=1 \n    45: 0xace07a M=1 \n    46: 0xace1ab M=1 \n    47: 0xabd0ff M=1 \n    48: 0xabe2a8 M=1 \n    49: 0x72f52d M=1 \n    50: 0x655375 M=1 \n    51: 0x6558d2 M=1 \n    52: 0x41c710 M=1 \n    53: 0xc25cc5 M=1 \n    54: 0x40651a M=1 \n    55: 0xc63567 M=1 \n    56: 0xbc462d M=1 \n    57: 0xbc4bb4 M=1 \n    58: 0xbc4ed9 M=1 \n    59: 0x4a57b7 M=1 \n    60: 0x4b152b M=1 \n    61: 0x4ae04b M=1 \n    62: 0x4ad224 M=1 \n    63: 0x7be149 M=1 \n    64: 0x7be674 M=1 \n    65: 0x6b312c M=1 \n    66: 0xbaa17e M=1 \n    67: 0xbaa141 M=1 \n    68: 0xbaabc5 M=1 \n    69: 0xbb092b M=1 \n    70: 0x40bce3 M=1 \n    71: 0x7f47a4bab11c M=4 \n    72: 0x8168ff M=1 \n    73: 0x8149fc M=1 \n    74: 0x8139f3 M=1 \n    75: 0xbbff76 M=1 \n    76: 0x81421b M=1 \n    77: 0x4ed413 M=1 \n    78: 0x4fd706 M=1 \n    79: 0x4de2a1 M=1 \n    80: 0x721a58 M=1 \n    81: 0x43005d M=1 \n    82: 0x7382a3 M=1 \n    83: 0x43058f M=1 \n    84: 0x435424 M=1 \n    85: 0x5413af M=1 \n    86: 0x541ab1 M=1 \n    87: 0x53507b M=1 \n    88: 0xbaa9f8 M=1 \n    89: 0xbb0d20 M=1 \n    90: 0x578704 M=1 \n    91: 0x586246 M=1 \n    92: 0x592614 M=1 \n    93: 0x592744 M=1 \n    94: 0x592cb8 M=1 \n    95: 0xaaf468 M=1 \n    96: 0x52cad6 M=1 \n    97: 0x52e89a M=1 \n    98: 0x527f31 M=1 \n    99: 0xbc463a M=1 \n   100: 0xafca3a M=1 \n   101: 0xb09b9f M=1 \n   102: 0xb09ebf M=1 \n   103: 0xb12feb M=1 \n   104: 0xb13a91 M=1 \n   105: 0xb13c92 M=1 \n   106: 0xb13d9c M=1 \n   107: 0xa02776 M=1 \n   108: 0xa026eb M=1 \n   109: 0x5701e1 M=1 \n   110: 0x535419 M=1 \n   111: 0x4faa16 M=1 \n   112: 0x4fc5f5 M=1 \n   113: 0x4fd027 M=1 \n   114: 0x4fd22f M=1 \n   115: 0x75373a M=1 \n   116: 0x7eb2d2 M=1 \n   117: 0x7ecc86 M=1 \n   118: 0x7ece55 M=1 \n   119: 0x7ed1cd M=1 \n   120: 0x7ed35f M=1 \n   121: 0x7edb19 M=1 \n   122: 0x7edbb4 M=1 \n   123: 0x7d50af M=1 \n   124: 0x4b9ba5 M=1 \n   125: 0x4b9f61 M=1 \n   126: 0x4ba024 M=1 \n   127: 0x40bd85 M=1 \n   128: 0x430497 M=1 \n   129: 0x5a8b91 M=1 \n   130: 0x526bfe M=1 \n   131: 0x720c2d M=1 \n   132: 0x5d35ef M=1 \n   133: 0x42f03c M=1 \n   134: 0xaaf3e5 M=1 \n   135: 0xab0b9f M=1 \n   136: 0xab11bd M=1 \n   137: 0xab1638 M=1 \n   138: 0x52ebdb M=1 \n   139: 0xaad4cd M=1 \n   140: 0xb66bcc M=1 \n   141: 0xb670f1 M=1 \n   142: 0xb659b4 M=1 \n   143: 0x63689a M=1 \n   144: 0x548171 M=1 \n   145: 0x520cdb M=1 \n   146: 0x521b81 M=1 \n   147: 0x5194c8 M=1 \n   148: 0xaff129 M=1 \n   149: 0xb0b330 M=1 \n   150: 0xb12f0f M=1 \n   151: 0xb0c442 M=1 \n   152: 0x464378 M=1 \n   153: 0xa6318c M=1 \n   154: 0x7feee8 M=1 \n   155: 0x5ab69b M=1 \n   156: 0x7b0b25 M=1 \n   157: 0x79e819 M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 /home/cppbench_server_main  \n2: 0x7f47a4351000/0x7f47a4352000/0x0 /lib/libnss_borg-2.15.so  \n3: 0x7f47a4554000/0x7f47a4560000/0x0 /lib/libnss_files-2.15.so  \n4: 0x7f47a4b73000/0x7f47a4d21000/0x0 /lib/libc-2.15.so  \n5: 0x7f47a4f2b000/0x7f47a5026000/0x0 /lib/libm-2.15.so  \n6: 0x7f47a5227000/0x7f47a522e000/0x0 /lib/librt-2.15.so  \n7: 0x7f47a542f000/0x7f47a5447000/0x0 /lib/libpthread-2.15.so  \n8: 0x7f47a564c000/0x7f47a564e000/0x0 /lib/libdl-2.15.so  \n9: 0x7f47a5850000/0x7f47a5859000/0x0 /lib/libcrypt-2.15.so  \n10: 0x7f47a5a89000/0x7f47a5aad000/0x0 /lib/ld-2.15.so  \n11: 0x7fff63dfe000/0x7fff63e00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.thread",
    "content": "--- threadz 1 ---\n\n--- Thread 7f794ab90940 (name: main/14748) stack: ---\n  PC:  0x00bc8f1c: helper(arg *)\n  0x0040be31: main\n  0x7f7949a9811d: __libc_start_main\n--- Thread 7f794964e700 (name: thread1/14751) stack: ---\n  PC:  0x7f794a32bf7d: nanosleep\n  0x7f794a32414e: start_thread\n      creator: 0xa45b96 0xa460b4 0xbaa17f 0xbaa9f9 0xbb0d21 0x40bce4 0x7f7949a9811d\n--- Thread 7f794934c700 (name: thread2/14752) stack: ---\n  PC:  0x00bc8f1c: Wait(int)\n  0x7f794a32414e: start_thread\n      creator: 0xa45b96 0xa48928 0xbaa17f 0xbaa9f9 0xbb0d21 0x40bce4 0x7f7949a9811d\n--- Thread 7f7948978700 (name: thread3/14759) stack: ---\n  [same as previous thread]\n--- Memory map: ---\n  00400000-00fcb000: /home/rsilvera/cppbench/cppbench_server_main\n  7f794964f000-7f7949652000: /lib/libnss_cache-2.15.so\n  7f7949853000-7f794985f000: /lib/libnss_files-2.15.so\n  7f7949a60000-7f7949c0e000: /lib/libc-2.15.so\n  7f7949e19000-7f7949f14000: /lib/libm-2.15.so\n  7f794a115000-7f794a11c000: /lib/librt-2.15.so\n  7f794a31d000-7f794a335000: /lib/libpthread-2.15.so\n  7f794a53a000-7f794a53d000: /lib/libdl-2.15.so\n  7f794a73e000-7f794a747000: /lib/libcrypt-2.15.so\n  7f794a977000-7f794a99b000: /lib/ld-2.15.so\n  7fffb8dff000-7fffb8e00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "profile/testdata/cppbench.thread.all",
    "content": "--- threadz 1 ---\n\n--- Thread 7eff063d9940 (name: main/25376) stack: ---\n  PC:  0x00bc8f1c: helper(arg*)\n  0x0040be31: main\n  0x7eff052e111d: __libc_start_main\n--- Thread 7eff04e97700 (name: thread1/25379) stack: ---\n  PC:  0x7eff05b74f7d: nanosleep\n  0x7eff05b6d14e: start_thread\n      creator:\n  0x0040bce4: main\n  0x7eff052e111d: __libc_start_main\n--- Thread 7eff04770700 (name: thread2/25382) stack: ---\n  PC:  0x00bc8f1c: Wait(int)\n  0x7eff05b6d14e: start_thread\n      creator:\n  0x0040bd6e: main\n  0x7eff052e111d: __libc_start_main\n--- Thread 7eff0464d700 (name: thread3/25383) stack: ---\n  [same as previous thread]\n--- Memory map: ---\n  00400000-00fcb000: /home/rsilvera/cppbench/cppbench_server_main\n  7eff04e98000-7eff04e9b000: /lib/libnss_cache-2.15.so\n  7eff0509c000-7eff050a8000: /lib/libnss_files-2.15.so\n  7eff052a9000-7eff05457000: /lib/libc-2.15.so\n  7eff05662000-7eff0575d000: /lib/libm-2.15.so\n  7eff0595e000-7eff05965000: /lib/librt-2.15.so\n  7eff05b66000-7eff05b7e000: /lib/libpthread-2.15.so\n  7eff05d83000-7eff05d86000: /lib/libdl-2.15.so\n  7eff05f87000-7eff05f90000: /lib/libcrypt-2.15.so\n  7eff061c0000-7eff061e4000: /lib/ld-2.15.so\n  7fff2edff000-7fff2ee00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "profile/testdata/cppbench.thread.all.string",
    "content": "PeriodType: thread count\nPeriod: 1\nSamples:\nthread/count\n          1: 1 2 3 \n          1: 4 5 6 3 \n          2: 1 5 7 3 \nLocations\n     1: 0xbc8f1c M=1 \n     2: 0x40be30 M=1 \n     3: 0x7eff052e111c M=4 \n     4: 0x7eff05b74f7d M=7 \n     5: 0x7eff05b6d14d M=7 \n     6: 0x40bce3 M=1 \n     7: 0x40bd6d M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 /home/rsilvera/cppbench/cppbench_server_main  \n2: 0x7eff04e98000/0x7eff04e9b000/0x0 /lib/libnss_cache-2.15.so  \n3: 0x7eff0509c000/0x7eff050a8000/0x0 /lib/libnss_files-2.15.so  \n4: 0x7eff052a9000/0x7eff05457000/0x0 /lib/libc-2.15.so  \n5: 0x7eff05662000/0x7eff0575d000/0x0 /lib/libm-2.15.so  \n6: 0x7eff0595e000/0x7eff05965000/0x0 /lib/librt-2.15.so  \n7: 0x7eff05b66000/0x7eff05b7e000/0x0 /lib/libpthread-2.15.so  \n8: 0x7eff05d83000/0x7eff05d86000/0x0 /lib/libdl-2.15.so  \n9: 0x7eff05f87000/0x7eff05f90000/0x0 /lib/libcrypt-2.15.so  \n10: 0x7eff061c0000/0x7eff061e4000/0x0 /lib/ld-2.15.so  \n11: 0x7fff2edff000/0x7fff2ee00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.thread.none",
    "content": "--- threadz 1 ---\n\n--- Thread 7eff063d9940 (name: main/25376) stack: ---\n  PC: 0xbc8f1c 0xbcae55 0xbcb5f5 0x40b688 0x4d5f51 0x40be31 0x7eff052e111d\n--- Thread 7eff04b95700 (name: thread1/25380) stack: ---\n  PC: 0xbc8f1c 0xbcbd00 0xa47f60 0xa456e4 0x7eff05b6d14e\n      creator: 0xa45b96 0xa48928 0xbaa17f 0xbaa9f9 0xbb0d21 0x40bce4 0x7eff052e111d\n--- Thread 7eff04893700 (name: thread2/25381) stack: ---\n  PC: 0x7eff052dfa93 0x7a1956 0x7a1c45 0x7a2727 0x7a296d 0xa456e4\n      0x7eff05b6d14e\n      creator: 0xa45b96 0x7a37d2 0x7a3e8d 0xbbff77 0x79ec1c 0x40bd6e 0x7eff052e111d\n--- Thread 7eff04770700 (name: thread3/25382) stack: ---\n  PC: 0xbc8f1c 0x7a2691 0x7a296d 0xa456e4 0x7eff05b6d14e\n      creator: 0xa45b96 0x7a37d2 0x7a3e8d 0xbbff77 0x79ec1c 0x40bd6e 0x7eff052e111d\n--- Memory map: ---\n  00400000-00fcb000: /home/rsilvera/cppbench/cppbench_server_main.unstripped\n  7eff04e98000-7eff04e9b000: /lib/libnss_cache-2.15.so\n  7eff0509c000-7eff050a8000: /lib/libnss_files-2.15.so\n  7eff052a9000-7eff05457000: /lib/libc-2.15.so\n  7eff05662000-7eff0575d000: /lib/libm-2.15.so\n  7eff0595e000-7eff05965000: /lib/librt-2.15.so\n  7eff05b66000-7eff05b7e000: /lib/libpthread-2.15.so\n  7eff05d83000-7eff05d86000: /lib/libdl-2.15.so\n  7eff05f87000-7eff05f90000: /lib/libcrypt-2.15.so\n  7eff061c0000-7eff061e4000: /lib/ld-2.15.so\n  7fff2edff000-7fff2ee00000: [vdso]\n  ffffffffff600000-ffffffffff601000: [vsyscall]\n"
  },
  {
    "path": "profile/testdata/cppbench.thread.none.string",
    "content": "PeriodType: thread count\nPeriod: 1\nSamples:\nthread/count\n          1: 1 2 3 4 5 6 7 \n          1: 1 8 9 10 11 12 13 14 15 16 17 7 \n          1: 18 19 20 21 22 10 11 12 23 24 25 26 27 7 \n          1: 1 28 22 10 11 12 23 24 25 26 27 7 \nLocations\n     1: 0xbc8f1c M=1 \n     2: 0xbcae54 M=1 \n     3: 0xbcb5f4 M=1 \n     4: 0x40b687 M=1 \n     5: 0x4d5f50 M=1 \n     6: 0x40be30 M=1 \n     7: 0x7eff052e111c M=4 \n     8: 0xbcbcff M=1 \n     9: 0xa47f5f M=1 \n    10: 0xa456e3 M=1 \n    11: 0x7eff05b6d14d M=7 \n    12: 0xa45b95 M=1 \n    13: 0xa48927 M=1 \n    14: 0xbaa17e M=1 \n    15: 0xbaa9f8 M=1 \n    16: 0xbb0d20 M=1 \n    17: 0x40bce3 M=1 \n    18: 0x7eff052dfa93 M=4 \n    19: 0x7a1955 M=1 \n    20: 0x7a1c44 M=1 \n    21: 0x7a2726 M=1 \n    22: 0x7a296c M=1 \n    23: 0x7a37d1 M=1 \n    24: 0x7a3e8c M=1 \n    25: 0xbbff76 M=1 \n    26: 0x79ec1b M=1 \n    27: 0x40bd6d M=1 \n    28: 0x7a2690 M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 /home/rsilvera/cppbench/cppbench_server_main.unstripped  \n2: 0x7eff04e98000/0x7eff04e9b000/0x0 /lib/libnss_cache-2.15.so  \n3: 0x7eff0509c000/0x7eff050a8000/0x0 /lib/libnss_files-2.15.so  \n4: 0x7eff052a9000/0x7eff05457000/0x0 /lib/libc-2.15.so  \n5: 0x7eff05662000/0x7eff0575d000/0x0 /lib/libm-2.15.so  \n6: 0x7eff0595e000/0x7eff05965000/0x0 /lib/librt-2.15.so  \n7: 0x7eff05b66000/0x7eff05b7e000/0x0 /lib/libpthread-2.15.so  \n8: 0x7eff05d83000/0x7eff05d86000/0x0 /lib/libdl-2.15.so  \n9: 0x7eff05f87000/0x7eff05f90000/0x0 /lib/libcrypt-2.15.so  \n10: 0x7eff061c0000/0x7eff061e4000/0x0 /lib/ld-2.15.so  \n11: 0x7fff2edff000/0x7fff2ee00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/cppbench.thread.string",
    "content": "PeriodType: thread count\nPeriod: 1\nSamples:\nthread/count\n          1: 1 2 3 \n          1: 4 5 6 7 8 9 10 11 3 \n          2: 1 5 6 12 8 9 10 11 3 \nLocations\n     1: 0xbc8f1c M=1 \n     2: 0x40be30 M=1 \n     3: 0x7f7949a9811c M=4 \n     4: 0x7f794a32bf7d M=7 \n     5: 0x7f794a32414d M=7 \n     6: 0xa45b95 M=1 \n     7: 0xa460b3 M=1 \n     8: 0xbaa17e M=1 \n     9: 0xbaa9f8 M=1 \n    10: 0xbb0d20 M=1 \n    11: 0x40bce3 M=1 \n    12: 0xa48927 M=1 \nMappings\n1: 0x400000/0xfcb000/0x0 /home/rsilvera/cppbench/cppbench_server_main  \n2: 0x7f794964f000/0x7f7949652000/0x0 /lib/libnss_cache-2.15.so  \n3: 0x7f7949853000/0x7f794985f000/0x0 /lib/libnss_files-2.15.so  \n4: 0x7f7949a60000/0x7f7949c0e000/0x0 /lib/libc-2.15.so  \n5: 0x7f7949e19000/0x7f7949f14000/0x0 /lib/libm-2.15.so  \n6: 0x7f794a115000/0x7f794a11c000/0x0 /lib/librt-2.15.so  \n7: 0x7f794a31d000/0x7f794a335000/0x0 /lib/libpthread-2.15.so  \n8: 0x7f794a53a000/0x7f794a53d000/0x0 /lib/libdl-2.15.so  \n9: 0x7f794a73e000/0x7f794a747000/0x0 /lib/libcrypt-2.15.so  \n10: 0x7f794a977000/0x7f794a99b000/0x0 /lib/ld-2.15.so  \n11: 0x7fffb8dff000/0x7fffb8e00000/0x0 [vdso]  \n12: 0xffffffffff600000/0xffffffffff601000/0x0 [vsyscall]  \n"
  },
  {
    "path": "profile/testdata/go.crc32.cpu.string",
    "content": "PeriodType: cpu nanoseconds\nPeriod: 10000000\nSamples:\nsamples/count cpu/nanoseconds\n          1   10000000: 1 2 3 4 5 \n          2   20000000: 6 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          2   20000000: 8 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          4   40000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          2   20000000: 6 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          2   20000000: 6 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          2   20000000: 8 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          2   20000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          3   30000000: 7 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          2   20000000: 1 2 3 4 5 \n          2   20000000: 7 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          2   20000000: 7 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n          2   20000000: 6 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 6 2 3 4 5 \n          1   10000000: 8 2 3 4 5 \n          1   10000000: 1 2 3 4 5 \n         85  850000000: 9 2 3 4 5 \n         21  210000000: 10 2 3 4 5 \n          1   10000000: 7 2 3 4 5 \n         24  240000000: 11 2 3 4 5 \nLocations\n     1: 0x430b93 M=1 \n     2: 0x4317eb M=1 \n     3: 0x42a065 M=1 \n     4: 0x42a31b M=1 \n     5: 0x415d0f M=1 \n     6: 0x430baa M=1 \n     7: 0x430bb5 M=1 \n     8: 0x430ba6 M=1 \n     9: 0x430bac M=1 \n    10: 0x430b9f M=1 \n    11: 0x430bb3 M=1 \nMappings\n1: 0x0/0xffffffffffffffff/0x0   \n"
  },
  {
    "path": "profile/testdata/go.godoc.thread",
    "content": "threadcreate profile: total 7\n1 @ 0x44cb3 0x45045 0x45323 0x45534 0x47e9c 0x47c98 0x44ba2 0x2720fe 0x271fb5\n1 @ 0x44cb3 0x45045 0x45323 0x45534 0x46716 0x51584 0x461e0\n1 @ 0x44cb3 0x45045 0x45323 0x45547 0x46716 0x40963 0x461e0\n1 @ 0x44cb3 0x45045 0x45323 0x45547 0x4562e 0x460ed 0x51a59\n1 @ 0x44cb3 0x45045 0x441ae 0x461e0\n1 @ 0x44cb3 0x44e04 0x44b80 0x5192d\n1 @ 0x440e2 0x5191a\n"
  },
  {
    "path": "profile/testdata/go.godoc.thread.string",
    "content": "PeriodType: threadcreate count\nPeriod: 1\nSamples:\nthreadcreate/count\n          1: 1 2 3 4 5 6 7 8 9 \n          1: 1 2 3 4 10 11 12 \n          1: 1 2 3 13 10 14 12 \n          1: 1 2 3 13 15 16 17 \n          1: 1 2 18 12 \n          1: 1 19 20 21 \n          1: 22 23 \nLocations\n     1: 0x44cb2 M=1 \n     2: 0x45044 M=1 \n     3: 0x45322 M=1 \n     4: 0x45533 M=1 \n     5: 0x47e9b M=1 \n     6: 0x47c97 M=1 \n     7: 0x44ba1 M=1 \n     8: 0x2720fd M=1 \n     9: 0x271fb4 M=1 \n    10: 0x46715 M=1 \n    11: 0x51583 M=1 \n    12: 0x461df M=1 \n    13: 0x45546 M=1 \n    14: 0x40962 M=1 \n    15: 0x4562d M=1 \n    16: 0x460ec M=1 \n    17: 0x51a58 M=1 \n    18: 0x441ad M=1 \n    19: 0x44e03 M=1 \n    20: 0x44b7f M=1 \n    21: 0x5192c M=1 \n    22: 0x440e1 M=1 \n    23: 0x51919 M=1 \nMappings\n1: 0x0/0xffffffffffffffff/0x0   \n"
  },
  {
    "path": "profile/testdata/gobench.cpu.string",
    "content": "PeriodType: cpu nanoseconds\nPeriod: 10000000\nSamples:\nsamples/count cpu/nanoseconds\n          1   10000000: 1 2 \n          1   10000000: 3 2 \n          1   10000000: 4 2 \n          1   10000000: 5 2 \n          1   10000000: 6 2 \n          1   10000000: 7 2 \n          1   10000000: 8 2 \n          1   10000000: 9 2 \n          1   10000000: 10 2 \n          1   10000000: 11 2 \n          1   10000000: 12 2 \n          1   10000000: 13 2 \n          1   10000000: 14 2 \n          1   10000000: 15 2 \n          1   10000000: 16 2 \n          1   10000000: 17 2 \n          1   10000000: 18 2 \n          1   10000000: 16 2 \n          1   10000000: 19 2 \n          1   10000000: 20 2 \n          1   10000000: 21 2 \n          1   10000000: 22 2 \n          1   10000000: 23 2 \n          1   10000000: 24 2 \n          1   10000000: 25 2 \n          1   10000000: 15 2 \n          1   10000000: 26 2 \n          1   10000000: 9 2 \n          1   10000000: 27 2 \n          1   10000000: 28 2 \n          1   10000000: 29 2 \n          1   10000000: 30 2 \n          1   10000000: 31 2 \n          1   10000000: 32 2 \n          1   10000000: 24 2 \n          1   10000000: 30 2 \n          1   10000000: 33 2 \n          1   10000000: 34 2 \n          1   10000000: 35 2 \n          1   10000000: 36 2 \n          1   10000000: 27 2 \n          1   10000000: 37 2 \n          1   10000000: 38 2 \n          1   10000000: 19 2 \n          1   10000000: 39 2 \n          1   10000000: 40 2 \n          1   10000000: 41 2 \n          1   10000000: 16 2 \n          1   10000000: 42 2 \n          1   10000000: 43 2 \n          1   10000000: 44 2 \n          1   10000000: 45 2 \n          1   10000000: 46 2 \n          1   10000000: 47 2 \n          1   10000000: 48 2 \n          1   10000000: 40 2 \n          1   10000000: 10 2 \n          1   10000000: 49 2 \n          1   10000000: 50 2 \n          1   10000000: 51 2 \n          1   10000000: 52 2 \n          1   10000000: 53 2 \n          1   10000000: 30 2 \n          1   10000000: 54 2 \n          1   10000000: 55 2 \n          1   10000000: 36 2 \n          1   10000000: 56 2 \n          1   10000000: 57 2 \n          1   10000000: 58 2 \n          1   10000000: 59 2 \n          1   10000000: 60 2 \n          1   10000000: 61 2 \n          1   10000000: 57 2 \n          1   10000000: 62 2 \n          1   10000000: 63 2 \n          1   10000000: 30 2 \n          1   10000000: 64 2 \n          1   10000000: 16 2 \n          1   10000000: 65 2 \n          1   10000000: 26 2 \n          1   10000000: 40 2 \n          1   10000000: 66 2 \n          1   10000000: 58 2 \n          1   10000000: 67 2 \n          1   10000000: 68 2 \n          1   10000000: 69 2 \n          1   10000000: 70 2 \n          1   10000000: 71 2 \n          1   10000000: 72 2 \n          1   10000000: 51 2 \n          1   10000000: 73 2 \n          1   10000000: 74 2 \n          1   10000000: 75 2 \n          1   10000000: 76 2 \n          1   10000000: 77 2 \n          1   10000000: 78 2 \n          1   10000000: 79 2 \n          1   10000000: 80 2 \n          1   10000000: 81 2 \n          1   10000000: 82 2 \n          1   10000000: 83 2 \n          1   10000000: 84 2 \n          1   10000000: 85 2 \n          1   10000000: 86 2 \n          1   10000000: 10 2 \n          1   10000000: 87 2 \n          1   10000000: 88 2 \n          1   10000000: 89 2 \n          1   10000000: 90 2 \n          1   10000000: 63 2 \n          1   10000000: 91 2 \n          1   10000000: 5 2 \n          1   10000000: 92 2 \n          1   10000000: 93 2 \n          1   10000000: 94 2 \n          1   10000000: 19 2 \n          1   10000000: 95 2 \n          1   10000000: 30 2 \n          1   10000000: 96 2 \n          1   10000000: 10 2 \n          1   10000000: 97 2 \n          1   10000000: 98 2 \n          1   10000000: 99 2 \n          1   10000000: 62 2 \n          1   10000000: 92 2 \n          1   10000000: 100 2 \n          1   10000000: 101 2 \n          1   10000000: 39 2 \n          1   10000000: 102 2 \n          1   10000000: 86 2 \n          1   10000000: 33 2 \n          1   10000000: 103 2 \n          1   10000000: 104 2 \n          1   10000000: 13 2 \n          2   20000000: 105 2 \n          1   10000000: 106 2 \n          1   10000000: 52 2 \n          1   10000000: 24 2 \n          1   10000000: 107 2 \n          1   10000000: 108 2 \n          1   10000000: 52 2 \n          1   10000000: 109 2 \n          1   10000000: 5 2 \n          1   10000000: 82 2 \n          1   10000000: 8 2 \n          1   10000000: 110 2 \n          1   10000000: 111 2 \n          1   10000000: 112 2 \n          1   10000000: 113 2 \n          1   10000000: 114 2 \n          1   10000000: 115 2 \n          1   10000000: 116 2 \n          1   10000000: 19 2 \n          1   10000000: 64 2 \n          1   10000000: 106 2 \n          1   10000000: 117 2 \n          1   10000000: 30 2 \n          1   10000000: 118 2 \n          1   10000000: 86 2 \n          1   10000000: 119 2 \n          1   10000000: 120 2 \n          1   10000000: 121 2 \n          1   10000000: 81 2 \n          2   20000000: 10 2 \n          1   10000000: 19 2 \n          1   10000000: 122 2 \n          1   10000000: 123 2 \n          1   10000000: 105 2 \n          1   10000000: 124 2 \n          1   10000000: 125 2 \n          1   10000000: 46 2 \n          1   10000000: 8 2 \n         10  100000000: 21 2 \n          7   70000000: 126 2 \n          3   30000000: 9 2 \n          1   10000000: 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 \n          1   10000000: 144 2 \n          5   50000000: 145 2 \n         25  250000000: 146 2 \n          1   10000000: 147 2 \n          1   10000000: 148 149 150 134 135 136 137 138 139 140 141 142 143 \n          1   10000000: 151 152 153 154 155 135 136 137 138 139 140 141 142 143 \n          1   10000000: 156 157 153 154 155 135 136 137 138 139 140 141 142 143 \n          1   10000000: 158 159 132 133 134 135 136 137 138 139 140 141 142 143 \n          4   40000000: 27 2 \n          4   40000000: 160 2 \n          1   10000000: 116 2 \n          5   50000000: 161 2 \n         20  200000000: 162 163 164 135 136 137 138 139 140 141 142 143 \n          1   10000000: 165 166 167 164 135 136 137 138 139 140 141 142 143 \n          1   10000000: 168 169 167 164 135 136 137 138 139 140 141 142 143 \n          2   20000000: 170 171 172 142 143 \n          2   20000000: 173 171 172 142 143 \n          1   10000000: 105 174 175 154 155 176 177 140 141 142 143 \n          1   10000000: 178 179 176 177 140 141 142 143 \n          1   10000000: 180 181 182 181 183 184 185 186 187 188 189 190 191 192 193 194 143 \n          7   70000000: 195 2 \n          2   20000000: 196 2 \n          8   80000000: 16 2 \n          1   10000000: 197 2 \n          1   10000000: 146 198 199 135 136 137 138 139 140 141 142 143 \n          1   10000000: 200 199 135 136 137 138 139 140 141 142 143 \n          3   30000000: 162 179 135 136 137 138 139 140 141 142 143 \n          1   10000000: 201 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 \n          1   10000000: 202 167 152 153 154 155 135 136 137 138 139 140 141 142 143 \n          6   60000000: 162 163 152 153 154 155 135 136 137 138 139 140 141 142 143 \nLocations\n     1: 0x410bc0 M=1 \n     2: 0x41a770 M=1 \n     3: 0x410b4b M=1 \n     4: 0x40f534 M=1 \n     5: 0x40f018 M=1 \n     6: 0x421f4f M=1 \n     7: 0x40e46f M=1 \n     8: 0x40f0e3 M=1 \n     9: 0x4286c7 M=1 \n    10: 0x40f15b M=1 \n    11: 0x40efb1 M=1 \n    12: 0x41250d M=1 \n    13: 0x427854 M=1 \n    14: 0x40e688 M=1 \n    15: 0x410b61 M=1 \n    16: 0x40fa72 M=1 \n    17: 0x40e92a M=1 \n    18: 0x421ff1 M=1 \n    19: 0x42830d M=1 \n    20: 0x41cf23 M=1 \n    21: 0x40e7cb M=1 \n    22: 0x40ea46 M=1 \n    23: 0x40f792 M=1 \n    24: 0x40f023 M=1 \n    25: 0x40ee50 M=1 \n    26: 0x40c6ab M=1 \n    27: 0x40fa51 M=1 \n    28: 0x40f14b M=1 \n    29: 0x421fca M=1 \n    30: 0x4285d3 M=1 \n    31: 0x410ba9 M=1 \n    32: 0x40e75f M=1 \n    33: 0x4277a1 M=1 \n    34: 0x40e89f M=1 \n    35: 0x40ea54 M=1 \n    36: 0x40f0ab M=1 \n    37: 0x40ef9b M=1 \n    38: 0x410d6a M=1 \n    39: 0x40e455 M=1 \n    40: 0x427856 M=1 \n    41: 0x40e80b M=1 \n    42: 0x40f5ef M=1 \n    43: 0x40fb2a M=1 \n    44: 0x422786 M=1 \n    45: 0x40f031 M=1 \n    46: 0x40f49d M=1 \n    47: 0x40f331 M=1 \n    48: 0x40e927 M=1 \n    49: 0x40f558 M=1 \n    50: 0x410b56 M=1 \n    51: 0x40eac1 M=1 \n    52: 0x40e813 M=1 \n    53: 0x40e7df M=1 \n    54: 0x40f53d M=1 \n    55: 0x40f180 M=1 \n    56: 0x410b94 M=1 \n    57: 0x40fbf6 M=1 \n    58: 0x40f026 M=1 \n    59: 0x40f0dc M=1 \n    60: 0x40e9d3 M=1 \n    61: 0x40fa7b M=1 \n    62: 0x40e877 M=1 \n    63: 0x4048a8 M=1 \n    64: 0x40f02e M=1 \n    65: 0x4048b8 M=1 \n    66: 0x4277d0 M=1 \n    67: 0x40f5cb M=1 \n    68: 0x40fbae M=1 \n    69: 0x40e8c2 M=1 \n    70: 0x40f64b M=1 \n    71: 0x40e82e M=1 \n    72: 0x421f22 M=1 \n    73: 0x40fa67 M=1 \n    74: 0x40fbb1 M=1 \n    75: 0x40f568 M=1 \n    76: 0x40e461 M=1 \n    77: 0x40ef85 M=1 \n    78: 0x40f58b M=1 \n    79: 0x40f08d M=1 \n    80: 0x40e75c M=1 \n    81: 0x410c22 M=1 \n    82: 0x40fa59 M=1 \n    83: 0x40f091 M=1 \n    84: 0x40eb69 M=1 \n    85: 0x41075a M=1 \n    86: 0x40e7e9 M=1 \n    87: 0x40fa97 M=1 \n    88: 0x4131eb M=1 \n    89: 0x40f769 M=1 \n    90: 0x40f54e M=1 \n    91: 0x4277d5 M=1 \n    92: 0x40f0ca M=1 \n    93: 0x40f051 M=1 \n    94: 0x40e94f M=1 \n    95: 0x40fc11 M=1 \n    96: 0x41815b M=1 \n    97: 0x40f4b3 M=1 \n    98: 0x421fe8 M=1 \n    99: 0x40e79e M=1 \n   100: 0x413f29 M=1 \n   101: 0x427822 M=1 \n   102: 0x40ef3d M=1 \n   103: 0x40e440 M=1 \n   104: 0x40e767 M=1 \n   105: 0x42783b M=1 \n   106: 0x40fa85 M=1 \n   107: 0x40fb36 M=1 \n   108: 0x410bae M=1 \n   109: 0x40f0d7 M=1 \n   110: 0x410ba4 M=1 \n   111: 0x40e87b M=1 \n   112: 0x40e7c0 M=1 \n   113: 0x40eae0 M=1 \n   114: 0x410a99 M=1 \n   115: 0x40e7bd M=1 \n   116: 0x40f09d M=1 \n   117: 0x410b70 M=1 \n   118: 0x40f32d M=1 \n   119: 0x4283ec M=1 \n   120: 0x40f010 M=1 \n   121: 0x40e97a M=1 \n   122: 0x40f19a M=1 \n   123: 0x40e779 M=1 \n   124: 0x40f61d M=1 \n   125: 0x40f4e1 M=1 \n   126: 0x40f58f M=1 \n   127: 0x41ef43 M=1 \n   128: 0x41ef96 M=1 \n   129: 0x41f089 M=1 \n   130: 0x41f360 M=1 \n   131: 0x41fc8e M=1 \n   132: 0x4204c7 M=1 \n   133: 0x422b03 M=1 \n   134: 0x420cee M=1 \n   135: 0x422150 M=1 \n   136: 0x4221d9 M=1 \n   137: 0x41dc0c M=1 \n   138: 0x41db47 M=1 \n   139: 0x672125 M=1 \n   140: 0x4ac6fd M=1 \n   141: 0x4abf98 M=1 \n   142: 0x491fbd M=1 \n   143: 0x41931f M=1 \n   144: 0x40e844 M=1 \n   145: 0x421ff8 M=1 \n   146: 0x4277e4 M=1 \n   147: 0x40e990 M=1 \n   148: 0x41c53f M=1 \n   149: 0x422746 M=1 \n   150: 0x422b42 M=1 \n   151: 0x412b5f M=1 \n   152: 0x40d47b M=1 \n   153: 0x40cf5e M=1 \n   154: 0x40cceb M=1 \n   155: 0x420b5e M=1 \n   156: 0x413ab9 M=1 \n   157: 0x40d56e M=1 \n   158: 0x41f5a6 M=1 \n   159: 0x420149 M=1 \n   160: 0x40f531 M=1 \n   161: 0x410b8d M=1 \n   162: 0x427ac9 M=1 \n   163: 0x412b91 M=1 \n   164: 0x420ee3 M=1 \n   165: 0x4134a8 M=1 \n   166: 0x412dc7 M=1 \n   167: 0x412afa M=1 \n   168: 0x413a9d M=1 \n   169: 0x412bf6 M=1 \n   170: 0x671ed3 M=1 \n   171: 0x4ac6ad M=1 \n   172: 0x4abdd8 M=1 \n   173: 0x671ebe M=1 \n   174: 0x40c8ae M=1 \n   175: 0x40d00a M=1 \n   176: 0x422081 M=1 \n   177: 0x672148 M=1 \n   178: 0x427ad1 M=1 \n   179: 0x420e54 M=1 \n   180: 0x5718ff M=1 \n   181: 0x575ab6 M=1 \n   182: 0x572114 M=1 \n   183: 0x571257 M=1 \n   184: 0x462494 M=1 \n   185: 0x475ea6 M=1 \n   186: 0x473682 M=1 \n   187: 0x471fd7 M=1 \n   188: 0x471ac0 M=1 \n   189: 0x46f1b2 M=1 \n   190: 0x46ef32 M=1 \n   191: 0x4ab9e0 M=1 \n   192: 0x4acce1 M=1 \n   193: 0x4ac7b6 M=1 \n   194: 0x4ace6a M=1 \n   195: 0x410b8a M=1 \n   196: 0x40f56e M=1 \n   197: 0x428176 M=1 \n   198: 0x4120f3 M=1 \n   199: 0x420be8 M=1 \n   200: 0x412100 M=1 \n   201: 0x41ef39 M=1 \n   202: 0x412e38 M=1 \nMappings\n1: 0x0/0xffffffffffffffff/0x0   \n"
  },
  {
    "path": "profile/testdata/gobench.heap",
    "content": "heap profile: 13: 1595680 [47130736: 2584596557304] @ heap/1048576\n1: 524288 [3: 1572864] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x74920f 0x6295ac 0x629855 0x462769 0x419320\n1: 524288 [1: 524288] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x74920f 0x63963f 0x419320\n1: 262144 [1: 262144] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x451a39 0x451ba5 0x450683 0x450077 0x4525a4 0x58e034 0x419320\n1: 262144 [1: 262144] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x451a39 0x451ba5 0x450683 0x450077 0x4524d4 0x401090 0x4011a1 0x416dff 0x419320\n1: 10240 [642: 6574080] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x477637 0x47718b 0x477056 0x4799b2 0x46bfd7 0x419320\n1: 4096 [1: 4096] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x526126 0x5261ea 0x4683d4 0x467e09 0x419320\n1: 4096 [1: 4096] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41db48 0x53fbf3 0x53f85f 0x545f52 0x545a70 0x419320\n1: 2048 [1: 2048] @ 0x420cef 0x420fa9 0x414b22 0x414d20 0x4901be 0x419320\n1: 1280 [1: 1280] @ 0x420cef 0x422082 0x48dbe3 0x48d15c 0x48cdd0 0x4a9dc0 0x545bfe 0x543ac7 0x419320\n1: 384 [1: 384] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41dd68 0x41dcbd 0x429150 0x429add 0x42e013 0x4307e2 0x4366ff 0x42c1c2 0x653e4d 0x64bdc5 0x64c359 0x65a73d 0x64cdb1 0x64be73 0x64c359 0x64c59a 0x64c205 0x64c359 0x64b778 0x5cd55c 0x45dbc3 0x543e70 0x559166 0x55ba54 0x559691 0x559985 0x5a19ff 0x543e70\n1: 288 [1: 288] @ 0x420cef 0x420fa9 0x419e19 0x41a1a8 0x419f63 0x48f09f 0x48d991 0x48cdd0 0x4a9dc0 0x545bfe 0x543ac7 0x419320\n1: 288 [2: 296] @\n1: 96 [1: 96] @ 0x420cef 0x424f35 0x4255d1 0x6fc293 0x6f9c88 0x6f9944 0x6f96be 0x6f966b 0x59f39a 0x468318 0x467e09 0x419320\n0: 0 [1: 1024] @ 0x420cef 0x422151 0x4221da 0x41dc0d 0x41dd68 0x41dcbd 0x6d71a3 0x6da87d 0x7b2c3b 0x419320\n0: 0 [1: 16] @ 0x420cef 0x422048 0x40b517 0x40b746 0x6d9ca2 0x4761c5 0x475ea7 0x46fc4f 0x46f180 0x46ef33 0x4ab821 0x4acc32 0x4ac7b7 0x4ace36 0x419320\n"
  },
  {
    "path": "profile/testdata/gobench.heap.string",
    "content": "PeriodType: space bytes\nPeriod: 524288\nSamples:\nalloc_objects/count alloc_space/bytes inuse_objects/count inuse_space/bytes\n          4    2488234          1     829411: 1 2 3 4 5 6 7 8 9 10 \n                bytes:[524288]\n          1     829411          1     829411: 1 2 3 4 5 6 11 10 \n                bytes:[524288]\n          2     666237          2     666237: 1 2 3 4 5 12 13 14 15 16 17 10 \n                bytes:[262144]\n          2     666237          2     666237: 1 2 3 4 5 12 13 14 15 18 19 20 21 10 \n                bytes:[262144]\n      33192  339890635         51     529424: 1 2 3 4 5 22 23 24 25 26 10 \n                bytes:[10240]\n        128     526338        128     526338: 1 2 3 4 5 27 28 29 30 10 \n                bytes:[4096]\n        128     526338        128     526338: 1 2 3 4 5 31 32 33 34 10 \n                bytes:[4096]\n        256     525312        256     525312: 1 35 36 37 38 10 \n                bytes:[2048]\n        410     524928        410     524928: 1 39 40 41 42 43 44 45 10 \n                bytes:[1280]\n       1365     524480       1365     524480: 1 2 3 4 46 47 48 49 50 51 52 53 54 55 56 57 58 59 56 60 61 56 62 63 64 65 66 67 68 69 70 65 \n                bytes:[384]\n       1820     524432       1820     524432: 1 35 71 72 73 74 75 42 43 44 45 10 \n                bytes:[288]\n       7085    1048724       1820     524432: \n                bytes:[288]\n       5461     524336       5461     524336: 1 76 77 78 79 80 81 82 83 84 30 10 \n                bytes:[96]\n        512     524800          0          0: 1 2 3 4 46 47 85 86 87 10 \n                bytes:[1024]\n      32768     524296          0          0: 1 88 89 90 91 92 93 94 95 96 97 98 99 100 10 \n                bytes:[16]\nLocations\n     1: 0x420cee M=1 \n     2: 0x422150 M=1 \n     3: 0x4221d9 M=1 \n     4: 0x41dc0c M=1 \n     5: 0x41db47 M=1 \n     6: 0x74920e M=1 \n     7: 0x6295ab M=1 \n     8: 0x629854 M=1 \n     9: 0x462768 M=1 \n    10: 0x41931f M=1 \n    11: 0x63963e M=1 \n    12: 0x451a38 M=1 \n    13: 0x451ba4 M=1 \n    14: 0x450682 M=1 \n    15: 0x450076 M=1 \n    16: 0x4525a3 M=1 \n    17: 0x58e033 M=1 \n    18: 0x4524d3 M=1 \n    19: 0x40108f M=1 \n    20: 0x4011a0 M=1 \n    21: 0x416dfe M=1 \n    22: 0x477636 M=1 \n    23: 0x47718a M=1 \n    24: 0x477055 M=1 \n    25: 0x4799b1 M=1 \n    26: 0x46bfd6 M=1 \n    27: 0x526125 M=1 \n    28: 0x5261e9 M=1 \n    29: 0x4683d3 M=1 \n    30: 0x467e08 M=1 \n    31: 0x53fbf2 M=1 \n    32: 0x53f85e M=1 \n    33: 0x545f51 M=1 \n    34: 0x545a6f M=1 \n    35: 0x420fa8 M=1 \n    36: 0x414b21 M=1 \n    37: 0x414d1f M=1 \n    38: 0x4901bd M=1 \n    39: 0x422081 M=1 \n    40: 0x48dbe2 M=1 \n    41: 0x48d15b M=1 \n    42: 0x48cdcf M=1 \n    43: 0x4a9dbf M=1 \n    44: 0x545bfd M=1 \n    45: 0x543ac6 M=1 \n    46: 0x41dd67 M=1 \n    47: 0x41dcbc M=1 \n    48: 0x42914f M=1 \n    49: 0x429adc M=1 \n    50: 0x42e012 M=1 \n    51: 0x4307e1 M=1 \n    52: 0x4366fe M=1 \n    53: 0x42c1c1 M=1 \n    54: 0x653e4c M=1 \n    55: 0x64bdc4 M=1 \n    56: 0x64c358 M=1 \n    57: 0x65a73c M=1 \n    58: 0x64cdb0 M=1 \n    59: 0x64be72 M=1 \n    60: 0x64c599 M=1 \n    61: 0x64c204 M=1 \n    62: 0x64b777 M=1 \n    63: 0x5cd55b M=1 \n    64: 0x45dbc2 M=1 \n    65: 0x543e6f M=1 \n    66: 0x559165 M=1 \n    67: 0x55ba53 M=1 \n    68: 0x559690 M=1 \n    69: 0x559984 M=1 \n    70: 0x5a19fe M=1 \n    71: 0x419e18 M=1 \n    72: 0x41a1a7 M=1 \n    73: 0x419f62 M=1 \n    74: 0x48f09e M=1 \n    75: 0x48d990 M=1 \n    76: 0x424f34 M=1 \n    77: 0x4255d0 M=1 \n    78: 0x6fc292 M=1 \n    79: 0x6f9c87 M=1 \n    80: 0x6f9943 M=1 \n    81: 0x6f96bd M=1 \n    82: 0x6f966a M=1 \n    83: 0x59f399 M=1 \n    84: 0x468317 M=1 \n    85: 0x6d71a2 M=1 \n    86: 0x6da87c M=1 \n    87: 0x7b2c3a M=1 \n    88: 0x422047 M=1 \n    89: 0x40b516 M=1 \n    90: 0x40b745 M=1 \n    91: 0x6d9ca1 M=1 \n    92: 0x4761c4 M=1 \n    93: 0x475ea6 M=1 \n    94: 0x46fc4e M=1 \n    95: 0x46f17f M=1 \n    96: 0x46ef32 M=1 \n    97: 0x4ab820 M=1 \n    98: 0x4acc31 M=1 \n    99: 0x4ac7b6 M=1 \n   100: 0x4ace35 M=1 \nMappings\n1: 0x0/0xffffffffffffffff/0x0   \n"
  },
  {
    "path": "profile/testdata/java.contention",
    "content": "--- contentionz 1 ---\nformat = java\nresolution = microseconds\nsampling period = 100\nms since reset = 6019923\n            1     1 @ 0x00000003 0x00000004\n           14     1 @ 0x0000000d 0x0000000e 0x0000000f 0x00000010 0x00000011 0x00000012 0x00000013 0x00000014 0x00000017 0x00000018 0x00000019 0x0000001a 0x0000001b 0x0000001c 0x00000014 0x00000029 0x0000002a 0x0000002b 0x0000002c 0x0000002d 0x0000002e 0x0000002f 0x00000030 0x00000031 0x00000032 0x00000033 0x00000034 0x00000035\n            2     2 @ 0x00000003 0x00000004\n            2     3 @ 0x00000036 0x00000037 0x00000038\n\n\n 0x0000003 com.example.function03 (source.java:03)\n 0x0000004 com.example.function04 (source.java:04)\n 0x000000d com.example.function0d (source.java:0)\n 0x000000e com.example.function0e (source.java:0)\n 0x000000f com.example.function0f (source.java:0)\n 0x0000010 com.example.function10 (source.java:10)\n 0x0000011 com.example.function11 (source.java:11)\n 0x0000012 com.example.function12 (source.java:12)\n 0x0000013 com.example.function13 (source.java:13)\n 0x0000014 com.example.function14 (source.java:14)\n 0x0000017 com.example.function17 (source.java:17)\n 0x0000018 com.example.function18 (source.java:18)\n 0x0000019 com.example.function19 (source.java:19)\n 0x000001a com.example.function1a (source.java:1)\n 0x000001b com.example.function1b (source.java:1)\n 0x000001c com.example.function1c (source.java:1)\n 0x0000029 com.example.function29 (source.java:29)\n 0x000002a com.example.function2a (source.java:2)\n 0x000002b com.example.function2b (source.java:2)\n 0x000002c com.example.function2c (source.java:2)\n 0x000002d com.example.function2d (source.java:2)\n 0x000002e com.example.function2e (source.java:2)\n 0x000002f com.example.function2f (source.java:2)\n 0x0000030 com.example.function30 (source.java:30)\n 0x0000031 com.example.function31 (source.java:31)\n 0x0000032 com.example.function32 (source.java:32)\n 0x0000033 com.example.function33 (source.java:33)\n 0x0000034 com.example.function34 (source.java:34)\n 0x0000035 com.example.function35 (source.java:35)\n 0x0000036 com.example.function36 (source.java:36)\n 0x0000037 com.example.function37 (source.java:37)\n 0x0000038 com.example.function38 (source.java:38)\n"
  },
  {
    "path": "profile/testdata/java.contention.string",
    "content": "PeriodType: contentions count\nPeriod: 100\nDuration: 1h40\nSamples:\ncontentions/count delay/microseconds\n        100        100: 1 2 \n        100       1400: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 10 17 18 19 20 21 22 23 24 25 26 27 28 29 \n        200        200: 1 2 \n        300        200: 30 31 32 \nLocations\n     1: 0x0 com.example.function03 source.java:3:0 s=0\n     2: 0x0 com.example.function04 source.java:4:0 s=0\n     3: 0x0 com.example.function0d source.java:0:0 s=0\n     4: 0x0 com.example.function0e source.java:0:0 s=0\n     5: 0x0 com.example.function0f source.java:0:0 s=0\n     6: 0x0 com.example.function10 source.java:10:0 s=0\n     7: 0x0 com.example.function11 source.java:11:0 s=0\n     8: 0x0 com.example.function12 source.java:12:0 s=0\n     9: 0x0 com.example.function13 source.java:13:0 s=0\n    10: 0x0 com.example.function14 source.java:14:0 s=0\n    11: 0x0 com.example.function17 source.java:17:0 s=0\n    12: 0x0 com.example.function18 source.java:18:0 s=0\n    13: 0x0 com.example.function19 source.java:19:0 s=0\n    14: 0x0 com.example.function1a source.java:1:0 s=0\n    15: 0x0 com.example.function1b source.java:1:0 s=0\n    16: 0x0 com.example.function1c source.java:1:0 s=0\n    17: 0x0 com.example.function29 source.java:29:0 s=0\n    18: 0x0 com.example.function2a source.java:2:0 s=0\n    19: 0x0 com.example.function2b source.java:2:0 s=0\n    20: 0x0 com.example.function2c source.java:2:0 s=0\n    21: 0x0 com.example.function2d source.java:2:0 s=0\n    22: 0x0 com.example.function2e source.java:2:0 s=0\n    23: 0x0 com.example.function2f source.java:2:0 s=0\n    24: 0x0 com.example.function30 source.java:30:0 s=0\n    25: 0x0 com.example.function31 source.java:31:0 s=0\n    26: 0x0 com.example.function32 source.java:32:0 s=0\n    27: 0x0 com.example.function33 source.java:33:0 s=0\n    28: 0x0 com.example.function34 source.java:34:0 s=0\n    29: 0x0 com.example.function35 source.java:35:0 s=0\n    30: 0x0 com.example.function36 source.java:36:0 s=0\n    31: 0x0 com.example.function37 source.java:37:0 s=0\n    32: 0x0 com.example.function38 source.java:38:0 s=0\nMappings\n"
  },
  {
    "path": "profile/testdata/java.cpu.string",
    "content": "PeriodType: cpu nanoseconds\nPeriod: 10000000\nSamples:\nsamples/count cpu/nanoseconds\n          0          0: 1 \n          0          0: 2 \n          2   20000000: 3 \n          1   10000000: 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 \n          1   10000000: 19 20 21 22 23 16 17 18 \n          1   10000000: 24 25 26 27 28 29 30 31 32 \n          1   10000000: 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 29 30 31 32 \n          1   10000000: 54 55 56 57 58 59 60 61 62 11 63 64 16 17 18 \nLocations\n     1: 0x0 GC :0:0 s=0\n     2: 0x0 Compile :0:0 s=0\n     3: 0x0 VM :0:0 s=0\n     4: 0x0 com.example.function06 source.java:6:0 s=0\n     5: 0x0 com.example.function07 source.java:7:0 s=0\n     6: 0x0 com.example.function08 source.java:8:0 s=0\n     7: 0x0 com.example.function09 source.java:9:0 s=0\n     8: 0x0 com.example.function0a source.java:0:0 s=0\n     9: 0x0 com.example.function0b source.java:0:0 s=0\n    10: 0x0 com.example.function0c source.java:0:0 s=0\n    11: 0x0 com.example.function0d source.java:0:0 s=0\n    12: 0x0 com.example.function0e source.java:0:0 s=0\n    13: 0x0 com.example.function0f source.java:0:0 s=0\n    14: 0x0 com.example.function10 source.java:10:0 s=0\n    15: 0x0 com.example.function11 source.java:11:0 s=0\n    16: 0x0 com.example.function12 source.java:12:0 s=0\n    17: 0x0 com.example.function13 source.java:13:0 s=0\n    18: 0x0 com.example.function14 source.java:14:0 s=0\n    19: 0x0 com.example.function1d source.java:1:0 s=0\n    20: 0x0 com.example.function1e source.java:1:0 s=0\n    21: 0x0 com.example.function1f source.java:1:0 s=0\n    22: 0x0 com.example.function20 source.java:20:0 s=0\n    23: 0x0 com.example.function21 source.java:21:0 s=0\n    24: 0x0 com.example.function22 source.java:22:0 s=0\n    25: 0x0 com.example.function23 source.java:23:0 s=0\n    26: 0x0 com.example.function24 source.java:24:0 s=0\n    27: 0x0 com.example.function25 source.java:25:0 s=0\n    28: 0x0 com.example.function26 source.java:26:0 s=0\n    29: 0x0 com.example.function27 source.java:27:0 s=0\n    30: 0x0 com.example.function28 source.java:28:0 s=0\n    31: 0x0 com.example.function29 source.java:29:0 s=0\n    32: 0x0 com.example.function2a source.java:2:0 s=0\n    33: 0x0 com.example.function2b source.java:2:0 s=0\n    34: 0x0 com.example.function2c source.java:2:0 s=0\n    35: 0x0 com.example.function2d source.java:2:0 s=0\n    36: 0x0 com.example.function2e source.java:2:0 s=0\n    37: 0x0 com.example.function2f source.java:2:0 s=0\n    38: 0x0 com.example.function30 source.java:30:0 s=0\n    39: 0x0 com.example.function31 source.java:31:0 s=0\n    40: 0x0 com.example.function32 source.java:32:0 s=0\n    41: 0x0 com.example.function33 source.java:33:0 s=0\n    42: 0x0 com.example.function34 source.java:34:0 s=0\n    43: 0x0 com.example.function35 source.java:35:0 s=0\n    44: 0x0 com.example.function36 source.java:36:0 s=0\n    45: 0x0 com.example.function37 source.java:37:0 s=0\n    46: 0x0 com.example.function38 source.java:38:0 s=0\n    47: 0x0 com.example.function39 source.java:39:0 s=0\n    48: 0x0 com.example.function3a source.java:3:0 s=0\n    49: 0x0 com.example.function3b source.java:3:0 s=0\n    50: 0x0 com.example.function3c source.java:3:0 s=0\n    51: 0x0 com.example.function3d source.java:3:0 s=0\n    52: 0x0 com.example.function3e source.java:3:0 s=0\n    53: 0x0 com.example.function3f source.java:3:0 s=0\n    54: 0x0 com.example.function40 source.java:40:0 s=0\n    55: 0x0 com.example.function41 source.java:41:0 s=0\n    56: 0x0 com.example.function42 source.java:42:0 s=0\n    57: 0x0 com.example.function43 source.java:43:0 s=0\n    58: 0x0 com.example.function44 source.java:44:0 s=0\n    59: 0x0 com.example.function45 source.java:45:0 s=0\n    60: 0x0 com.example.function46 source.java:46:0 s=0\n    61: 0x0 com.example.function47 source.java:47:0 s=0\n    62: 0x0 com.example.function48 source.java:48:0 s=0\n    63: 0x0 com.example.function49 source.java:49:0 s=0\n    64: 0x0 com.example.function4a source.java:4:0 s=0\nMappings\n"
  },
  {
    "path": "profile/testdata/java.heap",
    "content": "--- heapz 1 ---\nformat = java\nresolution = bytes\n          7048     1 @ 0x00000003 0x00000004 0x00000005 0x00000006 0x00000007 0x00000008 0x00000009 0x0000000a 0x0000000b 0x0000000c 0x0000000d 0x0000000e 0x0000000f 0x00000010 0x00000011 0x00000018 0x00000019 0x0000001a 0x0000001b 0x0000001c 0x0000001d 0x0000001e 0x0000001f 0x00000020 0x00000021 0x00000022 0x00000023 0x00000024 0x00000025 0x00000026 0x00000027 0x00000023 0x00000028 0x00000029 0x0000001d 0x0000001e 0x0000001f 0x00000020 0x00000021 0x00000027 0x00000023 0x00000028 0x00000029 0x0000001d 0x0000001e 0x0000001f 0x00000020 0x00000021 0x0000002a 0x00000027 0x00000023 0x00000028 0x00000029 0x0000001d 0x0000001e 0x0000001f 0x00000020\n          4752     9 @ 0x0000002b 0x0000002c 0x0000002d 0x0000002e\n           880     1 @ 0x00000035 0x00000036 0x00000037 0x00000038 0x00000039 0x0000003a 0x0000003b 0x00000011 0x0000003d 0x0000003e 0x0000003f 0x00000040 0x00000041 0x00000042 0x00000011 0x00000049 0x0000004a 0x0000004b 0x0000004c 0x0000004d 0x0000004e 0x0000004b 0x0000004f 0x0000004b 0x00000050 0x00000051 0x00000052 0x00000053 0x00000054 0x00000055 0x00000056 0x00000057\n           560     1 @ 0x00000035 0x00000036 0x00000037 0x00000038 0x00000039 0x0000003a 0x0000003b 0x00000011 0x0000003d 0x0000003e 0x0000003f 0x00000040 0x00000041 0x00000042 0x00000011 0x0000005e 0x0000005f 0x00000060 0x00000061 0x00000062 0x00000063 0x00000064 0x00000065 0x00000066 0x00000067 0x00000068 0x00000069 0x0000006a 0x0000006b 0x0000006c 0x0000006d 0x0000006e 0x0000006f 0x00000070 0x00000071 0x00000072 0x00000073 0x00000074 0x00000075 0x00000067 0x00000068\n           528     1 @ 0x00000076 0x00000077 0x00000078 0x00000079 0x0000007a 0x0000007b 0x00000011 0x00000081 0x00000011 0x00000082 0x0000004e 0x0000004b 0x0000004f 0x0000004b 0x00000050 0x00000051 0x00000052 0x00000053 0x00000054 0x00000055 0x00000056 0x00000057\n           440     1 @ 0x00000083 0x00000084 0x00000085 0x00000086 0x00000087 0x00000088 0x00000089 0x0000008a 0x0000008b 0x0000008c 0x0000008d 0x0000008e 0x0000008f 0x00000090 0x00000091 0x00000092 0x00000093 0x00000094 0x00000095 0x00000096\n           240     5 @ 0x00000097\n\n\n 0x00000003 com.example.function003 (Source003.java:103)\n 0x00000004 com.example.function004 (Source004.java:104)\n 0x00000005 com.example.function005 (Source005.java:105)\n 0x00000006 com.example.function006 (Source006.java:106)\n 0x00000007 com.example.function007 (Source007.java:107)\n 0x00000008 com.example.function008 (Source008.java:108)\n 0x00000009 com.example.function009 (Source009.java:109)\n 0x0000000a com.example.function00a (Source00a.java:10)\n 0x0000000b com.example.function00b (Source00b.java:10)\n 0x0000000c com.example.function00c (Source00c.java:10)\n 0x0000000d com.example.function00d (Source00d.java:10)\n 0x0000000e com.example.function00e (Source00e.java:10)\n 0x0000000f com.example.function00f (Source00f.java:10)\n 0x00000010 com.example.function010 (Source010.java:110)\n 0x00000011 com.example.function011 (Source011.java:111)\n 0x00000018 com.example.function018 (Source018.java:118)\n 0x00000019 com.example.function019 (Source019.java:119)\n 0x0000001a com.example.function01a (Source01a.java:11)\n 0x0000001b com.example.function01b (Source01b.java:11)\n 0x0000001c com.example.function01c (Source01c.java:11)\n 0x0000001d com.example.function01d (Source01d.java:11)\n 0x0000001e com.example.function01e (Source01e.java:11)\n 0x0000001f com.example.function01f (Source01f.java:11)\n 0x00000020 com.example.function020 (Source020.java:120)\n 0x00000021 com.example.function021 (Source021.java:121)\n 0x00000022 com.example.function022 (Source022.java:122)\n 0x00000023 com.example.function023 (Source023.java:123)\n 0x00000024 com.example.function024 (Source024.java:124)\n 0x00000025 com.example.function025 (Source025.java:125)\n 0x00000026 com.example.function026 (Source026.java:126)\n 0x00000027 com.example.function027 (Source027.java:127)\n 0x00000028 com.example.function028 (Source028.java:128)\n 0x00000029 com.example.function029 (Source029.java:129)\n 0x0000002a com.example.function02a (Source02a.java:12)\n 0x0000002b com.example.function02b (Source02b.java:12)\n 0x0000002c com.example.function02c (Source02c.java:12)\n 0x0000002d com.example.function02d (Source02d.java:12)\n 0x0000002e com.example.function02e (Source02e.java:12)\n 0x00000035 com.example.function035 (Source035.java:135)\n 0x00000036 com.example.function036 (Source036.java:136)\n 0x00000037 com.example.function037 (Source037.java:137)\n 0x00000038 com.example.function038 (Source038.java:138)\n 0x00000039 com.example.function039 (Source039.java:139)\n 0x0000003a com.example.function03a (Source03a.java:13)\n 0x0000003b com.example.function03b (Source03b.java:13)\n 0x0000003d com.example.function03d (Source03d.java:13)\n 0x0000003e com.example.function03e (Source03e.java:13)\n 0x0000003f com.example.function03f (Source03f.java:13)\n 0x00000040 com.example.function040 (Source040.java:140)\n 0x00000041 com.example.function041 (Source041.java:141)\n 0x00000042 com.example.function042 (Source042.java:142)\n 0x00000049 com.example.function049 (Source049.java:149)\n 0x0000004a com.example.function04a (Source04a.java:14)\n 0x0000004b com.example.function04b (Source04b.java:14)\n 0x0000004c com.example.function04c (Source04c.java:14)\n 0x0000004d com.example.function04d (Source04d.java:14)\n 0x0000004e com.example.function04e (Source04e.java:14)\n 0x0000004f com.example.function04f (Source04f.java:14)\n 0x00000050 com.example.function050 (Source050.java:150)\n 0x00000051 com.example.function051 (Source051.java:151)\n 0x00000052 com.example.function052 (Source052.java:152)\n 0x00000053 com.example.function053 (Source053.java:153)\n 0x00000054 com.example.function054 (Source054.java:154)\n 0x00000055 com.example.function055 (Source055.java:155)\n 0x00000056 com.example.function056 (Source056.java:156)\n 0x00000057 com.example.function057 (Source057.java:157)\n 0x0000005a com.example.function05a (Source05a.java:15)\n 0x0000005e com.example.function05e (Source05e.java:15)\n 0x0000005f com.example.function05f (Source05f.java:15)\n 0x00000060 com.example.function060 (Source060.java:160)\n 0x00000061 com.example.function061 (Source061.java:161)\n 0x00000062 com.example.function062 (Source062.java:162)\n 0x00000063 com.example.function063 (Source063.java:163)\n 0x00000064 com.example.function064 (Source064.java:164)\n 0x00000065 com.example.function065 (Source065.java:165)\n 0x00000066 com.example.function066 (Source066.java:166)\n 0x00000067 com.example.function067 (Source067.java:167)\n 0x00000068 com.example.function068 (Source068.java:168)\n 0x00000069 com.example.function069 (Source069.java:169)\n 0x0000006a com.example.function06a (Source06a.java:16)\n 0x0000006b com.example.function06b (Source06b.java:16)\n 0x0000006c com.example.function06c (Source06c.java:16)\n 0x0000006d com.example.function06d (Source06d.java:16)\n 0x0000006e com.example.function06e (Source06e.java:16)\n 0x0000006f com.example.function06f (Source06f.java:16)\n 0x00000070 com.example.function070 (Source070.java:170)\n 0x00000071 com.example.function071 (Source071.java:171)\n 0x00000072 com.example.function072 (Source072.java:172)\n 0x00000073 com.example.function073 (Source073.java:173)\n 0x00000074 com.example.function074 (Source074.java:174)\n 0x00000075 com.example.function075 (Source075.java:175)\n 0x00000076 com.example.function076 (Source076.java:176)\n 0x00000077 com.example.function077 (Source077.java:177)\n 0x00000078 com.example.function078 (Source078.java:178)\n 0x00000079 com.example.function079 (Source079.java:179)\n 0x0000007a com.example.function07a (Source07a.java:17)\n 0x0000007b com.example.function07b (Source07b.java:17)\n 0x0000007d com.example.function07d (Source07d.java:17)\n 0x00000081 com.example.function081 (Source081.java:181)\n 0x00000082 com.example.function082 (Source082.java:182)\n 0x00000083 com.example.function083 (Source083.java:183)\n 0x00000084 com.example.function084 (Source084.java:184)\n 0x00000085 com.example.function085 (Source085.java:185)\n 0x00000086 com.example.function086 (Source086.java:186)\n 0x00000087 com.example.function087 (Source087.java:187)\n 0x00000088 com.example.function088 (Source088.java:188)\n 0x00000089 com.example.function089 (Source089.java:189)\n 0x0000008a com.example.function08a (Source08a.java:18)\n 0x0000008b com.example.function08b (Source08b.java:18)\n 0x0000008c com.example.function08c (Source08c.java:18)\n 0x0000008d com.example.function08d (Source08d.java:18)\n 0x0000008e com.example.function08e (Source08e.java:18)\n 0x0000008f com.example.function08f (Source08f.java:18)\n 0x00000090 com.example.function090 (Source090.java:190)\n 0x00000091 com.example.function091 (Source091.java:191)\n 0x00000092 com.example.function092 (Source092.java:192)\n 0x00000093 com.example.function093 (Source093.java:193)\n 0x00000094 com.example.function094 (Source094.java:194)\n 0x00000095 com.example.function095 (Source095.java:195)\n 0x00000096 com.example.function096 (Source096.java:196)\n 0x00000097 com.example.function097 (Source097.java:197)\n"
  },
  {
    "path": "profile/testdata/java.heap.string",
    "content": "PeriodType:  \nPeriod: 0\nSamples:\ninuse_objects/count inuse_space/bytes\n         74     527819: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 27 32 33 21 22 23 24 25 31 27 32 33 21 22 23 24 25 34 31 27 32 33 21 22 23 24 \n                bytes:[7048]\n       8941    4720968: 35 36 37 38 \n                bytes:[528]\n        596     524728: 39 40 41 42 43 44 45 15 46 47 48 49 50 51 15 52 53 54 55 56 57 54 58 54 59 60 61 62 63 64 65 66 \n                bytes:[880]\n        936     524568: 39 40 41 42 43 44 45 15 46 47 48 49 50 51 15 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 76 77 \n                bytes:[560]\n        993     524552: 91 92 93 94 95 96 15 97 15 98 57 54 58 54 59 60 61 62 63 64 65 66 \n                bytes:[528]\n       1192     524508: 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 \n                bytes:[440]\n      54615    2621560: 119 \n                bytes:[48]\nLocations\n     1: 0x0 com.example.function003 Source003.java:103:0 s=0\n     2: 0x0 com.example.function004 Source004.java:104:0 s=0\n     3: 0x0 com.example.function005 Source005.java:105:0 s=0\n     4: 0x0 com.example.function006 Source006.java:106:0 s=0\n     5: 0x0 com.example.function007 Source007.java:107:0 s=0\n     6: 0x0 com.example.function008 Source008.java:108:0 s=0\n     7: 0x0 com.example.function009 Source009.java:109:0 s=0\n     8: 0x0 com.example.function00a Source00a.java:10:0 s=0\n     9: 0x0 com.example.function00b Source00b.java:10:0 s=0\n    10: 0x0 com.example.function00c Source00c.java:10:0 s=0\n    11: 0x0 com.example.function00d Source00d.java:10:0 s=0\n    12: 0x0 com.example.function00e Source00e.java:10:0 s=0\n    13: 0x0 com.example.function00f Source00f.java:10:0 s=0\n    14: 0x0 com.example.function010 Source010.java:110:0 s=0\n    15: 0x0 com.example.function011 Source011.java:111:0 s=0\n    16: 0x0 com.example.function018 Source018.java:118:0 s=0\n    17: 0x0 com.example.function019 Source019.java:119:0 s=0\n    18: 0x0 com.example.function01a Source01a.java:11:0 s=0\n    19: 0x0 com.example.function01b Source01b.java:11:0 s=0\n    20: 0x0 com.example.function01c Source01c.java:11:0 s=0\n    21: 0x0 com.example.function01d Source01d.java:11:0 s=0\n    22: 0x0 com.example.function01e Source01e.java:11:0 s=0\n    23: 0x0 com.example.function01f Source01f.java:11:0 s=0\n    24: 0x0 com.example.function020 Source020.java:120:0 s=0\n    25: 0x0 com.example.function021 Source021.java:121:0 s=0\n    26: 0x0 com.example.function022 Source022.java:122:0 s=0\n    27: 0x0 com.example.function023 Source023.java:123:0 s=0\n    28: 0x0 com.example.function024 Source024.java:124:0 s=0\n    29: 0x0 com.example.function025 Source025.java:125:0 s=0\n    30: 0x0 com.example.function026 Source026.java:126:0 s=0\n    31: 0x0 com.example.function027 Source027.java:127:0 s=0\n    32: 0x0 com.example.function028 Source028.java:128:0 s=0\n    33: 0x0 com.example.function029 Source029.java:129:0 s=0\n    34: 0x0 com.example.function02a Source02a.java:12:0 s=0\n    35: 0x0 com.example.function02b Source02b.java:12:0 s=0\n    36: 0x0 com.example.function02c Source02c.java:12:0 s=0\n    37: 0x0 com.example.function02d Source02d.java:12:0 s=0\n    38: 0x0 com.example.function02e Source02e.java:12:0 s=0\n    39: 0x0 com.example.function035 Source035.java:135:0 s=0\n    40: 0x0 com.example.function036 Source036.java:136:0 s=0\n    41: 0x0 com.example.function037 Source037.java:137:0 s=0\n    42: 0x0 com.example.function038 Source038.java:138:0 s=0\n    43: 0x0 com.example.function039 Source039.java:139:0 s=0\n    44: 0x0 com.example.function03a Source03a.java:13:0 s=0\n    45: 0x0 com.example.function03b Source03b.java:13:0 s=0\n    46: 0x0 com.example.function03d Source03d.java:13:0 s=0\n    47: 0x0 com.example.function03e Source03e.java:13:0 s=0\n    48: 0x0 com.example.function03f Source03f.java:13:0 s=0\n    49: 0x0 com.example.function040 Source040.java:140:0 s=0\n    50: 0x0 com.example.function041 Source041.java:141:0 s=0\n    51: 0x0 com.example.function042 Source042.java:142:0 s=0\n    52: 0x0 com.example.function049 Source049.java:149:0 s=0\n    53: 0x0 com.example.function04a Source04a.java:14:0 s=0\n    54: 0x0 com.example.function04b Source04b.java:14:0 s=0\n    55: 0x0 com.example.function04c Source04c.java:14:0 s=0\n    56: 0x0 com.example.function04d Source04d.java:14:0 s=0\n    57: 0x0 com.example.function04e Source04e.java:14:0 s=0\n    58: 0x0 com.example.function04f Source04f.java:14:0 s=0\n    59: 0x0 com.example.function050 Source050.java:150:0 s=0\n    60: 0x0 com.example.function051 Source051.java:151:0 s=0\n    61: 0x0 com.example.function052 Source052.java:152:0 s=0\n    62: 0x0 com.example.function053 Source053.java:153:0 s=0\n    63: 0x0 com.example.function054 Source054.java:154:0 s=0\n    64: 0x0 com.example.function055 Source055.java:155:0 s=0\n    65: 0x0 com.example.function056 Source056.java:156:0 s=0\n    66: 0x0 com.example.function057 Source057.java:157:0 s=0\n    67: 0x0 com.example.function05e Source05e.java:15:0 s=0\n    68: 0x0 com.example.function05f Source05f.java:15:0 s=0\n    69: 0x0 com.example.function060 Source060.java:160:0 s=0\n    70: 0x0 com.example.function061 Source061.java:161:0 s=0\n    71: 0x0 com.example.function062 Source062.java:162:0 s=0\n    72: 0x0 com.example.function063 Source063.java:163:0 s=0\n    73: 0x0 com.example.function064 Source064.java:164:0 s=0\n    74: 0x0 com.example.function065 Source065.java:165:0 s=0\n    75: 0x0 com.example.function066 Source066.java:166:0 s=0\n    76: 0x0 com.example.function067 Source067.java:167:0 s=0\n    77: 0x0 com.example.function068 Source068.java:168:0 s=0\n    78: 0x0 com.example.function069 Source069.java:169:0 s=0\n    79: 0x0 com.example.function06a Source06a.java:16:0 s=0\n    80: 0x0 com.example.function06b Source06b.java:16:0 s=0\n    81: 0x0 com.example.function06c Source06c.java:16:0 s=0\n    82: 0x0 com.example.function06d Source06d.java:16:0 s=0\n    83: 0x0 com.example.function06e Source06e.java:16:0 s=0\n    84: 0x0 com.example.function06f Source06f.java:16:0 s=0\n    85: 0x0 com.example.function070 Source070.java:170:0 s=0\n    86: 0x0 com.example.function071 Source071.java:171:0 s=0\n    87: 0x0 com.example.function072 Source072.java:172:0 s=0\n    88: 0x0 com.example.function073 Source073.java:173:0 s=0\n    89: 0x0 com.example.function074 Source074.java:174:0 s=0\n    90: 0x0 com.example.function075 Source075.java:175:0 s=0\n    91: 0x0 com.example.function076 Source076.java:176:0 s=0\n    92: 0x0 com.example.function077 Source077.java:177:0 s=0\n    93: 0x0 com.example.function078 Source078.java:178:0 s=0\n    94: 0x0 com.example.function079 Source079.java:179:0 s=0\n    95: 0x0 com.example.function07a Source07a.java:17:0 s=0\n    96: 0x0 com.example.function07b Source07b.java:17:0 s=0\n    97: 0x0 com.example.function081 Source081.java:181:0 s=0\n    98: 0x0 com.example.function082 Source082.java:182:0 s=0\n    99: 0x0 com.example.function083 Source083.java:183:0 s=0\n   100: 0x0 com.example.function084 Source084.java:184:0 s=0\n   101: 0x0 com.example.function085 Source085.java:185:0 s=0\n   102: 0x0 com.example.function086 Source086.java:186:0 s=0\n   103: 0x0 com.example.function087 Source087.java:187:0 s=0\n   104: 0x0 com.example.function088 Source088.java:188:0 s=0\n   105: 0x0 com.example.function089 Source089.java:189:0 s=0\n   106: 0x0 com.example.function08a Source08a.java:18:0 s=0\n   107: 0x0 com.example.function08b Source08b.java:18:0 s=0\n   108: 0x0 com.example.function08c Source08c.java:18:0 s=0\n   109: 0x0 com.example.function08d Source08d.java:18:0 s=0\n   110: 0x0 com.example.function08e Source08e.java:18:0 s=0\n   111: 0x0 com.example.function08f Source08f.java:18:0 s=0\n   112: 0x0 com.example.function090 Source090.java:190:0 s=0\n   113: 0x0 com.example.function091 Source091.java:191:0 s=0\n   114: 0x0 com.example.function092 Source092.java:192:0 s=0\n   115: 0x0 com.example.function093 Source093.java:193:0 s=0\n   116: 0x0 com.example.function094 Source094.java:194:0 s=0\n   117: 0x0 com.example.function095 Source095.java:195:0 s=0\n   118: 0x0 com.example.function096 Source096.java:196:0 s=0\n   119: 0x0 com.example.function097 Source097.java:197:0 s=0\nMappings\n"
  },
  {
    "path": "proto/README.md",
    "content": "This is a description of the profile.proto format.\n\n# Overview\n\nProfile.proto is a data representation for profile data. It is independent of\nthe type of data being collected and the sampling process used to collect that\ndata. On disk, it is represented as a gzip-compressed protocol buffer, described\nin [profile.proto](profile.proto).\n\nA profile in this context refers to a collection of samples, each one\nrepresenting measurements performed at a certain point in the life of a job. A\nsample associates a set of measurement values with a list of locations, commonly\nrepresenting the program call stack when the sample was taken.\n\nTools such as pprof analyze these samples and display this information in\nmultiple forms, such as identifying hottest locations, building graphical call\ngraphs or trees, etc.\n\n# General structure of a profile\n\nA profile is represented on a Profile message, which contain the following\nfields:\n\n* *sample*: A profile sample, with the values measured and the associated call\n  stack as a list of location ids. Samples with identical call stacks can be\n  merged by adding their respective values, element by element.\n* *location*: A unique place in the program, commonly mapped to a single\n  instruction address. It has a unique nonzero id, to be referenced from the\n  samples. It contains source information in the form of lines, and a mapping id\n  that points to a binary.\n* *function*: A program function as defined in the program source. It has a\n  unique nonzero id, referenced from the location lines. It contains a\n  human-readable name for the function (eg a C++ demangled name), a system name\n  (eg a C++ mangled name), the name of the corresponding source file, and other\n  function attributes.\n* *mapping*: A binary that is part of the program during the profile\n  collection. It has a unique nonzero id, referenced from the locations. It\n  includes details on how the binary was mapped during program execution. By\n  convention the main program binary is the first mapping, followed by any\n  shared libraries.\n* *string_table*: All strings in the profile are represented as indices into\n  this repeating field. The first string is empty, so index == 0 always\n  represents the empty string.\n\n# Measurement values\n\nMeasurement values are represented as 64-bit integers. The profile contains an\nexplicit description of each value represented, using a ValueType message, with\ntwo fields:\n\n* *Type*: A human-readable description of the type semantics. For example “cpu”\n  to represent CPU time, “wall” or “time” for wallclock time, or “memory” for\n  bytes allocated.\n* *Unit*: A human-readable name of the unit represented by the 64-bit integer\n  values. For example, it could be “nanoseconds” or “milliseconds” for a time\n  value, or “bytes” or “megabytes” for a memory size. If this is just\n  representing a number of events, the recommended unit name is “count”.\n\nA profile can represent multiple measurements per sample, but all samples must\nhave the same number and type of measurements. The actual values are stored in\nthe Sample.value fields, each one described by the corresponding\nProfile.sample_type field.\n\nSome profiles have a uniform period that describe the granularity of the data\ncollection. For example, a CPU profile may have a period of 100ms, or a memory\nallocation profile may have a period of 512kb. Profiles can optionally describe\nsuch a value on the Profile.period and Profile.period_type fields. The profile\nperiod is meant for human consumption and does not affect the interpretation of\nthe profiling data.\n\nBy convention, the first value on all profiles is the number of samples\ncollected at this call stack, with unit “count”. Because the profile does not\ndescribe the sampling process beyond the optional period, it must include\nunsampled values for all measurements. For example, a CPU profile could have\nvalue[0] == samples, and value[1] == time in milliseconds.\n\n## Locations, functions and mappings\n\nEach sample lists the id of each location where the sample was collected, in\nbottom-up order. Each location has an explicit unique nonzero integer id,\nindependent of its position in the profile, and holds additional information to\nidentify the corresponding source.\n\nThe profile source is expected to perform any adjustment required to the\nlocations in order to point to the calls in the stack. For example, if the\nprofile source extracts the call stack by walking back over the program stack,\nit must adjust the instruction addresses to point to the actual call\ninstruction, instead of the instruction that each call will return to.\n\nSources usually generate profiles that fall into these two categories:\n\n* *Unsymbolized profiles*: These only contain instruction addresses, and are to\n  be symbolized by a separate tool. It is critical for each location to point to\n  a valid mapping, which will provide the information required for\n  symbolization. These are used for profiles of compiled languages, such as C++\n  and Go.\n\n* *Symbolized profiles*: These contain all the symbol information available for\n  the profile. Mappings and instruction addresses are optional for symbolized\n  locations. These are used for profiles of interpreted or jitted languages,\n  such as Java or Python.  Also, the profile format allows the generation of\n  mixed profiles, with symbolized and unsymbolized locations.\n\nThe symbol information is represented in the repeating lines field of the\nLocation message. A location has multiple lines if it reflects multiple program\nsources, for example if representing inlined call stacks. Lines reference\nfunctions by their unique nonzero id, and the source line number within the\nsource file listed by the function. A function contains the source attributes\nfor a function, including its name, source file, etc. Functions include both a\nuser and a system form of the name, for example to include C++ demangled and\nmangled names. For profiles where only a single name exists, both should be set\nto the same string.\n\nMappings are also referenced from locations by their unique nonzero id, and\ninclude all information needed to symbolize addresses within the mapping. It\nincludes similar information to the Linux /proc/self/maps file. Locations\nassociated to a mapping should have addresses that land between the mapping\nstart and limit. Also, if available, mappings should include a build id to\nuniquely identify the version of the binary being used.\n\n## Labels\n\nSamples optionally contain labels, which are annotations to discriminate samples\nwith identical locations. For example, a label can be used on a malloc profile\nto indicate allocation size, so two samples on the same call stack with sizes\n2MB and 4MB do not get merged into a single sample with two allocations and a\nsize of 6MB.\n\nLabels can be string-based or numeric. They are represented by the Label\nmessage, with a key identifying the label and either a string or numeric\nvalue. For numeric labels, the measurement unit can be specified in the profile.\nIf no unit is specified and the key is \"request\" or \"alignment\",\nthen the units are assumed to be \"bytes\". Otherwise when no unit is specified\nthe key will be used as the measurement unit of the numeric value. All tags with\nthe same key should have the same unit.\n\n## Keep and drop expressions\n\nSome profile sources may have knowledge of locations that are uninteresting or\nirrelevant. However, if symbolization is needed in order to identify these\nlocations, the profile source may not be able to remove them when the profile is\ngenerated. The profile format provides a mechanism to identify these frames by\nname, through regular expressions.\n\nThese expressions must match the function name in its entirety. Frames that\nmatch Profile.drop\\_frames will be dropped from the profile, along with any\nframes below it. Frames that match Profile.keep\\_frames will be kept, even if\nthey match drop\\_frames.\n\n"
  },
  {
    "path": "proto/profile.proto",
    "content": "// Copyright 2016 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Profile is a common stacktrace profile format.\n//\n// Measurements represented with this format should follow the\n// following conventions:\n//\n// - Consumers should treat unset optional fields as if they had been\n//   set with their default value.\n//\n// - When possible, measurements should be stored in \"unsampled\" form\n//   that is most useful to humans.  There should be enough\n//   information present to determine the original sampled values.\n//\n// - On-disk, the serialized proto must be gzip-compressed.\n//\n// - The profile is represented as a set of samples, where each sample\n//   references a sequence of locations, and where each location belongs\n//   to a mapping.\n// - There is a N->1 relationship from sample.location_id entries to\n//   locations. For every sample.location_id entry there must be a\n//   unique Location with that id.\n// - There is an optional N->1 relationship from locations to\n//   mappings. For every nonzero Location.mapping_id there must be a\n//   unique Mapping with that id.\n\nsyntax = \"proto3\";\n\npackage perftools.profiles;\n\noption java_package = \"com.google.perftools.profiles\";\noption java_outer_classname = \"ProfileProto\";\n\nmessage Profile {\n  // A description of the samples associated with each Sample.value.\n  // For a cpu profile this might be:\n  //   [[\"cpu\",\"nanoseconds\"]] or [[\"wall\",\"seconds\"]] or [[\"syscall\",\"count\"]]\n  // For a heap profile, this might be:\n  //   [[\"allocations\",\"count\"], [\"space\",\"bytes\"]],\n  // If one of the values represents the number of events represented\n  // by the sample, by convention it should be at index 0 and use\n  // sample_type.unit == \"count\".\n  repeated ValueType sample_type = 1;\n  // The set of samples recorded in this profile.\n  repeated Sample sample = 2;\n  // Mapping from address ranges to the image/binary/library mapped\n  // into that address range.  mapping[0] will be the main binary.\n  repeated Mapping mapping = 3;\n  // Locations referenced by samples.\n  repeated Location location = 4;\n  // Functions referenced by locations.\n  repeated Function function = 5;\n  // A common table for strings referenced by various messages.\n  // string_table[0] must always be \"\".\n  repeated string string_table = 6;\n  // frames with Function.function_name fully matching the following\n  // regexp will be dropped from the samples, along with their successors.\n  int64 drop_frames = 7;   // Index into string table.\n  // frames with Function.function_name fully matching the following\n  // regexp will be kept, even if it matches drop_frames.\n  int64 keep_frames = 8;  // Index into string table.\n\n  // The following fields are informational, do not affect\n  // interpretation of results.\n\n  // Time of collection (UTC) represented as nanoseconds past the epoch.\n  int64 time_nanos = 9;\n  // Duration of the profile, if a duration makes sense.\n  int64 duration_nanos = 10;\n  // The kind of events between sampled occurrences.\n  // e.g [ \"cpu\",\"cycles\" ] or [ \"heap\",\"bytes\" ]\n  ValueType period_type = 11;\n  // The number of events between sampled occurrences.\n  int64 period = 12;\n  // Free-form text associated with the profile. The text is displayed as is\n  // to the user by the tools that read profiles (e.g. by pprof). This field\n  // should not be used to store any machine-readable information, it is only\n  // for human-friendly content. The profile must stay functional if this field\n  // is cleaned.\n  repeated int64 comment = 13; // Indices into string table.\n  // Index into the string table of the type of the preferred sample\n  // value. If unset, clients should default to the last sample value.\n  int64 default_sample_type = 14;\n  // Documentation link for this profile type. The URL must be absolute,\n  // e.g., http://pprof.example.com/cpu-profile.html\n  //\n  // The URL may be missing if the profile was generated by code that did not\n  // supply a link.\n  int64 doc_url = 15;  // Index into string table.\n}\n\n// ValueType describes the semantics and measurement units of a value.\nmessage ValueType {\n  int64 type = 1; // Index into string table.\n  int64 unit = 2; // Index into string table.\n}\n\n// Each Sample records values encountered in some program\n// context. The program context is typically a stack trace, perhaps\n// augmented with auxiliary information like the thread-id, some\n// indicator of a higher level request being handled etc.\nmessage Sample {\n  // The ids recorded here correspond to a Profile.location.id.\n  // The leaf is at location_id[0].\n  repeated uint64 location_id = 1;\n  // The type and unit of each value is defined by the corresponding\n  // entry in Profile.sample_type. All samples must have the same\n  // number of values, the same as the length of Profile.sample_type.\n  // When aggregating multiple samples into a single sample, the\n  // result has a list of values that is the element-wise sum of the\n  // lists of the originals.\n  repeated int64 value = 2;\n  // label includes additional context for this sample. It can include\n  // things like a thread id, allocation size, etc.\n  //\n  // NOTE: While possible, having multiple values for the same label key is\n  // strongly discouraged and should never be used. Most tools (e.g. pprof) do\n  // not have good (or any) support for multi-value labels. And an even more\n  // discouraged case is having a string label and a numeric label of the same\n  // name on a sample.  Again, possible to express, but should not be used.\n  repeated Label label = 3;\n}\n\nmessage Label {\n  // Index into string table. An annotation for a sample (e.g.\n  // \"allocation_size\") with an associated value.\n  // Keys with \"pprof::\" prefix are reserved for internal use by pprof.\n  int64 key = 1;\n\n  // At most one of the following must be present\n  int64 str = 2;   // Index into string table\n  int64 num = 3;\n\n  // Should only be present when num is present.\n  // Specifies the units of num.\n  // Use arbitrary string (for example, \"requests\") as a custom count unit.\n  // If no unit is specified, consumer may apply heuristic to deduce the unit.\n  // Consumers may also  interpret units like \"bytes\" and \"kilobytes\" as memory\n  // units and units like \"seconds\" and \"nanoseconds\" as time units,\n  // and apply appropriate unit conversions to these.\n  int64 num_unit = 4;  // Index into string table\n}\n\nmessage Mapping {\n  // Unique nonzero id for the mapping.\n  uint64 id = 1;\n  // Address at which the binary (or DLL) is loaded into memory.\n  uint64 memory_start = 2;\n  // The limit of the address range occupied by this mapping.\n  uint64 memory_limit = 3;\n  // Offset in the binary that corresponds to the first mapped address.\n  uint64 file_offset = 4;\n  // The object this entry is loaded from.  This can be a filename on\n  // disk for the main binary and shared libraries, or virtual\n  // abstractions like \"[vdso]\".\n  int64 filename = 5;  // Index into string table\n  // A string that uniquely identifies a particular program version\n  // with high probability. E.g., for binaries generated by GNU tools,\n  // it could be the contents of the .note.gnu.build-id field.\n  int64 build_id = 6;  // Index into string table\n\n  // The following fields indicate the resolution of symbolic info.\n  bool has_functions = 7;\n  bool has_filenames = 8;\n  bool has_line_numbers = 9;\n  bool has_inline_frames = 10;\n}\n\n// Describes function and line table debug information.\nmessage Location {\n  // Unique nonzero id for the location.  A profile could use\n  // instruction addresses or any integer sequence as ids.\n  uint64 id = 1;\n  // The id of the corresponding profile.Mapping for this location.\n  // It can be unset if the mapping is unknown or not applicable for\n  // this profile type.\n  uint64 mapping_id = 2;\n  // The instruction address for this location, if available.  It\n  // should be within [Mapping.memory_start...Mapping.memory_limit]\n  // for the corresponding mapping. A non-leaf address may be in the\n  // middle of a call instruction. It is up to display tools to find\n  // the beginning of the instruction if necessary.\n  uint64 address = 3;\n  // Multiple line indicates this location has inlined functions,\n  // where the last entry represents the caller into which the\n  // preceding entries were inlined.\n  //\n  // E.g., if memcpy() is inlined into printf:\n  //    line[0].function_name == \"memcpy\"\n  //    line[1].function_name == \"printf\"\n  repeated Line line = 4;\n  // Provides an indication that multiple symbols map to this location's\n  // address, for example due to identical code folding by the linker. In that\n  // case the line information above represents one of the multiple\n  // symbols. This field must be recomputed when the symbolization state of the\n  // profile changes.\n  bool is_folded = 5;\n}\n\nmessage Line {\n  // The id of the corresponding profile.Function for this line.\n  uint64 function_id = 1;\n  // Line number in source code.\n  int64 line = 2;\n  // Column number in source code.\n  int64 column = 3;\n}\n\nmessage Function {\n  // Unique nonzero id for the function.\n  uint64 id = 1;\n  // Name of the function, in human-readable form if available.\n  int64 name = 2; // Index into string table\n  // Name of the function, as identified by the system.\n  // For instance, it can be a C++ mangled name.\n  int64 system_name = 3; // Index into string table\n  // Source file containing the function.\n  int64 filename = 4; // Index into string table\n  // Line number in source file.\n  int64 start_line = 5;\n}\n"
  },
  {
    "path": "test.sh",
    "content": "#  Copyright 2017 Google Inc. All Rights Reserved.\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  you may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Unless required by applicable law or agreed to in writing, software\n#  distributed under the License is distributed on an \"AS IS\" BASIS,\n#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#  See the License for the specific language governing permissions and\n#  limitations under the License.\n\n\n#!/usr/bin/env bash\n\nset -e\nset -x\nMODE=atomic\necho \"mode: $MODE\" > coverage.txt\n\n# Note: browsertests is in a separate module and is therefore not\n# covered by a local ./... pattern.\n\nif [ \"$RUN_STATICCHECK\" != \"false\" ]; then\n  staticcheck ./...\n  (cd browsertests && staticcheck ./...)\nfi\n\n# Packages that have any tests.\nPKG=$(go list -f '{{if .TestGoFiles}} {{.ImportPath}} {{end}}' ./...)\n\ngo test $PKG\n\nretry() {\n  for i in {1..3}; do\n    [[ $i == 1 ]] || sleep 10  # Backing off after a failed attempt.\n    \"${@}\" && return 0\n  done\n  return 1\n}\n\n# Retry browser tests in case of error since they are flaky.\n# See https://github.com/google/pprof/issues/925.\n(cd browsertests && retry go test ./...)\n(cd browsertests && retry go test -race ./...)\n\n# Skip browsertests since it test-only code and gives no useful coverage info\nfor d in $PKG; do\n  go test -race -coverprofile=profile.out -covermode=$MODE $d\n  if [ -f profile.out ]; then\n    cat profile.out | grep -v \"^mode: \" >> coverage.txt\n    rm profile.out\n  fi\ndone\n\ngo vet -all ./...\n(cd browsertests && go vet -all ./...)\nif [ \"$RUN_GOLANGCI_LINTER\" != \"false\" ];  then\n  golangci-lint run -D errcheck --timeout=3m ./...  # TODO: Enable errcheck back.\n  (cd browsertests && golangci-lint run --timeout=3m ./...)\nfi\n\n# A workaround for gofmt returning zero exit code even if there is diff.\n# See https://github.com/golang/go/issues/46289.\ngofmt_or_fail() {\n  test -z \"$(gofmt -d -s . | tee >(cat >&2))\"\n}\ngofmt_or_fail\n(cd browsertests && gofmt_or_fail)\n"
  },
  {
    "path": "third_party/svgpan/LICENSE",
    "content": "Copyright 2009-2017 Andrea Leofreddi <a.leofreddi@vleo.net>. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are\npermitted provided that the following conditions are met:\n\n   1. Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n   2. Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n   3. Neither the name of the copyright holder nor the names of its\n      contributors may be used to endorse or promote products derived from\n      this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS\nOR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nThe views and conclusions contained in the software and documentation are those of the\nauthors and should not be interpreted as representing official policies, either expressed\nor implied, of Andrea Leofreddi.\n"
  },
  {
    "path": "third_party/svgpan/svgpan.go",
    "content": "// SVG pan and zoom library.\n// See copyright notice in string constant below.\n\npackage svgpan\n\nimport _ \"embed\"\n\n// https://github.com/aleofreddi/svgpan\n\n//go:embed svgpan.js\nvar JSSource string\n"
  },
  {
    "path": "third_party/svgpan/svgpan.js",
    "content": "/** \n *  SVGPan library 1.2.2\n * ======================\n *\n * Given an unique existing element with id \"viewport\" (or when missing, the \n * first g-element), including the library into any SVG adds the following \n * capabilities:\n *\n *  - Mouse panning\n *  - Mouse zooming (using the wheel)\n *  - Object dragging\n *\n * You can configure the behaviour of the pan/zoom/drag with the variables\n * listed in the CONFIGURATION section of this file.\n *\n * This code is licensed under the following BSD license:\n *\n * Copyright 2009-2019 Andrea Leofreddi <a.leofreddi@vleo.net>. All rights reserved.\n * \n * Redistribution and use in source and binary forms, with or without modification, are\n * permitted provided that the following conditions are met:\n * \n *    1. Redistributions of source code must retain the above copyright\n *       notice, this list of conditions and the following disclaimer.\n *    2. Redistributions in binary form must reproduce the above copyright\n *       notice, this list of conditions and the following disclaimer in the\n *       documentation and/or other materials provided with the distribution.\n *    3. Neither the name of the copyright holder nor the names of its \n *       contributors may be used to endorse or promote products derived from \n *       this software without specific prior written permission.\n * \n * THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS \n * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY \n * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n * \n * The views and conclusions contained in the software and documentation are those of the\n * authors and should not be interpreted as representing official policies, either expressed\n * or implied, of Andrea Leofreddi.\n */\n\n\"use strict\";\n\n/// CONFIGURATION \n/// ====>\n\nvar enablePan = 1; // 1 or 0: enable or disable panning (default enabled)\nvar enableZoom = 1; // 1 or 0: enable or disable zooming (default enabled)\nvar enableDrag = 0; // 1 or 0: enable or disable dragging (default disabled)\nvar zoomScale = 0.2; // Zoom sensitivity\n\n/// <====\n/// END OF CONFIGURATION \n\nvar root = document.documentElement;\nvar state = 'none', svgRoot = null, stateTarget, stateOrigin, stateTf;\n\nsetupHandlers(root);\n\n/**\n * Register handlers\n */\nfunction setupHandlers(root){\n\tsetAttributes(root, {\n\t\t\"onmouseup\" : \"handleMouseUp(evt)\",\n\t\t\"onmousedown\" : \"handleMouseDown(evt)\",\n\t\t\"onmousemove\" : \"handleMouseMove(evt)\",\n\t\t//\"onmouseout\" : \"handleMouseUp(evt)\", // Decomment this to stop the pan functionality when dragging out of the SVG element\n\t});\n\n\tif(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)\n\t\twindow.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari\n\telse\n\t\twindow.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others\n}\n\n/**\n * Retrieves the root element for SVG manipulation. The element is then cached into the svgRoot global variable.\n */\nfunction getRoot(root) {\n\tif(svgRoot == null) {\n\t\tvar r = root.getElementById(\"viewport\") ? root.getElementById(\"viewport\") : root.documentElement, t = r;\n\n\t\twhile(t != root) {\n\t\t\tif(t.getAttribute(\"viewBox\")) {\n\t\t\t\tsetCTM(r, t.getCTM());\n\n\t\t\t\tt.removeAttribute(\"viewBox\");\n\t\t\t}\n\n\t\t\tt = t.parentNode;\n\t\t}\n\n\t\tsvgRoot = r;\n\t}\n\n\treturn svgRoot;\n}\n\n/**\n * Instance an SVGPoint object with given event coordinates.\n */\nfunction getEventPoint(evt) {\n\tvar p = root.createSVGPoint();\n\n\tp.x = evt.clientX;\n\tp.y = evt.clientY;\n\n\treturn p;\n}\n\n/**\n * Sets the current transform matrix of an element.\n */\nfunction setCTM(element, matrix) {\n\tvar s = \"matrix(\" + matrix.a + \",\" + matrix.b + \",\" + matrix.c + \",\" + matrix.d + \",\" + matrix.e + \",\" + matrix.f + \")\";\n\n\telement.setAttribute(\"transform\", s);\n}\n\n/**\n * Dumps a matrix to a string (useful for debug).\n */\nfunction dumpMatrix(matrix) {\n\tvar s = \"[ \" + matrix.a + \", \" + matrix.c + \", \" + matrix.e + \"\\n  \" + matrix.b + \", \" + matrix.d + \", \" + matrix.f + \"\\n  0, 0, 1 ]\";\n\n\treturn s;\n}\n\n/**\n * Sets attributes of an element.\n */\nfunction setAttributes(element, attributes){\n\tfor (var i in attributes)\n\t\telement.setAttributeNS(null, i, attributes[i]);\n}\n\n/**\n * Handle mouse wheel event.\n */\nfunction handleMouseWheel(evt) {\n\tif(!enableZoom)\n\t\treturn;\n\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar delta;\n\n\tif(evt.wheelDelta)\n\t\tdelta = evt.wheelDelta / 360; // Chrome/Safari\n\telse\n\t\tdelta = evt.detail / -9; // Mozilla\n\n\tvar z = Math.pow(1 + zoomScale, delta);\n\n\tvar g = getRoot(svgDoc);\n\t\n\tvar p = getEventPoint(evt);\n\n\tp = p.matrixTransform(g.getCTM().inverse());\n\n\t// Compute new scale matrix in current mouse position\n\tvar k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);\n\n\tsetCTM(g, g.getCTM().multiply(k));\n\n\tif(typeof(stateTf) == \"undefined\")\n\t\tstateTf = g.getCTM().inverse();\n\n\tstateTf = stateTf.multiply(k.inverse());\n}\n\n/**\n * Handle mouse move event.\n */\nfunction handleMouseMove(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar g = getRoot(svgDoc);\n\n\tif(state == 'pan' && enablePan) {\n\t\t// Pan mode\n\t\tvar p = getEventPoint(evt).matrixTransform(stateTf);\n\n\t\tsetCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));\n\t} else if(state == 'drag' && enableDrag) {\n\t\t// Drag mode\n\t\tvar p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());\n\n\t\tsetCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));\n\n\t\tstateOrigin = p;\n\t}\n}\n\n/**\n * Handle click event.\n */\nfunction handleMouseDown(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar g = getRoot(svgDoc);\n\n\tif(\n\t\tevt.target.tagName == \"svg\" \n\t\t|| !enableDrag // Pan anyway when drag is disabled and the user clicked on an element \n\t) {\n\t\t// Pan mode\n\t\tstate = 'pan';\n\n\t\tstateTf = g.getCTM().inverse();\n\n\t\tstateOrigin = getEventPoint(evt).matrixTransform(stateTf);\n\t} else {\n\t\t// Drag mode\n\t\tstate = 'drag';\n\n\t\tstateTarget = evt.target;\n\n\t\tstateTf = g.getCTM().inverse();\n\n\t\tstateOrigin = getEventPoint(evt).matrixTransform(stateTf);\n\t}\n}\n\n/**\n * Handle mouse button release event.\n */\nfunction handleMouseUp(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tif(state == 'pan' || state == 'drag') {\n\t\t// Quit pan mode\n\t\tstate = '';\n\t}\n}\n"
  }
]