/* This file is part of JOP, the Java Optimized Processor see <http://www.jopdesign.com/> Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.wcet.report; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.code.ControlFlowGraph; import com.jopdesign.common.code.ControlFlowGraph.CFGNode; import com.jopdesign.common.config.Config.BadConfigurationException; import com.jopdesign.common.graphutils.InvokeDot; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.MiscUtils; import com.jopdesign.common.processormodel.JOPConfig; import com.jopdesign.timing.jop.WCETInstruction; import com.jopdesign.wcet.WCETTool; import org.apache.bcel.classfile.LineNumber; import org.apache.log4j.Logger; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.exception.ResourceNotFoundException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.TreeMap; /** * Analysis reports, using HTML framesets. * <p/> * TODO: This is an ad-hoc implementation. Design a good report concept. * TODO: html resources should be bundled in this package * * @author Benedikt Huber <benedikt.huber@gmail.com> */ public class Report { static final Logger logger = Logger.getLogger(WCETTool.LOG_WCET_REPORT+".Report"); private ReportConfig config; private InvokeDot dotInvoker = null; private WCETTool project; private HashMap<ClassInfo, ClassReport> classReports = new HashMap<ClassInfo, ClassReport>(); private HashMap<MethodInfo, List<DetailedMethodReport>> detailedReports = new HashMap<MethodInfo, List<DetailedMethodReport>>(); private HashMap<String, Object> stats = new HashMap<String, Object>(); private ReportEntry rootReportEntry = ReportEntry.rootReportEntry("summary.html"); private HashMap<File, File> dotJobs = new HashMap<File, File>(); public Report(WCETTool p, LogConfig logConfig) throws BadConfigurationException { this.project = p; this.config = new ReportConfig(p, logConfig); if (config.doInvokeDot()) { this.dotInvoker = new InvokeDot(InvokeDot.getDotBinary(p.getConfig()), config.getReportDir()); } } /** * Initialize the velocity engine * * @throws Exception thrown by the velocity engine on init */ public void initVelocity() throws Exception { Properties ps = new Properties(); ps.put("resource.loader", "class"); ps.put("class.resource.loader.description", "velocity: wcet class resource loader"); ps.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); String templatedir = config.getTemplatePath(); if (templatedir != null) { ps.put("resource.loader", "file, class"); ps.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); ps.put("file.resource.loader.path", templatedir); ps.put("file.resource.loader.cache", "true"); } Velocity.init(ps); } public void addStat(String key, Object val) { this.stats.put(key, val); } /** * add a HTML page, with the given name, order number and link * * @param name the name of the page * @param link the relative path to the page (e.g. <code>details/m1.html<code>) */ public void addPage(String name, String link) { this.rootReportEntry.addPage(name, link); } public void generateFile(String templateName, File outFile, Map<String, Object> ctxMap) throws Exception { generateFile(templateName, outFile, new VelocityContext(ctxMap)); } /** * Write the reports to disk * * @throws Exception */ public void writeReport() throws Exception { this.addPage("logs/error.log", config.getErrorLogFile().toString()); this.addPage("logs/info.log", config.getInfoLogFile().toString()); generateBytecodeTable(); generateIndex(); generateSummary(); generateTOC(); generateDOT(); } private void generateDOT() throws IOException { if (config.doInvokeDot() && dotInvoker != null) { for (Entry<File, File> dotJob : this.dotJobs.entrySet()) { dotInvoker.runDot(dotJob.getKey(), dotJob.getValue()); } } else { FileWriter fw = new FileWriter(config.getReportFile("Makefile")); fw.append("dot:\n"); for (Entry<File, File> dotJob : this.dotJobs.entrySet()) { fw.append("\tdot -Tpng -o " + dotJob.getValue().getName() + " " + dotJob.getKey().getName() + "\n"); } fw.close(); } } private void generateBytecodeTable() throws IOException { File file = config.getReportFile("Bytecode WCET Table.txt"); FileWriter fw = new FileWriter(file); // FIXME: generate proper timing table JOPConfig jopConfig = new JOPConfig(project.getConfig()); fw.append(new WCETInstruction(jopConfig.rws(), jopConfig.wws()).toWCAString()); fw.close(); this.addPage("input/bytecodetable", file.getName()); } private void generateIndex() throws Exception { generateFile("index.vm", config.getReportFile("index.html"), new VelocityContext()); } private void generateTOC() throws Exception { VelocityContext context = new VelocityContext(); context.put("tree", this.rootReportEntry); generateFile("toc.vm", config.getReportFile("toc.html"), context); } private void generateSummary() throws Exception { VelocityContext context = new VelocityContext(); context.put("errorlog", config.getErrorLogFile()); context.put("infolog", config.getInfoLogFile()); context.put("stats", stats); generateFile("summary.vm", config.getReportFile("summary.html"), context); } private void generateFile(String templateName, File outFile, VelocityContext ctx) throws Exception { Template template; try { template = Velocity.getTemplate(templateName); } catch (ResourceNotFoundException ignored) { template = Velocity.getTemplate("com/jopdesign/wcet/report/" + templateName); } FileWriter fw = new FileWriter(outFile); template.merge(ctx, fw); fw.close(); } /** * Dump the project's input (callgraph,cfgs) * * @throws IOException */ public void generateInfoPages() throws IOException { this.addStat("#classes", project.getCallGraph().getClassInfos().size()); this.addStat("#methods", project.getCallGraph().getReachableImplementationsSet(project.getTargetMethod()).size()); this.addStat("max call stack ", project.getCallGraph().getMaximalCallStack()); this.addStat("largest method size (in bytes)", project.getCallGraph().getLargestMethod().getNumberOfBytes()); this.addStat("largest method size (in words)", project.getCallGraph().getLargestMethod().getNumberOfWords()); this.addStat("total size of task (in bytes)", project.getCallGraph().getTotalSizeInBytes()); generateInputOverview(); this.addPage("details", null); for (MethodInfo m : project.getCallGraph().getReachableImplementationsSet(project.getTargetMethod())) { for (LineNumber ln : m.getCode().getLineNumberTable().getLineNumberTable()) { getClassReport(m.getClassInfo()).addLinePropertyIfNull(ln.getLineNumber(), "color", "lightgreen"); } if (logger.isDebugEnabled()) { logger.debug("Generating report for method: " + m); } ControlFlowGraph flowGraph = project.getFlowGraph(m); Map<String, Object> stats = new TreeMap<String, Object>(); stats.put("#nodes", flowGraph.vertexSet().size() - 2 /* entry+exit */); stats.put("number of words", flowGraph.getNumberOfWords()); this.addDetailedReport(m, new DetailedMethodReport(config, project, m, "CFG", stats, null, null), true); generateDetailedReport(m); } for (ClassInfo c : project.getCallGraph().getClassInfos()) { ClassReport cr = getClassReport(c); String page = pageOf(c); HashMap<String, Object> ctx = new HashMap<String, Object>(); ctx.put("classreport", cr); try { this.generateFile("class.vm", config.getReportFile(page), ctx); } catch (Exception e) { logger.error(e); } addPage("details/" + c.getClassName(), page); } } public ClassReport getClassReport(ClassInfo cli) { ClassReport cr = this.classReports.get(cli); if (cr == null) { try { cr = new ClassReport(cli, project.getSourceFile(cli)); } catch (FileNotFoundException e) { throw new AssertionError("Unexpected FileNotFoundException: " + e); } this.classReports.put(cli, cr); } return cr; } private void generateInputOverview() throws IOException { HashMap<String, Object> ctx = new HashMap<String, Object>(); File cgdot = config.getReportFile("callgraph.dot"); File cgimg = config.getReportFile("callgraph.png"); FileWriter fw = new FileWriter(cgdot); project.getCallGraph().exportDOT(fw); fw.close(); recordDot(cgdot, cgimg); ctx.put("callgraph", "callgraph.png"); List<MethodReport> mrv = new ArrayList<MethodReport>(); for (MethodInfo m : project.getCallGraph().getReachableImplementationsSet(project.getTargetMethod())) { mrv.add(new MethodReport(project, m, pageOf(m))); } ctx.put("methods", mrv); try { this.generateFile("input_overview.vm", config.getReportFile("input_overview.html"), ctx); } catch (Exception e) { logger.error(e); } this.addPage("input", "input_overview.html"); } void recordDot(File cgdot, File cgimg) { this.dotJobs.put(cgdot, cgimg); } private static String pageOf(ClassInfo ci) { return MiscUtils.sanitizeFileName(ci.getClassName()) + ".html"; } private static String pageOf(MethodInfo i) { return MiscUtils.sanitizeFileName(i.getFQMethodName()) + ".html"; } protected void addDetailedReport(MethodInfo m, DetailedMethodReport e, boolean prepend) { List<DetailedMethodReport> reports = this.detailedReports.get(m); if (reports == null) { reports = new LinkedList<DetailedMethodReport>(); this.detailedReports.put(m, reports); } if (prepend) reports.add(0, e); else reports.add(e); } public void addDetailedReport(MethodInfo m, String key, Map<String, Object> stats, Map<CFGNode, ?> nodeAnnots, Map<ControlFlowGraph.CFGEdge, ?> edgeAnnots) { DetailedMethodReport re = new DetailedMethodReport(config, project, m, key, stats, nodeAnnots, edgeAnnots); this.addDetailedReport(m, re, false); } private void generateDetailedReport(MethodInfo method) { String page = pageOf(method); HashMap<String, Object> ctx = new HashMap<String, Object>(); ctx.put("m", method); ctx.put("dfaresults", project.doDataflowAnalysis() ? project.getDfaTool().dumpDFA(method) : ""); ctx.put("reports", this.detailedReports.get(method)); for (DetailedMethodReport m : this.detailedReports.get(method)) { m.getGraph(); } try { this.generateFile("method.vm", config.getReportFile(page), ctx); } catch (Exception e) { logger.error(e); } this.addPage("details/" + method.getClassInfo().getClassName() + "/" + sanitizePageKey(method.getMemberID().getMethodSignature()), page); } /* page keys may not contain a slash - replace it by backslash */ private String sanitizePageKey(String s) { return s.replace('/', '\\'); } }