if (i >= len(self._modules)):
raise InternalError()
+ moduleFinalLoadLevel = [0] * len(self._modules)
+ moduleMethodCount = [0] * len(self._modules)
+
+ for info in self._moduleOrMethodInfo:
+ moduleIndex = info.getModuleIndex()
+
+ if (info.isMethodInfo()):
+ indexes = info.getAllRefModuleIndexes()
+ for i in indexes:
+ moduleMethodCount[i] += 1
+ else:
+ moduleFinalLoadLevel[moduleIndex] = info.getModuleLoadLevel()
+
+ for index, module in enumerate(self._modules):
+ if (moduleFinalLoadLevel[index] != module.getModuleRecord().getLoadLevel()):
+ raise InternalError()
+ if (moduleMethodCount[index] != module.getModuleRecord().getJitMethodCount()):
+ raise InternalError()
+
# Print raw header
def printRawHeader(self, offsetStr=""):
if (offsetStr is None or not issubclass(type(offsetStr), str)):
return bytesArr
+ # Remove requested number of methods from tail of profile
+ def truncateTail(self, numMethodsToRemove):
+ if (numMethodsToRemove is None or not issubclass(type(numMethodsToRemove), int)):
+ raise InternalError()
+
+ methodCount = self._header.getMethodCount()
+
+ if (numMethodsToRemove >= methodCount or numMethodsToRemove <= 0):
+ raise InternalError()
+
+ removedCountMethods = 0
+ removedCountModuleDeps = 0
+
+ # I. ==== Remove all infos that are met, both method and module ====
+
+ # Module infos are used by further methods either explicitly or implicitly,
+ # so they are not needed if there's nothing after them
+
+ for index in range(len(self._moduleOrMethodInfo) - 1, -1, -1):
+ info = self._moduleOrMethodInfo[index]
+
+ if (info.isMethodInfo()):
+ if (removedCountMethods == numMethodsToRemove):
+ break
+
+ removedCountMethods += 1
+
+ indexes = info.getAllRefModuleIndexes()
+ for i in indexes:
+ moduleRecord = self._modules[i].getModuleRecord()
+ count = moduleRecord.getJitMethodCount()
+ moduleRecord.setJitMethodCount(count - 1)
+ else:
+ removedCountModuleDeps += 1
+
+ self._moduleOrMethodInfo.pop()
+
+ # II. ==== Remove modules and update final load level ====
+
+ # Module can be removed if there's no module dependency for such module
+
+ # Fill list of final load level for modules
+ finalLoadLevelMap = [0] * len(self._modules)
+
+ for info in self._moduleOrMethodInfo:
+ if (info.isModuleInfo()):
+ finalLoadLevelMap[info.getModuleIndex()] = info.getModuleLoadLevel()
+
+ # All removed should be at the end of list, find first index
+ index = 0
+ while (index < len(self._modules)):
+ if (finalLoadLevelMap[index] == 0):
+ break
+ self._modules[index].getModuleRecord().setLoadLevel(finalLoadLevelMap[index])
+ index += 1
+
+ indexModuleFirstToRemove = index
+
+ # There should be no mixing of used and not used modules, first part of list should be non 0, last part - 0
+ # (there's no way that first module dependency for module with higher index was added before
+ # first module dependency for module with lower index)
+ while (index < len(self._modules)):
+ if (finalLoadLevelMap[index] != 0):
+ raise InternalError()
+ if (self._modules[index].getModuleRecord().getJitMethodCount() != 0):
+ raise InternalError()
+ index += 1
+
+ del self._modules[indexModuleFirstToRemove:]
+
+ # III. ==== Update header ====
+
+ # update stats in headers
+ self._header.setMethodCount(methodCount - removedCountMethods)
+ moduleDepCount = self._header.getModuleDepCount()
+ self._header.setModuleCount(len(self._modules))
+ self._header.setModuleDepCount(moduleDepCount - removedCountModuleDeps)
+ # new profile was not used yet, so drop usage stats
+ self._header.dropGlobalUsageStats()
+
+ self.verify()
+
# Split mcj profile in two: app-dependent (app) and app-independent (system)
def split(self, systemModules): # pylint: disable=too-many-locals,too-many-statements,too-many-branches
if (systemModules is None or not issubclass(type(systemModules), list)):
newinfoToAdd.updateModuleIndex(moduleIndexMap)
self._moduleOrMethodInfo.append(newinfoToAdd)
- # IV. ==== Set stats ====
+ # V. ==== Set stats ====
methodCount = 0
moduleDepCount = 0
def __init__(self, args):
self._args = args
+ # Remove requested number of methods from tail of profile
+ def commandTruncate(self):
+ for filepath in self._args.input:
+ outFilepath = filepath + ".truncated"
+
+ mcjProfile = MCJProfile.readFromFile(filepath)
+ numMethods = mcjProfile.getHeader().getMethodCount()
+
+ percentMethodsToRemove = 0
+ numMethodsToRemove = 0
+
+ if ((self._args.percent is None) == (self._args.count is None)): # pylint: disable=no-else-raise
+ raise MessageError("either '--percent <N>' or '--count <N>' option should be passed")
+ elif (not self._args.percent is None):
+ percentMethodsToRemove = self._args.percent
+ numMethodsToRemove = int(numMethods * percentMethodsToRemove / 100)
+ elif (not self._args.count is None):
+ numMethodsToRemove = self._args.count
+ percentMethodsToRemove = int((numMethodsToRemove / numMethods) * 100)
+ else:
+ raise InternalError()
+
+ mcjProfile.truncateTail(numMethodsToRemove)
+ MCJProfile.writeToFile(outFilepath, mcjProfile)
+
+ print("MCJ profile " + filepath + " was truncated by " + str(percentMethodsToRemove)
+ + "% (" + str(numMethodsToRemove) + " methods), output: " + outFilepath)
+ print("")
+
# Split mcj profiles in two: app-dependent (app) and app-independent (system)
def commandSplit(self):
systemModulesFile = open(self._args.system_modules_list, "r")
splitSysFirstApp, splitSysFirstSys = mergedSysFirst.split(systemModules)
if (depth > depthCheck
- and (splitAppFirstApp != prevSplitAppFirstApp or splitAppFirstSys != prevSplitAppFirstSys
- or splitSysFirstApp != prevSplitSysFirstApp or splitSysFirstSys != prevSplitSysFirstSys)):
+ and (splitAppFirstApp != prevSplitAppFirstApp
+ or splitAppFirstSys != prevSplitAppFirstSys
+ or splitSysFirstApp != prevSplitSysFirstApp
+ or splitSysFirstSys != prevSplitSysFirstSys)):
isCorrect = False
break
print("Split-merge self test passed for " + filepath)
else:
print("Split-merge self test failed for " + filepath)
+ elif (self._args.tm):
+ # Truncate mcj profile, merge truncated profile with original one, result should match original one
+ # Repeat this for different truncation percent
+ for filepath in self._args.input:
+ isCorrect = True
+
+ mcjProfile = MCJProfile.readFromFile(filepath)
+ numMethods = mcjProfile.getHeader().getMethodCount()
+
+ for percent in range(30,91,30):
+ mcjProfile2 = mcjProfile.copy()
+
+ numMethodsToRemove = int(numMethods * percent / 100)
+
+ mcjProfile2.truncateTail(numMethodsToRemove)
+ mcjProfile2.merge(mcjProfile)
+
+ if (mcjProfile2 != mcjProfile):
+ isCorrect = False
+ break
+
+ if (isCorrect):
+ print("Truncate-merge self test passed for " + filepath)
+ else:
+ print("Truncate-merge self test failed for " + filepath)
elif (self._args.unit):
# TODO: add unit tests
pass
def main():
parser = argparse.ArgumentParser()
- commands = "split, merge, verify, find, compare, clean-stats, print, help, self-test"
+ commands = "truncate, split, merge, verify, find, compare, clean-stats, print, help, self-test"
parser.add_argument("command", help="Command to execute: " + commands)
# Overall options
parser.add_argument("-i", "--input", help="Input mcj profiles", action="append")
+ # Truncate options
+ parser.add_argument("--percent", help="Percent of methods to remove from tail of profile", type=int)
+ parser.add_argument("--count", help="Number of methods to remove from tail of profile", type=int)
+
# Split options
parser.add_argument("--system-modules-list", help="[split], file with app-independent (i.e. system) module names")
parser.add_argument("--rw-sha256",
help="[self-test], perform read-write self-test using sha256sum", action="store_true")
parser.add_argument("--sm", help="[self-test], perform split-merge self-test", action="store_true")
+ parser.add_argument("--tm", help="[self-test], perform truncate-merge self-test", action="store_true")
parser.add_argument("--unit", help="TODO, [self-test], perform unit testing self-test", action="store_true")
args = parser.parse_args()
cli = CLI(args)
- if (args.command == "split"):
+ if (args.command == "truncate"):
+ cli.commandTruncate()
+ elif (args.command == "split"):
cli.commandSplit()
elif (args.command == "merge"):
cli.commandMerge()