1 /*
2  * Copyright (c) 2016, 2021, 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.management;
27 
28 import java.io.IOException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.time.Duration;
32 import java.time.Instant;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.function.Consumer;
37 import java.security.AccessControlContext;
38 
39 import jdk.jfr.Configuration;
40 import jdk.jfr.EventSettings;
41 import jdk.jfr.EventType;
42 import jdk.jfr.Recording;
43 import jdk.jfr.consumer.EventStream;
44 import jdk.jfr.internal.JVMSupport;
45 import jdk.jfr.internal.LogLevel;
46 import jdk.jfr.internal.LogTag;
47 import jdk.jfr.internal.Logger;
48 import jdk.jfr.internal.MetadataRepository;
49 import jdk.jfr.internal.PlatformRecording;
50 import jdk.jfr.internal.PrivateAccess;
51 import jdk.jfr.internal.SecuritySupport.SafePath;
52 import jdk.jfr.internal.Utils;
53 import jdk.jfr.internal.WriteableUserPath;
54 import jdk.jfr.internal.consumer.EventDirectoryStream;
55 import jdk.jfr.internal.consumer.FileAccess;
56 import jdk.jfr.internal.instrument.JDKEvents;
57 
58 /**
59  * The management API in module jdk.management.jfr should be built on top of the
60  * public API in jdk.jfr. Before putting more functionality here, consider if it
61  * should not be part of the public API, and if not, please provide motivation
62  *
63  */
64 public final class ManagementSupport {
65 
66     // Purpose of this method is to expose the event types to the
67     // FlightRecorderMXBean without instantiating Flight Recorder.
68     //
69     // This allows:
70     //
71     // 1) discoverability, so event settings can be exposed without the need to
72     // create a new Recording in FlightRecorderMXBean.
73     //
74     // 2) a graphical JMX client to list all attributes to the user, without
75     // loading JFR memory buffers. This is especially important when there is
76     // no intent to use Flight Recorder.
77     //
78     // An alternative design would be to make FlightRecorder#getEventTypes
79     // static, but it would the make the API look strange
80     //
getEventTypes()81     public static List<EventType> getEventTypes() {
82         // would normally be checked when a Flight Recorder instance is created
83         Utils.checkAccessFlightRecorder();
84         if (JVMSupport.isNotAvailable()) {
85             return List.of();
86         }
87         JDKEvents.initialize(); // make sure JDK events are available
88         return Collections.unmodifiableList(MetadataRepository.getInstance().getRegisteredEventTypes());
89     }
90 
91     // Reuse internal code for parsing a timespan
parseTimespan(String s)92     public static long parseTimespan(String s) {
93         return Utils.parseTimespan(s);
94     }
95 
96     // Reuse internal code for converting nanoseconds since epoch to Instant
epochNanosToInstant(long epochNanos)97     public static Instant epochNanosToInstant(long epochNanos) {
98       return Utils.epochNanosToInstant(epochNanos);
99     }
100 
101     // Reuse internal code for formatting settings
formatTimespan(Duration dValue, String separation)102     public static final String formatTimespan(Duration dValue, String separation) {
103         return Utils.formatTimespan(dValue, separation);
104     }
105 
106     // Reuse internal logging mechanism
logError(String message)107     public static void logError(String message) {
108         Logger.log(LogTag.JFR, LogLevel.ERROR, message);
109     }
110 
111     // Reuse internal logging mechanism
logDebug(String message)112     public static void logDebug(String message) {
113         Logger.log(LogTag.JFR, LogLevel.DEBUG, message);
114     }
115 
116     // Get the textual representation when the destination was set, which
117     // requires access to jdk.jfr.internal.PlatformRecording
getDestinationOriginalText(Recording recording)118     public static String getDestinationOriginalText(Recording recording) {
119         PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
120         WriteableUserPath wup = pr.getDestination();
121         return wup == null ? null : wup.getOriginalText();
122     }
123 
124     // Needed to check if destination can be set, so FlightRecorderMXBean::setRecordingOption
125     // can abort if not all data is valid
checkSetDestination(Recording recording, String destination)126     public static void checkSetDestination(Recording recording, String destination) throws IOException{
127         PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
128         if(destination != null){
129             WriteableUserPath wup = new WriteableUserPath(Paths.get(destination));
130             pr.checkSetDestination(wup);
131         }
132     }
133 
134     // Needed to modify setting using fluent API.
newEventSettings(EventSettingsModifier esm)135     public static EventSettings newEventSettings(EventSettingsModifier esm) {
136         return PrivateAccess.getInstance().newEventSettings(esm);
137     }
138 
139     // When streaming an ongoing recording, consumed chunks should be removed
removeBefore(Recording recording, Instant timestamp)140     public static void removeBefore(Recording recording, Instant timestamp) {
141         PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
142         pr.removeBefore(timestamp);
143     }
144 
145     // Needed callback to detect when a chunk has been parsed.
removePath(Recording recording, Path path)146     public static void removePath(Recording recording, Path path) {
147         PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
148         pr.removePath(new SafePath(path));
149     }
150 
151     // Needed callback to detect when a chunk has been parsed.
setOnChunkCompleteHandler(EventStream stream, Consumer<Long> consumer)152     public static void setOnChunkCompleteHandler(EventStream stream, Consumer<Long> consumer) {
153         EventDirectoryStream eds = (EventDirectoryStream) stream;
154         eds.setChunkCompleteHandler(consumer);
155     }
156 
157     // Needed to start an ongoing stream at the right chunk, which
158     // can be identified by the start time with nanosecond precision.
getStartTimeNanos(Recording recording)159     public static long getStartTimeNanos(Recording recording) {
160         PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
161         return pr.getStartNanos();
162     }
163 
164     // Needed to produce Configuration objects for MetadataEvent
newConfiguration(String name, String label, String description, String provider, Map<String, String> settings, String contents)165     public static Configuration newConfiguration(String name, String label, String description, String provider,
166           Map<String, String> settings, String contents) {
167         return PrivateAccess.getInstance().newConfiguration(name, label, description, provider, settings, contents);
168     }
169 
170     // Can't use EventStream.openRepository(...) because
171     // EventStream::onMetadataData need to supply MetadataEvent
172     // with configuration objects
newEventDirectoryStream( @uppressWarningsR) AccessControlContext acc, Path directory, List<Configuration> confs)173     public static EventStream newEventDirectoryStream(
174             @SuppressWarnings("removal")
175             AccessControlContext acc,
176             Path directory,
177             List<Configuration> confs) throws IOException {
178         return new EventDirectoryStream(
179             acc,
180             directory,
181             FileAccess.UNPRIVILEGED,
182             null,
183             confs,
184             false
185         );
186     }
187 }
188