8 Replies Latest reply: Feb 20, 2014 4:50 AM by Hirt-Oracle RSS

    Is it possible to start a recording from source code?

    the_qa_guy

      Hi everybody!

       

      I'm currently using JProfiler in combination with JUnit tests to collect resource consumption metrics of features in our product.

      A homemade JUnit runner starts and stops the JProfiler recording and parses the recorded data. It then compares the recorded values to predefined limits and fails the test if they are exceeded.

       

      Now I want to change this to use the flight recorder instead. I found a nice article explaining how to parse .jfr files here: Using the Flight Recorder Parsers | Marcus Hirt

      Unfortunately I can't figure out how to start and stop a flight recording from a JUnit runner besides using the jcmd tool.

       

      Can anybody help me out?

       

      Thanks a lot

      Tobias

        • 1. Re: Is it possible to start a recording from source code?
          the_qa_guy

          Here's an update.

           

          I got to kind of work now by using the code from this page: 您的访问请求被拒绝 - ITeye技术社区

          Unfortunately the code

          final FlightRecorderClient frc = new FlightRecorderClient(server);

          final FlightRecordingClient flightRecording = frc.createRecordingObject(recordingName);

          fails.

          The exception shown is:

          Caused by: javax.management.InstanceNotFoundException: com.oracle.jrockit:type=FlightRecorder

           

          I can work around this problem by following these steps:

          - set a breakpoint in the first line

          - open JMC

          - connect to the VM

          - continue the code execution

           

          I can see in JConsole that the com.oracle.jrockit:type=FlightRecorder bean only appears AFTER connecting with JMC.

          I couldn't find the source code of JMC anywhere so I wonder what JMC does to register the bean - and how I'll be able to replicate it in my code.

           

          Can anybody help me out?

           

          Thanks a lot

          Tobias

          • 2. Re: Is it possible to start a recording from source code?
            Hirt-Oracle

            Hi Tobias,

             

            This is quite trivial in JDK 8 - just use the operations on the com.sun.management:type=DiagnosticCommand MBean mimicking the way you would use jcmd. Relying on the JMC MBean and corresponding APIs directly is not supported.

             

            That said, If you wish to register the JMC related MBeans, the following snippet would probably be helpful:

            import java.lang.management.ManagementFactory;

             

            import javax.management.MBeanServer;

            import javax.management.MalformedObjectNameException;

            import javax.management.ObjectName;

             

            public class RegisterMe {

              private static final String REGISTER_MBEANS_OPERATION = "registerMBeans"; //$NON-NLS-1$

              private static final String CLASS_NAME = "com.sun.management.MissionControl"; //$NON-NLS-1$

              private static final String MBEAN_NAME = "com.sun.management:type=MissionControl"; //$NON-NLS-1$

              private static final ObjectName OBJECT_NAME = createObjectName();

             

              private static ObjectName createObjectName() {

              try {

              return new ObjectName(MBEAN_NAME);

              } catch (MalformedObjectNameException e) {

              throw new Error("Should not be possible: Could not make a new ObjectName " + MBEAN_NAME); //$NON-NLS-1$

              }

              }

             

              public static void main(String[] args) throws Exception {

              MBeanServer ms = ManagementFactory.getPlatformMBeanServer();

              ms.createMBean(CLASS_NAME, OBJECT_NAME);

              ms.invoke(OBJECT_NAME, REGISTER_MBEANS_OPERATION, new Object[0], new String[0]);

              System.out.println("Press enter to quit"); //$NON-NLS-1$

              System.in.read();

              }

            }

             

            Kind regards,

            Marcus

            • 3. Re: Is it possible to start a recording from source code?
              Hirt-Oracle

              (PS. Glad you liked the article! )

              • 4. Re: Is it possible to start a recording from source code?
                the_qa_guy

                Answering myself once more since other people might stumble upon the same problem.

                 

                In the end I created a TestRule which I can add to the JUnit Test Class.

                 

                The interesting parts of the code are:

                 

                public class ProfilerTest implements TestRule {

                 

                    @Override

                 

                    public Statement apply(Statement base, Description description) {

                 

                        return new PerformanceLimitsStatement(base, description);

                 

                    }

                 

                    class PerformanceLimitsStatement extends Statement {

                 

                        private PublicFlightRecorderRunner runner;

                 

                        private final Description description;

                 

                        private final Statement base;

                 

                        public PerformanceLimitsStatement(Statement baseParam, Description descriptionParam) {

                 

                            description = descriptionParam;

                 

                            base = baseParam;

                 

                        }

                 

                        @Override

                 

                        public void evaluate() throws Throwable {

                 

                            runner = new FlightRecorderRunner(description.getTestClass());

                 

                          // start profiling

                 

                            runner.startupProfiling("jfr/"+description.getTestClass().getSimpleName()+"."+description.getMethodName()+".jfr", annotation.cpuRecording(), annotation.allocRecording(), annotation.methodStatsRecording(), annotation.monitorRecording(),

                 

                                    annotation.threadProfiling());

                 

                            // run a test method including its before and after methods inside a simple time measurement

                 

                            base.evaluate();

                 

                            // shutdown profiling

                 

                            final File performanceSnapshot = runner.shutdownProfiling(description);

                 

                            }

                 

                        }

                 

                }

                 

                This uses a PublicFlightRecorderRunner - code:

                 

                import java.io.File;

                import java.io.IOException;

                import java.lang.management.ManagementFactory;

                import java.nio.file.Files;

                import java.util.List;

                 

                import javax.management.Attribute;

                import javax.management.MBeanServer;

                import javax.management.MalformedObjectNameException;

                import javax.management.NotCompliantMBeanException;

                import javax.management.ObjectName;

                import javax.management.openmbean.CompositeData;

                import javax.management.openmbean.CompositeDataSupport;

                 

                import org.junit.internal.runners.statements.InvokeMethod;

                import org.junit.runner.Description;

                import org.junit.runners.BlockJUnit4ClassRunner;

                import org.junit.runners.model.FrameworkMethod;

                import org.junit.runners.model.InitializationError;

                import org.junit.runners.model.Statement;

                 

                /**

                *

                * <p>

                * (c) Copyright Vector Informatik GmbH. All Rights Reserved.

                * </p>

                *

                * @since 1.0

                */

                @SuppressWarnings({ "restriction"})

                public class PublicFlightRecorderRunner extends BlockJUnit4ClassRunner implements

                        IProfilerRunner {

                 

                      private static MBeanServer ms = null;

                      private File file;

                      private static final String REGISTER_MBEANS_OPERATION = "registerMBeans"; //$NON-NLS-1$

                      private static final String JFR_CREATE_RECORDING_OPERATION = "createRecording"; //$NON-NLS-1$

                      private static final String START_RECORDING_OPERATION = "start"; //$NON-NLS-1$

                      private static final String STOP_RECORDING_OPERATION = "stop"; //$NON-NLS-1$

                      private static final String CLOSE_RECORDING_OPERATION = "close"; //$NON-NLS-1$

                      private static final String SET_EVENT_ENABLED_OPERATION = "setEventEnabled"; //$NON-NLS-1$

                      private static final String SET_STACKTRACE_ENABLED_OPERATION = "setStackTraceEnabled"; //$NON-NLS-1$

                      private static final String SET_THRESHOLD_OPERATION = "setThreshold"; //$NON-NLS-1$

                      private static final String SET_PERIOD_OPERATION = "setPeriod"; //$NON-NLS-1$

                 

                 

                      private static final String MC_CLASS_NAME = "com.sun.management.MissionControl"; //$NON-NLS-1$

                      private static final String MC_MBEAN_NAME = "com.sun.management:type=MissionControl"; //$NON-NLS-1$

                      private static final ObjectName MC_OBJECT_NAME = createObjectName(MC_MBEAN_NAME);

                    

                      private static final String FRC_CLASS_NAME = "oracle.jrockit.jfr.FlightRecorder"; //$NON-NLS-1$

                      private static final String FRC_MBEAN_NAME = "com.oracle.jrockit:type=FlightRecorder";

                      private static final ObjectName FRC_OBJECT_NAME = createObjectName(FRC_MBEAN_NAME);

                     

                      private static ObjectName recordingObjectName;

                      private static CompositeDataSupport recording;

                 

                 

                      private static ObjectName createObjectName(String beanName) {

                          try {

                              return new ObjectName(beanName);

                          } catch (MalformedObjectNameException e) {

                              throw new Error("Should not be possible: Could not make a new ObjectName " + beanName); //$NON-NLS-1$

                          }

                      }

                 

                 

                    /**

                     * Constructor for FlightRecorderRunner.

                     *

                     * @param klass

                     * @throws InitializationError

                     */

                    public PublicFlightRecorderRunner(final Class<?> klass)

                            throws InitializationError {

                        super(klass);

                    }

                 

                    @Override

                    protected Statement methodInvoker(final FrameworkMethod method,

                            final Object test) {

                        return new InvokeMethod(method, test) {

                            @Override

                            public void evaluate() throws Throwable {

                                final String targetFile = "jfr/" + method.getClass().getSimpleName()

                                        + "." + method.getMethod().getName() + ".jfr";

                                startupProfiling(targetFile);

                                for (int i = 0; i < 10; i++) {

                                    super.evaluate();

                                }

                                shutdownProfiling(method, test);

                            }

                        };

                    }

                 

                    public void startupProfiling(final String targetFile) {

                        file = new File(targetFile);

                 

                        try {

                            createFlightRecordingClient(file.getName());

                            startFlightRecording(file);

                        } catch (Exception e) {

                            e.printStackTrace();

                        }

                    }

                 

                 

                    public File shutdownProfiling(final FrameworkMethod method,

                            final Object test) {

                        try {

                            stopFlightRecording();

                        } catch (Exception e) {

                            e.printStackTrace();

                        }

                        return file;

                    }

                 

                    public static void createFlightRecordingClient(

                            final String recordingName) throws Exception  {

                       

                        // register Flight Recorder Bean

                        ms = ManagementFactory.getPlatformMBeanServer();

                 

                        // Create MissonControl Bean

                        if (!ms.isRegistered(MC_OBJECT_NAME)) {

                            ms.createMBean(MC_CLASS_NAME, MC_OBJECT_NAME);

                            ms.invoke(MC_OBJECT_NAME, REGISTER_MBEANS_OPERATION, new Object[0],    new String[0]);

                        }

                       

                        // Create FlightRecorder Bean

                        try{

                            if(!ms.isRegistered(FRC_OBJECT_NAME))

                            {

                                ms.createMBean(FRC_CLASS_NAME, FRC_OBJECT_NAME);

                                ms.invoke(FRC_OBJECT_NAME, REGISTER_MBEANS_OPERATION, new Object[0], new String[0]);

                            }

                        }

                        catch (NotCompliantMBeanException e) {

                            @SuppressWarnings("unused")

                            boolean wedontcare = true;

                        }

                       

                        // create recording

                        ms.invoke(FRC_OBJECT_NAME, JFR_CREATE_RECORDING_OPERATION, new Object[] {recordingName}, new String[] {String.class.getName()});

                    }

                 

                    public static void startFlightRecording(File file)

                            throws Exception {

                       

                        // Check that only one recording exists and that it's not already running

                        @SuppressWarnings("unchecked")

                        List<CompositeDataSupport> recordings = (List<CompositeDataSupport>) ms.getAttribute(FRC_OBJECT_NAME, "Recordings");

                        if(recordings.size() > 1) {

                            throw new Error("More than one recording available");

                        }

                       

                        recording = recordings.get(0);

                        if( (boolean) recording.get("running")) {

                            throw new Error("Recording is already running");

                        }

                 

                        // store the recording name for later us

                        recordingObjectName = (ObjectName) recording.get("objectName");

                 

                        // set duration for the recording

                        final long duration = 10 * 60 * 1000; // 10 minutes in milliseconds - this number was determined by looking at the slowest test on jenkins

                        final Attribute durationAttribute = new Attribute("Duration", duration);

                        ms.setAttribute(recordingObjectName, durationAttribute);

                       

                        // set destination for the recording

                        try {

                            Files.createDirectories(file.getParentFile().toPath());

                            final Attribute destinationAttribute = new Attribute("Destination", file.getAbsolutePath());

                            ms.setAttribute(recordingObjectName, destinationAttribute);

                        } catch (IOException e) {

                            e.printStackTrace();

                        }

                       

                        // read event settings

                        @SuppressWarnings("unchecked")

                        List<CompositeDataSupport> eventSettings = (List<CompositeDataSupport>) ms.getAttribute(recordingObjectName, "EventSettings");

                       

                        final long period = 0;

                        final long threshold = 100 * 1000 * 1000; // 100 ms - given in ns

                       

                        // enable all events, set threshold to 100ms and stacktrace to false (because we only parse this in code)

                        for(CompositeData eventSetting: eventSettings) {

                            final Integer eventid = (Integer) eventSetting.get("id");

                            ms.invoke(recordingObjectName, SET_EVENT_ENABLED_OPERATION, new Object[] {eventid, true}, new String[] {int.class.getName(), boolean.class.getName()});

                            ms.invoke(recordingObjectName, SET_STACKTRACE_ENABLED_OPERATION, new Object[] {eventid, false}, new String[] {int.class.getName(), boolean.class.getName()});

                            ms.invoke(recordingObjectName, SET_PERIOD_OPERATION, new Object[] {eventid, period}, new String[] {int.class.getName(), long.class.getName()});

                            ms.invoke(recordingObjectName, SET_THRESHOLD_OPERATION, new Object[] {eventid, threshold}, new String[] {int.class.getName(), long.class.getName()});

                        }

                 

                        // start the recording

                        ms.invoke(recordingObjectName, START_RECORDING_OPERATION, new Object[0], new String[0]);

                    }

                 

                    public static void stopFlightRecording() throws Exception {

                        // make sure the recording is stopped

                        if ((boolean) ms.getAttribute(recordingObjectName, "Stopped")) {

                            throw new Error("The FlightRecording has already stopped in current thread. Consider increasing the duration!");

                        } else {

                            ms.invoke(recordingObjectName, STOP_RECORDING_OPERATION, new Object[0], new String[0]);

                        }

                        // close the recording to remove it from the flightrecorder

                        ms.invoke(recordingObjectName, CLOSE_RECORDING_OPERATION, new Object[0], new String[0]);

                    }

                }

                 

                This creates a .jfr file which can be used later.

                I parse it in the TestRule to determine if the test violated memory or time limits, in which case the test is failed. (see the link in my first post for information about parsing .jfr files)

                The code may not be the prettiest but so far it's working well.

                 

                Message was edited by: the_qa_guy Changed the code to use the JFR bean

                • 5. Re: Is it possible to start a recording from source code?
                  the_qa_guy

                  Thank you very much, that solved my problems!

                  • 7. Re: Is it possible to start a recording from source code?
                    the_qa_guy

                    One small hint regarding your blog post:

                    Using JMC 5.2.0 I had to add both the "com.jrockit.mc.flightrecorder_*.jar" and the "com.jrockit.mc.common_*jar" otherwise the parser would complain about missing classes

                    • 8. Re: Is it possible to start a recording from source code?
                      Hirt-Oracle

                      Doh, can't believe I didn't write that. I fixed the blog post. Thanks!