Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move lines create to profile model #27

Merged
merged 4 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions flameshow/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass, field
import datetime
import logging
import time
from typing import Dict, List
from typing_extensions import Self

Expand Down Expand Up @@ -88,18 +89,52 @@ class SampleType:

@dataclass
class Profile:
filename: str = ""

created_at: datetime.datetime | None = None
id_store: Dict[int, Frame] = field(default_factory=dict)

sample_types: List[SampleType] = field(default_factory=list)
# required
filename: str
root_stack: Frame
highest_lines: int
# total samples is one top most sample, it's a list that contains all
# its parents all the way up
total_sample: int
sample_types: List[SampleType]
id_store: Dict[int, Frame]

# optional
default_sample_type_index: int = -1

period_type: SampleType | None = None
period: int = 0
created_at: datetime.datetime | None = None

# init by post_init
lines: List = field(init=False)

def __post_init__(self):
"""
init_lines must be called before render
"""
t1 = time.time()
logger.info("start to create lines...")

root = self.root_stack

lines = [
[root],
]
current = root.children
line_no = 1

while len(current) > 0:
line = []
next_line = []

for child in current:
line.append(child)
next_line.extend(child.children)

lines.append(line)
line_no += 1
current = next_line

# TODO parse using protobuf
root_stack: Frame | None = None
highest_lines: int = 0
total_sample: int = 0
t2 = time.time()
logger.info("create lines done, took %.2f seconds", t2 - t1)
self.lines = lines
42 changes: 21 additions & 21 deletions flameshow/pprof_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import gzip
import logging
import os
from typing import List
from typing import Dict, List

from flameshow.models import Frame, Profile, SampleType
from flameshow.utils import sizeof
Expand Down Expand Up @@ -164,7 +164,7 @@ def __init__(self, filename):
self.locations = []
self.highest = 0

self.id_store = {self.root._id: self.root}
self.id_store: Dict[int, Frame] = {self.root._id: self.root}

def idgenerator(self):
i = self.next_id
Expand All @@ -185,34 +185,34 @@ def parse(self, binary_data):
pbdata = unmarshal(binary_data)
self.parse_internal_data(pbdata)

pprof_profile = Profile()
pprof_profile.filename = self.filename
pprof_profile.sample_types = self.parse_sample_types(
pbdata.sample_type
)
pprof_profile.created_at = self.parse_created_at(pbdata.time_nanos)
pprof_profile.period = pbdata.period
pprof_profile.period_type = self.to_smaple_type(pbdata.period_type)

if pbdata.default_sample_type:
pprof_profile.default_sample_type_index = (
pbdata.default_sample_type
)
sample_types = self.parse_sample_types(pbdata.sample_type)

# WIP
root = self.root
root.values = [0] * len(pprof_profile.sample_types)
root.values = [0] * len(sample_types)
for pbsample in pbdata.sample:
child_frame = self.parse_sample(pbsample)
if not child_frame:
continue
root.values = list(map(sum, zip(root.values, child_frame.values)))
root.pile_up(child_frame)

pprof_profile.root_stack = root
pprof_profile.id_store = self.id_store
pprof_profile.total_sample = len(pbdata.sample)
pprof_profile.highest_lines = self.highest
pprof_profile = Profile(
filename=self.filename,
root_stack=root,
highest_lines=self.highest,
total_sample=len(pbdata.sample),
sample_types=sample_types,
id_store=self.id_store,
)

if pbdata.default_sample_type:
pprof_profile.default_sample_type_index = (
pbdata.default_sample_type
)

pprof_profile.created_at = self.parse_created_at(pbdata.time_nanos)
pprof_profile.period = pbdata.period
pprof_profile.period_type = self.to_smaple_type(pbdata.period_type)

return pprof_profile

Expand Down
34 changes: 2 additions & 32 deletions flameshow/render/flamegraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,38 +68,8 @@ def __init__(
self.view_frame = view_frame

# pre-render
self.lines = self.create_lines(profile)
self.frame_maps = None

def create_lines(self, profile):
t1 = time.time()
logger.info("start to create lines...")

root = profile.root_stack

lines = [
[root],
]
current = [root.children]
line_no = 1

while len(current) > 0:
line = []
next_line = []

for children_group in current:
for child in children_group:
line.append(child)
next_line.append(child.children)

lines.append(line)
line_no += 1
current = next_line

t2 = time.time()
logger.info("create lines done, took %.2f seconds", t2 - t1)
return lines

def render_lines(self, crop):
logger.info("render_lines!! crop: %s", crop)
my_width = crop.size.width
Expand All @@ -116,7 +86,7 @@ def generate_frame_maps(self, width, focused_stack_id):
"""
compute attributes for render for every frame

only re-computes with width, focused_stack changeing
only re-computes with width, focused_stack changing
"""
logger.info(
"lru cache miss, Generates frame map, for width=%d,"
Expand Down Expand Up @@ -198,7 +168,7 @@ def _generate_for_children(frame):

def render_line(self, y: int) -> Strip:
# logger.info("container_size: %s", self.container_size)
line = self.lines[y]
line = self.profile.lines[y]

if not self.frame_maps:
raise Exception("frame_maps is not init yet!")
Expand Down
26 changes: 26 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from flameshow.models import Profile, SampleType
from flameshow.pprof_parser.parser import ProfileParser, Frame


Expand All @@ -23,3 +24,28 @@ def test_pile_up():
root.pile_up(s1)
assert root.children[0].values == [7]
assert root.children[0].children[0].values == [6]


def test_profile_creataion():
root = Frame("root", 0, values=[5])
s1 = Frame("s1", 1, values=[4], parent=root)
s2 = Frame("s2", 2, values=[1], parent=s1)
s3 = Frame("s3", 3, values=[2], parent=s1)

root.children = [s1]
s1.children = [s2, s3]

p = Profile(
filename="abc",
root_stack=root,
highest_lines=1,
total_sample=2,
sample_types=[SampleType("goroutine", "count")],
id_store={
0: root,
1: s1,
2: s2,
3: s3,
},
)
assert p.lines == [[root], [s1], [s2, s3]]
25 changes: 24 additions & 1 deletion tests/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from flameshow.pprof_parser import parse_profile
from flameshow.pprof_parser.parser import Line, Profile, SampleType, PprofFrame
from flameshow.render import FlameshowApp
from flameshow.models import Frame


@pytest.mark.asyncio
@pytest.mark.skip(reason="todo rewrite")
async def test_render_goroutine_child_not_100percent_of_parent(data_dir):
"""some goroutines are missing, child is not 100% of parent
should render 66.6% for the only child in some cases"""
Expand All @@ -27,8 +29,29 @@ async def test_render_goroutine_child_not_100percent_of_parent(data_dir):
assert child.styles.width.value == 66.67


@pytest.mark.skip(reason="todo rewrite")
def test_default_sample_types_heap():
p = Profile()
root = Frame("root", 0, values=[5])
s1 = Frame("s1", 1, values=[4], parent=root)
s2 = Frame("s2", 2, values=[1], parent=s1)
s3 = Frame("s3", 3, values=[2], parent=s1)

root.children = [s1]
s1.children = [s2, s3]

p = Profile(
filename="abc",
root_stack=root,
highest_lines=1,
total_sample=2,
sample_types=[SampleType("goroutine", "count")],
id_store={
0: root,
1: s1,
2: s2,
3: s3,
},
)
p.sample_types = [
SampleType("alloc_objects", "count"),
SampleType("alloc_space", "bytes"),
Expand Down
Loading