Skip to content

Commit

Permalink
Merge pull request #206 from Perfexionists/visualization-fixes
Browse files Browse the repository at this point in the history
Fix minor issues in visualizations
  • Loading branch information
tfiedor authored Jun 21, 2024
2 parents 59322be + 5d0dd3c commit d536764
Show file tree
Hide file tree
Showing 12 changed files with 97 additions and 82 deletions.
14 changes: 9 additions & 5 deletions perun/profile/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ def models_to_pandas_dataframe(profile: Profile) -> pandas.DataFrame:
return pandas.DataFrame(values)


def to_flame_graph_format(profile: Profile, profile_key: str = "amount") -> list[str]:
def to_flame_graph_format(
profile: Profile, profile_key: str = "amount", minimize: bool = False
) -> list[str]:
"""Transforms the **memory** profile w.r.t. :ref:`profile-spec` into the
format supported by perl script of Brendan Gregg.
Expand All @@ -129,6 +131,7 @@ def to_flame_graph_format(profile: Profile, profile_key: str = "amount") -> list
:param Profile profile: the memory profile
:param profile_key: key that is used to obtain the data
:param minimize: minimizes the uids
:returns: list of lines, each representing one allocation call stack
"""
stacks = []
Expand All @@ -139,7 +142,7 @@ def to_flame_graph_format(profile: Profile, profile_key: str = "amount") -> list
if alloc["uid"] != "%TOTAL_TIME%":
stack_str = to_uid(alloc["uid"]) + ";"
for frame in alloc["trace"][::-1]:
line = to_string_line(frame)
line = to_uid(frame, minimize)
stack_str += line + ";"
if stack_str and stack_str.endswith(";"):
final = stack_str[:-1]
Expand All @@ -149,16 +152,17 @@ def to_flame_graph_format(profile: Profile, profile_key: str = "amount") -> list
return stacks


def to_uid(record: dict[str, Any] | str) -> str:
def to_uid(record: dict[str, Any] | str, minimize: bool = False) -> str:
"""Retrieves uid from record
:param record: record for which we are retrieving uid
:return: single string representing uid
"""
if isinstance(record, str):
return record
result = record
else:
return to_string_line(record)
result = to_string_line(record)
return common_kit.hide_generics(result) if minimize else result


def to_string_line(frame: dict[str, Any] | str) -> str:
Expand Down
14 changes: 10 additions & 4 deletions perun/scripts/flamegraph.pl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
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 $offset = 0; # offset added to rectangles (this is used to align two flamegraphs)
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
Expand Down Expand Up @@ -137,6 +138,7 @@ sub usage {
--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)
--offset NUM # offset of the start of the flamegraph from top (default 0)
--fonttype FONT # font type (default "Verdana")
--fontsize NUM # font size (default 12)
--countname TEXT # count type label (default "samples")
Expand Down Expand Up @@ -165,6 +167,7 @@ sub usage {
'fonttype=s' => \$fonttype,
'width=i' => \$imagewidth,
'height=i' => \$frameheight,
'offset=i' => \$offset,
'encoding=s' => \$encoding,
'fontsize=f' => \$fontsize,
'fontwidth=f' => \$fontwidth,
Expand Down Expand Up @@ -767,6 +770,9 @@ sub flow {
}

# draw canvas, and embed interactive JavaScript program
if ($offset != 0) {
$offset = ($offset + 1) * $frameheight;
}
my $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2;
$imageheight += $ypad3 if $subtitletext ne "";
my $titlesize = $fontsize + 5;
Expand Down Expand Up @@ -1135,11 +1141,11 @@ sub flow {
$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("details", $xpad, $imageheight - ($ypad2 / 2) + $offset, " ");
$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), " ");
$im->stringTTF("matched", $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2) + $offset, " ");

if ($palette) {
read_palette();
Expand Down Expand Up @@ -1209,7 +1215,7 @@ sub flow {
} else {
$color = color($colors, $hash, $func);
}
$im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"');
$im->filledRectangle($x1, $y1+$offset, $x2, $y2+$offset, $color, 'rx="2" ry="2"');

my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth));
my $text = "";
Expand All @@ -1221,7 +1227,7 @@ sub flow {
$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
}
$im->stringTTF(undef, $x1 + 3, 3 + ($y1 + $y2) / 2, $text);
$im->stringTTF(undef, $x1 + 3, 3 + ($y1 + $y2) / 2 + $offset, $text);

