diff --git a/qualtran/bloqs/chemistry/thc/thc.ipynb b/qualtran/bloqs/chemistry/thc/thc.ipynb index 03f0e4231..6028d53a4 100644 --- a/qualtran/bloqs/chemistry/thc/thc.ipynb +++ b/qualtran/bloqs/chemistry/thc/thc.ipynb @@ -23,7 +23,7 @@ "source": [ "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", "from qualtran import QBit, QInt, QUInt, QAny\n", - "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma, show_flame_graph\n", "from typing import *\n", "import numpy as np\n", "import sympy\n", @@ -155,6 +155,24 @@ "show_counts_sigma(thc_uni_sigma)" ] }, + { + "cell_type": "markdown", + "id": "176027fd-e452-40d4-a526-9e1b9e86896a", + "metadata": {}, + "source": [ + "### Flame Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65f3c175-c87d-4bf5-8e5f-8bde322152f4", + "metadata": {}, + "outputs": [], + "source": [ + "show_flame_graph(thc_uni)" + ] + }, { "cell_type": "markdown", "id": "f2cbc8e6", @@ -360,6 +378,24 @@ "show_counts_sigma(thc_prep_sigma)" ] }, + { + "cell_type": "markdown", + "id": "c70647ea-256b-410c-abf7-33c9e2e12ac5", + "metadata": {}, + "source": [ + "### Flame Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c5e44f2-d481-4e41-b9f3-10e9ec5de498", + "metadata": {}, + "outputs": [], + "source": [ + "show_flame_graph(thc_prep)" + ] + }, { "cell_type": "markdown", "id": "85818cf5", @@ -543,11 +579,37 @@ "show_call_graph(thc_sel_g)\n", "show_counts_sigma(thc_sel_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "e98a837b-7df6-41b8-9685-f00817b33550", + "metadata": {}, + "source": [ + "### Flame Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec11328c-f2f0-4217-8ad5-00f711d8f78c", + "metadata": {}, + "outputs": [], + "source": [ + "show_flame_graph(thc_sel)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "002749a5-7118-4c86-9a36-da44c8ffb3a4", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -561,7 +623,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/bloqs/phase_estimation_of_quantum_walk.ipynb b/qualtran/bloqs/phase_estimation_of_quantum_walk.ipynb index 6ce6f82be..e0cff8958 100644 --- a/qualtran/bloqs/phase_estimation_of_quantum_walk.ipynb +++ b/qualtran/bloqs/phase_estimation_of_quantum_walk.ipynb @@ -189,12 +189,34 @@ "outputs": [], "source": [ "from qualtran.bloqs.phase_estimation.qubitization_qpe import _qubitization_qpe_hubbard_model_large\n", - "from qualtran.drawing import show_call_graph\n", "qpe = _qubitization_qpe_hubbard_model_large.make()\n", "t_complexity.cache_clear()\n", "%time result = qpe.t_complexity()\n", "print(result)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Flame Graphs to visualize cost for QPE on Qubitized walk operator for 2D Hubbard model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.bloqs.phase_estimation.qubitization_qpe import _qubitization_qpe_hubbard_model_small\n", + "from qualtran.drawing import show_flame_graph\n", + "\n", + "qpe_small = _qubitization_qpe_hubbard_model_small.make()\n", + "\n", + "print(qpe_small.t_complexity())\n", + "\n", + "show_flame_graph(qpe_small)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/reflection_using_prepare.py b/qualtran/bloqs/reflection_using_prepare.py index 89d697fe9..904a4a506 100644 --- a/qualtran/bloqs/reflection_using_prepare.py +++ b/qualtran/bloqs/reflection_using_prepare.py @@ -87,7 +87,8 @@ def decompose_from_registers( # 0. Allocate new ancillas, if needed. phase_target = qm.qalloc(1)[0] if self.control_val is None else quregs.pop('control')[0] state_prep_ancilla = { - reg.name: qm.qalloc(reg.total_bits()) for reg in self.prepare_gate.junk_registers + reg.name: np.array(qm.qalloc(reg.total_bits())).reshape(reg.shape + (reg.bitsize,)) + for reg in self.prepare_gate.junk_registers } state_prep_selection_regs = quregs prepare_op = self.prepare_gate.on_registers( @@ -113,7 +114,7 @@ def decompose_from_registers( yield prepare_op # 4. Deallocate ancilla. - qm.qfree([q for anc in state_prep_ancilla.values() for q in anc]) + qm.qfree([q for anc in state_prep_ancilla.values() for q in anc.flatten()]) if self.control_val is None: qm.qfree([phase_target]) diff --git a/qualtran/drawing/__init__.py b/qualtran/drawing/__init__.py index e3d4aee95..292879a95 100644 --- a/qualtran/drawing/__init__.py +++ b/qualtran/drawing/__init__.py @@ -41,4 +41,4 @@ from .bloq_counts_graph import GraphvizCounts, format_counts_sigma, format_counts_graph_markdown -from ._show_funcs import show_bloq, show_bloqs, show_call_graph, show_counts_sigma +from ._show_funcs import show_bloq, show_bloqs, show_call_graph, show_counts_sigma, show_flame_graph diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 08cc6390e..9d85d3b55 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -20,6 +20,7 @@ import ipywidgets from .bloq_counts_graph import format_counts_sigma, GraphvizCounts +from .flame_graph import get_flame_graph_svg_data from .graphviz import PrettyGraphDrawer, TypedGraphDrawer from .musical_score import draw_musical_score, get_musical_score_data @@ -77,3 +78,9 @@ def show_call_graph(g: 'nx.DiGraph') -> None: def show_counts_sigma(sigma: Dict['Bloq', Union[int, 'sympy.Expr']]): """Display nicely formatted bloq counts sums `sigma`.""" IPython.display.display(IPython.display.Markdown(format_counts_sigma(sigma))) + + +def show_flame_graph(*bloqs: 'Bloq', **kwargs): + """Display hiearchical decomposition and T-complexity costs as a Flame Graph.""" + svg_data = get_flame_graph_svg_data(*bloqs, **kwargs) + IPython.display.display(IPython.display.SVG(svg_data)) diff --git a/qualtran/drawing/flame_graph.py b/qualtran/drawing/flame_graph.py new file mode 100644 index 000000000..53702242c --- /dev/null +++ b/qualtran/drawing/flame_graph.py @@ -0,0 +1,192 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Classes for drawing bloqs with FlameGraph. This relies on third party flamegraph.pl""" +import functools +import pathlib +import subprocess +import tempfile +from typing import Any, List, Optional, Sequence, Union + +import networkx as nx +import numpy as np + +from qualtran import Bloq +from qualtran.resource_counting.bloq_counts import _compute_sigma +from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma + + +def _pretty_arg(val: Any) -> str: + if isinstance(val, (tuple, np.ndarray)): + return f'{val.shape if isinstance(val, np.ndarray) else len(val)}' + if isinstance(val, Bloq): + return _pretty_name(val) + if isinstance(val, float): + if np.isclose(val, 0): + val = 0 + return f'{val:0.2g}' + return f'{val}' + + +def _pretty_name(bloq: Bloq) -> str: + """Hack to get a reasonably concise, reasonably informative description of a bloq. + + This should be removed once we have a better way to get a descriptive and concise + representation for a bloq. See https://github.com/quantumlib/Qualtran/issues/791 + """ + + from qualtran.serialization.bloq import _iter_fields + + ret = bloq.pretty_name() + if bloq.pretty_name.__qualname__.startswith('Bloq.'): + for field in _iter_fields(bloq): + ret += f'[{_pretty_arg(getattr(bloq, field.name))}]' + return ret + + +@functools.lru_cache(maxsize=1024) +def _t_counts_for_bloq(bloq: Bloq, graph: nx.DiGraph) -> int: + sigma = _compute_sigma(bloq, graph) + return t_counts_from_sigma(sigma) + + +def _keep_if_small(bloq: Bloq) -> bool: + from qualtran.bloqs.basic_gates import Toffoli, TwoBitCSwap + from qualtran.bloqs.mcmt.and_bloq import And + + if isinstance(bloq, (And, Toffoli, TwoBitCSwap)): + return True + + +def _is_leaf_node(callees: List[Bloq]) -> bool: + from qualtran.bloqs.basic_gates import TGate + + return len(callees) == 0 or ( + len(callees) == 1 and callees[0] in [TGate(), TGate(is_adjoint=True)] + ) + + +def _populate_flame_graph_data( + bloq: Bloq, graph: nx.DiGraph, graph_t: nx.DiGraph, prefix: List[str] +) -> List[str]: + """Populates data for the flame graph. + + Args: + bloq: Bloq to get the flame graph data for. + graph: Callgraph of `bloq` with custom kwargs so users can control the depth / leaf nodes + for the flame graph. This is the graph we do a DFS ordering on. + graph_t: Callgraph to use to derive T-complexity of the Bloq. Ideally, we should just be able + to invoke the `bloq.t_complexity().t` but this hides the T-costs due to rotations, so we + use a temporary solution to invoke `_t_counts_for_bloq(bloq, graph_t)`. The graph is not + mutated over the course of DFS and hence can be used as a hash key for better performance. + prefix: A string that represents the bloqs visited in the path so far. This is mutated as the + graph is traversed and represents the current path from the root node to the current node + in the DFS traversal. This is used to populate the flame graph data once we hit leaf nodes + in `graph`. + + Returns: + The Flame graph data for the subgraph of `graph` for which `bloq` is a root node. + """ + + callees = [x for x in list(graph.successors(bloq)) if _t_counts_for_bloq(x, graph_t) > 0] + total_t_counts = _t_counts_for_bloq(bloq, graph_t) + prefix.append(_pretty_name(bloq) + f'(T:{total_t_counts})') + data = [] + if _is_leaf_node(callees): + data += [';'.join(prefix) + '\t' + str(total_t_counts)] + else: + succ_t_counts = 0 + for succ in callees: + curr_data = _populate_flame_graph_data(succ, graph, graph_t, prefix) + succ_t_counts += ( + _t_counts_for_bloq(succ, graph_t) * graph.get_edge_data(bloq, succ)['n'] + ) + data += curr_data * graph.get_edge_data(bloq, succ)['n'] + # TODO: This assertion should be enabled once, for each bloq, we verify that + # `assert_equivalent_bloq_example_counts` is True for `TGate`. This is currently not True + # and is tracked in https://github.com/quantumlib/Qualtran/issues/858 + # assert total_t_counts == succ_t_counts, f'{bloq=}, {total_t_counts=}, {succ_t_counts=}' + prefix.pop() + return data + + +def get_flame_graph_data( + *bloqs: Bloq, + file_path: Union[None, pathlib.Path, str] = None, + keep: Optional[Sequence['Bloq']] = _keep_if_small, + **kwargs, +) -> List[str]: + """Get the flame graph data for visualizing T-costs distribution of a sequence of bloqs. + + For each bloq in the input, this will do a DFS ordering over all edges in the DAG and + add an entry corresponding to each leaf node in the call graph. The string representation + added for a leaf node encodes the entire path taken from the root node to the leaf node + and is repeated a number of times that's equivalent to the weight of that path. Thus, the + length of the output would be roughly equal to the number of T-gates in the Bloq and can be + very high. If you want to limit the output size, consider specifying a `keep` predicate where + the leaf nodes are higher level Bloqs with a larger T-count weight. + + Args: + bloqs: Bloqs to plot the flame graph for + file_path: If specified, the output is stored at the file. + keep: A predicate to determine the leaf nodes in the call graph. The flame graph would use + these Bloqs as leaf nodes and thus would not contain decompositions for these nodes. + **kwargs: Additional arguments to be passed to `bloq.call_graph`, like generalizers etc. + + Returns: + A list of strings, one for each path from root node to the leaf node in the call graph x + the weight of the path, that can be passed to the `third_party/flame_graph/flame_graph.pl` + script. + """ + from qualtran.resource_counting.generalizers import cirq_to_bloqs + + data = [] + for bloq in bloqs: + call_graph, _ = bloq.call_graph(keep=keep, **kwargs, generalizer=cirq_to_bloqs) + call_graph_t_counts, _ = bloq.call_graph() + data += _populate_flame_graph_data(bloq, call_graph, call_graph_t_counts, prefix=[]) + if file_path: + with open(file_path, 'w') as f: + f.write('\n'.join(data)) + else: + return data + + +def get_flame_graph_svg_data( + *bloqs: Bloq, file_path: Union[None, pathlib.Path, str] = None, **kwargs +) -> Optional[str]: + """Invokes the `third_party/flamegraph/flamegraph.pl` using data from `get_flame_graph_data`.""" + + data = get_flame_graph_data(*bloqs, **kwargs) + + data_file = tempfile.NamedTemporaryFile(mode='w') + flame_graph_path = ( + pathlib.Path(__file__).resolve().parent.parent / "third_party/flamegraph/flamegraph.pl" + ) + + data_file.write('\n'.join(data)) + data_file.flush() + svg_data = subprocess.run( + [flame_graph_path, "--countname", "TCounts", f'{data_file.name}'], + capture_output=True, + text=True, + check=True, + ).stdout + data_file.close() + + if file_path: + with open(file_path, 'w') as f: + f.write(svg_data) + else: + return svg_data diff --git a/qualtran/drawing/flame_graph_test.py b/qualtran/drawing/flame_graph_test.py new file mode 100644 index 000000000..0325dfc1e --- /dev/null +++ b/qualtran/drawing/flame_graph_test.py @@ -0,0 +1,98 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from qualtran.bloqs.basic_gates.swap import CSwap +from qualtran.bloqs.mcmt import MultiAnd +from qualtran.bloqs.qft.qft_text_book import QFTTextBook +from qualtran.bloqs.state_preparation import PrepareUniformSuperposition +from qualtran.drawing.flame_graph import get_flame_graph_data + + +def test_get_flame_graph_data_cswap(): + bloq = CSwap(10) + data = get_flame_graph_data(bloq) + assert data == [ + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + 'CSwap[10](T:70);TwoBitCSwap(T:7)\t7', + ] + + +def test_get_flame_graph_data_multi_and(): + bloq = MultiAnd([0, 1] * 6) + data = get_flame_graph_data(bloq) + assert data == [ + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + 'MultiAnd[12](T:44);And(T:4)\t4', + ] + + +def test_get_flame_graph_data_qft_textbook(): + bloq = QFTTextBook(5) + data = get_flame_graph_data(bloq) + assert sorted(data) == sorted( + [ + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[4][0.5][True][0](T:200);CZPowGate[0.12][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[4][0.5][True][0](T:200);CZPowGate[0.25][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[4][0.5][True][0](T:200);CZPowGate[0.5][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[4][0.5][True][0](T:200);CZPowGate[0.062][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[3][0.5][True][0](T:150);CZPowGate[0.25][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[3][0.5][True][0](T:150);CZPowGate[0.5][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[3][0.5][True][0](T:150);CZPowGate[0.12][0][0](T:50)\t' + '50', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[1][0.5][True][0](T:48);CZPowGate[0.5][0][0](T:48)\t' + '48', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[2][0.5][True][0](T:98);CZPowGate[0.25][0][0](T:49)\t' + '49', + 'QFTTextBook[5][True](T:496);PhaseGradientUnitary[2][0.5][True][0](T:98);CZPowGate[0.5][0][0](T:49)\t' + '49', + ] + ) + + +def test_get_flame_graph_data_prep_uniform(): + bloq = PrepareUniformSuperposition(12) + data = get_flame_graph_data(bloq) + assert sorted(data) == sorted( + [ + 'PrepareUniformSuperposition[12][0](T:124);LessThanConstant[2][3](T:8);And(T:4)\t4', + 'PrepareUniformSuperposition[12][0](T:124);LessThanConstant[2][3](T:8);And(T:4)\t4', + 'PrepareUniformSuperposition[12][0](T:124);LessThanConstant[2][3](T:8);And(T:4)\t4', + 'PrepareUniformSuperposition[12][0](T:124);LessThanConstant[2][3](T:8);And(T:4)\t4', + 'PrepareUniformSuperposition[12][0](T:124);Rz[1.2][0](T:52)\t52', + 'PrepareUniformSuperposition[12][0](T:124);Rz[1.2][0](T:52)\t52', + 'PrepareUniformSuperposition[12][0](T:124);And(T:4)\t4', + ] + ) diff --git a/qualtran/resource_counting/t_counts_from_sigma.py b/qualtran/resource_counting/t_counts_from_sigma.py index efa7f46d5..1c111cd4d 100644 --- a/qualtran/resource_counting/t_counts_from_sigma.py +++ b/qualtran/resource_counting/t_counts_from_sigma.py @@ -17,7 +17,6 @@ import cirq -from qualtran.bloqs.basic_gates import TGate from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.symbolic_counting_utils import SymbolicInt @@ -46,6 +45,8 @@ def t_counts_from_sigma( rotation_types: Optional[Tuple['_HasEps', ...]] = None, ) -> SymbolicInt: """Aggregates T-counts from a sigma dictionary by summing T-costs for all rotation bloqs.""" + from qualtran.bloqs.basic_gates import TGate + if rotation_types is None: rotation_types = _get_all_rotation_types() ret = sigma.get(TGate(), 0) diff --git a/qualtran/third_party/flamegraph/LICENSE b/qualtran/third_party/flamegraph/LICENSE new file mode 100644 index 000000000..2230598eb --- /dev/null +++ b/qualtran/third_party/flamegraph/LICENSE @@ -0,0 +1,384 @@ +Unless otherwise noted, all files in this distribution are released +under the Common Development and Distribution License (CDDL). +Exceptions are noted within the associated source files. + +-------------------------------------------------------------------- + + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 + +1. Definitions. + + 1.1. "Contributor" means each individual or entity that creates + or contributes to the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Software, prior Modifications used by a Contributor (if any), + and the Modifications made by that particular Contributor. + + 1.3. "Covered Software" means (a) the Original Software, or (b) + Modifications, or (c) the combination of files containing + Original Software with files containing Modifications, in + each case including portions thereof. + + 1.4. "Executable" means the Covered Software in any form other + than Source Code. + + 1.5. "Initial Developer" means the individual or entity that first + makes Original Software available under this License. + + 1.6. "Larger Work" means a work which combines Covered Software or + portions thereof with code not governed by the terms of this + License. + + 1.7. "License" means this document. + + 1.8. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed + herein. + + 1.9. "Modifications" means the Source Code and Executable form of + any of the following: + + A. Any file that results from an addition to, deletion from or + modification of the contents of a file containing Original + Software or previous Modifications; + + B. Any new file that contains any part of the Original + Software or previous Modifications; or + + C. Any new file that is contributed or otherwise made + available under the terms of this License. + + 1.10. "Original Software" means the Source Code and Executable + form of computer software code that is originally released + under this License. + + 1.11. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, + process, and apparatus claims, in any patent Licensable by + grantor. + + 1.12. "Source Code" means (a) the common form of computer software + code in which modifications are made and (b) associated + documentation included in or with such code. + + 1.13. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms + of, this License. For legal entities, "You" includes any + entity which controls, is controlled by, or is under common + control with You. For purposes of this definition, + "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty + percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants. + + 2.1. The Initial Developer Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, the Initial + Developer hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer, to use, + reproduce, modify, display, perform, sublicense and + distribute the Original Software (or portions thereof), + with or without Modifications, and/or as part of a Larger + Work; and + + (b) under Patent Claims infringed by the making, using or + selling of Original Software, to make, have made, use, + practice, sell, and offer for sale, and/or otherwise + dispose of the Original Software (or portions thereof). + + (c) The licenses granted in Sections 2.1(a) and (b) are + effective on the date Initial Developer first distributes + or otherwise makes the Original Software available to a + third party under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: (1) for code that You delete from the Original + Software, or (2) for infringements caused by: (i) the + modification of the Original Software, or (ii) the + combination of the Original Software with other software + or devices. + + 2.2. Contributor Grant. + + Conditioned upon Your compliance with Section 3.1 below and + subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor to use, reproduce, + modify, display, perform, sublicense and distribute the + Modifications created by such Contributor (or portions + thereof), either on an unmodified basis, with other + Modifications, as Covered Software and/or as part of a + Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either + alone and/or in combination with its Contributor Version + (or portions of such combination), to make, use, sell, + offer for sale, have made, and/or otherwise dispose of: + (1) Modifications made by that Contributor (or portions + thereof); and (2) the combination of Modifications made by + that Contributor with its Contributor Version (or portions + of such combination). + + (c) The licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first distributes or + otherwise makes the Modifications available to a third + party. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: (1) for any code that Contributor has deleted + from the Contributor Version; (2) for infringements caused + by: (i) third party modifications of Contributor Version, + or (ii) the combination of Modifications made by that + Contributor with other software (except as part of the + Contributor Version) or other devices; or (3) under Patent + Claims infringed by Covered Software in the absence of + Modifications made by that Contributor. + +3. Distribution Obligations. + + 3.1. Availability of Source Code. + + Any Covered Software that You distribute or otherwise make + available in Executable form must also be made available in Source + Code form and that Source Code form must be distributed only under + the terms of this License. You must include a copy of this + License with every copy of the Source Code form of the Covered + Software You distribute or otherwise make available. You must + inform recipients of any such Covered Software in Executable form + as to how they can obtain such Covered Software in Source Code + form in a reasonable manner on or through a medium customarily + used for software exchange. + + 3.2. Modifications. + + The Modifications that You create or to which You contribute are + governed by the terms of this License. You represent that You + believe Your Modifications are Your original creation(s) and/or + You have sufficient rights to grant the rights conveyed by this + License. + + 3.3. Required Notices. + + You must include a notice in each of Your Modifications that + identifies You as the Contributor of the Modification. You may + not remove or alter any copyright, patent or trademark notices + contained within the Covered Software, or any notices of licensing + or any descriptive text giving attribution to any Contributor or + the Initial Developer. + + 3.4. Application of Additional Terms. + + You may not offer or impose any terms on any Covered Software in + Source Code form that alters or restricts the applicable version + of this License or the recipients' rights hereunder. You may + choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of + Covered Software. However, you may do so only on Your own behalf, + and not on behalf of the Initial Developer or any Contributor. + You must make it absolutely clear that any such warranty, support, + indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every + Contributor for any liability incurred by the Initial Developer or + such Contributor as a result of warranty, support, indemnity or + liability terms You offer. + + 3.5. Distribution of Executable Versions. + + You may distribute the Executable form of the Covered Software + under the terms of this License or under the terms of a license of + Your choice, which may contain terms different from this License, + provided that You are in compliance with the terms of this License + and that the license for the Executable form does not attempt to + limit or alter the recipient's rights in the Source Code form from + the rights set forth in this License. If You distribute the + Covered Software in Executable form under a different license, You + must make it absolutely clear that any terms which differ from + this License are offered by You alone, not by the Initial + Developer or Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred + by the Initial Developer or such Contributor as a result of any + such terms You offer. + + 3.6. Larger Works. + + You may create a Larger Work by combining Covered Software with + other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, + You must make sure the requirements of this License are fulfilled + for the Covered Software. + +4. Versions of the License. + + 4.1. New Versions. + + Sun Microsystems, Inc. is the initial license steward and may + publish revised and/or new versions of this License from time to + time. Each version will be given a distinguishing version number. + Except as provided in Section 4.3, no one other than the license + steward has the right to modify this License. + + 4.2. Effect of New Versions. + + You may always continue to use, distribute or otherwise make the + Covered Software available under the terms of the version of the + License under which You originally received the Covered Software. + If the Initial Developer includes a notice in the Original + Software prohibiting it from being distributed or otherwise made + available under any subsequent version of the License, You must + distribute and make the Covered Software available under the terms + of the version of the License under which You originally received + the Covered Software. Otherwise, You may also choose to use, + distribute or otherwise make the Covered Software available under + the terms of any subsequent version of the License published by + the license steward. + + 4.3. Modified Versions. + + When You are an Initial Developer and You want to create a new + license for Your Original Software, You may create and use a + modified version of this License if You: (a) rename the license + and remove any references to the name of the license steward + (except to note that the license differs from this License); and + (b) otherwise make it clear that the license contains terms which + differ from this License. + +5. DISCLAIMER OF WARRANTY. + + COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" + BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, + INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED + SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR + PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND + PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY + COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE + INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY + NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF + WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER. + +6. TERMINATION. + + 6.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. + Provisions which, by their nature, must remain in effect beyond + the termination of this License shall survive. + + 6.2. If You assert a patent infringement claim (excluding + declaratory judgment actions) against Initial Developer or a + Contributor (the Initial Developer or Contributor against whom You + assert such claim is referred to as "Participant") alleging that + the Participant Software (meaning the Contributor Version where + the Participant is a Contributor or the Original Software where + the Participant is the Initial Developer) directly or indirectly + infringes any patent, then any and all rights granted directly or + indirectly to You by such Participant, the Initial Developer (if + the Initial Developer is not the Participant) and all Contributors + under Sections 2.1 and/or 2.2 of this License shall, upon 60 days + notice from Participant terminate prospectively and automatically + at the expiration of such 60 day notice period, unless if within + such 60 day period You withdraw Your claim with respect to the + Participant Software against such Participant either unilaterally + or pursuant to a written agreement with Participant. + + 6.3. In the event of termination under Sections 6.1 or 6.2 above, + all end user licenses that have been validly granted by You or any + distributor hereunder prior to termination (excluding licenses + granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE + INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF + COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE + LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK + STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT + APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO + NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR + CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT + APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + + The Covered Software is a "commercial item," as that term is + defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial + computer software" (as that term is defined at 48 + C.F.R. 252.227-7014(a)(1)) and "commercial computer software + documentation" as such terms are used in 48 C.F.R. 12.212 + (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 + C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all + U.S. Government End Users acquire Covered Software with only those + rights set forth herein. This U.S. Government Rights clause is in + lieu of, and supersedes, any other FAR, DFAR, or other clause or + provision that addresses Government rights in computer software + under this License. + +9. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed + by the law of the jurisdiction specified in a notice contained + within the Original Software (except to the extent applicable law, + if any, provides otherwise), excluding such jurisdiction's + conflict-of-law provisions. Any litigation relating to this + License shall be subject to the jurisdiction of the courts located + in the jurisdiction and venue specified in a notice contained + within the Original Software, with the losing party responsible + for costs, including, without limitation, court costs and + reasonable attorneys' fees and expenses. The application of the + United Nations Convention on Contracts for the International Sale + of Goods is expressly excluded. Any law or regulation which + provides that the language of a contract shall be construed + against the drafter shall not apply to this License. You agree + that You alone are responsible for compliance with the United + States export administration regulations (and the export control + laws and regulation of any other countries) when You use, + distribute or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or + indirectly, out of its utilization of rights under this License + and You agree to work with Initial Developer and Contributors to + distribute such responsibility on an equitable basis. Nothing + herein is intended or shall be deemed to constitute any admission + of liability. + +-------------------------------------------------------------------- + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND +DISTRIBUTION LICENSE (CDDL) + +For Covered Software in this distribution, this License shall +be governed by the laws of the State of California (excluding +conflict-of-law provisions). + +Any litigation relating to this License shall be subject to the +jurisdiction of the Federal Courts of the Northern District of +California and the state courts of the State of California, with +venue lying in Santa Clara County, California. \ No newline at end of file diff --git a/qualtran/third_party/flamegraph/METADATA b/qualtran/third_party/flamegraph/METADATA new file mode 100644 index 000000000..8c6696d1a --- /dev/null +++ b/qualtran/third_party/flamegraph/METADATA @@ -0,0 +1,12 @@ +name: "Flamegraph" +description: + "The script allows plotting flamegraphs, useful to visualize T-costs for Bloqs." + +third_party { + url { + type: HOMEPAGE + value: "https://github.com/brendangregg/FlameGraph" + } + last_upgrade_date { year: 2023 month: 10 day: 07 } + license_type: CDDL1.0 +} diff --git a/qualtran/third_party/flamegraph/flamegraph.pl b/qualtran/third_party/flamegraph/flamegraph.pl new file mode 100755 index 000000000..8c917ecf3 --- /dev/null +++ b/qualtran/third_party/flamegraph/flamegraph.pl @@ -0,0 +1,1302 @@ +#!/usr/bin/perl -w +# +# flamegraph.pl flame stack grapher. +# +# This takes stack samples and renders a call graph, allowing hot functions +# and codepaths to be quickly identified. Stack samples can be generated using +# tools such as DTrace, perf, SystemTap, and Instruments. +# +# USAGE: ./flamegraph.pl [options] input.txt > graph.svg +# +# grep funcA input.txt | ./flamegraph.pl [options] > graph.svg +# +# Then open the resulting .svg in a web browser, for interactivity: mouse-over +# frames for info, click to zoom, and ctrl-F to search. +# +# Options are listed in the usage message (--help). +# +# The input is stack frames and sample counts formatted as single lines. Each +# frame in the stack is semicolon separated, with a space and count at the end +# of the line. These can be generated for Linux perf script output using +# stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools +# using the other stackcollapse programs. Example input: +# +# swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1 +# +# An optional extra column of counts can be provided to generate a differential +# flame graph of the counts, colored red for more, and blue for less. This +# can be useful when using flame graphs for non-regression testing. +# See the header comment in the difffolded.pl program for instructions. +# +# The input functions can optionally have annotations at the end of each +# function name, following a precedent by some tools (Linux perf's _[k]): +# _[k] for kernel +# _[i] for inlined +# _[j] for jit +# _[w] for waker +# Some of the stackcollapse programs support adding these annotations, eg, +# stackcollapse-perf.pl --kernel --jit. They are used merely for colors by +# some palettes, eg, flamegraph.pl --color=java. +# +# The output flame graph shows relative presence of functions in stack samples. +# The ordering on the x-axis has no meaning; since the data is samples, time +# order of events is not known. The order used sorts function names +# alphabetically. +# +# While intended to process stack samples, this can also process stack traces. +# For example, tracing stacks for memory allocation, or resource usage. You +# can use --title to set the title to reflect the content, and --countname +# to change "samples" to "bytes" etc. +# +# There are a few different palettes, selectable using --color. By default, +# the colors are selected at random (except for differentials). Functions +# called "-" will be printed gray, which can be used for stack separators (eg, +# between user and kernel stacks). +# +# HISTORY +# +# This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb +# program, which visualized function entry and return trace events. As Neel +# wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which +# was in turn inspired by the work on vftrace by Jan Boerhout". See: +# https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and +# +# Copyright 2016 Netflix, Inc. +# Copyright 2011 Joyent, Inc. All rights reserved. +# Copyright 2011 Brendan Gregg. All rights reserved. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at docs/cddl1.txt or +# http://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at docs/cddl1.txt. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# 11-Oct-2014 Adrien Mahieux Added zoom. +# 21-Nov-2013 Shawn Sterling Added consistent palette file option +# 17-Mar-2013 Tim Bunce Added options and more tunables. +# 15-Dec-2011 Dave Pacheco Support for frames with whitespace. +# 10-Sep-2011 Brendan Gregg Created this. + +use strict; + +use Getopt::Long; + +use open qw(:std :utf8); + +# tunables +my $encoding; +my $fonttype = "Verdana"; +my $imagewidth = 1200; # max width, pixels +my $frameheight = 16; # max height is dynamic +my $fontsize = 12; # base text size +my $fontwidth = 0.59; # avg width relative to fontsize +my $minwidth = 0.1; # min function width, pixels or percentage of time +my $nametype = "Function:"; # what are the names in the data? +my $countname = "samples"; # what are the counts in the data? +my $colors = "hot"; # color theme +my $bgcolors = ""; # background color theme +my $nameattrfile; # file holding function attributes +my $timemax; # (override the) sum of the counts +my $factor = 1; # factor to scale counts by +my $hash = 0; # color by function name +my $rand = 0; # color randomly +my $palette = 0; # if we use consistent palettes (default off) +my %palette_map; # palette map hash +my $pal_file = "palette.map"; # palette map file name +my $stackreverse = 0; # reverse stack order, switching merge end +my $inverted = 0; # icicle graph +my $flamechart = 0; # produce a flame chart (sort by time, do not merge stacks) +my $negate = 0; # switch differential hues +my $titletext = ""; # centered heading +my $titledefault = "Flame Graph"; # overwritten by --title +my $titleinverted = "Icicle Graph"; # " " +my $searchcolor = "rgb(230,0,230)"; # color for search highlighting +my $notestext = ""; # embedded notes in SVG +my $subtitletext = ""; # second level title (optional) +my $help = 0; + +sub usage { + die < outfile.svg\n + --title TEXT # change title text + --subtitle TEXT # second level title (optional) + --width NUM # width of image (default 1200) + --height NUM # height of each frame (default 16) + --minwidth NUM # omit smaller functions. In pixels or use "%" for + # percentage of time (default 0.1 pixels) + --fonttype FONT # font type (default "Verdana") + --fontsize NUM # font size (default 12) + --countname TEXT # count type label (default "samples") + --nametype TEXT # name type label (default "Function:") + --colors PALETTE # set color palette. choices are: hot (default), mem, + # io, wakeup, chain, java, js, perl, red, green, blue, + # aqua, yellow, purple, orange + --bgcolors COLOR # set background colors. gradient choices are yellow + # (default), blue, green, grey; flat colors use "#rrggbb" + --hash # colors are keyed by function name hash + --random # colors are randomly generated + --cp # use consistent palette (palette.map) + --reverse # generate stack-reversed flame graph + --inverted # icicle graph + --flamechart # produce a flame chart (sort by time, do not merge stacks) + --negate # switch differential hues (blue<->red) + --notes TEXT # add notes comment in SVG (for debugging) + --help # this message + + eg, + $0 --title="Flame Graph: malloc()" trace.txt > graph.svg +USAGE_END +} + +GetOptions( + 'fonttype=s' => \$fonttype, + 'width=i' => \$imagewidth, + 'height=i' => \$frameheight, + 'encoding=s' => \$encoding, + 'fontsize=f' => \$fontsize, + 'fontwidth=f' => \$fontwidth, + 'minwidth=s' => \$minwidth, + 'title=s' => \$titletext, + 'subtitle=s' => \$subtitletext, + 'nametype=s' => \$nametype, + 'countname=s' => \$countname, + 'nameattr=s' => \$nameattrfile, + 'total=s' => \$timemax, + 'factor=f' => \$factor, + 'colors=s' => \$colors, + 'bgcolors=s' => \$bgcolors, + 'hash' => \$hash, + 'random' => \$rand, + 'cp' => \$palette, + 'reverse' => \$stackreverse, + 'inverted' => \$inverted, + 'flamechart' => \$flamechart, + 'negate' => \$negate, + 'notes=s' => \$notestext, + 'help' => \$help, +) or usage(); +$help && usage(); + +# internals +my $ypad1 = $fontsize * 3; # pad top, include title +my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels +my $ypad3 = $fontsize * 2; # pad top, include subtitle (optional) +my $xpad = 10; # pad lefm and right +my $framepad = 1; # vertical padding for frames +my $depthmax = 0; +my %Events; +my %nameattr; + +if ($flamechart && $titletext eq "") { + $titletext = "Flame Chart"; +} + +if ($titletext eq "") { + unless ($inverted) { + $titletext = $titledefault; + } else { + $titletext = $titleinverted; + } +} + +if ($nameattrfile) { + # The name-attribute file format is a function name followed by a tab then + # a sequence of tab separated name=value pairs. + open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n"; + while (<$attrfh>) { + chomp; + my ($funcname, $attrstr) = split /\t/, $_, 2; + die "Invalid format in $nameattrfile" unless defined $attrstr; + $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr }; + } +} + +if ($notestext =~ /[<>]/) { + die "Notes string can't contain < or >" +} + +# Ensure minwidth is a valid floating-point number, +# print usage string if not +my $minwidth_f; +if ($minwidth =~ /^([0-9.]+)%?$/) { + $minwidth_f = $1; +} else { + warn "Value '$minwidth' is invalid for minwidth, expected a float.\n"; + usage(); +} + +# background colors: +# - yellow gradient: default (hot, java, js, perl) +# - green gradient: mem +# - blue gradient: io, wakeup, chain +# - gray gradient: flat colors (red, green, blue, ...) +if ($bgcolors eq "") { + # choose a default + if ($colors eq "mem") { + $bgcolors = "green"; + } elsif ($colors =~ /^(io|wakeup|chain)$/) { + $bgcolors = "blue"; + } elsif ($colors =~ /^(red|green|blue|aqua|yellow|purple|orange)$/) { + $bgcolors = "grey"; + } else { + $bgcolors = "yellow"; + } +} +my ($bgcolor1, $bgcolor2); +if ($bgcolors eq "yellow") { + $bgcolor1 = "#eeeeee"; # background color gradient start + $bgcolor2 = "#eeeeb0"; # background color gradient stop +} elsif ($bgcolors eq "blue") { + $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; +} elsif ($bgcolors eq "green") { + $bgcolor1 = "#eef2ee"; $bgcolor2 = "#e0ffe0"; +} elsif ($bgcolors eq "grey") { + $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; +} elsif ($bgcolors =~ /^#......$/) { + $bgcolor1 = $bgcolor2 = $bgcolors; +} else { + die "Unrecognized bgcolor option \"$bgcolors\"" +} + +# SVG functions +{ package SVG; + sub new { + my $class = shift; + my $self = {}; + bless ($self, $class); + return $self; + } + + sub header { + my ($self, $w, $h) = @_; + my $enc_attr = ''; + if (defined $encoding) { + $enc_attr = qq{ encoding="$encoding"}; + } + $self->{svg} .= < + + + + +SVG + } + + sub include { + my ($self, $content) = @_; + $self->{svg} .= $content; + } + + sub colorAllocate { + my ($self, $r, $g, $b) = @_; + return "rgb($r,$g,$b)"; + } + + sub group_start { + my ($self, $attr) = @_; + + my @g_attr = map { + exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : () + } qw(id class); + push @g_attr, $attr->{g_extra} if $attr->{g_extra}; + if ($attr->{href}) { + my @a_attr; + push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href}; + # default target=_top else links will open within SVG + push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top"; + push @a_attr, $attr->{a_extra} if $attr->{a_extra}; + $self->{svg} .= sprintf qq/\n/, join(' ', (@a_attr, @g_attr)); + } else { + $self->{svg} .= sprintf qq/\n/, join(' ', @g_attr); + } + + $self->{svg} .= sprintf qq/%s<\/title>/, $attr->{title} + if $attr->{title}; # should be first element within g container + } + + sub group_end { + my ($self, $attr) = @_; + $self->{svg} .= $attr->{href} ? qq/<\/a>\n/ : qq/<\/g>\n/; + } + + sub filledRectangle { + my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_; + $x1 = sprintf "%0.1f", $x1; + $x2 = sprintf "%0.1f", $x2; + my $w = sprintf "%0.1f", $x2 - $x1; + my $h = sprintf "%0.1f", $y2 - $y1; + $extra = defined $extra ? $extra : ""; + $self->{svg} .= qq/\n/; + } + + sub stringTTF { + my ($self, $id, $x, $y, $str, $extra) = @_; + $x = sprintf "%0.2f", $x; + $id = defined $id ? qq/id="$id"/ : ""; + $extra ||= ""; + $self->{svg} .= qq/$str<\/text>\n/; + } + + sub svg { + my $self = shift; + return "$self->{svg}\n"; + } + 1; +} + +sub namehash { + # Generate a vector hash for the name string, weighting early over + # later characters. We want to pick the same colors for function + # names across different flame graphs. + my $name = shift; + my $vector = 0; + my $weight = 1; + my $max = 1; + my $mod = 10; + # if module name present, trunc to 1st char + $name =~ s/.(.*?)`//; + foreach my $c (split //, $name) { + my $i = (ord $c) % $mod; + $vector += ($i / ($mod++ - 1)) * $weight; + $max += 1 * $weight; + $weight *= 0.70; + last if $mod > 12; + } + return (1 - $vector / $max) +} + +sub sum_namehash { + my $name = shift; + return unpack("%32W*", $name); +} + +sub random_namehash { + # Generate a random hash for the name string. + # This ensures that functions with the same name have the same color, + # both within a flamegraph and across multiple flamegraphs without + # needing to set a palette and while preserving the original flamegraph + # optic, unlike what happens with --hash. + my $name = shift; + my $hash = sum_namehash($name); + srand($hash); + return rand(1) +} + +sub color { + my ($type, $hash, $name) = @_; + my ($v1, $v2, $v3); + + if ($hash) { + $v1 = namehash($name); + $v2 = $v3 = namehash(scalar reverse $name); + } elsif ($rand) { + $v1 = rand(1); + $v2 = rand(1); + $v3 = rand(1); + } else { + $v1 = random_namehash($name); + $v2 = random_namehash($name); + $v3 = random_namehash($name); + } + + # theme palettes + if (defined $type and $type eq "hot") { + my $r = 205 + int(50 * $v3); + my $g = 0 + int(230 * $v1); + my $b = 0 + int(55 * $v2); + return "rgb($r,$g,$b)"; + } + if (defined $type and $type eq "mem") { + my $r = 0; + my $g = 190 + int(50 * $v2); + my $b = 0 + int(210 * $v1); + return "rgb($r,$g,$b)"; + } + if (defined $type and $type eq "io") { + my $r = 80 + int(60 * $v1); + my $g = $r; + my $b = 190 + int(55 * $v2); + return "rgb($r,$g,$b)"; + } + + # multi palettes + if (defined $type and $type eq "java") { + # Handle both annotations (_[j], _[i], ...; which are + # accurate), as well as input that lacks any annotations, as + # best as possible. Without annotations, we get a little hacky + # and match on java|org|com, etc. + if ($name =~ m:_\[j\]$:) { # jit annotation + $type = "green"; + } elsif ($name =~ m:_\[i\]$:) { # inline annotation + $type = "aqua"; + } elsif ($name =~ m:^L?(java|javax|jdk|net|org|com|io|sun)/:) { # Java + $type = "green"; + } elsif ($name =~ /:::/) { # Java, typical perf-map-agent method separator + $type = "green"; + } elsif ($name =~ /::/) { # C++ + $type = "yellow"; + } elsif ($name =~ m:_\[k\]$:) { # kernel annotation + $type = "orange"; + } elsif ($name =~ /::/) { # C++ + $type = "yellow"; + } else { # system + $type = "red"; + } + # fall-through to color palettes + } + if (defined $type and $type eq "perl") { + if ($name =~ /::/) { # C++ + $type = "yellow"; + } elsif ($name =~ m:Perl: or $name =~ m:\.pl:) { # Perl + $type = "green"; + } elsif ($name =~ m:_\[k\]$:) { # kernel + $type = "orange"; + } else { # system + $type = "red"; + } + # fall-through to color palettes + } + if (defined $type and $type eq "js") { + # Handle both annotations (_[j], _[i], ...; which are + # accurate), as well as input that lacks any annotations, as + # best as possible. Without annotations, we get a little hacky, + # and match on a "/" with a ".js", etc. + if ($name =~ m:_\[j\]$:) { # jit annotation + if ($name =~ m:/:) { + $type = "green"; # source + } else { + $type = "aqua"; # builtin + } + } elsif ($name =~ /::/) { # C++ + $type = "yellow"; + } elsif ($name =~ m:/.*\.js:) { # JavaScript (match "/" in path) + $type = "green"; + } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin) + $type = "aqua"; + } elsif ($name =~ m/^ $/) { # Missing symbol + $type = "green"; + } elsif ($name =~ m:_\[k\]:) { # kernel + $type = "orange"; + } else { # system + $type = "red"; + } + # fall-through to color palettes + } + if (defined $type and $type eq "wakeup") { + $type = "aqua"; + # fall-through to color palettes + } + if (defined $type and $type eq "chain") { + if ($name =~ m:_\[w\]:) { # waker + $type = "aqua" + } else { # off-CPU + $type = "blue"; + } + # fall-through to color palettes + } + + # color palettes + if (defined $type and $type eq "red") { + my $r = 200 + int(55 * $v1); + my $x = 50 + int(80 * $v1); + return "rgb($r,$x,$x)"; + } + if (defined $type and $type eq "green") { + my $g = 200 + int(55 * $v1); + my $x = 50 + int(60 * $v1); + return "rgb($x,$g,$x)"; + } + if (defined $type and $type eq "blue") { + my $b = 205 + int(50 * $v1); + my $x = 80 + int(60 * $v1); + return "rgb($x,$x,$b)"; + } + if (defined $type and $type eq "yellow") { + my $x = 175 + int(55 * $v1); + my $b = 50 + int(20 * $v1); + return "rgb($x,$x,$b)"; + } + if (defined $type and $type eq "purple") { + my $x = 190 + int(65 * $v1); + my $g = 80 + int(60 * $v1); + return "rgb($x,$g,$x)"; + } + if (defined $type and $type eq "aqua") { + my $r = 50 + int(60 * $v1); + my $g = 165 + int(55 * $v1); + my $b = 165 + int(55 * $v1); + return "rgb($r,$g,$b)"; + } + if (defined $type and $type eq "orange") { + my $r = 190 + int(65 * $v1); + my $g = 90 + int(65 * $v1); + return "rgb($r,$g,0)"; + } + + return "rgb(0,0,0)"; +} + +sub color_scale { + my ($value, $max) = @_; + my ($r, $g, $b) = (255, 255, 255); + $value = -$value if $negate; + if ($value > 0) { + $g = $b = int(210 * ($max - $value) / $max); + } elsif ($value < 0) { + $r = $g = int(210 * ($max + $value) / $max); + } + return "rgb($r,$g,$b)"; +} + +sub color_map { + my ($colors, $func) = @_; + if (exists $palette_map{$func}) { + return $palette_map{$func}; + } else { + $palette_map{$func} = color($colors, $hash, $func); + return $palette_map{$func}; + } +} + +sub write_palette { + open(FILE, ">$pal_file"); + foreach my $key (sort keys %palette_map) { + print FILE $key."->".$palette_map{$key}."\n"; + } + close(FILE); +} + +sub read_palette { + if (-e $pal_file) { + open(FILE, $pal_file) or die "can't open file $pal_file: $!"; + while ( my $line = ) { + chomp($line); + (my $key, my $value) = split("->",$line); + $palette_map{$key}=$value; + } + close(FILE) + } +} + +my %Node; # Hash of merged frame data +my %Tmp; + +# flow() merges two stacks, storing the merged frames and value data in %Node. +sub flow { + my ($last, $this, $v, $d) = @_; + + my $len_a = @$last - 1; + my $len_b = @$this - 1; + + my $i = 0; + my $len_same; + for (; $i <= $len_a; $i++) { + last if $i > $len_b; + last if $last->[$i] ne $this->[$i]; + } + $len_same = $i; + + for ($i = $len_a; $i >= $len_same; $i--) { + my $k = "$last->[$i];$i"; + # a unique ID is constructed from "func;depth;etime"; + # func-depth isn't unique, it may be repeated later. + $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime}; + if (defined $Tmp{$k}->{delta}) { + $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta}; + } + delete $Tmp{$k}; + } + + for ($i = $len_same; $i <= $len_b; $i++) { + my $k = "$this->[$i];$i"; + $Tmp{$k}->{stime} = $v; + if (defined $d) { + $Tmp{$k}->{delta} += $i == $len_b ? $d : 0; + } + } + + return $this; +} + +# parse input +my @Data; +my @SortedData; +my $last = []; +my $time = 0; +my $delta = undef; +my $ignored = 0; +my $line; +my $maxdelta = 1; + +# reverse if needed +foreach (<>) { + chomp; + $line = $_; + if ($stackreverse) { + # there may be an extra samples column for differentials + # XXX todo: redo these REs as one. It's repeated below. + my($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); + my $samples2 = undef; + if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { + $samples2 = $samples; + ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); + unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2"; + } else { + unshift @Data, join(";", reverse split(";", $stack)) . " $samples"; + } + } else { + unshift @Data, $line; + } +} + +if ($flamechart) { + # In flame chart mode, just reverse the data so time moves from left to right. + @SortedData = reverse @Data; +} else { + @SortedData = sort @Data; +} + +# process and merge frames +foreach (@SortedData) { + chomp; + # process: folded_stack count + # eg: func_a;func_b;func_c 31 + my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); + unless (defined $samples and defined $stack) { + ++$ignored; + next; + } + + # there may be an extra samples column for differentials: + my $samples2 = undef; + if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { + $samples2 = $samples; + ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); + } + $delta = undef; + if (defined $samples2) { + $delta = $samples2 - $samples; + $maxdelta = abs($delta) if abs($delta) > $maxdelta; + } + + # for chain graphs, annotate waker frames with "_[w]", for later + # coloring. This is a hack, but has a precedent ("_[k]" from perf). + if ($colors eq "chain") { + my @parts = split ";--;", $stack; + my @newparts = (); + $stack = shift @parts; + $stack .= ";--;"; + foreach my $part (@parts) { + $part =~ s/;/_[w];/g; + $part .= "_[w]"; + push @newparts, $part; + } + $stack .= join ";--;", @parts; + } + + # merge frames and populate %Node: + $last = flow($last, [ '', split ";", $stack ], $time, $delta); + + if (defined $samples2) { + $time += $samples2; + } else { + $time += $samples; + } +} +flow($last, [], $time, $delta); + +if ($countname eq "samples") { + # If $countname is used, it's likely that we're not measuring in stack samples + # (e.g. time could be the unit), so don't warn. + warn "Stack count is low ($time). Did something go wrong?\n" if $time < 100; +} + +warn "Ignored $ignored lines with invalid format\n" if $ignored; +unless ($time) { + warn "ERROR: No stack counts found\n"; + my $im = SVG->new(); + # emit an error message SVG, for tools automating flamegraph use + my $imageheight = $fontsize * 5; + $im->header($imagewidth, $imageheight); + $im->stringTTF(undef, int($imagewidth / 2), $fontsize * 2, + "ERROR: No valid input provided to flamegraph.pl."); + print $im->svg; + exit 2; +} +if ($timemax and $timemax < $time) { + warn "Specified --total $timemax is less than actual total $time, so ignored\n" + if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc) + undef $timemax; +} +$timemax ||= $time; + +my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax; + +# Treat as a percentage of time if the string ends in a "%". +my $minwidth_time; +if ($minwidth =~ /%$/) { + $minwidth_time = $timemax * $minwidth_f / 100; +} else { + $minwidth_time = $minwidth_f / $widthpertime; +} + +# prune blocks that are too narrow and determine max depth +while (my ($id, $node) = each %Node) { + my ($func, $depth, $etime) = split ";", $id; + my $stime = $node->{stime}; + die "missing start for $id" if not defined $stime; + + if (($etime-$stime) < $minwidth_time) { + delete $Node{$id}; + next; + } + $depthmax = $depth if $depth > $depthmax; +} + +# draw canvas, and embed interactive JavaScript program +my $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2; +$imageheight += $ypad3 if $subtitletext ne ""; +my $titlesize = $fontsize + 5; +my $im = SVG->new(); +my ($black, $vdgrey, $dgrey) = ( + $im->colorAllocate(0, 0, 0), + $im->colorAllocate(160, 160, 160), + $im->colorAllocate(200, 200, 200), + ); +$im->header($imagewidth, $imageheight); +my $inc = < + + + + + + + +INC +$im->include($inc); +$im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)'); +$im->stringTTF("title", int($imagewidth / 2), $fontsize * 2, $titletext); +$im->stringTTF("subtitle", int($imagewidth / 2), $fontsize * 4, $subtitletext) if $subtitletext ne ""; +$im->stringTTF("details", $xpad, $imageheight - ($ypad2 / 2), " "); +$im->stringTTF("unzoom", $xpad, $fontsize * 2, "Reset Zoom", 'class="hide"'); +$im->stringTTF("search", $imagewidth - $xpad - 100, $fontsize * 2, "Search"); +$im->stringTTF("ignorecase", $imagewidth - $xpad - 16, $fontsize * 2, "ic"); +$im->stringTTF("matched", $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), " "); + +if ($palette) { + read_palette(); +} + +# draw frames +$im->group_start({id => "frames"}); +while (my ($id, $node) = each %Node) { + my ($func, $depth, $etime) = split ";", $id; + my $stime = $node->{stime}; + my $delta = $node->{delta}; + + $etime = $timemax if $func eq "" and $depth == 0; + + my $x1 = $xpad + $stime * $widthpertime; + my $x2 = $xpad + $etime * $widthpertime; + my ($y1, $y2); + unless ($inverted) { + $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad; + $y2 = $imageheight - $ypad2 - $depth * $frameheight; + } else { + $y1 = $ypad1 + $depth * $frameheight; + $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad; + } + + # Add commas per perlfaq5: + # https://perldoc.perl.org/perlfaq5#How-can-I-output-my-numbers-with-commas-added? + my $samples = sprintf "%.0f", ($etime - $stime) * $factor; + (my $samples_txt = $samples) + =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; + + my $info; + if ($func eq "" and $depth == 0) { + $info = "all ($samples_txt $countname, 100%)"; + } else { + my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor)); + my $escaped_func = $func; + # clean up SVG breaking characters: + $escaped_func =~ s/&/&/g; + $escaped_func =~ s//>/g; + $escaped_func =~ s/"/"/g; + $escaped_func =~ s/_\[[kwij]\]$//; # strip any annotation + unless (defined $delta) { + $info = "$escaped_func ($samples_txt $countname, $pct%)"; + } else { + my $d = $negate ? -$delta : $delta; + my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor)); + $deltapct = $d > 0 ? "+$deltapct" : $deltapct; + $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)"; + } + } + + my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone + $nameattr->{title} ||= $info; + $im->group_start($nameattr); + + my $color; + if ($func eq "--") { + $color = $vdgrey; + } elsif ($func eq "-") { + $color = $dgrey; + } elsif (defined $delta) { + $color = color_scale($delta, $maxdelta); + } elsif ($palette) { + $color = color_map($colors, $func); + } else { + $color = color($colors, $hash, $func); + } + $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"'); + + my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth)); + my $text = ""; + if ($chars >= 3) { # room for one char plus two dots + $func =~ s/_\[[kwij]\]$//; # strip any annotation + $text = substr $func, 0, $chars; + substr($text, -2, 2) = ".." if $chars < length $func; + $text =~ s/&/&/g; + $text =~ s//>/g; + } + $im->stringTTF(undef, $x1 + 3, 3 + ($y1 + $y2) / 2, $text); + + $im->group_end($nameattr); +} +$im->group_end(); + +print $im->svg; + +if ($palette) { + write_palette(); +} + +# vim: ts=8 sts=8 sw=8 noexpandtab