Skip to content

Commit

Permalink
added scaling option
Browse files Browse the repository at this point in the history
  • Loading branch information
cracked-machine committed Feb 9, 2024
1 parent 9ea12db commit 3dc3544
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 50 deletions.
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"kernel", "0x10", "0x10",
"rootfs", "0x40", "0xDD",
"dtb", "0xCC", "0xCC",
"-o", "doc/example/report.md"
"-o", "doc/example/report.md",
"-s", "3"
],
"justMyCode": false
}
Expand Down
10 changes: 5 additions & 5 deletions doc/example/report.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
![memory map diagram](report.png)
|name|origin|size|remaining|collisions
|:-|:-|:-|:-|:-|
|<span style='color:gray'>kernel</span>|0x10|0x50|-0x10|{'rootfs': '0x50'}|
|<span style='color:dimgray'>uboot</span>|0xD0|0x50|-0x10|{'uboot-scr': '0x110'}|
|<span style='color:slategrey'>rootfs</span>|0x50|0x30|0x10|{'kernel': '0x50'}|
|<span style='color:lawngreen'>dtb</span>|0x90|0x30|0x10|{}|
|<span style='color:sienna'>uboot-scr</span>|0x110|0x30|0x50|{'uboot': '0x110'}|
|<span style='color:steelblue'>kernel</span>|0x10|0x50|-0x10|{'rootfs': '0x50'}|
|<span style='color:darkcyan'>uboot</span>|0xD0|0x50|-0x10|{'uboot-scr': '0x110'}|
|<span style='color:aqua'>rootfs</span>|0x50|0x30|0x10|{'kernel': '0x50'}|
|<span style='color:cornflowerblue'>dtb</span>|0x90|0x30|0x10|{}|
|<span style='color:blue'>uboot-scr</span>|0x110|0x30|0x50|{'uboot': '0x110'}|
Binary file modified doc/example/report.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 39 additions & 18 deletions mm/diagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,29 @@
@typeguard.typechecked
class MemoryMap:

height: int = 400
"""height of the diagram image"""
width: int = 400
"""width of the diagram image"""
bgcolour = "oldlace"

def __init__(self):
self._legend_width = 100
"""width of the area used for text annotations/legend"""
self.bgcolour = "oldlace"

self.height: int = 400
"""height of the diagram image"""
self.width: int = 400
"""width of the diagram image"""

self.default_region_text_size: int = 12
self.fixed_legend_text_size = 12

self._region_list = None
"""List of region objects"""

# create a list of region objects populated with input data
self._region_list = self._process_input()

self.scale_factor: int = self.args.scale
self._rescale()

self._legend_width = self.width // 2
"""width of the area used for text annotations/legend"""

# temporarily sort by ascending origin attribute and assign the draw indent
self._region_list.sort(key=lambda x: x.origin, reverse=False)
region_indent = 0
Expand All @@ -50,40 +58,50 @@ def __init__(self):
# sort in descending order so largest regions are drawn first in z-order (background)
self._region_list.sort(key=lambda x: x.size, reverse=True)

self._generate()

def _generate(self):
# output image diagram
self._create_diagram(self._region_list)
# output markdown report (refs image)
self._create_markdown(self._region_list)

def _rescale(self):
self.height = self.height * self.scale_factor
self.width = self.width * self.scale_factor
# self.default_region_text_size = self.default_region_text_size * self.scale_factor

def _create_diagram(self, region_list: List[mm.types.MemoryRegion]):

# init the main image
img_main = PIL.Image.new("RGB", (MemoryMap.width, MemoryMap.height), color=MemoryMap.bgcolour)
img_main = PIL.Image.new("RGB", (self.width, self.height), color=self.bgcolour)

# add a new layer (region_img) for each region block
# to the main image object (img_main)
# paste each new graphic element image to main image
for region in region_list:

region.create_img(img_width=MemoryMap.width - self._legend_width)
# Regions
region.create_img(img_width=(self.width - self._legend_width), font_size=self.default_region_text_size)
if not region.img:
continue
img_main.paste(region.img, (self._legend_width + region.draw_indent, region.origin), region.img)

# Origin Address Text
origin_text_label = mm.types.TextLabel(region._origin, 8)
origin_text_label = mm.types.TextLabel(region._origin, self.fixed_legend_text_size)
img_main.paste(origin_text_label.img, (0, region.origin - origin_text_label.height + 1))

