src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java
changeset 52850 f527b24990d7
child 57690 9316d02dd4a5
child 58863 c16ac7a2eba4
equal deleted inserted replaced
52849:eef755718cb2 52850:f527b24990d7
       
     1 /*
       
     2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.jfr.internal.tool;
       
    27 
       
    28 import java.io.BufferedInputStream;
       
    29 import java.io.DataInputStream;
       
    30 import java.io.File;
       
    31 import java.io.FileInputStream;
       
    32 import java.io.FileOutputStream;
       
    33 import java.io.IOException;
       
    34 import java.io.InputStream;
       
    35 import java.io.PrintStream;
       
    36 import java.nio.file.Files;
       
    37 import java.nio.file.InvalidPathException;
       
    38 import java.nio.file.Path;
       
    39 import java.util.ArrayList;
       
    40 import java.util.Deque;
       
    41 import java.util.List;
       
    42 
       
    43 import jdk.jfr.internal.consumer.ChunkHeader;
       
    44 import jdk.jfr.internal.consumer.RecordingInput;
       
    45 
       
    46 final class Disassemble extends Command {
       
    47 
       
    48     @Override
       
    49     public String getName() {
       
    50         return "disassemble";
       
    51     }
       
    52 
       
    53     @Override
       
    54     public List<String> getOptionSyntax() {
       
    55         List<String> list = new ArrayList<>();
       
    56         list.add("[--output <directory>]");
       
    57         list.add("[--max-chunks <chunks>]");
       
    58         list.add("[--max-size <size>]");
       
    59         list.add("<file>");
       
    60         return list;
       
    61     }
       
    62 
       
    63     @Override
       
    64     public void displayOptionUsage(PrintStream stream) {
       
    65         stream.println(" --output <directory>    The location to write the disassembled file,");
       
    66         stream.println("                         by default the current directory");
       
    67         stream.println("");
       
    68         stream.println(" --max-chunks <chunks>   Maximum number of chunks per disassembled file,");
       
    69         stream.println("                         by default 5. The chunk size varies, but is ");
       
    70         stream.println("                         typically around 15 MB.");
       
    71         stream.println("");
       
    72         stream.println(" --max-size <size>       Maximum number of bytes per file.");
       
    73         stream.println("");
       
    74         stream.println("  <file>                 Location of the recording file (.jfr)");
       
    75     }
       
    76 
       
    77     @Override
       
    78     public String getDescription() {
       
    79         return "Disassamble a recording file into smaller files/chunks";
       
    80     }
       
    81 
       
    82     @Override
       
    83     public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
       
    84         if (options.isEmpty()) {
       
    85             throw new UserSyntaxException("missing file");
       
    86         }
       
    87         Path file = getJFRInputFile(options);
       
    88         int maxChunks = Integer.MAX_VALUE;
       
    89         int maxsize = Integer.MAX_VALUE;
       
    90         String output = System.getProperty("user.dir");
       
    91         int optionCount = options.size();
       
    92         while (optionCount > 0) {
       
    93             if (acceptOption(options, "--output")) {
       
    94                 output = options.pop();
       
    95             }
       
    96             if (acceptOption(options, "--max-size")) {
       
    97                 String value = options.pop();
       
    98                 try {
       
    99                     maxsize = Integer.parseInt(value);
       
   100                     if (maxsize < 1) {
       
   101                         throw new UserDataException("max size must be at least 1");
       
   102                     }
       
   103                 } catch (NumberFormatException nfe) {
       
   104                     throw new UserDataException("not a valid value for --max-size.");
       
   105                 }
       
   106             }
       
   107             if (acceptOption(options, "--max-chunks")) {
       
   108                 String value = options.pop();
       
   109                 try {
       
   110                     maxChunks = Integer.parseInt(value);
       
   111                     if (maxChunks < 1) {
       
   112                         throw new UserDataException("max chunks must be at least 1.");
       
   113                     }
       
   114                 } catch (NumberFormatException nfe) {
       
   115                     throw new UserDataException("not a valid value for --max-size.");
       
   116                 }
       
   117             }
       
   118             if (optionCount == options.size()) {
       
   119                 // No progress made
       
   120                 throw new UserSyntaxException("unknown option " + options.peek());
       
   121             }
       
   122             optionCount = options.size();
       
   123         }
       
   124         Path outputPath = getDirectory(output);
       
   125 
       
   126         println();
       
   127         println("Examining recording " + file + " ...");
       
   128         List<Long> sizes;
       
   129         if (maxsize != Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
       
   130             try {
       
   131                 long fileSize = Files.size(file);
       
   132                 if (maxsize >=fileSize) {
       
   133                     println();
       
   134                     println("File size (" + fileSize +") does not exceed max size (" + maxsize + ")");
       
   135                     return;
       
   136                 }
       
   137             } catch (IOException e) {
       
   138                 throw new UserDataException("unexpected i/o error when determining file size" + e.getMessage());
       
   139             }
       
   140         }
       
   141         if (maxsize == Integer.MAX_VALUE && maxChunks == Integer.MAX_VALUE) {
       
   142             maxChunks = 5;
       
   143         }
       
   144 
       
   145         try {
       
   146             sizes = findChunkSizes(file);
       
   147         } catch (IOException e) {
       
   148             throw new UserDataException("unexpected i/o error. " + e.getMessage());
       
   149         }
       
   150         if (maxsize == Integer.MAX_VALUE == sizes.size() <= maxChunks) {
       
   151             throw new UserDataException("number of chunks in recording (" + sizes.size() + ") doesn't exceed max chunks (" + maxChunks + ")");
       
   152         }
       
   153         println();
       
   154         if (sizes.size() > 0) {
       
   155             List<Long> combinedSizes = combineChunkSizes(sizes, maxChunks, maxsize);
       
   156             print("File consists of " + sizes.size() + " chunks. The recording will be split into ");
       
   157             println(combinedSizes.size() + " files");
       
   158             println();
       
   159             splitFile(outputPath, file, combinedSizes);
       
   160         } else {
       
   161             throw new UserDataException("no JFR chunks found in file.");
       
   162         }
       
   163     }
       
   164 
       
   165     private List<Long> findChunkSizes(Path p) throws IOException {
       
   166         try (RecordingInput input = new RecordingInput(p.toFile())) {
       
   167             List<Long> sizes = new ArrayList<>();
       
   168             ChunkHeader ch = new ChunkHeader(input);
       
   169             sizes.add(ch.getSize());
       
   170             while (!ch.isLastChunk()) {
       
   171                 ch = ch.nextHeader();
       
   172                 sizes.add(ch.getSize());
       
   173             }
       
   174             return sizes;
       
   175         }
       
   176     }
       
   177 
       
   178     private List<Long> combineChunkSizes(List<Long> sizes, int maxChunks, long maxSize) {
       
   179         List<Long> reduced = new ArrayList<Long>();
       
   180         int chunks = 1;
       
   181         long fileSize = sizes.get(0);
       
   182         for (int i = 1; i < sizes.size(); i++) {
       
   183             long size = sizes.get(i);
       
   184             if (fileSize + size > maxSize) {
       
   185                 reduced.add(fileSize);
       
   186                 chunks = 1;
       
   187                 fileSize = size;
       
   188                 continue;
       
   189             }
       
   190             fileSize += size;
       
   191             if (chunks == maxChunks) {
       
   192                 reduced.add(fileSize);
       
   193                 fileSize = 0;
       
   194                 chunks = 1;
       
   195                 continue;
       
   196             }
       
   197             chunks++;
       
   198         }
       
   199         if (fileSize != 0) {
       
   200             reduced.add(fileSize);
       
   201         }
       
   202         return reduced;
       
   203     }
       
   204 
       
   205     private void splitFile(Path directory, Path file, List<Long> splitPositions) throws UserDataException {
       
   206         int padAmountZeros = String.valueOf(splitPositions.size() - 1).length();
       
   207         String fileName = file.getFileName().toString();
       
   208         String fileFormatter = fileName.subSequence(0, fileName.length() - 4) + "_%0" + padAmountZeros + "d.jfr";
       
   209         for (int i = 0; i < splitPositions.size(); i++) {
       
   210             String formattedFilename = String.format(fileFormatter, i);
       
   211             try {
       
   212                 Path p = directory.resolve(formattedFilename);
       
   213                 if (Files.exists(p)) {
       
   214                     throw new UserDataException("can't create disassembled file " + p + ", a file with that name already exist");
       
   215                 }
       
   216             } catch (InvalidPathException ipe) {
       
   217                 throw new UserDataException("can't construct path with filename" + formattedFilename);
       
   218             }
       
   219         }
       
   220 
       
   221         try (DataInputStream stream = new DataInputStream(new BufferedInputStream(new FileInputStream(file.toFile())))) {
       
   222             for (int i = 0; i < splitPositions.size(); i++) {
       
   223                 Long l = splitPositions.get(i);
       
   224                 byte[] bytes = readBytes(stream, l.intValue());
       
   225                 String formattedFilename = String.format(fileFormatter, i);
       
   226                 Path p = directory.resolve(formattedFilename);
       
   227                 File splittedFile = p.toFile();
       
   228                 println("Writing " + splittedFile + " ... " + bytes.length);
       
   229                 FileOutputStream fos = new FileOutputStream(splittedFile);
       
   230                 fos.write(bytes);
       
   231                 fos.close();
       
   232             }
       
   233         } catch (IOException ioe) {
       
   234             throw new UserDataException("i/o error writing file " + file);
       
   235         }
       
   236     }
       
   237 
       
   238     private byte[] readBytes(InputStream stream, int count) throws UserDataException, IOException {
       
   239         byte[] data = new byte[count];
       
   240         int totalRead = 0;
       
   241         while (totalRead < data.length) {
       
   242             int read = stream.read(data, totalRead, data.length - totalRead);
       
   243             if (read == -1) {
       
   244                 throw new UserDataException("unexpected end of data");
       
   245             }
       
   246             totalRead += read;
       
   247         }
       
   248         return data;
       
   249     }
       
   250 }