All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/2] Add list_fn_callees.py and list_helpers.py scripts
@ 2020-07-16 10:39 Ahmed Karaman
  2020-07-16 10:39 ` [PATCH v2 1/2] scripts/performance: Add list_fn_callees.py script Ahmed Karaman
  2020-07-16 10:39 ` [PATCH v2 2/2] scripts/performance: Add list_helpers.py script Ahmed Karaman
  0 siblings, 2 replies; 7+ messages in thread
From: Ahmed Karaman @ 2020-07-16 10:39 UTC (permalink / raw)
  To: qemu-devel, aleksandar.qemu.devel, philmd, alex.bennee, eblake,
	ldoktor, rth, ehabkost, crosa
  Cc: Ahmed Karaman

Hi,

This series adds the two new scripts introduced in report 4 of the
"TCG Continuous Benchmarking" GSoC project.

"list_fn_callees.py" is used for printing the callees of a given list
of QEMU functions.

"list_helpers.py" is used for printing the executed helpers of a QEMU
invocation.

To learn more about how the scripts work and how they can be used for
analyzing the performance of different targets, please check the
"Listing QEMU Helpers and Function Callees" report.

Report link:
https://lists.nongnu.org/archive/html/qemu-devel/2020-07/msg04227.html

Best regards,
Ahmed Karaman

v1->v2:
- Indent script example output in commit message to pass the "Test checkpatch"
  on patchew.

Ahmed Karaman (2):
  scripts/performance: Add list_fn_callees.py script
  scripts/performance: Add list_helpers.py script

 scripts/performance/list_fn_callees.py | 228 +++++++++++++++++++++++++
 scripts/performance/list_helpers.py    | 207 ++++++++++++++++++++++
 2 files changed, 435 insertions(+)
 create mode 100755 scripts/performance/list_fn_callees.py
 create mode 100755 scripts/performance/list_helpers.py

-- 
2.17.1



^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2 1/2] scripts/performance: Add list_fn_callees.py script
  2020-07-16 10:39 [PATCH v2 0/2] Add list_fn_callees.py and list_helpers.py scripts Ahmed Karaman
@ 2020-07-16 10:39 ` Ahmed Karaman
  2020-07-16 10:39 ` [PATCH v2 2/2] scripts/performance: Add list_helpers.py script Ahmed Karaman
  1 sibling, 0 replies; 7+ messages in thread
From: Ahmed Karaman @ 2020-07-16 10:39 UTC (permalink / raw)
  To: qemu-devel, aleksandar.qemu.devel, philmd, alex.bennee, eblake,
	ldoktor, rth, ehabkost, crosa
  Cc: Ahmed Karaman

Python script that prints the callees of a given list of QEMU
functions.

Syntax:
list_fn_callees.py [-h] -f FUNCTION [FUNCTION ...] -- \
               <qemu executable> [<qemu executable options>] \
               <target executable> [<target executable options>]

[-h] - Print the script arguments help message.
-f FUNCTION [FUNCTION ...] - List of function names

Example of usage:
list_fn_callees.py -f helper_float_sub_d helper_float_mul_d -- \
                      qemu-mips coulomb_double-mips -n10

Example output:
 Total number of instructions: 108,952,851

 Callees of helper_float_sub_d:

 No. Instructions Percentage  Calls Ins/Call Function Name Source File
 --- ------------ ---------- ------ -------- ------------- ---------------
   1      153,160     0.141%  1,305     117  float64_sub   <qemu>/fpu/softfloat.c

 Callees of helper_float_mul_d:

 No. Instructions Percentage  Calls Ins/Call Function Name Source File
 --- ------------ ---------- ------ -------- ------------- ---------------
   1      131,137     0.120%  1,014      129 float64_mul   <qemu>/fpu/softfloat.c

Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
---
 scripts/performance/list_fn_callees.py | 228 +++++++++++++++++++++++++
 1 file changed, 228 insertions(+)
 create mode 100755 scripts/performance/list_fn_callees.py