# Region End Address Text
end_addr_val = region.origin + region.size
end_addr_text_label = mm.types.TextLabel(hex(end_addr_val).upper(), 8)
end_addr_text_label = mm.types.TextLabel(hex(end_addr_val), self.fixed_legend_text_size)
img_main.paste(end_addr_text_label.img, (0, end_addr_val - end_addr_text_label.height + 1))

# Dash Lines from text to region
line_width = 1
line_canvas = PIL.ImageDraw.Draw(img_main)
dash_gap = 4
dash_len = dash_gap / 2
for x in range(40, 90, dash_gap):
for x in range(end_addr_text_label.width * 2, self._legend_width - 10, dash_gap):
line_canvas.line((x, end_addr_val - line_width, x + dash_len, end_addr_val - line_width), fill="black", width=line_width)
for x in range(origin_text_label.width * 2, self._legend_width - 10, dash_gap):
line_canvas.line((x, region.origin - line_width, x + dash_len, region.origin - line_width), fill="black", width=1)

# rotate the entire diagram so the origin is at the bottom
Expand All @@ -109,10 +127,12 @@ def _process_input(self) -> List[mm.types.MemoryRegion]:
self.parser.add_argument("-o", "--out", help='path to the markdown output report file. Default: "out/report.md"',
default="out/report.md")
self.parser.add_argument("-l", "--limit", help="The maximum memory address for the diagram. Default: 400", default=400, type=int)
self.parser.add_argument("-s", "--scale", help="The scale factor for the diagram. Default: 1", default=1, type=int)

self.args = self.parser.parse_args()

if self.args.limit:
MemoryMap.height = self.args.limit
self.height = self.args.limit

if len(sys.argv) == 1:
self.parser.error("must pass in data points")
Expand All @@ -135,7 +155,7 @@ def _process_input(self) -> List[mm.types.MemoryRegion]:
region_list.append(mm.types.MemoryRegion(name, origin, size))

for r in region_list:
r.calc_nearest_region(region_list)
r.calc_nearest_region(region_list, self.height)

return region_list

Expand All @@ -149,4 +169,5 @@ def _batched(self, iterable, n):


if __name__ == "__main__":
MemoryMap()
d = MemoryMap()

12 changes: 6 additions & 6 deletions mm/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def _pick_available_colour(self):
logging.debug(f"\t### {len(MemoryRegion._remaining_colours)} colours left ###")
return chosen_colour_name

def calc_nearest_region(self, region_list: List['MemoryRegion']):
import mm.diagram
def calc_nearest_region(self, region_list: List['MemoryRegion'], diagram_height: int):

"""Calculate the remaining number of bytes until next region block"""
region_distances = {}
logging.debug(f"Calculating nearest distances to {self.name} region:")
Expand Down Expand Up @@ -132,15 +132,15 @@ def calc_nearest_region(self, region_list: List['MemoryRegion']):
lowest = min(region_distances, key=region_distances.get)
self.remain = hex(region_distances[lowest])
else:
self.remain = hex(mm.diagram.MemoryMap.height - this_region_end)
self.remain = hex(diagram_height - this_region_end)
elif self.collisons and not self.remain:
self.remain = hex(mm.diagram.MemoryMap.height - this_region_end)
self.remain = hex(diagram_height - this_region_end)


@typeguard.typechecked
class MemoryRegion(Region):

def create_img(self, img_width: int):
def create_img(self, img_width: int, font_size: int):

logging.info(self)
if not self.size:
Expand All @@ -160,7 +160,7 @@ def create_img(self, img_width: int):
)

# draw name text
region_img.paste(TextLabel(text=self.name, font_size=7).img, (5, 5))
region_img.paste(TextLabel(text=self.name, font_size=font_size).img, (5, 5))

self.img = region_img

Expand Down
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ An example can be found in [doc/example/report.md](doc/example/report.md)
### Usage:

```
usage: mm.diagram [-h] [-o OUT] [-l LIMIT] [regions ...]
usage: diagram.py [-h] [-o OUT] [-l LIMIT] [-s SCALE] [regions ...]
positional arguments:
regions command line input for regions should be tuples of name, origin and size.
Expand All @@ -24,6 +24,8 @@ options:
-o OUT, --out OUT path to the markdown output report file. Default: "out/report.md"
-l LIMIT, --limit LIMIT
The maximum memory address for the diagram. Default: 400
-s SCALE, --scale SCALE
The scale factor for the diagram. Default: 1
```

