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 }