diff --git a/scripts/performance/list_fn_callees.py b/scripts/performance/list_fn_callees.py
new file mode 100755
index 0000000000..f0ec5c8e81
--- /dev/null
+++ b/scripts/performance/list_fn_callees.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env python3
+
+#  Print the callees of a given list of QEMU functions.
+#
+#  Syntax:
+#  list_fn_callees.py [-h] -f FUNCTION [FUNCTION ...] -- \
+#                 <qemu executable> [<qemu executable options>] \
+#                 <target executable> [<target executable options>]
+#
+#  [-h] - Print the script arguments help message.
+#  -f FUNCTION [FUNCTION ...] - List of function names
+#
+#  Example of usage:
+#  list_fn_callees.py -f helper_float_sub_d helper_float_mul_d -- \
+#                        qemu-mips coulomb_double-mips
+#
+#  This file is a part of the project "TCG Continuous Benchmarking".
+#
+#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
+#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import argparse
+import os
+import subprocess
+import sys
+import tempfile
+
+
+def find_function_lines(function_name, callgrind_data):
+    """
+    Search for the line with the function name in the
+    callgrind_annotate output when ran using --tre=calling.
+    All the function callees should be listed after that line.
+
+    Parameters:
+    function_name (string): The desired function name to print its callees
+    callgrind_data (list): callgrind_annotate output
+
+    Returns:
+    (list): List of function line numbers
+    """
+    lines = []
+    for i in range(len(callgrind_data)):
+        split_line = callgrind_data[i].split()
+        if len(split_line) > 2 and \
+                split_line[1] == "*" and \
+                split_line[2].split(":")[-1] == function_name:
+            # Function might be in the callgrind_annotate output more than
+            # once, so don't break after finding an instance
+            if callgrind_data[i + 1] != "\n":
+                # Only append the line number if the found instance has
+                # callees
+                lines.append(i)
+    return lines
+
+
+def get_function_calles(function_lines, callgrind_data):
+    """
+    Get all callees data for a function given its list of line numbers in
+    callgrind_annotate output.
+
+    Parameters:
+    function_lines (list): Line numbers of the function to get its callees
+    callgrind_data (list): callgrind_annotate output
+
+    Returns:
+    (list):[[number_of_instructions(int), callee_name(str),
+             number_of_calls(int), source_file(str)]]
+    """
+    callees = []
+    for function_line in function_lines:
+        next_callee = function_line + 1
+        while (callgrind_data[next_callee] != "\n"):
+            split_line = callgrind_data[next_callee].split()
+            number_of_instructions = int(split_line[0].replace(",", ""))
+            source_file = split_line[2].split(":")[0]
+            callee_name = split_line[2].split(":")[1]
+            number_of_calls = int(split_line[3][1:-2])
+            callees.append([number_of_instructions, callee_name,
+                            number_of_calls, source_file])
+            next_callee += 1
+    return sorted(callees, reverse=True)
+
+
+def main():
+    # Parse the command line arguments
+    parser = argparse.ArgumentParser(
+        usage="list_fn_callees.py [-h] -f FUNCTION [FUNCTION ...] -- "
+        "<qemu executable> [<qemu executable options>] "
+        "<target executable> [<target executable options>]")
+
+    parser.add_argument("-f", dest="function", type=str,
+                        nargs="+", required=True,
+                        help="list of function names to print their callees")
+
+    parser.add_argument("command", type=str, nargs="+", help=argparse.SUPPRESS)
+
+    args = parser.parse_args()
+
+    # Extract the needed variables from the args
+    command = args.command
+    function_names = args.function
+
+    # Insure that valgrind is installed
+    check_valgrind = subprocess.run(
+        ["which", "valgrind"], stdout=subprocess.DEVNULL)
+    if check_valgrind.returncode:
+        sys.exit("Please install valgrind before running the script.")
+
+    # Save all intermediate files in a temporary directory
+    with tempfile.TemporaryDirectory() as tmpdirname:
+        # callgrind output file path
+        data_path = os.path.join(tmpdirname, "callgrind.data")
+        # callgrind_annotate output file path
+        annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
+
+        # Run callgrind
+        callgrind = subprocess.run((["valgrind",
+                                     "--tool=callgrind",
+                                     "--callgrind-out-file=" + data_path]
+                                    + command),
+                                   stdout=subprocess.DEVNULL,
+                                   stderr=subprocess.PIPE)
+        if callgrind.returncode:
+            sys.exit(callgrind.stderr.decode("utf-8"))
+
+        # Save callgrind_annotate output
+        with open(annotate_out_path, "w") as output:
+            callgrind_annotate = subprocess.run(
+                ["callgrind_annotate", data_path,
+                    "--threshold=100", "--tree=calling"],
+                stdout=output,
+                stderr=subprocess.PIPE)
+            if callgrind_annotate.returncode:
+                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
+
+        # Read the callgrind_annotate output to callgrind_data[]
+        callgrind_data = []
+        with open(annotate_out_path, "r") as data:
+            callgrind_data = data.readlines()
+
+        # Line number with the total number of instructions
+        total_instructions_line_number = 20
+        # Get the total number of instructions
+        total_instructions_line_data = \
+            callgrind_data[total_instructions_line_number]
+        total_instructions = total_instructions_line_data.split()[0]
+
+        print("Total number of instructions: {}\n".format(total_instructions))
+
+        # Remove commas and convert to int
+        total_instructions = int(total_instructions.replace(",", ""))
+
+        for function_name in function_names:
+            # Line numbers with the desired function
+            function_lines = find_function_lines(function_name, callgrind_data)
+
+            if len(function_lines) == 0:
+                print("Couldn't locate function: {}.\n".format(
+                    function_name))
+                continue
+
+            # Get function callees
+            function_callees = get_function_calles(
+                function_lines, callgrind_data)
+
+            print("Callees of {}:\n".format(function_name))
+
+            # Print table header
+            print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
+                  format(
+                      "No.",
+                      "Instructions",
+                      "Percentage",
+                      "Calls",
+                      "Ins/Call",
+                      "Function Name",
+                      "Source File")
+                  )
+
+            print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
+                  format(
+                      "-" * 4,
+                      "-" * 15,
+                      "-" * 10,
+                      "-" * 15,
+                      "-" * 10,
+                      "-" * 25,
+                      "-" * 30)
+                  )
+
+            for (index, callee) in enumerate(function_callees, start=1):
+                instructions = callee[0]
+                percentage = (callee[0] / total_instructions) * 100
+                calls = callee[2]
+                instruction_per_call = int(callee[0] / callee[2])
+                function_name = callee[1]
+                source_file = callee[3]
+                # Print extracted data
+                print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}  {}".
+                      format(
+                          index,
+                          format(instructions, ","),
+                          round(percentage, 3),
+                          format(calls, ","),
+                          format(instruction_per_call, ","),
+                          function_name,
+                          source_file)
+                      )
+
+            print("\n")
+
+
+if __name__ == "__main__":
+    main()
-- 
2.17.1



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 2/2] scripts/performance: Add list_helpers.py script
  2020-07-16 10:39 [PATCH v2 0/2] Add list_fn_callees.py and list_helpers.py scripts Ahmed Karaman
  2020-07-16 10:39 ` [PATCH v2 1/2] scripts/performance: Add list_fn_callees.py script Ahmed Karaman
@ 2020-07-16 10:39 ` Ahmed Karaman
  2020-07-28 10:30   ` Aleksandar Markovic
  1 sibling, 1 reply; 7+ messages in thread
From: Ahmed Karaman @ 2020-07-16 10:39 UTC (permalink / raw)
  To: qemu-devel, aleksandar.qemu.devel, philmd, alex.bennee, eblake,
	ldoktor, rth, ehabkost, crosa
  Cc: Ahmed Karaman

Python script that prints executed helpers of a QEMU invocation.

Syntax:
list_helpers.py [-h] -- \
               <qemu executable> [<qemu executable options>] \
               <target executable> [<target executable options>]

[-h] - Print the script arguments help message.

Example of usage:
list_helpers.py -- qemu-mips coulomb_double-mips -n10

Example output:
 Total number of instructions: 108,933,695

 Executed QEMU Helpers:

 No. Ins     Percent  Calls Ins/Call Helper Name             Source File
 --- ------- ------- ------ -------- --------------------    ---------------
   1 183,021  0.168%  1,305      140 helper_float_sub_d      <qemu>/target/mips/fpu_helper.c
   2 177,111  0.163%    770      230 helper_float_madd_d     <qemu>/target/mips/fpu_helper.c
   3 171,537  0.157%  1,014      169 helper_float_mul_d      <qemu>/target/mips/fpu_helper.c
   4 157,298  0.144%  2,443       64 helper_lookup_tb_ptr    <qemu>/accel/tcg/tcg-runtime.c
   5 138,123  0.127%    897      153 helper_float_add_d      <qemu>/target/mips/fpu_helper.c
   6  47,083  0.043%    207      227 helper_float_msub_d     <qemu>/target/mips/fpu_helper.c
   7  24,062  0.022%    487       49 helper_cmp_d_lt         <qemu>/target/mips/fpu_helper.c
   8  22,910  0.021%    150      152 helper_float_div_d      <qemu>/target/mips/fpu_helper.c
   9  15,497  0.014%    321       48 helper_cmp_d_eq         <qemu>/target/mips/fpu_helper.c
  10   9,100  0.008%     52      175 helper_float_trunc_w_d  <qemu>/target/mips/fpu_helper.c
  11   7,059  0.006%     10      705 helper_float_sqrt_d     <qemu>/target/mips/fpu_helper.c
  12   3,000  0.003%     40       75 helper_cmp_d_ule        <qemu>/target/mips/fpu_helper.c
  13   2,720  0.002%     20      136 helper_float_cvtd_w     <qemu>/target/mips/fpu_helper.c
  14   2,477  0.002%     27       91 helper_swl              <qemu>/target/mips/op_helper.c
  15   2,000  0.002%     40       50 helper_cmp_d_le         <qemu>/target/mips/fpu_helper.c
  16   1,800  0.002%     40       45 helper_cmp_d_un         <qemu>/target/mips/fpu_helper.c
  17   1,164  0.001%     12       97 helper_raise_exception_ <qemu>/target/mips/op_helper.c
  18     720  0.001%     10       72 helper_cmp_d_ult        <qemu>/target/mips/fpu_helper.c
  19     560  0.001%    140        4 helper_cfc1             <qemu>/target/mips/fpu_helper.c

Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
---
 scripts/performance/list_helpers.py | 207 ++++++++++++++++++++++++++++
 1 file changed, 207 insertions(+)
 create mode 100755 scripts/performance/list_helpers.py

diff --git a/scripts/performance/list_helpers.py b/scripts/performance/list_helpers.py
new file mode 100755
index 0000000000..a97c7ed4fe
--- /dev/null
+++ b/scripts/performance/list_helpers.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+
+#  Print the executed helpers of a QEMU invocation.
+#
+#  Syntax:
+#  list_helpers.py [-h] -- \
+#                 <qemu executable> [<qemu executable options>] \
+#                 <target executable> [<target executable options>]
+#
+#  [-h] - Print the script arguments help message.
+#
+#  Example of usage:
+#  list_helpers.py -- qemu-mips coulomb_double-mips
+#
+#  This file is a part of the project "TCG Continuous Benchmarking".
+#
+#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
+#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import argparse
+import os
+import subprocess
+import sys
+import tempfile
+
+
+def find_JIT_line(callgrind_data):
+    """
+    Search for the line with the JIT call in the callgrind_annotate
+    output when ran using --tre=calling.
+    All the helpers should be listed after that line.
+
+    Parameters:
+    callgrind_data (list): callgrind_annotate output
+
+    Returns:
+    (int): Line number of JIT call
+    """
+    line = -1
+    for i in range(len(callgrind_data)):
+        split_line = callgrind_data[i].split()
+        if len(split_line) > 2 and \
+                split_line[1] == "*" and \
+                split_line[-1] == "[???]":
+            line = i
+            break
+    return line
+
+
+def get_helpers(JIT_line, callgrind_data):
+    """
+    Get all helpers data given the line number of the JIT call.
+
+    Parameters:
+    JIT_line (int): Line number of the JIT call
+    callgrind_data (list): callgrind_annotate output
+
+    Returns:
+    (list):[[number_of_instructions(int), helper_name(str),
+             number_of_calls(int), source_file(str)]]
+    """
+    helpers = []
+    next_helper = JIT_line + 1
+    while (callgrind_data[next_helper] != "\n"):
+        split_line = callgrind_data[next_helper].split()
+        number_of_instructions = int(split_line[0].replace(",", ""))
+        source_file = split_line[2].split(":")[0]
+        callee_name = split_line[2].split(":")[1]
+        number_of_calls = int(split_line[3][1:-2])
+        helpers.append([number_of_instructions, callee_name,
+                        number_of_calls, source_file])
+        next_helper += 1
+    return sorted(helpers, reverse=True)
+
+
+def main():
+    # Parse the command line arguments
+    parser = argparse.ArgumentParser(
+        usage="list_helpers.py [-h] -- "
+        "<qemu executable> [<qemu executable options>] "
+        "<target executable> [<target executable options>]")
+
+    parser.add_argument("command", type=str, nargs="+", help=argparse.SUPPRESS)
+
+    args = parser.parse_args()
+
+    # Extract the needed variables from the args
+    command = args.command
+
+    # Insure that valgrind is installed
+    check_valgrind = subprocess.run(
+        ["which", "valgrind"], stdout=subprocess.DEVNULL)
+    if check_valgrind.returncode:
+        sys.exit("Please install valgrind before running the script.")
+
+    # Save all intermediate files in a temporary directory
+    with tempfile.TemporaryDirectory() as tmpdirname:
+        # callgrind output file path
+        data_path = os.path.join(tmpdirname, "callgrind.data")
+        # callgrind_annotate output file path
+        annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
+
+        # Run callgrind
+        callgrind = subprocess.run((["valgrind",
+                                     "--tool=callgrind",
+                                     "--callgrind-out-file=" + data_path]
+                                    + command),
+                                   stdout=subprocess.DEVNULL,
+                                   stderr=subprocess.PIPE)
+        if callgrind.returncode:
+            sys.exit(callgrind.stderr.decode("utf-8"))
+
+        # Save callgrind_annotate output
+        with open(annotate_out_path, "w") as output:
+            callgrind_annotate = subprocess.run(
+                ["callgrind_annotate", data_path,
+                    "--threshold=100", "--tree=calling"],
+                stdout=output,
+                stderr=subprocess.PIPE)
+            if callgrind_annotate.returncode:
+                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
+
+        # Read the callgrind_annotate output to callgrind_data[]
+        callgrind_data = []
+        with open(annotate_out_path, "r") as data:
+            callgrind_data = data.readlines()
+
+        # Line number with the total number of instructions
+        total_instructions_line_number = 20
+        # Get the total number of instructions
+        total_instructions_line_data = \
+            callgrind_data[total_instructions_line_number]
+        total_instructions = total_instructions_line_data.split()[0]
+
+        print("Total number of instructions: {}\n".format(total_instructions))
+
+        # Remove commas and convert to int
+        total_instructions = int(total_instructions.replace(",", ""))
+
+        # Line number with the JIT call
+        JIT_line = find_JIT_line(callgrind_data)
+        if JIT_line == -1:
+            sys.exit("Couldn't locate the JIT call ... Exiting")
+
+        # Get helpers
+        helpers = get_helpers(JIT_line, callgrind_data)
+
+        print("Executed QEMU Helpers:\n")
+
+        # Print table header
+        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
+              format(
+                  "No.",
+                  "Instructions",
+                  "Percentage",
+                  "Calls",
+                  "Ins/Call",
+                  "Helper Name",
+                  "Source File")
+              )
+
+        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
+              format(
+                  "-" * 4,
+                  "-" * 15,
+                  "-" * 10,
+                  "-" * 15,
+                  "-" * 10,
+                  "-" * 25,
+                  "-" * 30)
+              )
+
+        for (index, callee) in enumerate(helpers, start=1):
+            instructions = callee[0]
+            percentage = (callee[0] / total_instructions) * 100
+            calls = callee[2]
+            instruction_per_call = int(callee[0] / callee[2])
+            helper_name = callee[1]
+            source_file = callee[3]
+            # Print extracted data
+            print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}  {}".
+                  format(
+                      index,
+                      format(instructions, ","),
+                      round(percentage, 3),
+                      format(calls, ","),
+                      format(instruction_per_call, ","),
+                      helper_name,
+                      source_file)
+                  )
+
+
+if __name__ == "__main__":
+    main()
-- 
2.17.1



^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v2 2/2] scripts/performance: Add list_helpers.py script
  2020-07-16 10:39 ` [PATCH v2 2/2] scripts/performance: Add list_helpers.py script Ahmed Karaman
