diff --git a/.gitignore b/.gitignore
index c67167b..3277198 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,10 @@ doc/test_project/results
pypiemma.egg-info/
site/
+# Exclude all .svg except in the doc/image folder
+*.svg
+!doc/images/*
+
# Exclude all but .png files for UML/call graphs
doc/images/call_graph_uml/*
diff --git a/.travis.yml b/.travis.yml
index 132bc3a..4937990 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -47,7 +47,7 @@ install:
- pip3 install -U pyenchant # needed for pylint spell checking
- pip3 install -U coverage
# Packages for Emma
- - pip3 install -U Pygments Markdown matplotlib pandas "pypiscout>=2.0" graphviz
+ - pip3 install -U Pygments Markdown matplotlib pandas "pypiscout>=2.0" graphviz svgwrite
# Packages for Emma reports + html doc
- sudo apt-get update
- sudo apt-get install graphviz
diff --git a/Emma/__init__.py b/Emma/__init__.py
index 1be363b..e2fd3cf 100644
--- a/Emma/__init__.py
+++ b/Emma/__init__.py
@@ -23,10 +23,9 @@ class SUBPARSER_STRINGS:
DELTAS: str = "d"
-VERSION_MAJOR = "3"
-VERSION_MINOR = "4"
-VERSION_PATCH = "4"
-
+VERSION_MAJOR = "4"
+VERSION_MINOR = "0"
+VERSION_PATCH = "0"
EMMA_VERSION = ".".join([VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH])
EMMA_VISUALISER_VERSION = EMMA_VERSION
EMMA_DELTAS_VERSION = EMMA_VERSION
diff --git a/Emma/emma.py b/Emma/emma.py
index 23dc49b..27f1492 100644
--- a/Emma/emma.py
+++ b/Emma/emma.py
@@ -51,7 +51,7 @@ def main(arguments):
if memoryManager.settings.createCategories or memoryManager.settings.dryRun:
sc().info("No results were generated since categorisation or dryRun option is active.")
else:
- memoryManager.createReports(arguments.teamscale)
+ memoryManager.createReports(arguments.teamscale, arguments.memVis, arguments.memVisResolved, arguments.noprompt)
# Stop and display time measurement
TIME_END = timeit.default_timer()
@@ -141,6 +141,18 @@ def initParser():
action="store_true",
default=False
)
+ parser.add_argument(
+ "--memVis",
+ help="Plot unresolved view of sections and objects for a specified address area",
+ default=False,
+ action="store_true"
+ )
+ parser.add_argument(
+ "--memVisResolved",
+ help="Plot figure visualising how Emma resolved the overlaps for a specified address area. Not possible if noResolveOverlap is active",
+ default=False,
+ action="store_true"
+ )
parser.add_argument(
"--teamscale",
@@ -148,7 +160,6 @@ def initParser():
default=False,
action="store_true",
)
-
parser.add_argument(
"--dryRun",
help="Do not store any standard reports",
@@ -171,7 +182,7 @@ def parseArgs(arguments=""):
def processArguments(arguments):
"""
- Function to extract the settings values from the command line arguments.
+ Extract the settings values from the command line arguments.
:param arguments: The command line arguments, that is the result of the parser.parse_args().
:return: The setting values.
"""
@@ -188,6 +199,13 @@ def processArguments(arguments):
# Get paths straight (only forward slashes) or set it to empty if it was empty
subDir = Emma.shared_libs.emma_helper.joinPath(arguments.subdir) if arguments.subdir is not None else ""
+ if arguments.memVis and arguments.memVisResolved:
+ sc().error("Select either `--memVis` or `--memVisResolved`")
+ if arguments.memVisResolved and arguments.noResolveOverlap:
+ sc().warning("Incompatible arguments `--noResolveOverlap` and `--memVisResolved` were found. SVG figure will depict the unresolved scenario.")
+ arguments.memVisResolved = False
+ arguments.memVis = True
+
outputPath = Emma.shared_libs.emma_helper.joinPath(directory, subDir, OUTPUT_DIR)
analyseDebug = arguments.analyseDebug
createCategories = arguments.createCategories
@@ -196,9 +214,12 @@ def processArguments(arguments):
noResolveOverlap = arguments.noResolveOverlap
teamscale = arguments.teamscale
dryRun = arguments.dryRun
+ memVis = arguments.memVis
+ memVisResolved = arguments.memVisResolved
+
# TODO: It would be more convenient if arguments which are not modified are passed without manually modifying the code (MSc)
- return projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamscale, dryRun
+ return projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamscale, dryRun, memVis, memVisResolved
def runEmma():
diff --git a/Emma/emma_libs/memoryManager.py b/Emma/emma_libs/memoryManager.py
index d4d25aa..4130ab2 100644
--- a/Emma/emma_libs/memoryManager.py
+++ b/Emma/emma_libs/memoryManager.py
@@ -16,10 +16,11 @@
along with this program. If not, see
"""
-
import os
+from enum import IntEnum
from pypiscout.SCout_Logger import Logger as sc
+import svgwrite
# import graphviz
from Emma.shared_libs.stringConstants import * # pylint: disable=unused-wildcard-import,wildcard-import
@@ -35,13 +36,14 @@ class MemoryManager:
"""
A class to organize the processing of the configuration and the mapfiles and the storage of the created reports.
"""
+
class Settings:
# pylint: disable=too-many-instance-attributes, too-many-arguments, too-few-public-methods
# Rationale: This class´s only purpose is to store settings, thus having too many members and parameters is not an error.
"""
Settings that influence the operation of the MemoryManager object.
"""
- def __init__(self, projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun):
+ def __init__(self, projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun, memVis, memVisResolved):
self.projectName = projectName
self.configurationPath = configurationPath
self.mapfilesPath = mapfilesPath
@@ -51,23 +53,25 @@ def __init__(self, projectName, configurationPath, mapfilesPath, outputPath, ana
self.removeUnmatched = removeUnmatched
self.noPrompt = noPrompt
self.noResolveOverlap = noResolveOverlap
+ self.memVis = memVis
+ self.memVisResolved = memVisResolved
self.teamScale = teamScale
self.dryRun = dryRun
- def __init__(self, projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun):
+ def __init__(self, projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun, memVis, memVisResolved):
# pylint: disable=too-many-arguments
# Rationale: We need to initialize the Settings, so the number of arguments are needed.
# Processing the command line arguments and storing it into the settings member
- self.settings = MemoryManager.Settings(projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun)
+ self.settings = MemoryManager.Settings(projectName, configurationPath, mapfilesPath, outputPath, analyseDebug, createCategories, removeUnmatched, noPrompt, noResolveOverlap, teamScale, dryRun, memVis, memVisResolved)
# Check whether the configuration and the mapfiles folders exist
Emma.shared_libs.emma_helper.checkIfFolderExists(self.settings.mapfilesPath)
- self.configuration = None # The configuration is empty at this moment, it can be read in with another method
+ self.configuration = None # The configuration is empty at this moment, it can be read in with another method
# memoryContent [dict(list(memEntry))]
# Each key of this dict represents a configID; dict values are lists of consumerCollections
# consumerCollection: [list(memEntry)] lists of memEntry's; e.g. a Section_Summary which contains all memEnty objects per configID)
- self.memoryContent = None # The memory content is empty at this moment, it can be loaded with another method
- self.categorisation = None # The categorisation object does not exist yet, it can be created after reading in the configuration
+ self.memoryContent = None # The memory content is empty at this moment, it can be loaded with another method
+ self.categorisation = None # The categorisation object does not exist yet, it can be created after reading in the configuration
def readConfiguration(self):
"""
@@ -130,15 +134,21 @@ def processMapfiles(self):
# Creating a common consumerCollection
sc().info("Calculating objects in sections. This may take some time...")
- self.memoryContent[configId][FILE_IDENTIFIER_OBJECTS_IN_SECTIONS] = Emma.emma_libs.memoryMap.calculateObjectsInSections(self.memoryContent[configId][FILE_IDENTIFIER_SECTION_SUMMARY], self.memoryContent[configId][FILE_IDENTIFIER_OBJECT_SUMMARY])
+ self.memoryContent[configId][FILE_IDENTIFIER_OBJECTS_IN_SECTIONS] = Emma.emma_libs.memoryMap.calculateObjectsInSections(
+ self.memoryContent[configId][FILE_IDENTIFIER_SECTION_SUMMARY],
+ self.memoryContent[configId][FILE_IDENTIFIER_OBJECT_SUMMARY])
else:
pass
else:
sc().error("The configuration needs to be loaded before processing the mapfiles!")
- def createReports(self, teamscale=False):
+ def createReports(self, teamscale=False, memVis=False, memVisResolved=False, noprompt=False):
"""
Creates the reports
+ :param teamscale: create teamscale reports
+ :param memVis: Create svg report with unresolved overlaps if True
+ :param noprompt: No prompt is active if True
+ :param memVisResolved: Create svg report visualising resolved overlaps if True
:return: None
"""
def consumerCollections2GlobalList():
@@ -186,6 +196,158 @@ def createStandardReports():
# graph.node('C', 'C', _attributes={'shape': 'triangle'})
#
# print(graph.source)
+ def createSvgReport(startPoint, endPoint, xScalingValue="1", yScalingValue="1"):
+ """
+ Plot sections and objects of a given memory area
+ :param startPoint: Beginning of address area
+ :param endPoint: End of address area
+ :param xScalingValue: Scaling value of x axe, default 1
+ :param yScalingValue: Scaling value of y axe, default 1
+ """
+ class Element(IntEnum):
+ addressStart = 0
+ addressEnd = 1
+ addressLength = 2
+ fqn = 3
+ originalAddressStart = 4
+
+ class PlottedElement(IntEnum):
+ addressEnd = 0
+ yAxe = 1
+
+ def drawElements(image, elementsToPlot, startPoint, y, colour, scaling):
+ """
+ :param image: svgwrite.Drawing object
+ :param elementsToPlot: [[addressStart, addressEnd, addressLength, fqn, originalAddressStart], ...] List of objects to plot (-> accessed via Element enum)
+ :param startPoint: Beginning of the address area
+ :param y: Start point on the y axis
+ :param colour: [str] Text colour (see HTML reference)
+ :param scaling: Scaling value
+ :return: None
+ """
+ currYLvl = y
+ biggestYSoFar = 5 # The highest y value section element, used to plot objects so they do not cross section elements; in pixel
+ biggestEndAddrSoFar = 0
+ plottedElements = []
+ drawingOffset = 15
+ smallSpacing = 1 # Small spacing in px used for spacing between edges of the rectangle and start/ end address label
+ fontSize = 2
+ startOfDrawingArea = 3
+ fontColour = "black"
+ rectHeight = 11
+
+ for index, element in enumerate(elementsToPlot):
+ endAddrBigger = False # Flag that marks that the end address of the current element is bigger than the given end point
+ xAxeRectStart = element[Element.addressStart] - startPoint + startOfDrawingArea
+ rectLength = element[Element.addressLength] - 1
+ originalStartAddress = element[Element.addressStart]
+ if len(str(element[Element.addressEnd])) > rectHeight or len(str(originalStartAddress)) > rectHeight: # Check if address start / end fits in the rectangle
+ currYLvl += len(str(element[Element.addressEnd])) - rectHeight
+ if element[Element.addressStart] < startPoint:
+ xAxeRectStart = startOfDrawingArea
+ rectLength = element[Element.addressEnd] - startPoint
+ originalStartAddress = element[Element.originalAddressStart]
+ # Check if address end of a drawing object is bigger than end point
+ if element[Element.addressEnd] > endPoint:
+ rectLength = endPoint - startPoint - xAxeRectStart + startOfDrawingArea
+ xAxeEnd = endPoint - startPoint + startOfDrawingArea
+ endAddrBigger = True
+ else:
+ xAxeEnd = element[Element.addressEnd] - startPoint + startOfDrawingArea
+ if index == 0:
+ biggestEndAddrSoFar = element[Element.addressEnd]
+ else:
+ # Check if the actual element is overlapped by the last element
+ startAddrPrevElement = elementsToPlot[index - 1][Element.addressStart]
+ endAddrPrevElement = elementsToPlot[index - 1][Element.addressEnd]
+ if element[Element.addressStart] <= startAddrPrevElement or element[Element.addressStart] < endAddrPrevElement:
+ currYLvl = currYLvl + drawingOffset
+ plottedElements.append((element[Element.addressEnd], currYLvl))
+ if element[Element.addressEnd] > biggestEndAddrSoFar:
+ biggestEndAddrSoFar = element[Element.addressEnd]
+ elif element[Element.addressStart] < biggestEndAddrSoFar:
+ for plottedElement in plottedElements:
+ if element[Element.addressStart] < plottedElement[PlottedElement.addressEnd] and currYLvl <= plottedElement[PlottedElement.yAxe]:
+ currYLvl = plottedElement[PlottedElement.yAxe] + drawingOffset
+ # No overlap
+ else:
+ currYLvl = y
+ biggestEndAddrSoFar = element[Element.addressEnd]
+ # Plot new element
+ image.add(image.rect((xAxeRectStart, currYLvl), size=(rectLength, 10), fill=colour, transform=scaling))
+ if endAddrBigger:
+ # Add a shape (triangle) visualising that the end address of a drawing object is bigger than given end point
+ image.add(image.path(d="M " + str(endPoint - startPoint + 6) + " " + str(currYLvl + 5) + " L " + str(endPoint - startPoint + startOfDrawingArea) + " " + str(currYLvl) + " L " + str(endPoint - startPoint + startOfDrawingArea) + " " + str(currYLvl + 10), fill=colour, transform=scaling))
+ if originalStartAddress < startPoint and rectLength > 3:
+ # Add a shape (triangle) visualising that the start address of a drawing object is smaller than given start point
+ image.add(image.path(d="M 0 " + str(currYLvl + 5) + " L" + str(xAxeRectStart + 0.1) + " " + str(currYLvl) + " L " + str(xAxeRectStart + 0.1) + " " + str(currYLvl + 10), fill=colour, transform=scaling))
+ # Add metadata to drawn element
+
+ # Check if the FQN fits in the rectangle (assumption: FQN is always longer than start, end address + obj/sec length)
+ if rectLength <= len(element[Element.fqn]):
+ image.add(image.text(element[Element.fqn], insert=(xAxeRectStart, currYLvl - smallSpacing), font_size=str(fontSize) + "px", writing_mode="lr", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ # Prepare plot of start and end address
+ xAxeStart = xAxeRectStart + smallSpacing # Add spacing for start address (one px); rectangles do have no border
+ xAxeEnd = element[Element.addressEnd] - startPoint + startOfDrawingArea - smallSpacing
+ # If the rectangle smaller than two times font size, then write the end address outside the rectangle
+ if rectLength < fontSize * 2:
+ xAxeEnd = element[Element.addressEnd] - startPoint + startOfDrawingArea + smallSpacing
+ image.add(image.text(hex(originalStartAddress), insert=(xAxeStart, currYLvl), font_size=str(fontSize)+"px", writing_mode="tb", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ image.add(image.text(hex(element[Element.addressEnd]), insert=(xAxeEnd, currYLvl), font_size=str(fontSize)+"px", writing_mode="tb", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ additionalSpace = len(element[Element.fqn]) - rectLength # Compute how much space after the rectangle is needed to plot FQN
+ plottedElements.append((element[Element.addressEnd] + additionalSpace, currYLvl))
+ if biggestEndAddrSoFar < element[Element.addressEnd] + additionalSpace:
+ biggestEndAddrSoFar = element[Element.addressEnd] + additionalSpace
+ # FQN fits into element
+ else:
+ image.add(image.text(hex(originalStartAddress), insert=(xAxeRectStart + smallSpacing, currYLvl), font_size=str(fontSize)+"px", writing_mode="tb", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ image.add(image.text(hex(element[Element.addressEnd]), insert=(xAxeEnd - smallSpacing, currYLvl), font_size=str(fontSize) + "px", writing_mode="tb", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ image.add(image.text(element[Element.fqn], insert=(xAxeRectStart + 5, currYLvl + 2), font_size=str(fontSize)+"px", writing_mode="lr", font_family="Helvetica, sans-serif", fill=fontColour, transform=scaling))
+ plottedElements.append((element[Element.addressEnd], currYLvl))
+ # Update y level
+ if currYLvl > biggestYSoFar:
+ biggestYSoFar = currYLvl
+ return biggestYSoFar
+
+ consumerCollections = consumerCollections2GlobalList()
+ reportPath = Emma.emma_libs.memoryMap.createReportPath(self.settings.outputPath, self.settings.projectName, str(hex(startPoint)) + "-" + str(hex(endPoint)), "svg")
+
+ def getElementsToPlot(ElementName):
+ elementsToPlot = []
+ if memVisResolved:
+ for elementToPlot in consumerCollections[ElementName]:
+ if elementToPlot.addressLength != 0:
+ if startPoint <= elementToPlot.addressStart < endPoint:
+ elementsToPlot.append((elementToPlot.addressStart, elementToPlot.addressEnd(), elementToPlot.addressLength, elementToPlot.getFQN()))
+ elif elementToPlot.addressEnd() > startPoint and elementToPlot.addressStart < endPoint:
+ elementsToPlot.append((0, elementToPlot.addressEnd(), elementToPlot.addressLength, elementToPlot.getFQN(), elementToPlot.addressStart))
+ if memVis:
+ for elementToPlot in consumerCollections[ElementName]:
+ if elementToPlot.addressLengthOriginal != 0:
+ if startPoint <= elementToPlot.addressStartOriginal < endPoint:
+ elementsToPlot.append((elementToPlot.addressStartOriginal, elementToPlot.addressEndOriginal(),
+ elementToPlot.addressLengthOriginal, elementToPlot.getFQN()))
+ elif elementToPlot.addressEndOriginal() > startPoint and elementToPlot.addressStartOriginal < endPoint:
+ elementsToPlot.append((0, elementToPlot.addressEndOriginal(), elementToPlot.addressLengthOriginal,
+ elementToPlot.getFQN(), elementToPlot.addressStartOriginal))
+
+ return elementsToPlot
+
+ imageHeight = 3000 # Define some height of the image
+ imageWidth = endPoint - startPoint + 100
+ scaling = "scale(" + xScalingValue + ", " + yScalingValue + ")"
+ image = svgwrite.Drawing(reportPath, size=(imageWidth, imageHeight))
+ # Plot a line defining the beginning of the chosen address area
+ image.add(image.rect((3, 0), size=(0.2, imageHeight), fill="grey", opacity=0.1, transform=scaling))
+ # Plot a line defining the end of the chosen address area
+ image.add(image.rect((endPoint - startPoint + 3, 0), size=(0.2, imageHeight), fill="grey", opacity=0.1, transform=scaling))
+ # Plot sections
+ y2 = drawElements(image, getElementsToPlot("Section_Summary"), startPoint, 5, svgwrite.rgb(255, 230, 128), scaling) + 15 # Distance 15 px from the lowest section element
+ # Plot objects
+ imageHeight = drawElements(image, getElementsToPlot("Object_Summary"), startPoint, y2, svgwrite.rgb(198, 233, 175), scaling) + 15
+ image.update({"height": str(imageHeight * float(yScalingValue)), "width": imageWidth * float(xScalingValue)})
+ image.save()
+ sc().info("An SVG file was stored:", os.path.abspath(reportPath))
def createTeamScaleReports():
"""
@@ -199,11 +361,11 @@ def _createTeamScalePath(memEntryRow):
"""
Return TeamScale path in the format configID::memType::category::section::object
:param memEntryRow:
- :return: [str] TeamScale path
+ :return: [str] TeamScale path
"""
sep = "::"
- r = memEntryRow
- return f"{r.configID}{sep}{r.memType}{sep}{r.category}{sep}{r.sectionName}{sep}{r.objectName}" if r.objectName != "" and r.objectName != OBJECTS_IN_SECTIONS_SECTION_ENTRY and r.objectName != OBJECTS_IN_SECTIONS_SECTION_RESERVE else f"{r.configID}{sep}{r.memType}{sep}{r.category}{sep}{r.sectionName}"
+ row = memEntryRow
+ return f"{row.configID}{sep}{row.memType}{sep}{row.category}{sep}{row.sectionName}{sep}{row.objectName}" if row.objectName != "" and row.objectName != OBJECTS_IN_SECTIONS_SECTION_ENTRY and row.objectName != OBJECTS_IN_SECTIONS_SECTION_RESERVE else f"{row.configID}{sep}{row.memType}{sep}{row.category}{sep}{row.sectionName}"
# Creating reports from the consumer collections
for memEntryRow in consumerCollections["Section_Summary"]:
@@ -216,6 +378,51 @@ def _createTeamScalePath(memEntryRow):
if self.memoryContent is not None:
# TODO: Implement handling and choosing of which reports to create (via cmd line argument (like a comma separated string) (MSc)
createStandardReports()
+ svgReport = False
+ if memVis or memVisResolved:
+ svgReport = True
+ if svgReport and noprompt:
+ sc().wwarning("No prompt is active. No SVG report will be created")
+ elif svgReport and noprompt is False:
+ while True:
+ print("Enter the start address of the region to be plotted (start with `0x` for hex; otherwise dec is assumed):")
+ startRegion = input("> ")
+ if startRegion.startswith("0x"):
+ try:
+ startRegion = int(startRegion, 16)
+ except Exception:
+ sc().wwarning("The input is not a valid hex number. Please enter the start and end address again: \n")
+ continue
+ print("Enter the end address of the region to be plotted (start with `0x` for hex; otherwise dec is assumed):")
+ endRegion = input("> ")
+ if endRegion.startswith("0x"):
+ try:
+ endRegion = int(endRegion, 16)
+ except Exception:
+ sc().wwarning("The input is not a valid hex number. Please enter the start and end address again: \n")
+ continue
+ if str(startRegion).isdigit() and str(endRegion).isdigit():
+ startRegion = int(startRegion)
+ endRegion = int(endRegion)
+ # Exit while loop and continue when everything is O.K.
+ break
+ else:
+ sc().wwarning("The input is not a valid dec number. Please enter the start and end address again: \n")
+ # Define scaling value of x axe.
+ print("Enter the scaling x-value. If you don't enter the valid float number, scaling value 1 will be assumed.")
+ xValue = input("> ")
+ try:
+ float(xValue)
+ except Exception:
+ xValue = "1"
+ # Define scaling value of y axe.
+ print("Enter the scaling y-value. If you don't enter the valid float number, scaling value 1 will be assumed.")
+ yValue = input("> ")
+ try:
+ float(yValue)
+ except Exception:
+ yValue = "1"
+ createSvgReport(startRegion, endRegion, xValue, yValue)
# createDotReports()
if teamscale:
diff --git a/Emma/emma_libs/memoryMap.py b/Emma/emma_libs/memoryMap.py
index d9b13cf..ceb5f3a 100644
--- a/Emma/emma_libs/memoryMap.py
+++ b/Emma/emma_libs/memoryMap.py
@@ -16,7 +16,6 @@
along with this program. If not, see
"""
-
import csv
import bisect
import copy
@@ -356,6 +355,6 @@ def writeReportToDisk(reportPath, consumerCollection):
# FQN
row.getFQN()
])
-
# Writing the data to the file
+
writer.writerow(rowData)
diff --git a/Emma/emma_vis_libs/dataReports.py b/Emma/emma_vis_libs/dataReports.py
index 914fcda..2d14721 100644
--- a/Emma/emma_vis_libs/dataReports.py
+++ b/Emma/emma_vis_libs/dataReports.py
@@ -27,6 +27,7 @@
import pandas
import matplotlib.pyplot
+from Emma import shared_libs
from Emma.shared_libs.stringConstants import * # pylint: disable=unused-wildcard-import,wildcard-import
import Emma.shared_libs.emma_helper
diff --git a/Emma/emma_vis_libs/helper.py b/Emma/emma_vis_libs/helper.py
index 8d91a5a..e20950f 100644
--- a/Emma/emma_vis_libs/helper.py
+++ b/Emma/emma_vis_libs/helper.py
@@ -71,5 +71,5 @@ def getLastModFileOrPrompt(subStringIdentifier: str, inOutPath: str, quiet: bool
pypiscout.SCout.warning("Invalid input.")
if fileToUse is None:
- sc().error("No file containing '" + subStringIdentifier + "' found in " + path)
+ sc().error(f"No file containing `{subStringIdentifier}` found in {path}")
return fileToUse
diff --git a/README.md b/README.md
index a06daf9..b9d0f76 100644
--- a/README.md
+++ b/README.md
@@ -166,6 +166,7 @@ emma-dev (. at) googlegroups.com
| Pygments (v2.3.1+) | [Pygments](https://pypi.org/project/Pygments/) | BSD-2-Clause | [https://bitbucket.org/birkenfeld/pygments-main/src/default/](https://bitbucket.org/birkenfeld/pygments-main/src/default/); [http://pygments.org/download/](http://pygments.org/download/) |
| Matplotlib (v3.0.0+) | [matplotlib](https://pypi.org/project/matplotlib/) | Matplotlib License (BSD compatible) | [https://matplotlib.org/users/installing.html](https://matplotlib.org/users/installing.html); [https://github.com/matplotlib/matplotlib](https://github.com/matplotlib/matplotlib) |
| SCout (v2.0+) | [pypiscout](https://pypi.org/project/pypiscout/) | MIT | [https://github.com/holzkohlengrill/SCout](https://github.com/holzkohlengrill/SCout) |
+| svgwrite (v1.4+) | [svgwrite](https://pypi.org/project/svgwrite/) | MIT License (MIT License) | [https://github.com/mozman/svgwrite](https://github.com/mozman/svgwrite); [https://svgwrite.readthedocs.io/en/latest/](https://svgwrite.readthedocs.io/en/latest/) |
**Optional dependencies:**
@@ -188,8 +189,8 @@ Utility scripts used to build GitHub pages documentation. As a normal user you c
| Library (version) | pip package name | Licence | URL |
|-------------------------------|-------------------------------------------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
-| MkDocs (v1.1.2+) | [mkdocs](https://pypi.org/project/mkdocs/) | BSD-3Clause | [https://github.com/mkdocs/mkdocs](https://github.com/mkdocs/mkdocs) |
-| Material for MkDocs (v5.2.1+) | [mkdocs-material](https://pypi.org/project/mkdocs-material/) | MIT | [https://github.com/squidfunk/mkdocs-material](https://github.com/squidfunk/mkdocs-material) |
+| MkDocs (v1.0.4+) | [mkdocs](https://pypi.org/project/mkdocs/) | BSD-3Clause | [https://github.com/mkdocs/mkdocs](https://github.com/mkdocs/mkdocs) |
+| Material for MkDocs (v4.4.1+) | [mkdocs-material](https://pypi.org/project/mkdocs-material/) | MIT | [https://github.com/squidfunk/mkdocs-material](https://github.com/squidfunk/mkdocs-material) |
diff --git a/doc/images/memVis.png b/doc/images/memVis.png
new file mode 100644
index 0000000..b43509d
Binary files /dev/null and b/doc/images/memVis.png differ
diff --git a/doc/readme-emma.md b/doc/readme-emma.md
index 7d9b959..573bc1a 100644
--- a/doc/readme-emma.md
+++ b/doc/readme-emma.md
@@ -44,20 +44,33 @@ The devices must have a single linear physical address space:
--mapfiles MAPFILES, --map MAPFILES
-### Optional Arguments
+### Some Optional Arguments
+This section will provide a more in-depth description about selected command line arguments when a short description (like in `--help`) might be to short, the behaviour is too complex or background knowledge might assist you to understand the whole picture. For the full list execute Emma with `--help`.
* `--dir`
* User defined path for the top folder holding the `memStats`/output files. Per default it uses the same directory as the configuration files.
* `--stats_dir`
- * User defined path inside the folder given in the `--dir` argument. This is usefull when batch analysing mapfiles from various development stages. Every analysis output gets it's own directory.
+ * User defined path inside the folder given in the `--dir` argument. This is usefull when batch analysing mapfiles from various development stages. Every analysis output gets it's own directory.
* `--create_categories`
- * Create `categories*.json` from `categories*Keywords.json` for easier categorisation.
+ * Create `categories*.json` from `categories*Keywords.json` for easier categorisation.
* `--remove_unmatched`,
- * Remove unmatched entries from `categories*.json`. This is useful when a `categories*.json` from another project is used.
+ * Remove unmatched entries from `categories*.json`. This is useful when a `categories*.json` from another project is used.
* `--analyse_debug`, `--dbg`
- * Normally we remove DWARF debug sections from the analysis to show the relevant information for a possible release software. This can be prevented if this argument is set. DWARF section names are defined in `stringConstants.py`. `.unused_ram` is always excluded (regardless of this flag)
+ * Normally we remove DWARF debug sections from the analysis to show the relevant information for a possible release software. This can be prevented if this argument is set. DWARF section names are defined in `stringConstants.py`. `.unused_ram` is always excluded (regardless of this flag)
* `--noprompt`
- * Exit and fail on user prompt. Normally this happens when some files or configurations are ambiguous. This is useful when running Emma on CI systems.
+ * Exit and fail on user prompt. Normally this happens when some files or configurations are ambiguous. This is useful when running Emma on CI systems.
+* `--memVis`
+ * This is a visualisation based on data you actually see in the map files (i.e. the data *before* the containment/duplicate/overlap resolution)
+ * Prompts for a start and end address (and x/y scaling) for which memory region a visualisation should be created (as `.svg`)
+ * This visualisation allows to better see complex overlaps/alignments of objects/sections (e.g. check your linker configuration, ...)
+ * Note that huge address ranges containing many objects/sections may cause your viewer to get slow/unresponsive due to the high amount of objects/sections; it is recommended to keep your viewing area small
+ * For huge `.svg`s the authors made good experiences with Inkscape and Google Chrome
+ * Usually you detect an interesting scenario in the `.csv` reports. It might be hard to see what is actually happening (e.g. many overlaps/containments, ...). That is where a visualisation is helpful
+ * If `--noPrompt` is active you will get a weak warning that no `.svg` reports will be generated
+
+* `--memVisResolved`
+ * Basically the same as `--memVis` but plots the *resolved* view (i.e. after Emma resolved the containment/duplicate overlap -> basically you will see what stands in `Objects_in_Sections`)
+ * Skipped if `--noResolveOverlap` is active
## Project Configuration
diff --git a/setup.py b/setup.py
index c7800e7..8a75ee4 100644
--- a/setup.py
+++ b/setup.py
@@ -63,7 +63,8 @@
"matplotlib",
"pandas",
"pypiscout>=2.0",
- "graphviz"
+ "graphviz",
+ "svgwrite"
],
extras_require={"dev": # Install dev version via `pip3 install pypiemma[dev]`
["gprof2dot",