$im->group_end($nameattr);
}
Expand Down
4 changes: 1 addition & 3 deletions perun/utils/common/cli_kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,7 @@ def vcs_path_callback(_: click.Context, __: click.Option, value: Any) -> Any:
:param str value: value that is being read from the commandline
:returns tuple: tuple of flags or parameters
"""
if not value:
return common_kit.locate_dir_on(".", ".git")
return value
return common_kit.locate_dir_on(".", ".git") if not value else value


def vcs_parameter_callback(ctx: click.Context, param: click.Option, value: Any) -> Any:
Expand Down
20 changes: 20 additions & 0 deletions perun/utils/common/common_kit.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,4 +583,24 @@ def add_to_sorted(
values.pop(0)


def hide_generics(uid: str) -> str:
"""Hides the generics in the uid
This transforms std::<std::<type>> into std::<*>
:param uid: uid with generics
:return uid without generics
"""
nesting = 0
chars = []
for c in uid:
if c == "<":
nesting += 1
elif c == ">":
nesting -= 1
if nesting == 0 or (c == '<' and nesting == 1):
chars.append(c)
return "".join(chars)


MODULE_CACHE: dict[str, types.ModuleType] = {}
27 changes: 17 additions & 10 deletions perun/view/flamegraph/flamegraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,28 @@
def draw_flame_graph_difference(
lhs_profile: Profile,
rhs_profile: Profile,
height: int,
width: int = 1200,
title: str = "",
profile_key: str = "amount",
minimize: bool = False,
) -> str:
"""Draws difference of two flame graphs from two profiles
:param lhs_profile: baseline profile
:param rhs_profile: target_profile
:param width: width of the graph
:param height: graphs height
:param profile_key: key for which we are constructing profile
:param title: if set to empty, then title will be generated
"""
# First we create two flamegraph formats
lhs_flame = convert.to_flame_graph_format(lhs_profile, profile_key=profile_key)
lhs_flame = convert.to_flame_graph_format(
lhs_profile, profile_key=profile_key, minimize=minimize
)
with open("lhs.flame", "w") as lhs_handle:
lhs_handle.write("".join(lhs_flame))
rhs_flame = convert.to_flame_graph_format(rhs_profile, profile_key=profile_key)
rhs_flame = convert.to_flame_graph_format(
rhs_profile, profile_key=profile_key, minimize=minimize
)
with open("rhs.flame", "w") as rhs_handle:
rhs_handle.write("".join(rhs_flame))

Expand All @@ -56,7 +59,7 @@ def draw_flame_graph_difference(
difference_script = (
f"{diff_script} -n lhs.flame rhs.flame "
f"| {flame_script} --title '{title}' --countname {units} --reverse "
f"--width {width * 2} --height {height}"
f"--width {width * 2}"
)
out, _ = commands.run_safely_external_command(difference_script)
os.remove("lhs.flame")
Expand All @@ -66,7 +69,12 @@ def draw_flame_graph_difference(


def draw_flame_graph(
profile: Profile, height: int, width: int = 1200, title: str = "", profile_key: str = "amount"
profile: Profile,
width: int = 1200,
offset: int = 0,
title: str = "",
profile_key: str = "amount",
minimize: bool = False,
) -> str:
"""Draw Flame graph from profile.
Expand All @@ -75,12 +83,11 @@ def draw_flame_graph(
:param profile: the memory profile
:param width: width of the graph
:param height: graphs height
:param profile_key: key for which we are constructing profile
:param title: if set to empty, then title will be generated
"""
# converting profile format to format suitable to Flame graph visualization
flame = convert.to_flame_graph_format(profile, profile_key=profile_key)
flame = convert.to_flame_graph_format(profile, profile_key=profile_key, minimize=minimize)

header = profile["header"]
profile_type = header["type"]
Expand All @@ -103,8 +110,8 @@ def draw_flame_graph(
"--reverse",
"--width",
str(width),
"--height",
str(height),
"--offset",
f"{offset}",
"--minwidth",
"1",
]
Expand Down
19 changes: 5 additions & 14 deletions perun/view/flamegraph/run.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Flame graph visualization of the profiles."""

from __future__ import annotations

# Standard Imports
Expand All @@ -12,14 +13,13 @@
import perun.profile.factory as profile_factory


def save_flamegraph(profile: profile_factory.Profile, filename: str, graph_height: int) -> None:
def save_flamegraph(profile: profile_factory.Profile, filename: str) -> None:
"""Draws and saves flamegraph to file
:param profile: profile for which we are saving flamegraph
:param filename: name of the file where the flamegraph will be saved
:param graph_height: height of the graph
"""
flamegraph_content = flame.draw_flame_graph(profile, graph_height)
flamegraph_content = flame.draw_flame_graph(profile)
with open(filename, "w") as file_handle:
file_handle.write(flamegraph_content)

Expand All @@ -31,17 +31,8 @@ def save_flamegraph(profile: profile_factory.Profile, filename: str, graph_heigh
default="flame.svg",
help="Sets the output file of the resulting flame graph.",
)
@click.option(
"--graph-height",
"-h",
default=20,
type=int,
help="Increases the width of the resulting flame graph.",
)
@profile_factory.pass_profile
def flamegraph(
profile: profile_factory.Profile, filename: str, graph_height: int, **_: Any
) -> None:
def flamegraph(profile: profile_factory.Profile, filename: str, **_: Any) -> None:
"""Flame graph interprets the relative and inclusive presence of the
resources according to the stack depth of the origin of resources.
Expand Down Expand Up @@ -82,4 +73,4 @@ def flamegraph(
:func:`perun.profile.convert.to_flame_graph_format` for more details how
the profiles are converted to the flame graph format.
"""
save_flamegraph(profile, filename, graph_height)
save_flamegraph(profile, filename)
24 changes: 10 additions & 14 deletions perun/view_diff/flamegraph/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from perun.view_diff.short import run as table_run


DEFAULT_HEIGHT: int = 14
DEFAULT_WIDTH: int = 600
TAGS_TO_INDEX: list[str] = []

Expand Down Expand Up @@ -114,38 +113,43 @@ def generate_flamegraphs(
lhs_profile: Profile,
rhs_profile: Profile,
data_types: list[str],
height: int = DEFAULT_HEIGHT,
width: int = DEFAULT_WIDTH,
skip_diff: bool = False,
minimize: bool = False,
offsets: list[int] = [0, 0],
) -> list[tuple[str, str, str, str]]:
"""Constructs a list of tuples of flamegraphs for list of data_types
:param lhs_profile: baseline profile
:param rhs_profile: target profile
:param minimize: whether the flamegraph should be minimized or not
:param offset: offset of start of drawing rectangles
:param data_types: list of data types (resources)
:param height: height of the flame graph
:param width: width of the flame graph
"""
max_offset = max(offsets)
flamegraphs = []
for i, dtype in enumerate(data_types):
try:
data_type = mapping.from_readable_key(dtype)
lhs_graph = flamegraph_factory.draw_flame_graph(
lhs_profile,
height,
width,
title="Baseline Flamegraph",
profile_key=data_type,
minimize=minimize,
offset=abs(offsets[0] - max_offset),
)
escaped_lhs = escape_content(f"lhs_{i}", lhs_graph)
log.minor_success(f"Baseline flamegraph ({dtype})", "generated")

rhs_graph = flamegraph_factory.draw_flame_graph(
rhs_profile,
height,
width,
title="Target Flamegraph",
profile_key=data_type,
minimize=minimize,
offset=abs(offsets[1] - max_offset),
)
escaped_rhs = escape_content(f"rhs_{i}", rhs_graph)
log.minor_success(f"Target flamegraph ({dtype})", "generated")
Expand All @@ -156,10 +160,10 @@ def generate_flamegraphs(
diff_graph = flamegraph_factory.draw_flame_graph_difference(
lhs_profile,
rhs_profile,
height,
width,
title="Difference Flamegraph",
profile_key=data_type,
minimize=minimize,
)
escaped_diff = escape_content(f"diff_{i}", diff_graph)
log.minor_success(f"Diff flamegraph ({dtype})", "generated")
Expand Down Expand Up @@ -188,7 +192,6 @@ def generate_flamegraph_difference(
lhs_profile,
rhs_profile,
data_types,
kwargs.get("height", DEFAULT_HEIGHT),
kwargs.get("width", DEFAULT_WIDTH),
)
lhs_header, rhs_header = diff_kit.generate_headers(lhs_profile, rhs_profile)
Expand Down Expand Up @@ -224,13 +227,6 @@ def generate_flamegraph_difference(
default=DEFAULT_WIDTH,
help="Sets the width of the flamegraph (default=600px).",
)
@click.option(
"-h",
"--height",
type=click.INT,
default=DEFAULT_HEIGHT,
help="Sets the height of the flamegraph (default=14).",
)
@click.option("-o", "--output-file", help="Sets the output file (default=automatically generated).")
def flamegraph(ctx: click.Context, *_: Any, **kwargs: Any) -> None:
""" """
Expand Down
Loading

0 comments on commit d536764

Please sign in to comment.