2
0

topN_callgrind.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. #!/usr/bin/env python3
  2. # Print the top N most executed functions in QEMU using callgrind.
  3. # Syntax:
  4. # topN_callgrind.py [-h] [-n] <number of displayed top functions> -- \
  5. # <qemu executable> [<qemu executable options>] \
  6. # <target executable> [<target executable options>]
  7. #
  8. # [-h] - Print the script arguments help message.
  9. # [-n] - Specify the number of top functions to print.
  10. # - If this flag is not specified, the tool defaults to 25.
  11. #
  12. # Example of usage:
  13. # topN_callgrind.py -n 20 -- qemu-arm coulomb_double-arm
  14. #
  15. # This file is a part of the project "TCG Continuous Benchmarking".
  16. #
  17. # Copyright (C) 2020 Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
  18. # Copyright (C) 2020 Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
  19. #
  20. # This program is free software: you can redistribute it and/or modify
  21. # it under the terms of the GNU General Public License as published by
  22. # the Free Software Foundation, either version 2 of the License, or
  23. # (at your option) any later version.
  24. #
  25. # This program is distributed in the hope that it will be useful,
  26. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  28. # GNU General Public License for more details.
  29. #
  30. # You should have received a copy of the GNU General Public License
  31. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  32. import argparse
  33. import os
  34. import subprocess
  35. import sys
  36. # Parse the command line arguments
  37. parser = argparse.ArgumentParser(
  38. usage='topN_callgrind.py [-h] [-n] <number of displayed top functions> -- '
  39. '<qemu executable> [<qemu executable options>] '
  40. '<target executable> [<target executable options>]')
  41. parser.add_argument('-n', dest='top', type=int, default=25,
  42. help='Specify the number of top functions to print.')
  43. parser.add_argument('command', type=str, nargs='+', help=argparse.SUPPRESS)
  44. args = parser.parse_args()
  45. # Extract the needed variables from the args
  46. command = args.command
  47. top = args.top
  48. # Insure that valgrind is installed
  49. check_valgrind_presence = subprocess.run(["which", "valgrind"],
  50. stdout=subprocess.DEVNULL)
  51. if check_valgrind_presence.returncode:
  52. sys.exit("Please install valgrind before running the script!")
  53. # Run callgrind
  54. callgrind = subprocess.run((
  55. ["valgrind", "--tool=callgrind", "--callgrind-out-file=/tmp/callgrind.data"]
  56. + command),
  57. stdout=subprocess.DEVNULL,
  58. stderr=subprocess.PIPE)
  59. if callgrind.returncode:
  60. sys.exit(callgrind.stderr.decode("utf-8"))
  61. # Save callgrind_annotate output to /tmp/callgrind_annotate.out
  62. with open("/tmp/callgrind_annotate.out", "w") as output:
  63. callgrind_annotate = subprocess.run(["callgrind_annotate",
  64. "/tmp/callgrind.data"],
  65. stdout=output,
  66. stderr=subprocess.PIPE)
  67. if callgrind_annotate.returncode:
  68. os.unlink('/tmp/callgrind.data')
  69. output.close()
  70. os.unlink('/tmp/callgrind_annotate.out')
  71. sys.exit(callgrind_annotate.stderr.decode("utf-8"))
  72. # Read the callgrind_annotate output to callgrind_data[]
  73. callgrind_data = []
  74. with open('/tmp/callgrind_annotate.out', 'r') as data:
  75. callgrind_data = data.readlines()
  76. # Line number with the total number of instructions
  77. total_instructions_line_number = 20
  78. # Get the total number of instructions
  79. total_instructions_line_data = callgrind_data[total_instructions_line_number]
  80. total_number_of_instructions = total_instructions_line_data.split(' ')[0]
  81. total_number_of_instructions = int(
  82. total_number_of_instructions.replace(',', ''))
  83. # Line number with the top function
  84. first_func_line = 25
  85. # Number of functions recorded by callgrind, last two lines are always empty
  86. number_of_functions = len(callgrind_data) - first_func_line - 2
  87. # Limit the number of top functions to "top"
  88. number_of_top_functions = (top if number_of_functions >
  89. top else number_of_functions)
  90. # Store the data of the top functions in top_functions[]
  91. top_functions = callgrind_data[first_func_line:
  92. first_func_line + number_of_top_functions]
  93. # Print table header
  94. print('{:>4} {:>10} {:<30} {}\n{} {} {} {}'.format('No.',
  95. 'Percentage',
  96. 'Function Name',
  97. 'Source File',
  98. '-' * 4,
  99. '-' * 10,
  100. '-' * 30,
  101. '-' * 30,
  102. ))
  103. # Print top N functions
  104. for (index, function) in enumerate(top_functions, start=1):
  105. function_data = function.split()
  106. # Calculate function percentage
  107. function_instructions = float(function_data[0].replace(',', ''))
  108. function_percentage = (function_instructions /
  109. total_number_of_instructions)*100
  110. # Get function name and source files path
  111. function_source_file, function_name = function_data[1].split(':')
  112. # Print extracted data
  113. print('{:>4} {:>9.3f}% {:<30} {}'.format(index,
  114. round(function_percentage, 3),
  115. function_name,
  116. function_source_file))
  117. # Remove intermediate files
  118. os.unlink('/tmp/callgrind.data')
  119. os.unlink('/tmp/callgrind_annotate.out')