@ 2020-07-28 10:30   ` Aleksandar Markovic
  2020-07-28 10:43     ` Aleksandar Markovic
  2020-07-28 16:55     ` Ahmed Karaman
  0 siblings, 2 replies; 7+ messages in thread
From: Aleksandar Markovic @ 2020-07-28 10:30 UTC (permalink / raw)
  To: Ahmed Karaman
  Cc: ldoktor, ehabkost, philmd, qemu-devel, crosa, alex.bennee, rth

[-- Attachment #1: Type: text/plain, Size: 12132 bytes --]

On Thursday, July 16, 2020, Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
wrote:

> Python script that prints executed helpers of a QEMU invocation.
>
>
Hi, Ahmed.

You outlined the envisioned user workflow regarding this script in your
report. As I understand it, it generally goes like this:

1) The user first discovers helpers, and their performance data.
2) The user examines the callees of a particular helper of choice (usually,
the most instruction-consuming helper).
3) The user perhaps further examines a callee of a particular callee of the
particular helper.
4) The user continues this way until the conclusion can be drawn, or
maximal depth is reached.

The procedure might be time consuming since each step requires running an
emulation of the test program.

This makes me think that the faster and easier tool for the user (but, to
some, not that great, extent, harder for you) would be improved
list_helpers.py (and list_fn_calees.py) that provides list of all callees
for all helpers, in the tree form (so, callees of callees, callees of
callees of callees, etc.), rather than providing just a list of immediate
callees, like it currently does.

I think you can provide such functionality relatively easily using
recursion. See, let's say:

https://realpython.com/python-thinking-recursively/

Perhaps you can have a switch (let's say, --tree <yes|no>) that specifies
whether the script outputs just immediate callee list, or entire callee
tree.

Thanks,
Aleksandar


> Syntax:
> list_helpers.py [-h] -- \
>                <qemu executable> [<qemu executable options>] \
>                <target executable> [<target executable options>]
>
> [-h] - Print the script arguments help message.
>
> Example of usage:
> list_helpers.py -- qemu-mips coulomb_double-mips -n10
>
> Example output:
>  Total number of instructions: 108,933,695
>
>  Executed QEMU Helpers:
>
>  No. Ins     Percent  Calls Ins/Call Helper Name             Source File
>  --- ------- ------- ------ -------- --------------------
> ---------------
>    1 183,021  0.168%  1,305      140 helper_float_sub_d
> <qemu>/target/mips/fpu_helper.c
>    2 177,111  0.163%    770      230 helper_float_madd_d
>  <qemu>/target/mips/fpu_helper.c
>    3 171,537  0.157%  1,014      169 helper_float_mul_d
> <qemu>/target/mips/fpu_helper.c
>    4 157,298  0.144%  2,443       64 helper_lookup_tb_ptr
> <qemu>/accel/tcg/tcg-runtime.c
>    5 138,123  0.127%    897      153 helper_float_add_d
> <qemu>/target/mips/fpu_helper.c
>    6  47,083  0.043%    207      227 helper_float_msub_d
>  <qemu>/target/mips/fpu_helper.c
>    7  24,062  0.022%    487       49 helper_cmp_d_lt
>  <qemu>/target/mips/fpu_helper.c
>    8  22,910  0.021%    150      152 helper_float_div_d
> <qemu>/target/mips/fpu_helper.c
>    9  15,497  0.014%    321       48 helper_cmp_d_eq
>  <qemu>/target/mips/fpu_helper.c
>   10   9,100  0.008%     52      175 helper_float_trunc_w_d
> <qemu>/target/mips/fpu_helper.c
>   11   7,059  0.006%     10      705 helper_float_sqrt_d
>  <qemu>/target/mips/fpu_helper.c
>   12   3,000  0.003%     40       75 helper_cmp_d_ule
> <qemu>/target/mips/fpu_helper.c
>   13   2,720  0.002%     20      136 helper_float_cvtd_w
>  <qemu>/target/mips/fpu_helper.c
>   14   2,477  0.002%     27       91 helper_swl
> <qemu>/target/mips/op_helper.c
>   15   2,000  0.002%     40       50 helper_cmp_d_le
>  <qemu>/target/mips/fpu_helper.c
>   16   1,800  0.002%     40       45 helper_cmp_d_un
>  <qemu>/target/mips/fpu_helper.c
>   17   1,164  0.001%     12       97 helper_raise_exception_
> <qemu>/target/mips/op_helper.c
>   18     720  0.001%     10       72 helper_cmp_d_ult
> <qemu>/target/mips/fpu_helper.c
>   19     560  0.001%    140        4 helper_cfc1
>  <qemu>/target/mips/fpu_helper.c
>
> Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
> ---
>  scripts/performance/list_helpers.py | 207 ++++++++++++++++++++++++++++
>  1 file changed, 207 insertions(+)
>  create mode 100755 scripts/performance/list_helpers.py
>
> diff --git a/scripts/performance/list_helpers.py
> b/scripts/performance/list_helpers.py
> new file mode 100755
> index 0000000000..a97c7ed4fe
> --- /dev/null
> +++ b/scripts/performance/list_helpers.py
> @@ -0,0 +1,207 @@
> +#!/usr/bin/env python3
> +
> +#  Print the executed helpers of a QEMU invocation.
> +#
> +#  Syntax:
> +#  list_helpers.py [-h] -- \
> +#                 <qemu executable> [<qemu executable options>] \
> +#                 <target executable> [<target executable options>]
> +#
> +#  [-h] - Print the script arguments help message.
> +#
> +#  Example of usage:
> +#  list_helpers.py -- qemu-mips coulomb_double-mips
> +#
> +#  This file is a part of the project "TCG Continuous Benchmarking".
> +#
> +#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
> +#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.c
> om>
> +#
> +#  This program is free software: you can redistribute it and/or modify
> +#  it under the terms of the GNU General Public License as published by
> +#  the Free Software Foundation, either version 2 of the License, or
> +#  (at your option) any later version.
> +#
> +#  This program is distributed in the hope that it will be useful,
> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +#  GNU General Public License for more details.
> +#
> +#  You should have received a copy of the GNU General Public License
> +#  along with this program. If not, see <https://www.gnu.org/licenses/>.
> +
> +import argparse
> +import os
> +import subprocess
> +import sys
> +import tempfile
> +
> +
> +def find_JIT_line(callgrind_data):
> +    """
> +    Search for the line with the JIT call in the callgrind_annotate
> +    output when ran using --tre=calling.
> +    All the helpers should be listed after that line.
> +
> +    Parameters:
> +    callgrind_data (list): callgrind_annotate output
> +
> +    Returns:
> +    (int): Line number of JIT call
> +    """
> +    line = -1
> +    for i in range(len(callgrind_data)):
> +        split_line = callgrind_data[i].split()
> +        if len(split_line) > 2 and \
> +                split_line[1] == "*" and \
> +                split_line[-1] == "[???]":
> +            line = i
> +            break
> +    return line
> +
> +
> +def get_helpers(JIT_line, callgrind_data):
> +    """
> +    Get all helpers data given the line number of the JIT call.
> +
> +    Parameters:
> +    JIT_line (int): Line number of the JIT call
> +    callgrind_data (list): callgrind_annotate output
> +
> +    Returns:
> +    (list):[[number_of_instructions(int), helper_name(str),
> +             number_of_calls(int), source_file(str)]]
> +    """
> +    helpers = []
> +    next_helper = JIT_line + 1
> +    while (callgrind_data[next_helper] != "\n"):
> +        split_line = callgrind_data[next_helper].split()
> +        number_of_instructions = int(split_line[0].replace(",", ""))
> +        source_file = split_line[2].split(":")[0]
> +        callee_name = split_line[2].split(":")[1]
> +        number_of_calls = int(split_line[3][1:-2])
> +        helpers.append([number_of_instructions, callee_name,
> +                        number_of_calls, source_file])
> +        next_helper += 1
> +    return sorted(helpers, reverse=True)
> +
> +
> +def main():
> +    # Parse the command line arguments
> +    parser = argparse.ArgumentParser(
> +        usage="list_helpers.py [-h] -- "
> +        "<qemu executable> [<qemu executable options>] "
> +        "<target executable> [<target executable options>]")
> +
> +    parser.add_argument("command", type=str, nargs="+",
> help=argparse.SUPPRESS)
> +
> +    args = parser.parse_args()
> +
> +    # Extract the needed variables from the args
> +    command = args.command
> +
> +    # Insure that valgrind is installed
> +    check_valgrind = subprocess.run(
> +        ["which", "valgrind"], stdout=subprocess.DEVNULL)
> +    if check_valgrind.returncode:
> +        sys.exit("Please install valgrind before running the script.")
> +
> +    # Save all intermediate files in a temporary directory
> +    with tempfile.TemporaryDirectory() as tmpdirname:
> +        # callgrind output file path
> +        data_path = os.path.join(tmpdirname, "callgrind.data")
> +        # callgrind_annotate output file path
> +        annotate_out_path = os.path.join(tmpdirname,
> "callgrind_annotate.out")
> +
> +        # Run callgrind
> +        callgrind = subprocess.run((["valgrind",
> +                                     "--tool=callgrind",
> +                                     "--callgrind-out-file=" + data_path]
> +                                    + command),
> +                                   stdout=subprocess.DEVNULL,
> +                                   stderr=subprocess.PIPE)
> +        if callgrind.returncode:
> +            sys.exit(callgrind.stderr.decode("utf-8"))
> +
> +        # Save callgrind_annotate output
> +        with open(annotate_out_path, "w") as output:
> +            callgrind_annotate = subprocess.run(
> +                ["callgrind_annotate", data_path,
> +                    "--threshold=100", "--tree=calling"],
> +                stdout=output,
> +                stderr=subprocess.PIPE)
> +            if callgrind_annotate.returncode:
> +                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
> +
> +        # Read the callgrind_annotate output to callgrind_data[]
> +        callgrind_data = []
> +        with open(annotate_out_path, "r") as data:
> +            callgrind_data = data.readlines()
> +
> +        # Line number with the total number of instructions
> +        total_instructions_line_number = 20
> +        # Get the total number of instructions
> +        total_instructions_line_data = \
> +            callgrind_data[total_instructions_line_number]
> +        total_instructions = total_instructions_line_data.split()[0]
> +
> +        print("Total number of instructions:
> {}\n".format(total_instructions))
> +
> +        # Remove commas and convert to int
> +        total_instructions = int(total_instructions.replace(",", ""))
> +
> +        # Line number with the JIT call
> +        JIT_line = find_JIT_line(callgrind_data)
> +        if JIT_line == -1:
> +            sys.exit("Couldn't locate the JIT call ... Exiting")
> +
> +        # Get helpers
> +        helpers = get_helpers(JIT_line, callgrind_data)
> +
> +        print("Executed QEMU Helpers:\n")
> +
> +        # Print table header
> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
> +              format(
> +                  "No.",
> +                  "Instructions",
> +                  "Percentage",
> +                  "Calls",
> +                  "Ins/Call",
> +                  "Helper Name",
> +                  "Source File")
> +              )
> +
> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
> +              format(
> +                  "-" * 4,
> +                  "-" * 15,
> +                  "-" * 10,
> +                  "-" * 15,
> +                  "-" * 10,
> +                  "-" * 25,
> +                  "-" * 30)
> +              )
> +
> +        for (index, callee) in enumerate(helpers, start=1):
> +            instructions = callee[0]
> +            percentage = (callee[0] / total_instructions) * 100
> +            calls = callee[2]
> +            instruction_per_call = int(callee[0] / callee[2])
> +            helper_name = callee[1]
> +            source_file = callee[3]
> +            # Print extracted data
> +            print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}  {}".
> +                  format(
> +                      index,
> +                      format(instructions, ","),
> +                      round(percentage, 3),
> +                      format(calls, ","),
> +                      format(instruction_per_call, ","),
> +                      helper_name,
> +                      source_file)
> +                  )
> +
> +
> +if __name__ == "__main__":
> +    main()
> --
> 2.17.1
>
>

[-- Attachment #2: Type: text/html, Size: 16085 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2 2/2] scripts/performance: Add list_helpers.py script
  2020-07-28 10:30   ` Aleksandar Markovic