- Generate five regions called `kernel`, `rootfs`, `dtb`, `uboot` and `uboot-scr` where four of the five regions intersect/collide. The default report output path is used. Diagram output is shown at the top of the page.
Expand Down
23 changes: 18 additions & 5 deletions tests/test_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import mm.diagram
import pathlib


# Check the output report at /tmp/pytest/tests.test_args.md


Expand Down Expand Up @@ -51,14 +52,20 @@ def test_invalid_out_arg():

def test_valid_default_out_arg():
''' should create default report dir/files '''

report = pathlib.Path("out/report.md")
image = pathlib.Path("out/report.png")
report.unlink(missing_ok=True)
image.unlink(missing_ok=True)

with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'a',
'0x10',
'0x10']):
mm.diagram.MemoryMap()
assert pathlib.Path("out/report.md").exists()
assert pathlib.Path("out/report.png").exists()
assert report.exists()
assert image.exists()


def test_invalid_duplicate_name_arg():
Expand All @@ -77,13 +84,19 @@ def test_invalid_duplicate_name_arg():

def test_valid_custom_out_arg():
''' should create custom report dir/files '''

report = pathlib.Path(f"/tmp/pytest/{__name__}.md")
image = pathlib.Path(f"/tmp/pytest/{__name__}.png")
report.unlink(missing_ok=True)
image.unlink(missing_ok=True)

with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'a',
'0x10',
'0x10',
"-o",
f"/tmp/pytest/{__name__}.md"]):
str(report)]):
mm.diagram.MemoryMap()
assert pathlib.Path(f"/tmp/pytest/{__name__}.md").exists()
assert pathlib.Path(f"/tmp/pytest/{__name__}.png").exists()
assert report.exists()
assert image.exists()
25 changes: 13 additions & 12 deletions tests/test_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

def test_distance_three_regions_same_size_no_collisions():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -21,8 +22,7 @@ def test_distance_three_regions_same_size_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):

d = mm.diagram.MemoryMap()
for region in d._region_list:
Expand All @@ -42,6 +42,7 @@ def test_distance_three_regions_same_size_no_collisions():

def test_distance_three_regions_touching_no_collisions():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -56,8 +57,7 @@ def test_distance_three_regions_touching_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):

d = mm.diagram.MemoryMap()
for region in d._region_list:
Expand All @@ -74,8 +74,10 @@ def test_distance_three_regions_touching_no_collisions():
assert region._size == "0x30"
assert region.remain == "0x348"


def test_distance_three_regions_diff_size_no_collisions():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -90,8 +92,7 @@ def test_distance_three_regions_diff_size_no_collisions():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
Expand All @@ -110,6 +111,7 @@ def test_distance_three_regions_diff_size_no_collisions():

def test_distance_three_regions_bottom_collision():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -124,8 +126,7 @@ def test_distance_three_regions_bottom_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
Expand All @@ -144,6 +145,7 @@ def test_distance_three_regions_bottom_collision():

def test_distance_three_regions_bottom_middle_collision():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -158,8 +160,7 @@ def test_distance_three_regions_bottom_middle_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
Expand All @@ -178,6 +179,7 @@ def test_distance_three_regions_bottom_middle_collision():

def test_distance_five_regions_bottom_top_collision():
""" """
diagram_height = 1000
with unittest.mock.patch('sys.argv',
['mmap_digram.diagram',
'kernel',
Expand All @@ -198,8 +200,7 @@ def test_distance_five_regions_bottom_top_collision():
"-o",
f"/tmp/pytest/{__name__}.md",
"-l",
"1000"],
mm.diagram.MemoryMap.height, 1000):
str(diagram_height)]):
d = mm.diagram.MemoryMap()
for region in d._region_list:
if region.name == "kernel":
Expand Down
4 changes: 2 additions & 2 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def test_generate_doc_example():
'uboot-scr',
'0x110',
'0x30',
"-o",
"doc/example/report.md"],
"-o", "doc/example/report.md",
"-s", "2"],
):
d = mm.diagram.MemoryMap()
for region in d._region_list:
Expand Down
Loading

0 comments on commit 3dc3544

Please sign in to comment.