Skip to content

Commit

Permalink
Merge pull request #23 from laixintao/loading-status
Browse files Browse the repository at this point in the history
Display loading status on footer.
  • Loading branch information
laixintao authored Sep 30, 2023
2 parents 442bdf9 + ee26b75 commit 8f18e0f
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ jobs:
- name: Black test
run: |
. venv/bin/activate
black --check .
black --check --diff .
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
pull_request:
push:
branches:
- master
- main

jobs:
build:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Flameshow

<a href="https://badge.fury.io/py/flameshow"><img src="https://badge.fury.io/py/flameshow.svg" alt="PyPI version"></a>
[![Test](https://github.com/laixintao/flameshow/actions/workflows/pytest.yaml/badge.svg)](https://github.com/laixintao/flameshow/actions/workflows/pytest.yaml)
[![tests](https://github.com/laixintao/flameshow/actions/workflows/pytest.yaml/badge.svg?branch=main)](https://github.com/laixintao/flameshow/actions/workflows/pytest.yaml)

Flameshow is a terminal Flamegraph viewer.

Expand Down
2 changes: 0 additions & 2 deletions flameshow/const.py
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
MIN_RENDER_DEPTH = 5
MAX_RENDER_DEPTH = 15
12 changes: 0 additions & 12 deletions flameshow/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import click

from flameshow import __version__
from flameshow.const import MAX_RENDER_DEPTH, MIN_RENDER_DEPTH
from flameshow.pprof_parser import parse_profile
from flameshow.render import FlameGraphApp

Expand Down Expand Up @@ -48,20 +47,9 @@ def run_app(verbose, log_to, profile_f, _debug_exit_after_rednder):

t01 = time.time()
logger.info("Parse profile, took %.3fs", t01 - t0)
render_depth = MAX_RENDER_DEPTH - int(profile.total_sample / 100)
# limit to 3 - 10
render_depth = max(MIN_RENDER_DEPTH, render_depth)
render_depth = min(MAX_RENDER_DEPTH, render_depth)

logger.info(
"Start to render app, total samples=%d, render_depth=%d",
profile.total_sample,
render_depth,
)

app = FlameGraphApp(
profile,
render_depth,
_debug_exit_after_rednder,
)
app.run()
Expand Down
87 changes: 71 additions & 16 deletions flameshow/render/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import logging
import time
from typing import ClassVar
from rich.style import Style
from rich.text import Text

import textual
from textual import on
Expand Down Expand Up @@ -81,22 +83,18 @@ class FlameGraphApp(App):
def __init__(
self,
profile,
max_level,
_debug_exit_after_rednder=False,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)
self.profile = profile
self.root_stack = profile.root_stack
self._create_time = time.time()
self._rendered_once = False

self._max_level = max_level
# +1 is extra "root" node
self.viewer_height = self.profile.highest_lines + 1
self._debug_exit_after_rednder = _debug_exit_after_rednder
logger.info("App render level limit to %d", self._max_level)

self.view_info_stack = self.root_stack
self.parents_that_only_one_child = []
Expand Down Expand Up @@ -147,6 +145,7 @@ def compose(self) -> ComposeResult:
id="flamegraph-out-container",
)

yield Static(id="loading-status")
yield self._profile_info(self.profile.created_at)
yield Footer()

Expand All @@ -158,6 +157,20 @@ def _center_header_text(self, sample_index):
)
return center_header

def set_status_loading(self):
widget = self.query_one("#loading-status")
widget.update(Text("● loading...", Style(color="green")))
self.loading_start_time = time.time()

def set_status_loading_done(self):
widget = self.query_one("#loading-status")
widget.update("")
self.loading_end_time = time.time()
logger.info(
"rerender done, took %.3f seconds.",
self.loading_end_time - self.loading_start_time,
)

def _profile_info(self, created_at: datetime):
if not created_at:
return Static("")
Expand All @@ -171,19 +184,34 @@ def post_display_hook(self):
if self._rendered_once:
return
self._rendered_once = True
rendered_time = time.time()
render_cost = rendered_time - self._create_time
logger.info("First render cost %.2f s", render_cost)
if self._debug_exit_after_rednder:
logger.warn("_debug_exit_after_rednder set to True, exit now")
self.exit()

def render_flamegraph(self, stack):
parents = self._render_parents(stack)

t1 = time.time()
total_frame = self._get_frames_should_render(stack)

# 15, 100 and 4 is magic number that I tuned
# they fit the performance best while rendering enough information
# 4 keeps every render < 1second
max_level = round(15 - total_frame / 100)
max_level = max(4, max_level)
t2 = time.time()

logger.debug(
"compute spans that should render, took %.3f, total sample=%d,"
" max_level=%d",
t2 - t1,
total_frame,
max_level,
)
children = SpanContainer(
stack,
"100%",
level=self._max_level,
level=max_level,
i=self.sample_index,
sample_unit=self.sample_unit,
)
Expand All @@ -196,6 +224,16 @@ def render_flamegraph(self, stack):
v.styles.height = self.viewer_height
return v

def _get_frames_should_render(self, frame) -> int:
if frame.values[self.sample_index] == 0:
return 0

count = 1
for c in frame.children:
count += self._get_frames_should_render(c)

return count

def _render_parents(self, stack):
parents = []
parent = stack.parent
Expand Down Expand Up @@ -253,6 +291,7 @@ async def _rerender(self, stack, sample_index):
return
logger.info("re-render the new focused_stack: %s", stack.name)

self.set_status_loading()
try:
old_container = self.query_one("#flamegraph-container")
except NoMatches:
Expand All @@ -272,6 +311,8 @@ async def _rerender(self, stack, sample_index):
flamegraph_continer = self.query_one("#flamegraph-out-container")
flamegraph_continer.focus()

self.set_status_loading_done()

def __debug_dom(self, node, indent: int):
for c in node.children:
logger.debug("%s %s", indent * " ", c)
Expand Down Expand Up @@ -377,6 +418,7 @@ def action_move_right(self):

right = self._find_right_sibling(self.view_info_stack)

logger.debug("found right sibling: %s, %s", right, right.values)
if not right:
logger.debug("Got no right sibling")
return
Expand Down Expand Up @@ -404,10 +446,18 @@ def _find_left_sibling(self, me):
while my_parent:
siblings = my_parent.children
if len(siblings) >= 2:
my_index = siblings.index(me)
left_index = my_index - 1
if left_index >= 0:
return siblings[left_index]
choose_index = siblings.index(me)
# move left, until:
# got a sibling while value is not 0 (0 won't render)
# and index >= 0
while choose_index >= 0:
choose_index = choose_index - 1
if (
choose_index >= 0
and siblings[choose_index].values[self.sample_index]
> 0
):
return siblings[choose_index]

me = my_parent
my_parent = my_parent.parent
Expand All @@ -417,10 +467,15 @@ def _find_right_sibling(self, me):
while my_parent:
siblings = my_parent.children
if len(siblings) >= 2:
my_index = siblings.index(me)
right_index = my_index + 1
if right_index < len(siblings):
return siblings[right_index]
choose_index = siblings.index(me)
while choose_index < len(siblings):
choose_index = choose_index + 1
if (
choose_index < len(siblings)
and siblings[choose_index].values[self.sample_index]
> 0
):
return siblings[choose_index]

me = my_parent
my_parent = my_parent.parent
Expand Down
1 change: 0 additions & 1 deletion flameshow/render/span_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ def render_children(self, stack, parent_width):
if not w:
continue
style_w = f"{w:.2f}%"
logger.debug("render %s width=%s", child, style_w)

if child._id in render_children_ids:
level = self.level - 1
Expand Down
89 changes: 7 additions & 82 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ async def test_render_goroutine_child_not_100percent_of_parent(data_dir):

app = FlameGraphApp(
profile,
15,
False,
)
async with app.run_test() as pilot:
Expand All @@ -38,7 +37,6 @@ def test_default_sample_types_heap():
]
app = FlameGraphApp(
p,
15,
False,
)
assert app.sample_index == 3
Expand Down

0 comments on commit 8f18e0f

Please sign in to comment.