@ 2020-07-28 10:43     ` Aleksandar Markovic
  2020-07-28 10:51       ` Aleksandar Markovic
  2020-07-28 16:55     ` Ahmed Karaman
  1 sibling, 1 reply; 7+ messages in thread
From: Aleksandar Markovic @ 2020-07-28 10:43 UTC (permalink / raw)
  To: Ahmed Karaman
  Cc: ldoktor, ehabkost, philmd, qemu-devel, crosa, alex.bennee, rth

[-- Attachment #1: Type: text/plain, Size: 13146 bytes --]

On Tuesday, July 28, 2020, Aleksandar Markovic <
aleksandar.qemu.devel@gmail.com> wrote:

>
>
> On Thursday, July 16, 2020, Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
> wrote:
>
>> Python script that prints executed helpers of a QEMU invocation.
>>
>>
> Hi, Ahmed.
>
> You outlined the envisioned user workflow regarding this script in your
> report. As I understand it, it generally goes like this:
>
> 1) The user first discovers helpers, and their performance data.
> 2) The user examines the callees of a particular helper of choice
> (usually, the most instruction-consuming helper).
> 3) The user perhaps further examines a callee of a particular callee of
> the particular helper.
> 4) The user continues this way until the conclusion can be drawn, or
> maximal depth is reached.
>
> The procedure might be time consuming since each step requires running an
> emulation of the test program.
>
> This makes me think that the faster and easier tool for the user (but, to
> some, not that great, extent, harder for you) would be improved
> list_helpers.py (and list_fn_calees.py) that provides list of all callees
> for all helpers, in the tree form (so, callees of callees, callees of
> callees of callees, etc.), rather than providing just a list of immediate
> callees, like it currently does.
>
> I think you can provide such functionality relatively easily using
> recursion. See, let's say:
>
> https://realpython.com/python-thinking-recursively/
>
>
For printing trees like this:

foo
├── bar
│ ├── a
│ └── b
├── baz
└── qux
└── c⏎
d

you can potentialy use tree-format library:

https://pypi.org/project/tree-format/ .


> Perhaps you can have a switch (let's say, --tree <yes|no>) that specifies
> whether the script outputs just immediate callee list, or entire callee
> tree.
>
> Thanks,
> Aleksandar
>
>
>> Syntax:
>> list_helpers.py [-h] -- \
>>                <qemu executable> [<qemu executable options>] \
>>                <target executable> [<target executable options>]
>>
>> [-h] - Print the script arguments help message.
>>
>> Example of usage:
>> list_helpers.py -- qemu-mips coulomb_double-mips -n10
>>
>> Example output:
>>  Total number of instructions: 108,933,695
>>
>>  Executed QEMU Helpers:
>>
>>  No. Ins     Percent  Calls Ins/Call Helper Name             Source File
>>  --- ------- ------- ------ -------- --------------------
>> ---------------
>>    1 183,021  0.168%  1,305      140 helper_float_sub_d
>> <qemu>/target/mips/fpu_helper.c
>>    2 177,111  0.163%    770      230 helper_float_madd_d
>>  <qemu>/target/mips/fpu_helper.c
>>    3 171,537  0.157%  1,014      169 helper_float_mul_d
>> <qemu>/target/mips/fpu_helper.c
>>    4 157,298  0.144%  2,443       64 helper_lookup_tb_ptr
>> <qemu>/accel/tcg/tcg-runtime.c
>>    5 138,123  0.127%    897      153 helper_float_add_d
>> <qemu>/target/mips/fpu_helper.c
>>    6  47,083  0.043%    207      227 helper_float_msub_d
>>  <qemu>/target/mips/fpu_helper.c
>>    7  24,062  0.022%    487       49 helper_cmp_d_lt
>>  <qemu>/target/mips/fpu_helper.c
>>    8  22,910  0.021%    150      152 helper_float_div_d
>> <qemu>/target/mips/fpu_helper.c
>>    9  15,497  0.014%    321       48 helper_cmp_d_eq
>>  <qemu>/target/mips/fpu_helper.c
>>   10   9,100  0.008%     52      175 helper_float_trunc_w_d
>> <qemu>/target/mips/fpu_helper.c
>>   11   7,059  0.006%     10      705 helper_float_sqrt_d
>>  <qemu>/target/mips/fpu_helper.c
>>   12   3,000  0.003%     40       75 helper_cmp_d_ule
>> <qemu>/target/mips/fpu_helper.c
>>   13   2,720  0.002%     20      136 helper_float_cvtd_w
>>  <qemu>/target/mips/fpu_helper.c
>>   14   2,477  0.002%     27       91 helper_swl
>> <qemu>/target/mips/op_helper.c
>>   15   2,000  0.002%     40       50 helper_cmp_d_le
>>  <qemu>/target/mips/fpu_helper.c
>>   16   1,800  0.002%     40       45 helper_cmp_d_un
>>  <qemu>/target/mips/fpu_helper.c
>>   17   1,164  0.001%     12       97 helper_raise_exception_
>> <qemu>/target/mips/op_helper.c
>>   18     720  0.001%     10       72 helper_cmp_d_ult
>> <qemu>/target/mips/fpu_helper.c
>>   19     560  0.001%    140        4 helper_cfc1
>>  <qemu>/target/mips/fpu_helper.c
>>
>> Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>> ---
>>  scripts/performance/list_helpers.py | 207 ++++++++++++++++++++++++++++
>>  1 file changed, 207 insertions(+)
>>  create mode 100755 scripts/performance/list_helpers.py
>>
>> diff --git a/scripts/performance/list_helpers.py
>> b/scripts/performance/list_helpers.py
>> new file mode 100755
>> index 0000000000..a97c7ed4fe
>> --- /dev/null
>> +++ b/scripts/performance/list_helpers.py
>> @@ -0,0 +1,207 @@
>> +#!/usr/bin/env python3
>> +
>> +#  Print the executed helpers of a QEMU invocation.
>> +#
>> +#  Syntax:
>> +#  list_helpers.py [-h] -- \
>> +#                 <qemu executable> [<qemu executable options>] \
>> +#                 <target executable> [<target executable options>]
>> +#
>> +#  [-h] - Print the script arguments help message.
>> +#
>> +#  Example of usage:
>> +#  list_helpers.py -- qemu-mips coulomb_double-mips
>> +#
>> +#  This file is a part of the project "TCG Continuous Benchmarking".
>> +#
>> +#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>> +#  Copyright (C) 2020  Aleksandar Markovic <
>> aleksandar.qemu.devel@gmail.com>
>> +#
>> +#  This program is free software: you can redistribute it and/or modify
>> +#  it under the terms of the GNU General Public License as published by
>> +#  the Free Software Foundation, either version 2 of the License, or
>> +#  (at your option) any later version.
>> +#
>> +#  This program is distributed in the hope that it will be useful,
>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> +#  GNU General Public License for more details.
>> +#
>> +#  You should have received a copy of the GNU General Public License
>> +#  along with this program. If not, see <https://www.gnu.org/licenses/>.
>> +
>> +import argparse
>> +import os
>> +import subprocess
>> +import sys
>> +import tempfile
>> +
>> +
>> +def find_JIT_line(callgrind_data):
>> +    """
>> +    Search for the line with the JIT call in the callgrind_annotate
>> +    output when ran using --tre=calling.
>> +    All the helpers should be listed after that line.
>> +
>> +    Parameters:
>> +    callgrind_data (list): callgrind_annotate output
>> +
>> +    Returns:
>> +    (int): Line number of JIT call
>> +    """
>> +    line = -1
>> +    for i in range(len(callgrind_data)):
>> +        split_line = callgrind_data[i].split()
>> +        if len(split_line) > 2 and \
>> +                split_line[1] == "*" and \
>> +                split_line[-1] == "[???]":
>> +            line = i
>> +            break
>> +    return line
>> +
>> +
>> +def get_helpers(JIT_line, callgrind_data):
>> +    """
>> +    Get all helpers data given the line number of the JIT call.
>> +
>> +    Parameters:
>> +    JIT_line (int): Line number of the JIT call
>> +    callgrind_data (list): callgrind_annotate output
>> +
>> +    Returns:
>> +    (list):[[number_of_instructions(int), helper_name(str),
>> +             number_of_calls(int), source_file(str)]]
>> +    """
>> +    helpers = []
>> +    next_helper = JIT_line + 1
>> +    while (callgrind_data[next_helper] != "\n"):
>> +        split_line = callgrind_data[next_helper].split()
>> +        number_of_instructions = int(split_line[0].replace(",", ""))
>> +        source_file = split_line[2].split(":")[0]
>> +        callee_name = split_line[2].split(":")[1]
>> +        number_of_calls = int(split_line[3][1:-2])
>> +        helpers.append([number_of_instructions, callee_name,
>> +                        number_of_calls, source_file])
>> +        next_helper += 1
>> +    return sorted(helpers, reverse=True)
>> +
>> +
>> +def main():
>> +    # Parse the command line arguments
>> +    parser = argparse.ArgumentParser(
>> +        usage="list_helpers.py [-h] -- "
>> +        "<qemu executable> [<qemu executable options>] "
>> +        "<target executable> [<target executable options>]")
>> +
>> +    parser.add_argument("command", type=str, nargs="+",
>> help=argparse.SUPPRESS)
>> +
>> +    args = parser.parse_args()
>> +
>> +    # Extract the needed variables from the args
>> +    command = args.command
>> +
>> +    # Insure that valgrind is installed
>> +    check_valgrind = subprocess.run(
>> +        ["which", "valgrind"], stdout=subprocess.DEVNULL)
>> +    if check_valgrind.returncode:
>> +        sys.exit("Please install valgrind before running the script.")
>> +
>> +    # Save all intermediate files in a temporary directory
>> +    with tempfile.TemporaryDirectory() as tmpdirname:
>> +        # callgrind output file path
>> +        data_path = os.path.join(tmpdirname, "callgrind.data")
>> +        # callgrind_annotate output file path
>> +        annotate_out_path = os.path.join(tmpdirname,
>> "callgrind_annotate.out")
>> +
>> +        # Run callgrind
>> +        callgrind = subprocess.run((["valgrind",
>> +                                     "--tool=callgrind",
>> +                                     "--callgrind-out-file=" + data_path]
>> +                                    + command),
>> +                                   stdout=subprocess.DEVNULL,
>> +                                   stderr=subprocess.PIPE)
>> +        if callgrind.returncode:
>> +            sys.exit(callgrind.stderr.decode("utf-8"))
>> +
>> +        # Save callgrind_annotate output
>> +        with open(annotate_out_path, "w") as output:
>> +            callgrind_annotate = subprocess.run(
>> +                ["callgrind_annotate", data_path,
>> +                    "--threshold=100", "--tree=calling"],
>> +                stdout=output,
>> +                stderr=subprocess.PIPE)
>> +            if callgrind_annotate.returncode:
>> +                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
>> +
>> +        # Read the callgrind_annotate output to callgrind_data[]
>> +        callgrind_data = []
>> +        with open(annotate_out_path, "r") as data:
>> +            callgrind_data = data.readlines()
>> +
>> +        # Line number with the total number of instructions
>> +        total_instructions_line_number = 20
>> +        # Get the total number of instructions
>> +        total_instructions_line_data = \
>> +            callgrind_data[total_instructions_line_number]
>> +        total_instructions = total_instructions_line_data.split()[0]
>> +
>> +        print("Total number of instructions:
>> {}\n".format(total_instructions))
>> +
>> +        # Remove commas and convert to int
>> +        total_instructions = int(total_instructions.replace(",", ""))
>> +
>> +        # Line number with the JIT call
>> +        JIT_line = find_JIT_line(callgrind_data)
>> +        if JIT_line == -1:
>> +            sys.exit("Couldn't locate the JIT call ... Exiting")
>> +
>> +        # Get helpers
>> +        helpers = get_helpers(JIT_line, callgrind_data)
>> +
>> +        print("Executed QEMU Helpers:\n")
>> +
>> +        # Print table header
>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>> +              format(
>> +                  "No.",
>> +                  "Instructions",
>> +                  "Percentage",
>> +                  "Calls",
>> +                  "Ins/Call",
>> +                  "Helper Name",
>> +                  "Source File")
>> +              )
>> +
>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>> +              format(
>> +                  "-" * 4,
>> +                  "-" * 15,
>> +                  "-" * 10,
>> +                  "-" * 15,
>> +                  "-" * 10,
>> +                  "-" * 25,
>> +                  "-" * 30)
>> +              )
>> +
>> +        for (index, callee) in enumerate(helpers, start=1):
>> +            instructions = callee[0]
>> +            percentage = (callee[0] / total_instructions) * 100
>> +            calls = callee[2]
>> +            instruction_per_call = int(callee[0] / callee[2])
>> +            helper_name = callee[1]
>> +            source_file = callee[3]
>> +            # Print extracted data
>> +            print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}  {}".
>> +                  format(
>> +                      index,
>> +                      format(instructions, ","),
>> +                      round(percentage, 3),
>> +                      format(calls, ","),
>> +                      format(instruction_per_call, ","),
>> +                      helper_name,
>> +                      source_file)
>> +                  )
>> +
>> +
>> +if __name__ == "__main__":
>> +    main()
>> --
>> 2.17.1
>>
>>

[-- Attachment #2: Type: text/html, Size: 20720 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2 2/2] scripts/performance: Add list_helpers.py script
  2020-07-28 10:43     ` Aleksandar Markovic
