1 /*
2  * Copyright (c) 2016, 2020, 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.IOException;
29 import java.io.PrintStream;
30 import java.nio.file.Path;
31 import java.time.Instant;
32 import java.time.ZoneOffset;
33 import java.time.format.DateTimeFormatter;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Deque;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Locale;
40 
41 import jdk.jfr.EventType;
42 import jdk.jfr.internal.MetadataDescriptor;
43 import jdk.jfr.internal.Type;
44 import jdk.jfr.internal.consumer.ChunkHeader;
45 import jdk.jfr.internal.consumer.FileAccess;
46 import jdk.jfr.internal.consumer.RecordingInput;
47 
48 final class Summary extends Command {
49     private final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.UK).withZone(ZoneOffset.UTC);
50 
51     @Override
getName()52     public String getName() {
53         return "summary";
54     }
55 
56     private static class Statistics {
Statistics(String name)57         Statistics(String name) {
58             this.name = name;
59         }
60         String name;
61         long count;
62         long size;
63     }
64 
65     @Override
getOptionSyntax()66     public List<String> getOptionSyntax() {
67         return Collections.singletonList("<file>");
68     }
69 
70     @Override
displayOptionUsage(PrintStream stream)71     public void displayOptionUsage(PrintStream stream) {
72         stream.println("  <file>   Location of the recording file (.jfr) to display information about");
73     }
74 
75     @Override
getDescription()76     public String getDescription() {
77         return "Display general information about a recording file (.jfr)";
78     }
79 
80     @Override
execute(Deque<String> options)81     public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
82         ensureMaxArgumentCount(options, 1);
83         Path p = getJFRInputFile(options);
84         try {
85             printInformation(p);
86         } catch (IOException e) {
87             couldNotReadError(p, e);
88         }
89     }
90 
printInformation(Path p)91     private void printInformation(Path p) throws IOException {
92         long totalDuration = 0;
93         long chunks = 0;
94 
95         try (RecordingInput input = new RecordingInput(p.toFile(), FileAccess.UNPRIVILEGED)) {
96             ChunkHeader first = new ChunkHeader(input);
97             ChunkHeader ch = first;
98             String eventPrefix = Type.EVENT_NAME_PREFIX;
99             if (first.getMajor() == 1) {
100                 eventPrefix = "com.oracle.jdk.";
101             }
102             HashMap<Long, Statistics> stats = new HashMap<>();
103             stats.put(0L, new Statistics(eventPrefix + "Metadata"));
104             stats.put(1L, new Statistics(eventPrefix + "CheckPoint"));
105             int minWidth = 0;
106             while (true) {
107                 long chunkEnd = ch.getEnd();
108                 MetadataDescriptor md = ch.readMetadata();
109 
110                 for (EventType eventType : md.getEventTypes()) {
111                     stats.computeIfAbsent(eventType.getId(), (e) -> new Statistics(eventType.getName()));
112                     minWidth = Math.max(minWidth, eventType.getName().length());
113                 }
114 
115                 totalDuration += ch.getDurationNanos();
116                 chunks++;
117                 input.position(ch.getEventStart());
118                 while (input.position() < chunkEnd) {
119                     long pos = input.position();
120                     int size = input.readInt();
121                     long eventTypeId = input.readLong();
122                     Statistics s = stats.get(eventTypeId);
123                     if (s != null) {
124                         s.count++;
125                         s.size += size;
126                     }
127                     input.position(pos + size);
128                 }
129                 if (ch.isLastChunk()) {
130                     break;
131                 }
132                 ch = ch.nextHeader();
133             }
134             println();
135             long epochSeconds = first.getStartNanos() / 1_000_000_000L;
136             long adjustNanos = first.getStartNanos() - epochSeconds * 1_000_000_000L;
137             println(" Version: " + first.getMajor() + "." + first.getMinor());
138             println(" Chunks: " + chunks);
139             println(" Start: " + DATE_FORMAT.format(Instant.ofEpochSecond(epochSeconds, adjustNanos)) + " (UTC)");
140             println(" Duration: " + (totalDuration + 500_000_000) / 1_000_000_000 + " s");
141 
142             List<Statistics> statsList = new ArrayList<>(stats.values());
143             Collections.sort(statsList, (u, v) -> Long.compare(v.count, u.count));
144             println();
145             String header = "      Count  Size (bytes) ";
146             String typeHeader = " Event Type";
147             minWidth = Math.max(minWidth, typeHeader.length());
148             println(typeHeader + pad(minWidth - typeHeader.length(), ' ') + header);
149             println(pad(minWidth + header.length(), '='));
150             for (Statistics s : statsList) {
151                 System.out.printf(" %-" + minWidth + "s%10d  %12d\n", s.name, s.count, s.size);
152             }
153         }
154     }
155 
pad(int count, char c)156     private String pad(int count, char c) {
157         StringBuilder sb = new StringBuilder();
158         for (int i = 0; i < count; i++) {
159             sb.append(c);
160         }
161         return sb.toString();
162     }
163 }
164