| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.utils; |
| |
| // Helper for collecting timing information during execution. |
| // Timing t = new Timing("R8"); |
| // A timing tree is collected by calling the following pair (nesting will create the tree): |
| // t.begin("My task); |
| // try { ... } finally { t.end(); } |
| // or alternatively: |
| // t.scope("My task", () -> { ... }); |
| // Finally a report is printed by: |
| // t.report(); |
| |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Stack; |
| |
| public class Timing { |
| |
| private final Stack<Node> stack; |
| private final boolean trackMemory; |
| |
| public Timing() { |
| this("<no title>"); |
| } |
| |
| public Timing(String title) { |
| this(title, false); |
| } |
| |
| public Timing(String title, boolean trackMemory) { |
| this.trackMemory = trackMemory; |
| stack = new Stack<>(); |
| stack.push(new Node("Recorded timings for " + title)); |
| } |
| |
| private static class MemInfo { |
| final long used; |
| |
| MemInfo(long used) { |
| this.used = used; |
| } |
| |
| public static MemInfo fromTotalAndFree(long total, long free) { |
| return new MemInfo(total - free); |
| } |
| |
| long usedDelta(MemInfo previous) { |
| return used - previous.used; |
| } |
| } |
| |
| class Node { |
| final String title; |
| |
| final Map<String, Node> children = new LinkedHashMap<>(); |
| long duration = 0; |
| long start_time; |
| Map<String, MemInfo> startMemory; |
| Map<String, MemInfo> endMemory; |
| |
| Node(String title) { |
| this.title = title; |
| if (trackMemory) { |
| startMemory = computeMemoryInformation(); |
| } |
| this.start_time = System.nanoTime(); |
| } |
| |
| void restart() { |
| assert start_time == -1; |
| if (trackMemory) { |
| startMemory = computeMemoryInformation(); |
| } |
| start_time = System.nanoTime(); |
| } |
| |
| void end() { |
| duration += System.nanoTime() - start_time; |
| start_time = -1; |
| assert duration() >= 0; |
| if (trackMemory) { |
| endMemory = computeMemoryInformation(); |
| } |
| } |
| |
| long duration() { |
| return duration; |
| } |
| |
| @Override |
| public String toString() { |
| return title + ": " + prettyTime(duration()); |
| } |
| |
| public String toString(Node top) { |
| if (this == top) return toString(); |
| return toString() + " (" + prettyPercentage(duration(), top.duration()) + ")"; |
| } |
| |
| public void report(int depth, Node top) { |
| assert duration() >= 0; |
| if (depth > 0) { |
| for (int i = 0; i < depth; i++) { |
| System.out.print(" "); |
| } |
| System.out.print("- "); |
| } |
| System.out.println(toString(top)); |
| if (trackMemory) { |
| printMemory(depth); |
| } |
| children.values().forEach(p -> p.report(depth + 1, top)); |
| } |
| |
| private void printMemory(int depth) { |
| for (Entry<String, MemInfo> start : startMemory.entrySet()) { |
| if (start.getKey().equals("Memory")) { |
| for (int i = 0; i <= depth; i++) { |
| System.out.print(" "); |
| } |
| MemInfo endValue = endMemory.get(start.getKey()); |
| MemInfo startValue = start.getValue(); |
| System.out.println( |
| start.getKey() |
| + " start: " |
| + prettySize(startValue.used) |
| + ", end: " |
| + prettySize(endValue.used) |
| + ", delta: " |
| + prettySize(endValue.usedDelta(startValue))); |
| } |
| } |
| } |
| } |
| |
| private static String prettyPercentage(long part, long total) { |
| return (part * 100 / total) + "%"; |
| } |
| |
| private static String prettyTime(long value) { |
| return (value / 1000000) + "ms"; |
| } |
| |
| private static String prettySize(long value) { |
| return prettyNumber(value / 1024) + "k"; |
| } |
| |
| private static String prettyNumber(long value) { |
| String printed = "" + Math.abs(value); |
| if (printed.length() < 4) { |
| return "" + value; |
| } |
| StringBuilder builder = new StringBuilder(); |
| if (value < 0) { |
| builder.append('-'); |
| } |
| int prefix = printed.length() % 3; |
| builder.append(printed, 0, prefix); |
| for (int i = prefix; i < printed.length(); i += 3) { |
| if (i > 0) { |
| builder.append('.'); |
| } |
| builder.append(printed, i, i + 3); |
| } |
| return builder.toString(); |
| } |
| |
| public void begin(String title) { |
| Node parent = stack.peek(); |
| Node child; |
| if (parent.children.containsKey(title)) { |
| child = parent.children.get(title); |
| child.restart(); |
| } else { |
| child = new Node(title); |
| parent.children.put(title, child); |
| } |
| stack.push(child); |
| } |
| |
| public void end() { |
| stack.peek().end(); // record time. |
| stack.pop(); |
| } |
| |
| public void report() { |
| Node top = stack.peek(); |
| top.end(); |
| System.out.println(); |
| top.report(0, top); |
| } |
| |
| public void scope(String title, TimingScope fn) { |
| begin(title); |
| try { |
| fn.apply(); |
| } finally { |
| end(); |
| } |
| } |
| |
| public interface TimingScope { |
| void apply(); |
| } |
| |
| private Map<String, MemInfo> computeMemoryInformation() { |
| System.gc(); |
| Map<String, MemInfo> info = new LinkedHashMap<>(); |
| info.put( |
| "Memory", |
| MemInfo.fromTotalAndFree( |
| Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory())); |
| return info; |
| } |
| } |