1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.test.util; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.OutputStreamWriter; 39 import java.io.Writer; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Scanner; 43 44 import org.openjdk.jmc.common.item.IItemCollection; 45 import org.openjdk.jmc.common.test.TestToolkit; 46 import org.openjdk.jmc.common.test.io.IOResource; 47 import org.openjdk.jmc.common.test.io.IOResourceSet; 48 import org.openjdk.jmc.common.util.StringToolkit; 49 import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException; 50 import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit; 51 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator; 52 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator.FrameCategorization; 53 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFormatToolkit; 54 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFrame; 55 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel; 56 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Branch; 57 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Fork; 58 59 /** 60 * Utility for presenting aggregates stack traces in textual form. 61 */ 62 // TODO: Add support for formatting different frame categorizations, sorting traces with root on top or bottom, and "distinguish on optimization" 63 @SuppressWarnings("nls") 64 public class StacktraceTestToolkit { 65 private static final String STACKTRACE_DIRECTORY = "stacktraces"; 66 private static final String STACKTRACE_INDEXFILE = "index.txt"; 67 private static final FrameSeparator methodFrameSeparator = new FrameSeparator(FrameCategorization.METHOD, false); 68 69 /** 70 * Return the files that can be used for comparing old printouts. 71 * 72 * @return the test file need for comparing files. 73 * @throws IOException 74 * if the files could not be located. 75 */ 76 public static IOResourceSet[] getTestResources() throws IOException { 77 IOResourceSet recordings = RecordingToolkit.getRecordings(); 78 IOResourceSet stacktraces = getStackTraceBaselines(); 79 if (recordings.getResources().size() != stacktraces.getResources().size()) { 80 throw new RuntimeException("The number of stacktraces baselines ( " + stacktraces.getResources().size() 81 + ") does not match the number of recording files (" + recordings.getResources().size()); 82 } 83 List<IOResourceSet> list = new ArrayList<>(); 84 for (IOResource recordingfile : recordings) { 85 IOResource stacktraceFile = stacktraces.findWithPrefix(recordingfile.getName()); 86 if (stacktraceFile == null) { 87 throw new RuntimeException("Could not find stacktrace baseline file for " + recordingfile); 88 } 89 list.add(new IOResourceSet(recordingfile, stacktraceFile)); 90 } 91 92 return list.toArray(new IOResourceSet[list.size()]); 93 } 94 95 /** 96 * Return the directory where the stacktrace baseline files reside. 97 * 98 * @return the printout file directory 99 * @throws IOException 100 * if the directory could not be found 101 */ 102 public static File getStacktracesDirectory() throws IOException { 103 return TestToolkit.getProjectDirectory(StacktraceTestToolkit.class, STACKTRACE_DIRECTORY); 104 } 105 106 private static IOResourceSet getStackTraceBaselines() throws IOException { 107 return TestToolkit.getResourcesInDirectory(StacktraceTestToolkit.class, STACKTRACE_DIRECTORY, 108 STACKTRACE_INDEXFILE); 109 } 110 111 /** 112 * Prints the aggregated stacktraces from of a recording to another file in text format. 113 * 114 * @param sourceFile 115 * the source recording file 116 * @param destinationFile 117 * the destination file for the printing. 118 */ 119 public static void printStacktraces(File sourceFile, File destinationFile) 120 throws IOException, CouldNotLoadRecordingException { 121 try (FileOutputStream output = new FileOutputStream(destinationFile); 122 Writer writer = new OutputStreamWriter(output, RecordingToolkit.RECORDING_TEXT_FILE_CHARSET)) { 123 IItemCollection events = JfrLoaderToolkit.loadEvents(sourceFile); 124 for (String e : getAggregatedStacktraceLines(events, methodFrameSeparator)) { 125 writer.append(e).append('\n'); 126 } 127 } 128 } 129 130 public static List<String> getStacktracesBaseline(IOResourceSet resourceSet) throws IOException, Exception { 131 return getLines(PrintoutsToolkit.stripHeader(StringToolkit.readString(resourceSet.getResource(1).open()))); 132 } 133 134 /** 135 * Get lines, including empty lines represented by empty string. 136 */ 137 public static List<String> getLines(String content) { 138 List<String> result = new ArrayList<>(); 139 Scanner scanner = new Scanner(content); 140 while (scanner.hasNextLine()) { 141 result.add(scanner.nextLine()); 142 } 143 scanner.close(); 144 return result; 145 } 146 147 /** 148 * Gets the aggregated stack traces from an item collection as a list of strings. 149 * 150 * @param items 151 * the item collection 152 * @return a list of strings representing the aggregated stack traces 153 */ 154 public static List<String> getAggregatedStacktraceLines(IItemCollection items) { 155 return getAggregatedStacktraceLines(items, methodFrameSeparator); 156 } 157 158 /** 159 * Gets the aggregated stack traces grouped on frameCategorization from an item collection as a 160 * list of strings. 161 * 162 * @param items 163 * the item collection 164 * @param frameCategorization 165 * the frame categorization 166 * @return a list of strings representing the aggregated stack traces 167 */ 168 private static List<String> getAggregatedStacktraceLines(IItemCollection items, FrameSeparator frameSeparator) { 169 return walkStacktraceTree(items, frameSeparator); 170 } 171 172 private static List<String> walkStacktraceTree(IItemCollection items, FrameSeparator frameSeparator) { 173 StacktraceModel sModel = new StacktraceModel(false, frameSeparator, items); 174 Fork root = sModel.getRootFork(); 175 List<String> traces = new ArrayList<>(); 176 return walkStacktraceTree(root, frameSeparator, "", traces); 177 } 178 179 private static List<String> walkStacktraceTree( 180 Fork fork, FrameSeparator frameSeparator, String indent, List<String> traces) { 181 for (Branch branch : fork.getBranches()) { 182 handleBranch(branch, frameSeparator, indent, traces); 183 Fork endFork = branch.getEndFork(); 184 walkStacktraceTree(endFork, frameSeparator, indent + " ", traces); 185 traces.add(""); 186 } 187 return traces; 188 } 189 190 private static void handleBranch(Branch branch, FrameSeparator frameSeparator, String indent, List<String> traces) { 191 StacktraceFrame sFrame = branch.getFirstFrame(); 192 traces.add(createFrameLine(frameSeparator, indent, sFrame)); 193 for (StacktraceFrame frame : branch.getTailFrames()) { 194 traces.add(createFrameLine(frameSeparator, indent, frame)); 195 } 196 } 197 198 private static String createFrameLine(FrameSeparator frameSeparator, String indent, StacktraceFrame sFrame) { 199 return indent + StacktraceFormatToolkit.formatFrame(sFrame.getFrame(), frameSeparator) + " (" 200 + sFrame.getItemCount() + ")"; 201 } 202 203 }