77
88import java .io .IOException ;
99import java .io .InputStream ;
10- import java .lang .management .ManagementFactory ;
11- import java .lang .management .RuntimeMXBean ;
1210import java .time .Instant ;
1311import java .util .ArrayList ;
12+ import java .util .Arrays ;
1413import java .util .Collections ;
1514import java .util .List ;
1615import java .util .Objects ;
16+ import java .util .Optional ;
1717import java .util .regex .Matcher ;
1818import java .util .regex .Pattern ;
1919import java .util .stream .Collectors ;
2020import javax .management .InstanceNotFoundException ;
21+ import javax .management .IntrospectionException ;
2122import javax .management .MBeanException ;
23+ import javax .management .MBeanInfo ;
24+ import javax .management .MBeanOperationInfo ;
2225import javax .management .MBeanServerConnection ;
2326import javax .management .MalformedObjectNameException ;
2427import javax .management .ObjectInstance ;
@@ -37,6 +40,8 @@ final class FlightRecorderDiagnosticCommandConnection implements FlightRecorderC
3740 "com.sun.management:type=DiagnosticCommand" ;
3841 private static final String JFR_START_REGEX = "Started recording (\\ d+?)\\ ." ;
3942 private static final Pattern JFR_START_PATTERN = Pattern .compile (JFR_START_REGEX , Pattern .DOTALL );
43+ private static final String JFR_CHECK_REGEX = "(?:recording|name)=(\\ d+)" ;
44+ private static final Pattern JFR_CHECK_PATTERN = Pattern .compile (JFR_CHECK_REGEX , Pattern .DOTALL );
4045
4146 // All JFR commands take String[] parameters
4247 private static final String [] signature = new String [] {"[Ljava.lang.String;" };
@@ -59,9 +64,7 @@ static FlightRecorderConnection connect(MBeanServerConnection mBeanServerConnect
5964 mBeanServerConnection .getObjectInstance (new ObjectName (DIAGNOSTIC_COMMAND_OBJECT_NAME ));
6065 ObjectName objectName = objectInstance .getObjectName ();
6166
62- if (jdkHasUnlockCommercialFeatures (mBeanServerConnection )) {
63- assertCommercialFeaturesUnlocked (mBeanServerConnection , objectName );
64- }
67+ assertCommercialFeaturesUnlocked (mBeanServerConnection , objectName );
6568
6669 return new FlightRecorderDiagnosticCommandConnection (
6770 mBeanServerConnection , objectInstance .getObjectName ());
@@ -123,21 +126,22 @@ public long startRecording(
123126 Object [] params = formOptions (recordingOptions , recordingConfiguration );
124127
125128 // jfrStart returns "Started recording 2." and some more stuff, but all we care about is the
126- // name of the recording.
129+ // id of the recording.
130+ String jfrStart ;
127131 try {
128- String jfrStart =
129- (String ) mBeanServerConnection .invoke (objectName , "jfrStart" , params , signature );
130- String name ;
132+ jfrStart = (String ) mBeanServerConnection .invoke (objectName , "jfrStart" , params , signature );
131133 Matcher matcher = JFR_START_PATTERN .matcher (jfrStart );
132134 if (matcher .find ()) {
133- name = matcher .group (1 );
134- return Long .parseLong (name );
135+ String id = matcher .group (1 );
136+ return Long .parseLong (id );
135137 }
136138 } catch (InstanceNotFoundException | ReflectionException | MBeanException e ) {
137139 throw JfrConnectionException .canonicalJfrConnectionException (getClass (), "startRecording" , e );
138140 }
139141 throw JfrConnectionException .canonicalJfrConnectionException (
140- getClass (), "startRecording" , new IllegalStateException ("Failed to parse jfrStart output" ));
142+ getClass (),
143+ "startRecording" ,
144+ new IllegalStateException ("Failed to parse: '" + jfrStart + "'" ));
141145 }
142146
143147 private static Object [] formOptions (
@@ -156,10 +160,33 @@ private static Object[] formOptions(
156160 return mkParamsArray (params );
157161 }
158162
163+ //
164+ // Whether to use the 'name' or 'recording' parameter depends on the JVM.
165+ // Use JFR.check to determine which one to use.
166+ //
167+ private String getRecordingParam (long recordingId ) throws JfrConnectionException , IOException {
168+ String jfrCheck ;
169+ try {
170+ Object [] params = new Object [] {new String [] {}};
171+ jfrCheck = (String ) mBeanServerConnection .invoke (objectName , "jfrCheck" , params , signature );
172+ Matcher matcher = JFR_CHECK_PATTERN .matcher (jfrCheck );
173+ while (matcher .find ()) {
174+ String id = matcher .group (1 );
175+ if (id .equals (Long .toString (recordingId ))) {
176+ return matcher .group (0 );
177+ }
178+ }
179+ } catch (InstanceNotFoundException | MBeanException | ReflectionException e ) {
180+ throw JfrConnectionException .canonicalJfrConnectionException (getClass (), "jfrCheck" , e );
181+ }
182+ throw JfrConnectionException .canonicalJfrConnectionException (
183+ getClass (), "jfrCheck" , new IllegalStateException ("Failed to parse: '" + jfrCheck + "'" ));
184+ }
185+
159186 @ Override
160187 public void stopRecording (long id ) throws JfrConnectionException {
161188 try {
162- Object [] params = mkParams ("name=" + id );
189+ Object [] params = mkParams (getRecordingParam ( id ) );
163190 mBeanServerConnection .invoke (objectName , "jfrStop" , params , signature );
164191 } catch (InstanceNotFoundException | MBeanException | ReflectionException | IOException e ) {
165192 throw JfrConnectionException .canonicalJfrConnectionException (getClass (), "stopRecording" , e );
@@ -169,7 +196,7 @@ public void stopRecording(long id) throws JfrConnectionException {
169196 @ Override
170197 public void dumpRecording (long id , String outputFile ) throws IOException , JfrConnectionException {
171198 try {
172- Object [] params = mkParams ("filename=" + outputFile , "name=" + id );
199+ Object [] params = mkParams ("filename=" + outputFile , getRecordingParam ( id ) );
173200 mBeanServerConnection .invoke (objectName , "jfrDump" , params , signature );
174201 } catch (InstanceNotFoundException | MBeanException | ReflectionException e ) {
175202 throw JfrConnectionException .canonicalJfrConnectionException (getClass (), "dumpRecording" , e );
@@ -197,41 +224,34 @@ public void closeRecording(long id) {
197224 "closeRecording not available through the DiagnosticCommand connection" );
198225 }
199226
200- // Do this check separate from assertCommercialFeatures because reliance
201- // on System properties makes it difficult to test.
202- static boolean jdkHasUnlockCommercialFeatures (MBeanServerConnection mBeanServerConnection ) {
203- try {
204- RuntimeMXBean runtimeMxBean =
205- ManagementFactory .getPlatformMXBean (mBeanServerConnection , RuntimeMXBean .class );
206- String javaVmVendor = runtimeMxBean .getVmVendor ();
207- String javaVersion = runtimeMxBean .getVmVersion ();
208- return javaVmVendor .contains ("Oracle Corporation" )
209- && javaVersion .matches ("(?:^1\\ .8|9|10).*" );
210- } catch (IOException e ) {
211- return false ;
212- }
213- }
214-
215227 // visible for testing
216228 static void assertCommercialFeaturesUnlocked (
217229 MBeanServerConnection mBeanServerConnection , ObjectName objectName )
218230 throws IOException , JfrConnectionException {
219231
220232 try {
221- Object unlockedMessage =
222- mBeanServerConnection .invoke (objectName , "vmCheckCommercialFeatures" , null , null );
223- if (unlockedMessage instanceof String ) {
224- boolean unlocked = ((String ) unlockedMessage ).contains ("unlocked" );
225- if (!unlocked ) {
226- throw JfrConnectionException .canonicalJfrConnectionException (
227- FlightRecorderDiagnosticCommandConnection .class ,
228- "assertCommercialFeaturesUnlocked" ,
229- new UnsupportedOperationException (
230- "Unlocking commercial features may be required. This must be explicitly enabled by adding -XX:+UnlockCommercialFeatures" ));
231- }
233+ Object [] params = new Object [] {new String [] {}};
234+ MBeanInfo mBeanInfo = mBeanServerConnection .getMBeanInfo (objectName );
235+ if (mBeanInfo == null ) {
236+ throw JfrConnectionException .canonicalJfrConnectionException (
237+ FlightRecorderDiagnosticCommandConnection .class ,
238+ "assertCommercialFeaturesUnlocked" ,
239+ new NullPointerException ("Could not get MBeanInfo for " + objectName ));
240+ }
241+ Optional <MBeanOperationInfo > operation =
242+ Arrays .stream (mBeanInfo .getOperations ())
243+ .filter (it -> "vmUnlockCommercialFeatures" .equals (it .getName ()))
244+ .findFirst ();
245+
246+ if (operation .isPresent ()) {
247+ mBeanServerConnection .invoke (objectName , "vmUnlockCommercialFeatures" , params , signature );
232248 }
233- } catch (InstanceNotFoundException | MBeanException | ReflectionException ignored ) {
234- // If the MBean doesn't have the vmCheckCommercialFeatures method, then we can't check it.
249+ } catch (InstanceNotFoundException
250+ | IntrospectionException
251+ | MBeanException
252+ | ReflectionException e ) {
253+ throw JfrConnectionException .canonicalJfrConnectionException (
254+ FlightRecorderDiagnosticCommandConnection .class , "assertCommercialFeaturesUnlocked" , e );
235255 }
236256 }
237257
0 commit comments