@ 2020-07-28 10:51       ` Aleksandar Markovic
  0 siblings, 0 replies; 7+ messages in thread
From: Aleksandar Markovic @ 2020-07-28 10:51 UTC (permalink / raw)
  To: Ahmed Karaman
  Cc: ldoktor, ehabkost, philmd, qemu-devel, crosa, alex.bennee, rth

[-- Attachment #1: Type: text/plain, Size: 13895 bytes --]

On Tuesday, July 28, 2020, Aleksandar Markovic <
aleksandar.qemu.devel@gmail.com> wrote:

>
>
> On Tuesday, July 28, 2020, Aleksandar Markovic <
> aleksandar.qemu.devel@gmail.com> wrote:
>
>>
>>
>> On Thursday, July 16, 2020, Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>> wrote:
>>
>>> Python script that prints executed helpers of a QEMU invocation.
>>>
>>>
>> Hi, Ahmed.
>>
>> You outlined the envisioned user workflow regarding this script in your
>> report. As I understand it, it generally goes like this:
>>
>> 1) The user first discovers helpers, and their performance data.
>> 2) The user examines the callees of a particular helper of choice
>> (usually, the most instruction-consuming helper).
>> 3) The user perhaps further examines a callee of a particular callee of
>> the particular helper.
>> 4) The user continues this way until the conclusion can be drawn, or
>> maximal depth is reached.
>>
>> The procedure might be time consuming since each step requires running an
>> emulation of the test program.
>>
>> This makes me think that the faster and easier tool for the user (but, to
>> some, not that great, extent, harder for you) would be improved
>> list_helpers.py (and list_fn_calees.py) that provides list of all callees
>> for all helpers, in the tree form (so, callees of callees, callees of
>> callees of callees, etc.), rather than providing just a list of immediate
>> callees, like it currently does.
>>
>> I think you can provide such functionality relatively easily using
>> recursion. See, let's say:
>>
>> https://realpython.com/python-thinking-recursively/
>>
>>
> For printing trees like this:
>
> foo
> ├── bar
> │ ├── a
> │ └── b
> ├── baz
> └── qux
> └── c⏎
> d
>
> you can potentialy use tree-format library:
>
> https://pypi.org/project/tree-format/ .
>
>

Aah, probably you can't - license incompatibility.

However, you can write your own function for tree-like outputing, it is
really not that difficult - and, in that case, you have the full output
control, maybe that is the best approach.

Thanks,
Aleksandar



> Perhaps you can have a switch (let's say, --tree <yes|no>) that specifies
>> whether the script outputs just immediate callee list, or entire callee
>> tree.
>>
>> Thanks,
>> Aleksandar
>>
>>
>>> Syntax:
>>> list_helpers.py [-h] -- \
>>>                <qemu executable> [<qemu executable options>] \
>>>                <target executable> [<target executable options>]
>>>
>>> [-h] - Print the script arguments help message.
>>>
>>> Example of usage:
>>> list_helpers.py -- qemu-mips coulomb_double-mips -n10
>>>
>>> Example output:
>>>  Total number of instructions: 108,933,695
>>>
>>>  Executed QEMU Helpers:
>>>
>>>  No. Ins     Percent  Calls Ins/Call Helper Name             Source File
>>>  --- ------- ------- ------ -------- --------------------
>>> ---------------
>>>    1 183,021  0.168%  1,305      140 helper_float_sub_d
>>> <qemu>/target/mips/fpu_helper.c
>>>    2 177,111  0.163%    770      230 helper_float_madd_d
>>>  <qemu>/target/mips/fpu_helper.c
>>>    3 171,537  0.157%  1,014      169 helper_float_mul_d
>>> <qemu>/target/mips/fpu_helper.c
>>>    4 157,298  0.144%  2,443       64 helper_lookup_tb_ptr
>>> <qemu>/accel/tcg/tcg-runtime.c
>>>    5 138,123  0.127%    897      153 helper_float_add_d
>>> <qemu>/target/mips/fpu_helper.c
>>>    6  47,083  0.043%    207      227 helper_float_msub_d
>>>  <qemu>/target/mips/fpu_helper.c
>>>    7  24,062  0.022%    487       49 helper_cmp_d_lt
>>>  <qemu>/target/mips/fpu_helper.c
>>>    8  22,910  0.021%    150      152 helper_float_div_d
>>> <qemu>/target/mips/fpu_helper.c
>>>    9  15,497  0.014%    321       48 helper_cmp_d_eq
>>>  <qemu>/target/mips/fpu_helper.c
>>>   10   9,100  0.008%     52      175 helper_float_trunc_w_d
>>> <qemu>/target/mips/fpu_helper.c
>>>   11   7,059  0.006%     10      705 helper_float_sqrt_d
>>>  <qemu>/target/mips/fpu_helper.c
>>>   12   3,000  0.003%     40       75 helper_cmp_d_ule
>>> <qemu>/target/mips/fpu_helper.c
>>>   13   2,720  0.002%     20      136 helper_float_cvtd_w
>>>  <qemu>/target/mips/fpu_helper.c
>>>   14   2,477  0.002%     27       91 helper_swl
>>> <qemu>/target/mips/op_helper.c
>>>   15   2,000  0.002%     40       50 helper_cmp_d_le
>>>  <qemu>/target/mips/fpu_helper.c
>>>   16   1,800  0.002%     40       45 helper_cmp_d_un
>>>  <qemu>/target/mips/fpu_helper.c
>>>   17   1,164  0.001%     12       97 helper_raise_exception_
>>> <qemu>/target/mips/op_helper.c
>>>   18     720  0.001%     10       72 helper_cmp_d_ult
>>> <qemu>/target/mips/fpu_helper.c
>>>   19     560  0.001%    140        4 helper_cfc1
>>>  <qemu>/target/mips/fpu_helper.c
>>>
>>> Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>>> ---
>>>  scripts/performance/list_helpers.py | 207 ++++++++++++++++++++++++++++
>>>  1 file changed, 207 insertions(+)
>>>  create mode 100755 scripts/performance/list_helpers.py
>>>
>>> diff --git a/scripts/performance/list_helpers.py
>>> b/scripts/performance/list_helpers.py
>>> new file mode 100755
>>> index 0000000000..a97c7ed4fe
>>> --- /dev/null
>>> +++ b/scripts/performance/list_helpers.py
>>> @@ -0,0 +1,207 @@
>>> +#!/usr/bin/env python3
>>> +
>>> +#  Print the executed helpers of a QEMU invocation.
>>> +#
>>> +#  Syntax:
>>> +#  list_helpers.py [-h] -- \
>>> +#                 <qemu executable> [<qemu executable options>] \
>>> +#                 <target executable> [<target executable options>]
>>> +#
>>> +#  [-h] - Print the script arguments help message.
>>> +#
>>> +#  Example of usage:
>>> +#  list_helpers.py -- qemu-mips coulomb_double-mips
>>> +#
>>> +#  This file is a part of the project "TCG Continuous Benchmarking".
>>> +#
>>> +#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>>> +#  Copyright (C) 2020  Aleksandar Markovic <
>>> aleksandar.qemu.devel@gmail.com>
>>> +#
>>> +#  This program is free software: you can redistribute it and/or modify
>>> +#  it under the terms of the GNU General Public License as published by
>>> +#  the Free Software Foundation, either version 2 of the License, or
>>> +#  (at your option) any later version.
>>> +#
>>> +#  This program is distributed in the hope that it will be useful,
>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>>> +#  GNU General Public License for more details.
>>> +#
>>> +#  You should have received a copy of the GNU General Public License
>>> +#  along with this program. If not, see <https://www.gnu.org/licenses/
>>> >.
>>> +
>>> +import argparse
>>> +import os
>>> +import subprocess
>>> +import sys
>>> +import tempfile
>>> +
>>> +
>>> +def find_JIT_line(callgrind_data):
>>> +    """
>>> +    Search for the line with the JIT call in the callgrind_annotate
>>> +    output when ran using --tre=calling.
>>> +    All the helpers should be listed after that line.
>>> +
>>> +    Parameters:
>>> +    callgrind_data (list): callgrind_annotate output
>>> +
>>> +    Returns:
>>> +    (int): Line number of JIT call
>>> +    """
>>> +    line = -1
>>> +    for i in range(len(callgrind_data)):
>>> +        split_line = callgrind_data[i].split()
>>> +        if len(split_line) > 2 and \
>>> +                split_line[1] == "*" and \
>>> +                split_line[-1] == "[???]":
>>> +            line = i
>>> +            break
>>> +    return line
>>> +
>>> +
>>> +def get_helpers(JIT_line, callgrind_data):
>>> +    """
>>> +    Get all helpers data given the line number of the JIT call.
>>> +
>>> +    Parameters:
>>> +    JIT_line (int): Line number of the JIT call
>>> +    callgrind_data (list): callgrind_annotate output
>>> +
>>> +    Returns:
>>> +    (list):[[number_of_instructions(int), helper_name(str),
>>> +             number_of_calls(int), source_file(str)]]
>>> +    """
>>> +    helpers = []
>>> +    next_helper = JIT_line + 1
>>> +    while (callgrind_data[next_helper] != "\n"):
>>> +        split_line = callgrind_data[next_helper].split()
>>> +        number_of_instructions = int(split_line[0].replace(",", ""))
>>> +        source_file = split_line[2].split(":")[0]
>>> +        callee_name = split_line[2].split(":")[1]
>>> +        number_of_calls = int(split_line[3][1:-2])
>>> +        helpers.append([number_of_instructions, callee_name,
>>> +                        number_of_calls, source_file])
>>> +        next_helper += 1
>>> +    return sorted(helpers, reverse=True)
>>> +
>>> +
>>> +def main():
>>> +    # Parse the command line arguments
>>> +    parser = argparse.ArgumentParser(
>>> +        usage="list_helpers.py [-h] -- "
>>> +        "<qemu executable> [<qemu executable options>] "
>>> +        "<target executable> [<target executable options>]")
>>> +
>>> +    parser.add_argument("command", type=str, nargs="+",
>>> help=argparse.SUPPRESS)
>>> +
>>> +    args = parser.parse_args()
>>> +
>>> +    # Extract the needed variables from the args
>>> +    command = args.command
>>> +
>>> +    # Insure that valgrind is installed
>>> +    check_valgrind = subprocess.run(
>>> +        ["which", "valgrind"], stdout=subprocess.DEVNULL)
>>> +    if check_valgrind.returncode:
>>> +        sys.exit("Please install valgrind before running the script.")
>>> +
>>> +    # Save all intermediate files in a temporary directory
>>> +    with tempfile.TemporaryDirectory() as tmpdirname:
>>> +        # callgrind output file path
>>> +        data_path = os.path.join(tmpdirname, "callgrind.data")
>>> +        # callgrind_annotate output file path
>>> +        annotate_out_path = os.path.join(tmpdirname,
>>> "callgrind_annotate.out")
>>> +
>>> +        # Run callgrind
>>> +        callgrind = subprocess.run((["valgrind",
>>> +                                     "--tool=callgrind",
>>> +                                     "--callgrind-out-file=" +
>>> data_path]
>>> +                                    + command),
>>> +                                   stdout=subprocess.DEVNULL,
>>> +                                   stderr=subprocess.PIPE)
>>> +        if callgrind.returncode:
>>> +            sys.exit(callgrind.stderr.decode("utf-8"))
>>> +
>>> +        # Save callgrind_annotate output
>>> +        with open(annotate_out_path, "w") as output:
>>> +            callgrind_annotate = subprocess.run(
>>> +                ["callgrind_annotate", data_path,
>>> +                    "--threshold=100", "--tree=calling"],
>>> +                stdout=output,
>>> +                stderr=subprocess.PIPE)
>>> +            if callgrind_annotate.returncode:
>>> +                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
>>> +
>>> +        # Read the callgrind_annotate output to callgrind_data[]
>>> +        callgrind_data = []
>>> +        with open(annotate_out_path, "r") as data:
>>> +            callgrind_data = data.readlines()
>>> +
>>> +        # Line number with the total number of instructions
>>> +        total_instructions_line_number = 20
>>> +        # Get the total number of instructions
>>> +        total_instructions_line_data = \
>>> +            callgrind_data[total_instructions_line_number]
>>> +        total_instructions = total_instructions_line_data.split()[0]
>>> +
>>> +        print("Total number of instructions:
>>> {}\n".format(total_instructions))
>>> +
>>> +        # Remove commas and convert to int
>>> +        total_instructions = int(total_instructions.replace(",", ""))
>>> +
>>> +        # Line number with the JIT call
>>> +        JIT_line = find_JIT_line(callgrind_data)
>>> +        if JIT_line == -1:
>>> +            sys.exit("Couldn't locate the JIT call ... Exiting")
>>> +
>>> +        # Get helpers
>>> +        helpers = get_helpers(JIT_line, callgrind_data)
>>> +
>>> +        print("Executed QEMU Helpers:\n")
>>> +
>>> +        # Print table header
>>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>>> +              format(
>>> +                  "No.",
>>> +                  "Instructions",
>>> +                  "Percentage",
>>> +                  "Calls",
>>> +                  "Ins/Call",
>>> +                  "Helper Name",
>>> +                  "Source File")
>>> +              )
>>> +
>>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>>> +              format(
>>> +                  "-" * 4,
>>> +                  "-" * 15,
>>> +                  "-" * 10,
>>> +                  "-" * 15,
>>> +                  "-" * 10,
>>> +                  "-" * 25,
>>> +                  "-" * 30)
>>> +              )
>>> +
>>> +        for (index, callee) in enumerate(helpers, start=1):
>>> +            instructions = callee[0]
>>> +            percentage = (callee[0] / total_instructions) * 100
>>> +            calls = callee[2]
>>> +            instruction_per_call = int(callee[0] / callee[2])
>>> +            helper_name = callee[1]
>>> +            source_file = callee[3]
>>> +            # Print extracted data
>>> +            print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}
>>> {}".
>>> +                  format(
>>> +                      index,
>>> +                      format(instructions, ","),
>>> +                      round(percentage, 3),
>>> +                      format(calls, ","),
>>> +                      format(instruction_per_call, ","),
>>> +                      helper_name,
>>> +                      source_file)
>>> +                  )
>>> +
>>> +
>>> +if __name__ == "__main__":
>>> +    main()
>>> --
>>> 2.17.1
>>>
>>>

[-- Attachment #2: Type: text/html, Size: 21392 bytes --]

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2 2/2] scripts/performance: Add list_helpers.py script
  2020-07-28 10:30   ` Aleksandar Markovic
  2020-07-28 10:43     ` Aleksandar Markovic
@ 2020-07-28 16:55     ` Ahmed Karaman
  1 sibling, 0 replies; 7+ messages in thread
