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.management.jfr;
27 
28 import java.nio.file.Path;
29 import java.time.Duration;
30 import java.time.Instant;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 
35 import javax.management.openmbean.CompositeData;
36 import javax.management.openmbean.TabularData;
37 
38 import jdk.jfr.Recording;
39 import jdk.jfr.RecordingState;
40 
41 /**
42  * Management representation of a {@code Recording}.
43  *
44  * @see Recording
45  *
46  * @since 9
47  */
48 public final class RecordingInfo {
49     private final long id;
50     private final String name;
51     private final String state;
52     private final boolean dumpOnExit;
53     private final long size;
54     private final boolean disk;
55     private final long maxAge;
56     private final long maxSize;
57     private final long startTime;
58     private final long stopTime;
59     private final String destination;
60     private final long durationInSeconds;
61     private final Map<String, String> settings;
62 
63     // package private
RecordingInfo(Recording recording)64     RecordingInfo(Recording recording) {
65         id = recording.getId();
66         name = recording.getName();
67         state = recording.getState().toString();
68         dumpOnExit = recording.getDumpOnExit();
69         size = recording.getSize();
70         disk = recording.isToDisk();
71 
72         Duration d = recording.getMaxAge();
73         if (d == null) {
74             maxAge = 0;
75         } else {
76             maxAge = d.getSeconds();
77         }
78         maxSize = recording.getMaxSize();
79         Instant s = recording.getStartTime();
80         startTime = s == null ? 0L : s.toEpochMilli();
81         Instant st = recording.getStopTime();
82         stopTime = st == null ? 0L : st.toEpochMilli();
83         Path p = recording.getDestination();
84         destination = p == null ? null : p.toString();
85         Duration duration = recording.getDuration();
86         durationInSeconds = duration == null ? 0 : duration.getSeconds();
87         settings = recording.getSettings();
88     }
89 
RecordingInfo(CompositeData cd)90     private RecordingInfo(CompositeData cd) {
91         id = (int) cd.get("id");
92         name = (String) cd.get("name");
93         state = (String) cd.get("state");
94         dumpOnExit = (boolean) cd.get("dumpOnExit");
95         size = (long) cd.get("size");
96         disk = (boolean) cd.get("disk");
97         maxAge = (Long) cd.get("maxAge");
98         maxSize = (Long) cd.get("maxSize");
99         startTime = (Long) cd.get("startTime");
100         stopTime = (Long) cd.get("stopTime");
101         destination = (String) cd.get("destination");
102         durationInSeconds = (long) cd.get("duration");
103         settings = new LinkedHashMap<>();
104         Object map = cd.get("settings");
105         if (map instanceof TabularData) {
106             TabularData td = (TabularData) map;
107             List<String> keyNames = td.getTabularType().getIndexNames();
108             int size = keyNames.size();
109             for (Object keys : td.keySet()) {
110                 Object[] keyValues = ((List<?>) keys).toArray();
111                 for (int i = 0; i < size; i++) {
112                     String key = keyNames.get(i);
113                     Object value = keyValues[i];
114                     if (value instanceof String) {
115                         settings.put(key, (String) value);
116                     }
117                 }
118             }
119         }
120     }
121 
122     /**
123      * Returns the name of the recording associated with this
124      * {@code RecordingInfo}.
125      *
126      * @return the recording name, not {@code null}
127      *
128      * @see Recording#getName()
129      */
getName()130     public String getName() {
131         return name;
132     }
133 
134     /**
135      * Returns the unique ID for the recording associated with this
136      * {@code RecordingInfo}.
137      *
138      * @return the recording ID
139      *
140      * @see Recording#getId()
141      */
getId()142     public long getId() {
143         return id;
144     }
145 
146     /**
147      * Returns if the recording associated with this {@code RecordingInfo}
148      * should be dumped to file when the JVM exits.
149      *
150      * @return {@code true} if recording should be dumped on exit, {@code false}
151      *         otherwise
152      *
153      * @see Recording#getDumpOnExit()
154      */
getDumpOnExit()155     public boolean getDumpOnExit() {
156         return dumpOnExit;
157     }
158 
159     /**
160      * Returns how many seconds data should be kept on disk, or {@code 0} if
161      * data is to be kept forever.
162      * <p>
163      * In-memory recordings are not affected by maximum age.
164      *
165      * @see Recording#getMaxAge()
166      * @see Recording#setToDisk(boolean)
167      * @return how long data should be kept on disk, measured in seconds
168      *
169      */
getMaxAge()170     public long getMaxAge() {
171         return maxAge;
172     }
173 
174     /**
175      * Returns the amount of data, measured in bytes, the recording associated
176      * with this {@code RecordingInfo}, should be kept on disk, before it's
177      * rotated away, or {@code 0} if data is to be kept indefinitely.
178      * <p>
179      * In-memory recordings are not affected by maximum size.
180      *
181      * @return the amount of data should be kept on disk, in bytes
182      *
183      * @see Recording#setToDisk(boolean)
184      * @see Recording#getMaxSize()
185      */
getMaxSize()186     public long getMaxSize() {
187         return maxSize;
188     }
189 
190     /**
191      * Returns a {@code String} representation of state of the recording
192      * associated with this {@code RecordingInfo}.
193      * <p>
194      * Valid return values are {@code "NEW"}, {@code "DELAYED"}, {@code "STARTING"},
195      * {@code "RUNNING"}, {@code "STOPPING"}, {@code "STOPPED"} and {@code "CLOSED"}.
196      *
197      * @return the recording state, not {@code null}
198      *
199      * @see RecordingState#toString()
200      * @see Recording#getState()
201      */
getState()202     public String getState() {
203         return state;
204     }
205 
206     /**
207      * Returns start time of the recording associated with this
208      * {@code RecordingInfo}, measured as ms since epoch, or {@code null} if the
209      * recording hasn't started.
210      *
211      * @return the start time of the recording, or {@code null} if the recording
212      *         hasn't started
213      *
214      * @see Recording#getStartTime()
215      */
getStartTime()216     public long getStartTime() {
217         return startTime;
218     }
219 
220     /**
221      * Returns the actual or expected stop time of the recording associated with
222      * this {@code RecordingInfo}, measured as ms since epoch, or {@code null}
223      * if the expected or actual stop time is not known, which can only happen
224      * if the recording has not yet been stopped.
225      *
226      * @return the stop time of recording, or {@code null} if recording hasn't
227      *         been stopped.
228      *
229      * @see Recording#getStopTime()
230      */
getStopTime()231     public long getStopTime() {
232         return stopTime;
233     }
234 
235     /**
236      * Returns the settings for the recording associated with this
237      * {@code RecordingInfo}.
238      *
239      * @return the recording settings, not {@code null}
240      *
241      * @see Recording#getSettings()
242      */
getSettings()243     public Map<String, String> getSettings() {
244         return settings;
245     }
246 
247     /**
248      * Returns destination path where data, for the recording associated with
249      * this {@link RecordingInfo}, should be written when the recording stops,
250      * or {@code null} if the recording should not be written.
251      *
252      * @return the destination, or {@code null} if not set
253      *
254      * @see Recording#getDestination()
255      */
getDestination()256     public String getDestination() {
257         return destination;
258     }
259 
260     /**
261      * Returns a string description of the recording associated with this
262      * {@code RecordingInfo}
263      *
264      * @return description, not {@code null}
265      */
266     @Override
toString()267     public String toString() {
268         Stringifier s = new Stringifier();
269         s.add("name", name);
270         s.add("id", id);
271         s.add("maxAge", maxAge);
272         s.add("maxSize", maxSize);
273         return s.toString();
274     }
275 
276     /**
277      * Returns the amount data recorded by recording. associated with this
278      * {@link RecordingInfo}.
279      *
280      * @return the amount of recorded data, measured in bytes
281      */
getSize()282     public long getSize() {
283         return size;
284     }
285 
286     /**
287      * Returns {@code true} if the recording associated with this
288      * {@code RecordingInfo} should be flushed to disk, when memory buffers are
289      * full, {@code false} otherwise.
290      *
291      * @return {@code true} if recording is to disk, {@code false} otherwise
292      */
isToDisk()293     public boolean isToDisk() {
294         return disk;
295     }
296 
297     /**
298      * Returns the desired duration, measured in seconds, of the recording
299      * associated with this {@link RecordingInfo}, or {code 0} if no duration
300      * has been set.
301      *
302      * @return the desired duration, or {code 0} if no duration has been set
303      *
304      * @see Recording#getDuration()
305      */
getDuration()306     public long getDuration() {
307         return durationInSeconds;
308     }
309 
310     /**
311      * Returns a {@code RecordingInfo} represented by the specified
312      * {@code CompositeData} object.
313      * <p>
314      * The specified {@code CompositeData} must have the following item names and
315      * item types to be valid. <blockquote>
316      * <table class="striped">
317      * <caption>Supported names and types in a specified {@code CompositeData} object</caption>
318      * <thead>
319      * <tr>
320      * <th scope="col" style="text-align:left">Name</th>
321      * <th scope="col" style="text-align:left">Type</th>
322      * </tr>
323      * </thead>
324      * <tbody>
325      * <tr>
326      * <th scope="row">id</th>
327      * <td>{@code Long}</td>
328      * </tr>
329      * <tr>
330      * <th scope="row">name</th>
331      * <td>{@code String}</td>
332      * </tr>
333      * <tr>
334      * <th scope="row">state</th>
335      * <td>{@code String}</td>
336      * </tr>
337      * <tr>
338      * <th scope="row">dumpOnExit</th>
339      * <td>{@code Boolean}</td>
340      * </tr>
341      * <tr>
342      * <th scope="row">size</th>
343      * <td>{@code Long}</td>
344      * </tr>
345      * <tr>
346      * <th scope="row">disk</th>
347      * <td>{@code Boolean}</td>
348      * </tr>
349      * <tr>
350      * <th scope="row">maxAge</th>
351      * <td>{@code Long}</td>
352      * </tr>
353      * <tr>
354      * <th scope="row">maxSize</th>
355      * <td>{@code Long}</td>
356      * </tr>
357      * <tr>
358      * <th scope="row">startTime</th>
359      * <td>{@code Long}</td>
360      * </tr>
361      * <tr>
362      * <th scope="row">stopTime</th>
363      * <td>{@code Long}</td>
364      * </tr>
365      * <tr>
366      * <th scope="row">destination</th>
367      * <td>{@code String}</td>
368      * </tr>
369      * <tr>
370      * <th scope="row">duration</th>
371      * <td>{@code Long}</td>
372      * </tr>
373      * <tr>
374      * <th scope="row">settings</th>
375      * <td>{@code javax.management.openmbean.CompositeData[]} whose element type
376      * is the mapped type for {@link SettingDescriptorInfo} as specified in the
377      * {@link SettingDescriptorInfo#from} method.</td>
378      * </tr>
379      * </tbody>
380      * </table>
381      * </blockquote>
382      *
383      * @param cd {@code CompositeData} representing the {@code RecordingInfo} to
384      *        return
385      *
386      * @throws IllegalArgumentException if {@code cd} does not represent a valid
387      *         {@code RecordingInfo}
388      *
389      * @return the {@code RecordingInfo} represented by {@code cd}, or
390      *         {@code null} if {@code cd} is {@code null}
391      */
from(CompositeData cd)392     public static RecordingInfo from(CompositeData cd) {
393         if (cd == null) {
394             return null;
395         }
396         return new RecordingInfo(cd);
397     }
398 }
399