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",