From: Ahmed Karaman @ 2020-07-28 16:55 UTC (permalink / raw)
  To: Aleksandar Markovic
  Cc: ldoktor, ehabkost, philmd, qemu-devel, John Snow, crosa,
	alex.bennee, rth

On Tue, Jul 28, 2020 at 12:30 PM Aleksandar Markovic
<aleksandar.qemu.devel@gmail.com> wrote:
>
>
>
> On Thursday, July 16, 2020, Ahmed Karaman <ahmedkhaledkaraman@gmail.com> wrote:
>>
>> Python script that prints executed helpers of a QEMU invocation.
>>
>
> Hi, Ahmed.
>
> You outlined the envisioned user workflow regarding this script in your report. As I understand it, it generally goes like this:
>
> 1) The user first discovers helpers, and their performance data.
> 2) The user examines the callees of a particular helper of choice (usually, the most instruction-consuming helper).
> 3) The user perhaps further examines a callee of a particular callee of the particular helper.
> 4) The user continues this way until the conclusion can be drawn, or maximal depth is reached.
>
> The procedure might be time consuming since each step requires running an emulation of the test program.
>
> This makes me think that the faster and easier tool for the user (but, to some, not that great, extent, harder for you) would be improved list_helpers.py (and list_fn_calees.py) that provides list of all callees for all helpers, in the tree form (so, callees of callees, callees of callees of callees, etc.), rather than providing just a list of immediate callees, like it currently does.
>
> I think you can provide such functionality relatively easily using recursion. See, let's say:
>
> https://realpython.com/python-thinking-recursively/
>
> Perhaps you can have a switch (let's say, --tree <yes|no>) that specifies whether the script outputs just immediate callee list, or entire callee tree.

I have to say, this is a very nice suggestion. I will start working on it!

>
> Thanks,
> Aleksandar
>
>>
>> Syntax:
>> list_helpers.py [-h] -- \
>>                <qemu executable> [<qemu executable options>] \
>>                <target executable> [<target executable options>]
>>
>> [-h] - Print the script arguments help message.
>>
>> Example of usage:
>> list_helpers.py -- qemu-mips coulomb_double-mips -n10
>>
>> Example output:
>>  Total number of instructions: 108,933,695
>>
>>  Executed QEMU Helpers:
>>
>>  No. Ins     Percent  Calls Ins/Call Helper Name             Source File
>>  --- ------- ------- ------ -------- --------------------    ---------------
>>    1 183,021  0.168%  1,305      140 helper_float_sub_d      <qemu>/target/mips/fpu_helper.c
>>    2 177,111  0.163%    770      230 helper_float_madd_d     <qemu>/target/mips/fpu_helper.c
>>    3 171,537  0.157%  1,014      169 helper_float_mul_d      <qemu>/target/mips/fpu_helper.c
>>    4 157,298  0.144%  2,443       64 helper_lookup_tb_ptr    <qemu>/accel/tcg/tcg-runtime.c
>>    5 138,123  0.127%    897      153 helper_float_add_d      <qemu>/target/mips/fpu_helper.c
>>    6  47,083  0.043%    207      227 helper_float_msub_d     <qemu>/target/mips/fpu_helper.c
>>    7  24,062  0.022%    487       49 helper_cmp_d_lt         <qemu>/target/mips/fpu_helper.c
>>    8  22,910  0.021%    150      152 helper_float_div_d      <qemu>/target/mips/fpu_helper.c
>>    9  15,497  0.014%    321       48 helper_cmp_d_eq         <qemu>/target/mips/fpu_helper.c
>>   10   9,100  0.008%     52      175 helper_float_trunc_w_d  <qemu>/target/mips/fpu_helper.c
>>   11   7,059  0.006%     10      705 helper_float_sqrt_d     <qemu>/target/mips/fpu_helper.c
>>   12   3,000  0.003%     40       75 helper_cmp_d_ule        <qemu>/target/mips/fpu_helper.c
>>   13   2,720  0.002%     20      136 helper_float_cvtd_w     <qemu>/target/mips/fpu_helper.c
>>   14   2,477  0.002%     27       91 helper_swl              <qemu>/target/mips/op_helper.c
>>   15   2,000  0.002%     40       50 helper_cmp_d_le         <qemu>/target/mips/fpu_helper.c
>>   16   1,800  0.002%     40       45 helper_cmp_d_un         <qemu>/target/mips/fpu_helper.c
>>   17   1,164  0.001%     12       97 helper_raise_exception_ <qemu>/target/mips/op_helper.c
>>   18     720  0.001%     10       72 helper_cmp_d_ult        <qemu>/target/mips/fpu_helper.c
>>   19     560  0.001%    140        4 helper_cfc1             <qemu>/target/mips/fpu_helper.c
>>
>> Signed-off-by: Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>> ---
>>  scripts/performance/list_helpers.py | 207 ++++++++++++++++++++++++++++
>>  1 file changed, 207 insertions(+)
>>  create mode 100755 scripts/performance/list_helpers.py
>>
>> diff --git a/scripts/performance/list_helpers.py b/scripts/performance/list_helpers.py
>> new file mode 100755
>> index 0000000000..a97c7ed4fe
>> --- /dev/null
>> +++ b/scripts/performance/list_helpers.py
>> @@ -0,0 +1,207 @@
>> +#!/usr/bin/env python3
>> +
>> +#  Print the executed helpers of a QEMU invocation.
>> +#
>> +#  Syntax:
>> +#  list_helpers.py [-h] -- \
>> +#                 <qemu executable> [<qemu executable options>] \
>> +#                 <target executable> [<target executable options>]
>> +#
>> +#  [-h] - Print the script arguments help message.
>> +#
>> +#  Example of usage:
>> +#  list_helpers.py -- qemu-mips coulomb_double-mips
>> +#
>> +#  This file is a part of the project "TCG Continuous Benchmarking".
>> +#
>> +#  Copyright (C) 2020  Ahmed Karaman <ahmedkhaledkaraman@gmail.com>
>> +#  Copyright (C) 2020  Aleksandar Markovic <aleksandar.qemu.devel@gmail.com>
>> +#
>> +#  This program is free software: you can redistribute it and/or modify
>> +#  it under the terms of the GNU General Public License as published by
>> +#  the Free Software Foundation, either version 2 of the License, or
>> +#  (at your option) any later version.
>> +#
>> +#  This program is distributed in the hope that it will be useful,
>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> +#  GNU General Public License for more details.
>> +#
>> +#  You should have received a copy of the GNU General Public License
>> +#  along with this program. If not, see <https://www.gnu.org/licenses/>.
>> +
>> +import argparse
>> +import os
>> +import subprocess
>> +import sys
>> +import tempfile
>> +
>> +
>> +def find_JIT_line(callgrind_data):
>> +    """
>> +    Search for the line with the JIT call in the callgrind_annotate
>> +    output when ran using --tre=calling.
>> +    All the helpers should be listed after that line.
>> +
>> +    Parameters:
>> +    callgrind_data (list): callgrind_annotate output
>> +
>> +    Returns:
>> +    (int): Line number of JIT call
>> +    """
>> +    line = -1
>> +    for i in range(len(callgrind_data)):
>> +        split_line = callgrind_data[i].split()
>> +        if len(split_line) > 2 and \
>> +                split_line[1] == "*" and \
>> +                split_line[-1] == "[???]":
>> +            line = i
>> +            break
>> +    return line
>> +
>> +
>> +def get_helpers(JIT_line, callgrind_data):
>> +    """
>> +    Get all helpers data given the line number of the JIT call.
>> +
>> +    Parameters:
>> +    JIT_line (int): Line number of the JIT call
>> +    callgrind_data (list): callgrind_annotate output
>> +
>> +    Returns:
>> +    (list):[[number_of_instructions(int), helper_name(str),
>> +             number_of_calls(int), source_file(str)]]
>> +    """
>> +    helpers = []
>> +    next_helper = JIT_line + 1
>> +    while (callgrind_data[next_helper] != "\n"):
>> +        split_line = callgrind_data[next_helper].split()
>> +        number_of_instructions = int(split_line[0].replace(",", ""))
>> +        source_file = split_line[2].split(":")[0]
>> +        callee_name = split_line[2].split(":")[1]
>> +        number_of_calls = int(split_line[3][1:-2])
>> +        helpers.append([number_of_instructions, callee_name,
>> +                        number_of_calls, source_file])
>> +        next_helper += 1
>> +    return sorted(helpers, reverse=True)
>> +
>> +
>> +def main():
>> +    # Parse the command line arguments
>> +    parser = argparse.ArgumentParser(
>> +        usage="list_helpers.py [-h] -- "
>> +        "<qemu executable> [<qemu executable options>] "
>> +        "<target executable> [<target executable options>]")
>> +
>> +    parser.add_argument("command", type=str, nargs="+", help=argparse.SUPPRESS)
>> +
>> +    args = parser.parse_args()
>> +
>> +    # Extract the needed variables from the args
>> +    command = args.command
>> +
>> +    # Insure that valgrind is installed
>> +    check_valgrind = subprocess.run(
>> +        ["which", "valgrind"], stdout=subprocess.DEVNULL)
>> +    if check_valgrind.returncode:
>> +        sys.exit("Please install valgrind before running the script.")
>> +
>> +    # Save all intermediate files in a temporary directory
>> +    with tempfile.TemporaryDirectory() as tmpdirname:
>> +        # callgrind output file path
>> +        data_path = os.path.join(tmpdirname, "callgrind.data")
>> +        # callgrind_annotate output file path
>> +        annotate_out_path = os.path.join(tmpdirname, "callgrind_annotate.out")
>> +
>> +        # Run callgrind
>> +        callgrind = subprocess.run((["valgrind",
>> +                                     "--tool=callgrind",
>> +                                     "--callgrind-out-file=" + data_path]
>> +                                    + command),
>> +                                   stdout=subprocess.DEVNULL,
>> +                                   stderr=subprocess.PIPE)
>> +        if callgrind.returncode:
>> +            sys.exit(callgrind.stderr.decode("utf-8"))
>> +
>> +        # Save callgrind_annotate output
>> +        with open(annotate_out_path, "w") as output:
>> +            callgrind_annotate = subprocess.run(
>> +                ["callgrind_annotate", data_path,
>> +                    "--threshold=100", "--tree=calling"],
>> +                stdout=output,
>> +                stderr=subprocess.PIPE)
>> +            if callgrind_annotate.returncode:
>> +                sys.exit(callgrind_annotate.stderr.decode("utf-8"))
>> +
>> +        # Read the callgrind_annotate output to callgrind_data[]
>> +        callgrind_data = []
>> +        with open(annotate_out_path, "r") as data:
>> +            callgrind_data = data.readlines()
>> +
>> +        # Line number with the total number of instructions
>> +        total_instructions_line_number = 20
>> +        # Get the total number of instructions
>> +        total_instructions_line_data = \
>> +            callgrind_data[total_instructions_line_number]
>> +        total_instructions = total_instructions_line_data.split()[0]
>> +
>> +        print("Total number of instructions: {}\n".format(total_instructions))
>> +
>> +        # Remove commas and convert to int
>> +        total_instructions = int(total_instructions.replace(",", ""))
>> +
>> +        # Line number with the JIT call
>> +        JIT_line = find_JIT_line(callgrind_data)
>> +        if JIT_line == -1:
>> +            sys.exit("Couldn't locate the JIT call ... Exiting")
>> +
>> +        # Get helpers
>> +        helpers = get_helpers(JIT_line, callgrind_data)
>> +
>> +        print("Executed QEMU Helpers:\n")
>> +
>> +        # Print table header
>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>> +              format(
>> +                  "No.",
>> +                  "Instructions",
>> +                  "Percentage",
>> +                  "Calls",
>> +                  "Ins/Call",
>> +                  "Helper Name",
>> +                  "Source File")
>> +              )
>> +
>> +        print("{:>4}  {:>15}  {:>10}  {:>15}  {:>10}  {:<25}  {}".
>> +              format(
>> +                  "-" * 4,
>> +                  "-" * 15,
>> +                  "-" * 10,
>> +                  "-" * 15,
>> +                  "-" * 10,
>> +                  "-" * 25,
>> +                  "-" * 30)
>> +              )
>> +
>> +        for (index, callee) in enumerate(helpers, start=1):
>> +            instructions = callee[0]
>> +            percentage = (callee[0] / total_instructions) * 100
>> +            calls = callee[2]
>> +            instruction_per_call = int(callee[0] / callee[2])
>> +            helper_name = callee[1]
>> +            source_file = callee[3]
>> +            # Print extracted data
>> +            print("{:>4}  {:>15}  {:>9.3f}%  {:>15}  {:>10}  {:<25}  {}".
>> +                  format(
>> +                      index,
>> +                      format(instructions, ","),
>> +                      round(percentage, 3),
>> +                      format(calls, ","),
>> +                      format(instruction_per_call, ","),
>> +                      helper_name,
>> +                      source_file)
>> +                  )
>> +
>> +
>> +if __name__ == "__main__":
>> +    main()
>> --
>> 2.17.1
>>

Best regards,
Ahmed Karaman


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2020-07-28 16:56 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-16 10:39 [PATCH v2 0/2] Add list_fn_callees.py and list_helpers.py scripts Ahmed Karaman
2020-07-16 10:39 ` [PATCH v2 1/2] scripts/performance: Add list_fn_callees.py script Ahmed Karaman
2020-07-16 10:39 ` [PATCH v2 2/2] scripts/performance: Add list_helpers.py script Ahmed Karaman
2020-07-28 10:30   ` Aleksandar Markovic
2020-07-28 10:43     ` Aleksandar Markovic
2020-07-28 10:51       ` Aleksandar Markovic
2020-07-28 16:55     ` Ahmed Karaman

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.