├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── codeql-analysis.yml │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── microsoft │ │ └── jfr │ │ ├── FlightRecorderConnection.java │ │ ├── JfrStream.java │ │ ├── JfrStreamingException.java │ │ ├── OpenDataUtils.java │ │ ├── Recording.java │ │ ├── RecordingConfiguration.java │ │ ├── RecordingOptions.java │ │ ├── dcmd │ │ ├── FlightRecorderDiagnosticCommandConnection.java │ │ └── package-info.java │ │ └── package-info.java │ └── test │ ├── java │ └── com │ │ └── microsoft │ │ └── jfr │ │ ├── RecordingConfigurationTest.java │ │ ├── RecordingOptionsTest.java │ │ ├── RecordingTest.java │ │ └── dcmd │ │ └── FlightRecorderDiagnosticCommandConnectionTest.java │ └── resources │ ├── brokenJfcFile.jfc │ └── sampleJfcFile.jfc ├── mvnw ├── mvnw.cmd ├── pom.xml └── samples ├── .gitignore ├── README.md ├── introductory ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── microsoft │ └── jfr │ ├── LoadGenerator.java │ └── Main.java ├── jbang └── Sample.java └── pom.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.{cmd,[cC][mM][dD]} text eol=crlf 3 | *.{bat,[bB][aA][tT]} text eol=crlf 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '22 4 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'java' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | java: [8, 11, 17] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up JDK 22 | uses: actions/setup-java@v2-preview 23 | with: 24 | java-version: ${{ matrix.java }} 25 | distribution: 'adopt' 26 | - name: Build with Maven 27 | run: ./mvnw -B package --file pom.xml 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Maven build 35 | .mvn/wrapper/maven-wrapper.jar 36 | default.profraw 37 | target/ 38 | 39 | # Visual Studio 2015/2017 cache/options directory 40 | .vs/ 41 | 42 | # IntelliJ Idea 43 | .idea/ 44 | *.iml 45 | 46 | # jenv 47 | .java-version 48 | 49 | # Uncomment if you have tasks that create the project's static files in wwwroot 50 | #wwwroot/ 51 | 52 | # VS Code 53 | .vscode/ 54 | 55 | # Visual Studio 2017 auto generated files 56 | Generated\ Files/ 57 | 58 | # MSTest test Results 59 | [Tt]est[Rr]esult*/ 60 | [Bb]uild[Ll]og.* 61 | 62 | # NUnit 63 | *.VisualState.xml 64 | TestResult.xml 65 | nunit-*.xml 66 | 67 | # Build Results of an ATL Project 68 | [Dd]ebugPS/ 69 | [Rr]eleasePS/ 70 | dlldata.c 71 | 72 | # Benchmark Results 73 | BenchmarkDotNet.Artifacts/ 74 | 75 | # .NET Core 76 | project.lock.json 77 | project.fragment.lock.json 78 | artifacts/ 79 | 80 | # StyleCop 81 | StyleCopReport.xml 82 | 83 | # Files built by Visual Studio 84 | *_i.c 85 | *_p.c 86 | *_h.h 87 | *.ilk 88 | *.meta 89 | *.obj 90 | *.iobj 91 | *.pch 92 | *.pdb 93 | *.ipdb 94 | *.pgc 95 | *.pgd 96 | *.rsp 97 | *.sbr 98 | *.tlb 99 | *.tli 100 | *.tlh 101 | *.tmp 102 | *.tmp_proj 103 | *_wpftmp.csproj 104 | *.log 105 | *.vspscc 106 | *.vssscc 107 | .builds 108 | *.pidb 109 | *.svclog 110 | *.scc 111 | 112 | # Chutzpah Test files 113 | _Chutzpah* 114 | 115 | # Visual C++ cache files 116 | ipch/ 117 | *.aps 118 | *.ncb 119 | *.opendb 120 | *.opensdf 121 | *.sdf 122 | *.cachefile 123 | *.VC.db 124 | *.VC.VC.opendb 125 | 126 | # Visual Studio profiler 127 | *.psess 128 | *.vsp 129 | *.vspx 130 | *.sap 131 | 132 | # Visual Studio Trace Files 133 | *.e2e 134 | 135 | # TFS 2012 Local Workspace 136 | $tf/ 137 | 138 | # Guidance Automation Toolkit 139 | *.gpState 140 | 141 | # ReSharper is a .NET coding add-in 142 | _ReSharper*/ 143 | *.[Rr]e[Ss]harper 144 | *.DotSettings.user 145 | 146 | # TeamCity is a build add-in 147 | _TeamCity* 148 | 149 | # DotCover is a Code Coverage Tool 150 | *.dotCover 151 | 152 | # AxoCover is a Code Coverage Tool 153 | .axoCover/* 154 | !.axoCover/settings.json 155 | 156 | # Visual Studio code coverage results 157 | *.coverage 158 | *.coveragexml 159 | 160 | # NCrunch 161 | _NCrunch_* 162 | .*crunch*.local.xml 163 | nCrunchTemp_* 164 | 165 | # MightyMoose 166 | *.mm.* 167 | AutoTest.Net/ 168 | 169 | # Web workbench (sass) 170 | .sass-cache/ 171 | 172 | # Installshield output folder 173 | [Ee]xpress/ 174 | 175 | # DocProject is a documentation generator add-in 176 | DocProject/buildhelp/ 177 | DocProject/Help/*.HxT 178 | DocProject/Help/*.HxC 179 | DocProject/Help/*.hhc 180 | DocProject/Help/*.hhk 181 | DocProject/Help/*.hhp 182 | DocProject/Help/Html2 183 | DocProject/Help/html 184 | 185 | # Click-Once directory 186 | publish/ 187 | 188 | # Publish Web Output 189 | *.[Pp]ublish.xml 190 | *.azurePubxml 191 | # Note: Comment the next line if you want to checkin your web deploy settings, 192 | # but database connection strings (with potential passwords) will be unencrypted 193 | *.pubxml 194 | *.publishproj 195 | 196 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 197 | # checkin your Azure Web App publish settings, but sensitive information contained 198 | # in these scripts will be unencrypted 199 | PublishScripts/ 200 | 201 | # NuGet Packages 202 | *.nupkg 203 | # NuGet Symbol Packages 204 | *.snupkg 205 | # The packages folder can be ignored because of Package Restore 206 | **/[Pp]ackages/* 207 | # except build/, which is used as an MSBuild target. 208 | !**/[Pp]ackages/build/ 209 | # Uncomment if necessary however generally it will be regenerated when needed 210 | #!**/[Pp]ackages/repositories.config 211 | # NuGet v3's project.json files produces more ignorable files 212 | *.nuget.props 213 | *.nuget.targets 214 | 215 | # Microsoft Azure Build Output 216 | csx/ 217 | *.build.csdef 218 | 219 | # Microsoft Azure Emulator 220 | ecf/ 221 | rcf/ 222 | 223 | # Windows Store app package directories and files 224 | AppPackages/ 225 | BundleArtifacts/ 226 | Package.StoreAssociation.xml 227 | _pkginfo.txt 228 | *.appx 229 | *.appxbundle 230 | *.appxupload 231 | 232 | # Visual Studio cache files 233 | # files ending in .cache can be ignored 234 | *.[Cc]ache 235 | # but keep track of directories ending in .cache 236 | !?*.[Cc]ache/ 237 | 238 | # Others 239 | ClientBin/ 240 | ~$* 241 | *~ 242 | *.dbmdl 243 | *.dbproj.schemaview 244 | *.jfm 245 | *.pfx 246 | *.publishsettings 247 | orleans.codegen.cs 248 | 249 | # Including strong name files can present a security risk 250 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 251 | #*.snk 252 | 253 | # Since there are multiple workflows, uncomment next line to ignore bower_components 254 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 255 | #bower_components/ 256 | 257 | # RIA/Silverlight projects 258 | Generated_Code/ 259 | 260 | # Backup & report files from converting an old project file 261 | # to a newer Visual Studio version. Backup files are not needed, 262 | # because we have git ;-) 263 | _UpgradeReport_Files/ 264 | Backup*/ 265 | UpgradeLog*.XML 266 | UpgradeLog*.htm 267 | ServiceFabricBackup/ 268 | *.rptproj.bak 269 | 270 | # SQL Server files 271 | *.mdf 272 | *.ldf 273 | *.ndf 274 | 275 | # Business Intelligence projects 276 | *.rdl.data 277 | *.bim.layout 278 | *.bim_*.settings 279 | *.rptproj.rsuser 280 | *- [Bb]ackup.rdl 281 | *- [Bb]ackup ([0-9]).rdl 282 | *- [Bb]ackup ([0-9][0-9]).rdl 283 | 284 | # Microsoft Fakes 285 | FakesAssemblies/ 286 | 287 | # GhostDoc plugin setting file 288 | *.GhostDoc.xml 289 | 290 | # Node.js Tools for Visual Studio 291 | .ntvs_analysis.dat 292 | node_modules/ 293 | 294 | # Visual Studio 6 build log 295 | *.plg 296 | 297 | # Visual Studio 6 workspace options file 298 | *.opt 299 | 300 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 301 | *.vbw 302 | 303 | # Visual Studio LightSwitch build output 304 | **/*.HTMLClient/GeneratedArtifacts 305 | **/*.DesktopClient/GeneratedArtifacts 306 | **/*.DesktopClient/ModelManifest.xml 307 | **/*.Server/GeneratedArtifacts 308 | **/*.Server/ModelManifest.xml 309 | _Pvt_Extensions 310 | 311 | # Paket dependency manager 312 | .paket/paket.exe 313 | paket-files/ 314 | 315 | # FAKE - F# Make 316 | .fake/ 317 | 318 | # CodeRush personal settings 319 | .cr/personal 320 | 321 | # Python Tools for Visual Studio (PTVS) 322 | __pycache__/ 323 | *.pyc 324 | 325 | # Cake - Uncomment if you are using it 326 | # tools/** 327 | # !tools/packages.config 328 | 329 | # Tabs Studio 330 | *.tss 331 | 332 | # Telerik's JustMock configuration file 333 | *.jmconfig 334 | 335 | # BizTalk build output 336 | *.btp.cs 337 | *.btm.cs 338 | *.odx.cs 339 | *.xsd.cs 340 | 341 | # OpenCover UI analysis results 342 | OpenCover/ 343 | 344 | # Azure Stream Analytics local run output 345 | ASALocalRun/ 346 | 347 | # MSBuild Binary and Structured Log 348 | *.binlog 349 | 350 | # NVidia Nsight GPU debugger configuration file 351 | *.nvuser 352 | 353 | # MFractors (Xamarin productivity tool) working folder 354 | .mfractor/ 355 | 356 | # Local History for Visual Studio 357 | .localhistory/ 358 | 359 | # BeatPulse healthcheck temp database 360 | healthchecksdb 361 | 362 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 363 | MigrationBackup/ 364 | 365 | # Ionide (cross platform F# VS Code tools) working folder 366 | .ionide/ 367 | 368 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project welcomes contributions and suggestions. Most contributions require you to 2 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 3 | and actually do, grant us the rights to use your contribution. For details, visit 4 | https://cla.microsoft.com. 5 | 6 | When you submit a pull request, a CLA-bot will automatically determine whether you need 7 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 8 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 9 | 10 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 11 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 12 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft JFR Streaming 2 | 3 | The `jfr-streaming` project provides a core library for configuring, starting, stopping, 4 | and reading [Java Flight Recording](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH170) 5 | files from a JVM. The code does not depend on the `jdk.jfr` 6 | module and will compile and run against JDK 8 or higher. It uses a connection to an MBean 7 | server, which can be the platform MBean server, or a remote MBean server connected by 8 | means of JMX. 9 | 10 | The goal of this project is a low-level library. Solving higher level problems, such 11 | as managing JFR across multiple JVMs, is not a goal of this project. 12 | 13 | ## Getting Started 14 | 15 | ### Maven Coordinates 16 | 17 | [![Maven Central](https://img.shields.io/maven-central/v/com.microsoft.jfr/jfr-streaming.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.microsoft.jfr%22%20AND%20a:%22jfr-streaming%22) 18 | 19 | ```xml 20 | 21 | com.microsoft.jfr 22 | jfr-streaming 23 | 1.2.0 24 | 25 | ``` 26 | 27 | ### Example 28 | 29 | This example illustrates some of the API. 30 | 31 | ```java 32 | ///usr/bin/env jbang "$0" "$@" ; exit $? 33 | //DEPS com.microsoft.jfr:jfr-streaming:1.2.0 34 | import java.io.IOException; 35 | import java.nio.file.Paths; 36 | import java.util.concurrent.TimeUnit; 37 | import java.lang.management.ManagementFactory; 38 | import javax.management.*; 39 | import com.microsoft.jfr.*; 40 | 41 | public class Sample { 42 | public static void main(String[] args) { 43 | MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer(); 44 | try { 45 | FlightRecorderConnection flightRecorderConnection = FlightRecorderConnection.connect(mBeanServer); 46 | RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build(); 47 | RecordingConfiguration recordingConfiguration = RecordingConfiguration.PROFILE_CONFIGURATION; 48 | 49 | try (Recording recording = flightRecorderConnection.newRecording(recordingOptions, recordingConfiguration)) { 50 | recording.start(); 51 | TimeUnit.SECONDS.sleep(10); 52 | recording.stop(); 53 | 54 | recording.dump(Paths.get(System.getProperty("user.dir"), "recording.jfr").toString()); 55 | System.out.println("JFR recording ready: recording.jfr"); 56 | } 57 | } catch (InstanceNotFoundException | IOException | JfrStreamingException | InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | You can run the code above with [jbang](https://www.jbang.dev): 65 | 66 | 1. Install jbang. 67 | 1. Save the code above in a local `Sample.java` file, or [download directly](https://raw.githubusercontent.com/microsoft/jfr-streaming/main/samples/jbang/Sample.java). 68 | 1. Run the code: `jbang Sample.java` 69 | 70 | ### Note on Oracle JDK 8 71 | 72 | For Oracle JDK 8, it may be necessary to unlock the Java Flight Recorder 73 | commercial feature with the JVM arg `-XX:+UnlockCommercialFeatures -XX:+FlightRecorder`. 74 | Starting with JDK 8u262, Java Flight Recorder is available for all OpenJDK distributions. 75 | 76 | ## Build and Test 77 | 78 | The build is vanilla Maven. 79 | 80 |
`mvn clean` - remove build artifacts 81 |
`mvn compile` - compile the source code 82 |
`mvn test` - run unit tests (this project uses TestNG) 83 |
`mvn package` - build the .jar file 84 | 85 | ## Contributing 86 | 87 | This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, view [Microsoft's CLA](https://cla.microsoft.com). 88 | 89 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 90 | 91 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 92 | 93 | ## License 94 | 95 | Microsoft JFR Streaming Library is licensed under the [MIT](https://github.com/microsoft/jfr-streaming/blob/master/LICENSE) license. 96 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 10 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 11 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 12 | 13 | ## Microsoft Support Policy 14 | 15 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 16 | -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 4.0.0 9 | 10 | 11 | com.microsoft.jfr 12 | microsoft-jfr 13 | 1.2.1-SNAPSHOT 14 | ../pom.xml 15 | 16 | 17 | jfr-streaming 18 | JFR Streaming Core 19 | Core implemenation of a portable API for accessing JFR. 20 | ${project.parent.url} 21 | 22 | 23 | 24 | org.testng 25 | testng 26 | test 27 | 28 | 29 | org.mockito 30 | mockito-testng 31 | test 32 | 33 | 34 | org.openjdk.jmc 35 | flightrecorder 36 | test 37 | 38 | 39 | 40 | junit 41 | junit 42 | 4.13.2 43 | test 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/FlightRecorderConnection.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.time.Instant; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | import javax.management.InstanceNotFoundException; 10 | import javax.management.MBeanException; 11 | import javax.management.MBeanServerConnection; 12 | import javax.management.MalformedObjectNameException; 13 | import javax.management.ObjectInstance; 14 | import javax.management.ObjectName; 15 | import javax.management.ReflectionException; 16 | import javax.management.openmbean.OpenDataException; 17 | import javax.management.openmbean.TabularData; 18 | 19 | /** 20 | * Represents a connection to a {@code jdk.management.jfr.FlightRecorderMXBean} of a JVM. 21 | * {@code FlightRecorderConnection} provides {@link #newRecording(RecordingOptions, RecordingConfiguration) API} to create 22 | * Java flight {@link Recording recordings}. More than one {@code Recording} can be created. 23 | *

24 | * To use this class, a {@code javax.management.MBeanServerConnection} is needed. 25 | * This class uses the connection to make calls to the MBean server and does not change 26 | * the state of the connection. Management of the connection is the concern of the caller 27 | * and use of a {@code FlightRecorderConnection} for an MBean server connection that is no 28 | * longer valid will result in {@code IOException} being thrown. 29 | *

30 | * The {@code MBeanServerConnection} can be a connection to any MBean server. 31 | * Typically, the connection is to the platform MBean server obtained by calling 32 | * {@code java.lang.management.ManagementFactory.getPlatformMBeanServer()}. The connection can 33 | * also be to a remote MBean server via {@code javax.management.remote.JMXConnector}. 34 | * Refer to the summary in the javadoc of the {@code javax.management} package and of the 35 | * {@code javax.management.remote} package for details. 36 | */ 37 | public class FlightRecorderConnection { 38 | 39 | private static final String JFR_OBJECT_NAME = "jdk.management.jfr:type=FlightRecorder"; 40 | 41 | /** 42 | * Create a connection to the {@code FlightRecorder} via JMX. This method either returns a 43 | * {@code FlightRecorderConnection}, or throws an exception. An {@code IOException} 44 | * indicates a problem with the connection to the MBean server. An {@code InstanceNotFoundException} 45 | * indicates that the FlightRecorder MBean is not registered on the target JVM. This could happen 46 | * if the target JVM does not support Java Flight Recorder, or if experimental features need to be 47 | * enabled on the target JVM. If an {@code InstanceNotFoundException} is thrown by a Java 8 JVM, 48 | * consider using {@link com.microsoft.jfr.dcmd.FlightRecorderDiagnosticCommandConnection}. 49 | * 50 | * @param mBeanServerConnection The {@code MBeanServerConnection} to the JVM. 51 | * @return A {@code FlightRecorderConnection}. 52 | * @throws IOException A communication problem occurred when talking to the MBean server. 53 | * @throws InstanceNotFoundException The FlightRecorder MBean is not registered on the target JVM. 54 | * @throws JfrStreamingException Wraps a {@code javax.management.MalformedObjectNameException} 55 | * and indicates a bug in this class. 56 | * @throws NullPointerException The {@code mBeanServerConnection} parameter is {@code null}. 57 | */ 58 | public static FlightRecorderConnection connect(MBeanServerConnection mBeanServerConnection) 59 | throws IOException, InstanceNotFoundException, JfrStreamingException { 60 | Objects.requireNonNull(mBeanServerConnection); 61 | try { 62 | ObjectName objectName = new ObjectName(JFR_OBJECT_NAME); 63 | ObjectInstance objectInstance = mBeanServerConnection.getObjectInstance(objectName); 64 | return new FlightRecorderConnection(mBeanServerConnection, objectInstance.getObjectName()); 65 | } catch (MalformedObjectNameException e) { 66 | // Not expected to happen. This exception comes from the ObjectName constructor. If 67 | // JFR_OBJECT_NAME is malformed, then this is an internal bug. 68 | throw new JfrStreamingException(JFR_OBJECT_NAME, e); 69 | } 70 | } 71 | 72 | /** 73 | * Create a {@link Recording} with the given options and configuration. The {@code Recording} is created 74 | * in the {@link Recording.State#NEW} state. The recording will use the default values of 75 | * {@code jdk.management.jfr.FlightRecorderMXBean} for a parameter passed as {@code null}. 76 | * @param recordingOptions The options to be used for the recording, or {@code null} for defaults. 77 | * @param recordingConfiguration The configuration to be used for the recording, or {@code null} for defaults. 78 | * @return A {@link Recording} object associated with this {@code FlightRecorderConnection}. 79 | */ 80 | public Recording newRecording( 81 | RecordingOptions recordingOptions, 82 | RecordingConfiguration recordingConfiguration) { 83 | return new Recording(this, recordingOptions, recordingConfiguration); 84 | } 85 | 86 | /** 87 | * Start a recording. This method creates a new recording, sets the configuration, and then starts the recording. 88 | * This method is called from the {@link Recording#start()} method. 89 | * @param recordingOptions The {@code RecordingOptions} which was passed to 90 | * the {@link #newRecording(RecordingOptions, RecordingConfiguration)} method. {@code null} is allowed. 91 | * @param recordingConfiguration The {@code RecordingConfiguration} which was passed to 92 | * the {@link #newRecording(RecordingOptions, RecordingConfiguration)} method. {@code null} is allowed. 93 | * @return The id of the recording. 94 | * @throws IOException A communication problem occurred when talking to the MBean server. 95 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 96 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 97 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 98 | * The cause may also be a {@code javax.management.openmbean.OpenDataException} 99 | * which indicates a bug in the code of this class. 100 | */ 101 | public long startRecording(RecordingOptions recordingOptions, RecordingConfiguration recordingConfiguration) 102 | throws IOException, JfrStreamingException { 103 | 104 | try { 105 | Object[] args = new Object[]{}; 106 | String[] argTypes = new String[]{}; 107 | final long id = (long) mBeanServerConnection.invoke(objectName, "newRecording", args, argTypes); 108 | 109 | if (recordingConfiguration != null) { 110 | setConfiguration(recordingConfiguration, id); 111 | } 112 | 113 | if (recordingOptions != null) { 114 | setOptions(recordingOptions, id); 115 | } 116 | 117 | args = new Object[]{id}; 118 | argTypes = new String[]{long.class.getName()}; 119 | mBeanServerConnection.invoke(objectName, "startRecording", args, argTypes); 120 | 121 | return id; 122 | } catch (OpenDataException |InstanceNotFoundException| MBeanException | ReflectionException e) { 123 | // In theory, we should never get these. 124 | throw new JfrStreamingException(e.getMessage(), e); 125 | } 126 | } 127 | 128 | private void setOptions(RecordingOptions recordingOptions, long id) throws OpenDataException, InstanceNotFoundException, MBeanException, ReflectionException, IOException { 129 | Map options = recordingOptions.getRecordingOptions(); 130 | if (options != null && !options.isEmpty()) { 131 | TabularData recordingOptionsParam = OpenDataUtils.makeOpenData(options); 132 | Object[] args = new Object[]{id, recordingOptionsParam}; 133 | String[] argTypes = new String[]{long.class.getName(), TabularData.class.getName()}; 134 | mBeanServerConnection.invoke(objectName, "setRecordingOptions", args, argTypes); 135 | } 136 | } 137 | 138 | private void setConfiguration(RecordingConfiguration recordingConfiguration, long id) throws OpenDataException, InstanceNotFoundException, MBeanException, ReflectionException, IOException { 139 | recordingConfiguration.invokeSetConfiguration(id, mBeanServerConnection, objectName); 140 | } 141 | 142 | /** 143 | * Stop a recording. This method is called from the {@link Recording#stop()} method. 144 | * @param id The id of the recording. 145 | * @throws IOException A communication problem occurred when talking to the MBean server. 146 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 147 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 148 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 149 | */ 150 | public void stopRecording(long id) throws IOException, JfrStreamingException { 151 | try { 152 | Object[] args = new Object[]{id}; 153 | String[] argTypes = new String[]{long.class.getName()}; 154 | mBeanServerConnection.invoke(objectName, "stopRecording", args, argTypes); 155 | } catch (InstanceNotFoundException|MBeanException|ReflectionException e) { 156 | throw new JfrStreamingException(e.getMessage(), e); 157 | } 158 | } 159 | 160 | /** 161 | * Writes recording data to the specified file. The recording must be started, but not necessarily stopped. 162 | * The {@code outputFile} argument is relevant to the machine where the JVM is running. 163 | * @param id The id of the recording. 164 | * @param outputFile the system-dependent file name where data is written, not {@code null} 165 | * @throws IOException A communication problem occurred when talking to the MBean server. 166 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 167 | */ 168 | public void dumpRecording(long id, String outputFile) throws IOException, JfrStreamingException { 169 | try { 170 | Object[] args = new Object[]{id,outputFile}; 171 | String[] argTypes = new String[]{long.class.getName(),String.class.getName()}; 172 | mBeanServerConnection.invoke(objectName, "copyTo", args, argTypes); 173 | } catch (InstanceNotFoundException|MBeanException|ReflectionException e) { 174 | throw new JfrStreamingException(e.getMessage(), e); 175 | } 176 | } 177 | 178 | /** 179 | * Creates a copy of an existing recording, useful for extracting parts of a recording. 180 | * The cloned recording contains the same recording data as the original, but it has a 181 | * new ID. If the original recording is running, then the clone is also running. 182 | * @param id The id of the recording being cloned. 183 | * @param stop Whether to stop the cloned recording. 184 | * @throws IOException A communication problem occurred when talking to the MBean server. 185 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 186 | * @return id of the recording 187 | */ 188 | public long cloneRecording(long id, boolean stop) throws IOException, JfrStreamingException { 189 | try { 190 | Object[] args = new Object[]{id,stop}; 191 | String[] argTypes = new String[]{long.class.getName(),boolean.class.getName()}; 192 | return (long) mBeanServerConnection.invoke(objectName, "cloneRecording", args, argTypes); 193 | } catch (InstanceNotFoundException|MBeanException|ReflectionException e) { 194 | throw new JfrStreamingException(e.getMessage(), e); 195 | } 196 | } 197 | 198 | /** 199 | * Get the Java Flight Recording as an {@code java.io.InputStream}. 200 | * This method is called from the {@link Recording#getStream(Instant, Instant, long)} method. 201 | * 202 | * The recording may contain data outside the {@code startTime} and {@code endTime} parameters. 203 | * Either or both of {@code startTime} and {@code endTime} may be {@code null}, in which case the 204 | * {@code FlightRecorderMXBean} will use a default value indicating the beginning and the end of the 205 | * recording, respectively. 206 | * 207 | * The {@code blockSize} parameter specifies the number of bytes to read with a call to 208 | * the {@code FlightRecorderMXBean#readStream(long)} method. Setting blockSize to a very high value 209 | * may result in an OutOfMemoryError or an IllegalArgumentException, if the JVM deems the value too 210 | * large to handle. 211 | * 212 | * @param id The id of the recording. 213 | * @param startTime The point in time to start the recording stream, possibly {@code null}. 214 | * @param endTime The point in time to end the recording stream, possibly {@code null}. 215 | * @param blockSize The number of bytes to read at a time. 216 | * @return A {@code InputStream} of the Java Flight Recording data. 217 | * @throws IOException A communication problem occurred when talking to the MBean server. 218 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 219 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 220 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 221 | * The cause may also be a {@code javax.management.openmbean.OpenDataException} 222 | * which indicates a bug in the code of this class. 223 | */ 224 | public InputStream getStream(long id, Instant startTime, Instant endTime, long blockSize) 225 | throws IOException, JfrStreamingException { 226 | Map options = new HashMap<>(); 227 | if (startTime != null) options.put("startTime", startTime.toString()); 228 | if (endTime != null) options.put("endTime", endTime.toString()); 229 | if (blockSize > 0) options.put("blockSize", Long.toString(blockSize)); 230 | 231 | try { 232 | TabularData streamOptions = OpenDataUtils.makeOpenData(options); 233 | Object[] args = new Object[]{id, streamOptions}; 234 | String[] argTypes = new String[]{long.class.getName(), TabularData.class.getName()}; 235 | long streamId = (long) mBeanServerConnection.invoke(objectName, "openStream", args, argTypes); 236 | return new JfrStream(mBeanServerConnection, objectName, streamId); 237 | } catch(OpenDataException|InstanceNotFoundException|MBeanException|ReflectionException e) { 238 | throw new JfrStreamingException(e.getMessage(), e); 239 | } 240 | } 241 | 242 | /** 243 | * Close the recording. This method is called from the {@link Recording#close()} method. 244 | * @param id The id of the recording. 245 | * @throws IOException A communication problem occurred when talking to the MBean server. 246 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 247 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 248 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 249 | */ 250 | public void closeRecording(long id) throws IOException, JfrStreamingException { 251 | try { 252 | Object[] args = new Object[]{id}; 253 | String[] argTypes = new String[]{long.class.getName()}; 254 | mBeanServerConnection.invoke(objectName, "closeRecording", args, argTypes); 255 | } catch (InstanceNotFoundException|MBeanException|ReflectionException e) { 256 | throw new JfrStreamingException(e.getMessage(), e); 257 | } 258 | } 259 | 260 | /** 261 | * Constructor is called from the static {@link FlightRecorderConnection#connect(MBeanServerConnection)} 262 | * method, and from the {@link com.microsoft.jfr.dcmd.FlightRecorderDiagnosticCommandConnection#connect(MBeanServerConnection)} 263 | * method. It is not possible to create a FlightRecorderConnection directly. 264 | * @param mBeanServerConnection The connection to the MBeanServer. 265 | * @param objectName The name of the MBean we are connected to. 266 | */ 267 | protected FlightRecorderConnection(MBeanServerConnection mBeanServerConnection, ObjectName objectName) { 268 | this.mBeanServerConnection = mBeanServerConnection; 269 | this.objectName = objectName; 270 | } 271 | 272 | /** The MBeanServerConnection. */ 273 | protected final MBeanServerConnection mBeanServerConnection; 274 | /** The ObjectName of the MBean we are connecting to. */ 275 | protected final ObjectName objectName; 276 | } 277 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/JfrStream.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | package com.microsoft.jfr; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import javax.management.InstanceNotFoundException; 8 | import javax.management.MBeanException; 9 | import javax.management.MBeanServerConnection; 10 | import javax.management.ObjectName; 11 | import javax.management.ReflectionException; 12 | 13 | /** 14 | * A JFR data stream backed by {@code jdk.management.jfr.FlightRecorderMXBean#readStream(long)}. 15 | */ 16 | class JfrStream extends InputStream { 17 | 18 | /* A default value for blockSize used by FlightRecorderMXBean#readStream(long) */ 19 | private static final long DEFAULT_BLOCKSIZE = Long.getLong("jfr.stream.blocksize", 50000L); 20 | 21 | /** 22 | * Get the default value for blockSize used to configure the FlightRecorderMXBean#readStream(long) stream. 23 | * The default is configurable by setting the {@code jfr.stream.blocksize} system property. 24 | * The {@code blockSize} is used to configure the maximum number of bytes to read 25 | * from underlying stream at a time. Setting blockSize to a very high value may result 26 | * in an exception if the Java Virtual Machine (JVM) deems the value too large to handle. 27 | * Refer to the javadoc for {@code jdk.management.jfr.FlightRecorderMXBean#openStream}. 28 | * @return The default blockSize for reading flight recording data 29 | */ 30 | public static long getDefaultBlockSize() { return DEFAULT_BLOCKSIZE; } 31 | 32 | private byte[] buffer; 33 | private int index = 0; 34 | private boolean EOF = false; 35 | // There is a recording id and an id you get from the recording for the stream. 36 | // streamId is the id for the stream. 37 | private final long streamid; 38 | private final MBeanServerConnection connection; 39 | private final ObjectName flightRecorder; 40 | 41 | /* package scope */ JfrStream(MBeanServerConnection connection, ObjectName flightRecorder, long streamid) { 42 | this.streamid = streamid; 43 | this.connection = connection; 44 | this.flightRecorder = flightRecorder; 45 | } 46 | 47 | @Override 48 | public int read() throws IOException { 49 | 50 | if (!EOF && index == 0) { 51 | Object[] params = new Object[] {streamid}; 52 | String[] signature = new String[] {long.class.getName()}; 53 | try { 54 | buffer = (byte[]) connection.invoke(flightRecorder, "readStream", params, signature); 55 | } catch (InstanceNotFoundException | MBeanException | ReflectionException e) { 56 | throw new IOException(e.getMessage(), e); 57 | } 58 | } 59 | 60 | if (EOF || (EOF = (buffer == null))) return -1; 61 | 62 | int b = buffer[index] & 0xFF; 63 | index = ++index % buffer.length; 64 | return b; 65 | } 66 | 67 | @Override 68 | public void close() throws IOException { 69 | Object[] params = new Object[] {streamid}; 70 | String[] signature = new String[] {long.class.getName()}; 71 | try { 72 | connection.invoke(flightRecorder, "closeStream", params, signature); 73 | } catch (InstanceNotFoundException | MBeanException | ReflectionException e) { 74 | throw new IOException(e.getMessage(), e); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/JfrStreamingException.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | /** 4 | * An JfrStreamingException is a wrapper around specific {@code javax.management.JMException} 5 | * instances which might be thrown, either directly or indirectly, by methods of this package. 6 | * Exceptions of this type are not expected from a well behaved JVM. 7 | * 8 | * The cause of an {@code JfrStreamingException} will be one of the following: 9 | *

10 | *
javax.management.InstanceNotFoundException
11 | *
The FlightRecorderMXBean is not found on the MBean server. This could happen 12 | * if the target JVM does not support Java Flight Recorder, or if experimental features 13 | * need to be enabled on the target JVM. An InstanceNotFoundException is not expected after 14 | * the connection is made to the FlightRecorderMXBean. 15 | *
16 | *
javax.management.MBeanException
17 | *
Represents "user defined" exceptions thrown by MBean methods in the agent. It "wraps" 18 | * the actual "user defined" exception thrown. This exception will be built by the MBeanServer 19 | * when a call to an MBean method results in an unknown exception.
20 | *
javax.management.ReflectionException
21 | *
Represents exceptions thrown in the MBean server when using the java.lang.reflect 22 | * classes to invoke methods on MBeans. It "wraps" the actual java.lang.Exception thrown.
23 | *
javax.management.MalformedObjectNameException
24 | *
The format of the string does not correspond to a valid ObjectName. This cause indicates 25 | * a bug in the com.microsoft.jfr package code.
26 | *
javax.management.openmbean.OpenDataException
27 | *
This exception is thrown when an open type, an open data or an open MBean 28 | * metadata info instance could not be constructed because one or more validity constraints 29 | * were not met. This cause indicates a bug in the com.microsoft.jfr package code.
30 | *
31 | */ 32 | public class JfrStreamingException extends Exception { 33 | 34 | private static final long serialVersionUID = 7394612902107510439L; 35 | 36 | /** 37 | * Construct a {@code JfrStreamingException} with a message and cause. 38 | * @param message The exception message. 39 | * @param cause The cause of the exception. 40 | */ 41 | public JfrStreamingException(String message, Exception cause) { 42 | super(message, cause); 43 | } 44 | 45 | /** 46 | * Construct a {@code JfrStreamingException} with a message only. 47 | * @param message The exception message. 48 | */ 49 | public JfrStreamingException(String message) { 50 | super(message); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/OpenDataUtils.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import javax.management.openmbean.*; 4 | import java.util.Map; 5 | 6 | class OpenDataUtils { 7 | 8 | private OpenDataUtils() { 9 | } 10 | 11 | /** 12 | * Convert the Map to TabularData 13 | * @param options A map of key-value pairs. 14 | * @return TabularData 15 | * @throws OpenDataException Can only be raised if there is a bug in this code. 16 | */ 17 | static TabularData makeOpenData(final Map options) throws OpenDataException { 18 | // Copied from newrelic-jfr-core 19 | final String typeName = "java.util.Map"; 20 | final String[] itemNames = new String[]{"key", "value"}; 21 | final OpenType[] openTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING}; 22 | final CompositeType rowType = new CompositeType(typeName, typeName, itemNames, itemNames, openTypes); 23 | final TabularType tabularType = new TabularType(typeName, typeName, rowType, new String[]{"key"}); 24 | final TabularDataSupport table = new TabularDataSupport(tabularType); 25 | 26 | for (Map.Entry entry : options.entrySet()) { 27 | Object[] itemValues = {entry.getKey(), entry.getValue()}; 28 | CompositeData element = new CompositeDataSupport(rowType, itemNames, itemValues); 29 | table.put(element); 30 | } 31 | return table; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/Recording.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | package com.microsoft.jfr; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.text.MessageFormat; 8 | import java.time.Instant; 9 | import java.util.Objects; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | /** 13 | * Provides a means to start, stop and dump recording data. 14 | * To create a {@code Recording}, use {@link FlightRecorderConnection#newRecording(RecordingOptions, RecordingConfiguration)}. 15 | * @see jdk.jfr.Recording 16 | */ 17 | public class Recording implements AutoCloseable { 18 | 19 | /** 20 | * A {@code Recording} may be in one of these states. Note that a {@code Recording} is 21 | * no longer usable once it is in the {@code CLOSED} state. Valid state transitions are: 22 | *
    23 | *
  • {@code NEW -> [RECORDING, STOPPED, CLOSED]}
  • 24 | *
  • {@code RECORDING -> [RECORDING, STOPPED, CLOSED]}
  • 25 | *
  • {@code STOPPED -> [RECORDING, STOPPED, CLOSED]}
  • 26 | *
  • {@code CLOSED -> [CLOSED]}
  • 27 | *
28 | * Calling a method on {@code Recording} that would cause an invalid transition 29 | * will raise an IllegalStateException. 30 | */ 31 | public enum State { 32 | /** 33 | * The {@code Recording} has been created. 34 | */ 35 | NEW, 36 | /** 37 | * The {@code Recording} has been started. 38 | */ 39 | RECORDING, 40 | /** 41 | * The {@code Recording} has been stopped. 42 | */ 43 | STOPPED, 44 | /** 45 | * The {@code Recording} has been closed. Once the 46 | * recording has been closed, it is no longer usable. 47 | */ 48 | CLOSED; 49 | } 50 | 51 | // Format for IllegalStateException that this class might throw 52 | // {0} is the state the code is trying to transition to. 53 | // {1} are the states that the instance could be in for a valid transition. 54 | private final static MessageFormat illegalStateFormat = new MessageFormat("Recording state {0} not in [{1}]"); 55 | 56 | /** 57 | * Helper for formatting the message for an IllegalStateException that may be thrown by methods of this class. 58 | * @param actual This is the state that the Recording is in currently 59 | * @param expected This is the state that the Recording should be in for a valid transition to occur 60 | * @param others Additional expected states 61 | * @return 62 | */ 63 | private static String createIllegalStateExceptionMessage(State actual, State expected, State... others) { 64 | String[] args = new String[]{actual.name(), expected.name()}; 65 | if (others != null) { 66 | for (State state : others) args[1] = args[1].concat(", ").concat(state.name()); 67 | } 68 | String msg = illegalStateFormat.format(args); 69 | return msg; 70 | } 71 | 72 | final private FlightRecorderConnection connection; 73 | final private RecordingOptions recordingOptions; 74 | final private RecordingConfiguration recordingConfiguration; 75 | 76 | private volatile long id = -1; 77 | private final AtomicReference state; 78 | 79 | /** 80 | * Create a {@code Recording}. Recordings are created from 81 | * {@link FlightRecorderConnection#newRecording(RecordingOptions, RecordingConfiguration)} 82 | * @param connection A connection to the FlightRecorderMXBean on an MBean server 83 | * @param recordingOptions The options to be used for the recording 84 | * @param recordingConfiguration The settings for events to be collected by the recording 85 | */ 86 | public Recording( 87 | FlightRecorderConnection connection, 88 | RecordingOptions recordingOptions, 89 | RecordingConfiguration recordingConfiguration) { 90 | this.connection = connection; 91 | this.recordingOptions = recordingOptions; 92 | this.recordingConfiguration = recordingConfiguration; 93 | this.state = new AtomicReference<>(State.NEW); 94 | } 95 | 96 | /** 97 | * Get the recording id. The recording does not have an id until the recording is started. 98 | * @return The recording id, or {@code -1} if the recording was never started. 99 | */ 100 | public long getId() { 101 | return id; 102 | } 103 | 104 | /** 105 | * Start a recording. A recording may not be started after it is closed. 106 | * @throws IOException A communication problem occurred when talking to the MBean server. 107 | * @throws IllegalStateException This {@code Recording} is closed. 108 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 109 | * @return The recording id. 110 | */ 111 | public long start() throws IOException, IllegalStateException, JfrStreamingException { 112 | // state transitions: NEW -> RECORDING or STOPPED -> RECORDING, otherwise remain in state 113 | State oldState = state.getAndUpdate(s -> s == State.NEW || s == State.STOPPED ? State.RECORDING : s); 114 | 115 | if (oldState == State.NEW || oldState == State.STOPPED) { 116 | id = connection.startRecording(recordingOptions, recordingConfiguration); 117 | } else if (oldState == State.CLOSED) { 118 | throw new IllegalStateException(createIllegalStateExceptionMessage(oldState, State.NEW, State.RECORDING, State.STOPPED)); 119 | } 120 | return id; 121 | } 122 | 123 | /** 124 | * Stop a recording. 125 | * @throws IOException A communication problem occurred when talking to the MBean server. 126 | * @throws IllegalStateException If the {@code Recording} is closed. 127 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 128 | */ 129 | public void stop() throws IOException, IllegalStateException, JfrStreamingException { 130 | // state transitions: RECORDING -> STOPPED, otherwise remain in state 131 | State oldState = state.getAndUpdate(s -> s == State.RECORDING ? State.STOPPED : s); 132 | if (oldState == State.RECORDING) { 133 | connection.stopRecording(id); 134 | } else if (oldState == State.CLOSED) { 135 | throw new IllegalStateException(createIllegalStateExceptionMessage(oldState, State.NEW, State.RECORDING, State.STOPPED)); 136 | } 137 | } 138 | 139 | /** 140 | * Writes recording data to the specified file. The recording must be started, but not necessarily stopped. 141 | * The {@code outputFile} argument is relevant to the machine where the JVM is running. 142 | * @param outputFile the system-dependent file name where data is written, not {@code null} 143 | * @throws IOException A communication problem occurred when talking to the MBean server. 144 | * @throws IllegalStateException If the {@code Recording} has not been started, or has been closed. 145 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 146 | * @throws NullPointerException If the {@code outputFile} argument is null. 147 | */ 148 | public void dump(String outputFile) throws IOException, IllegalStateException, JfrStreamingException { 149 | Objects.requireNonNull(outputFile, "outputFile may not be null"); 150 | State currentState = state.get(); 151 | if (currentState == State.RECORDING || currentState == State.STOPPED) { 152 | connection.dumpRecording(id, outputFile); 153 | } else { 154 | throw new IllegalStateException(createIllegalStateExceptionMessage(currentState, State.RECORDING, State.STOPPED)); 155 | } 156 | } 157 | 158 | /** 159 | * Creates a copy of an existing recording, useful for extracting parts of a recording. 160 | * The cloned recording contains the same recording data as the original, but it has a 161 | * new ID. If the original recording is running, then the clone is also running. 162 | * @param stop Whether to stop the cloned recording. 163 | * @return The cloned recording. 164 | * @throws IOException A communication problem occurred when talking to the MBean server. 165 | * @throws IllegalStateException If the {@code Recording} has not been started, or has been closed. 166 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 167 | */ 168 | public Recording clone(boolean stop) throws IOException, JfrStreamingException { 169 | State currentState = state.get(); 170 | if (currentState == State.RECORDING || currentState == State.STOPPED) { 171 | long newId = connection.cloneRecording(id, stop); 172 | Recording recordingClone = new Recording(this.connection, this.recordingOptions, this.recordingConfiguration); 173 | recordingClone.id = newId; 174 | recordingClone.state.set(stop ? State.STOPPED : currentState); 175 | return recordingClone; 176 | } else { 177 | throw new IllegalStateException(createIllegalStateExceptionMessage(currentState, State.RECORDING, State.STOPPED)); 178 | } 179 | } 180 | 181 | /** 182 | * Create a data stream for the specified interval using the default {@code blockSize}. 183 | * The stream may contain some data outside the given range. 184 | * @param startTime The start time for the stream, or {@code null} to get data from the start time of the recording. 185 | * @param endTime The end time for the stream, or {@code null} to get data until the end of the recording. 186 | * @return An {@code InputStream}, or {@code null} if no data is available in the interval. 187 | * @throws IOException A communication problem occurred when talking to the MBean server. 188 | * @throws IllegalStateException If the {@code Recording} has not been stopped. 189 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 190 | * @see JfrStream#getDefaultBlockSize() 191 | */ 192 | public InputStream getStream(Instant startTime, Instant endTime) 193 | throws IOException, IllegalStateException, JfrStreamingException { 194 | return getStream(startTime, endTime, JfrStream.getDefaultBlockSize()); 195 | } 196 | 197 | /** 198 | * Create a data stream for the specified interval using the given {@code blockSize}. 199 | * The stream may contain some data outside the given range. 200 | * The {@code blockSize} is used to configure the maximum number of bytes to read 201 | * from underlying stream at a time. Setting blockSize to a very high value may result 202 | * in an exception if the Java Virtual Machine (JVM) deems the value too large to handle. 203 | * Refer to the javadoc for {@code jdk.management.jfr.FlightRecorderMXBean#openStream}. 204 | * @param startTime The start time for the stream, or {@code null} to get data from the start time of the recording. 205 | * @param endTime The end time for the stream, or {@code null} to get data until the end of the recording. 206 | * @param blockSize The maximum number of bytes to read at a time. 207 | * @return An {@code InputStream}, or {@code null} if no data is available in the interval. 208 | * @throws IOException A communication problem occurred when talking to the MBean server. 209 | * @throws IllegalStateException If the {@code Recording} has not been stopped. 210 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 211 | */ 212 | public InputStream getStream(Instant startTime, Instant endTime, long blockSize) 213 | throws IOException, IllegalStateException, JfrStreamingException { 214 | // state transitions: remain in state 215 | State currentState = state.get(); 216 | if (currentState == State.STOPPED) { 217 | return connection.getStream(id, startTime, endTime, blockSize); 218 | } else { 219 | throw new IllegalStateException(createIllegalStateExceptionMessage(currentState, State.STOPPED)); 220 | } 221 | } 222 | 223 | /** 224 | * Get the current state of this {@code Recording}. 225 | * @return The current state of this {@code Recording}. 226 | */ 227 | public State getState() { 228 | return state.get(); 229 | } 230 | 231 | /** {@inheritDoc} */ 232 | @Override 233 | public void close() throws IOException { 234 | // state transitions: any -> CLOSED 235 | State oldState = state.getAndSet(State.CLOSED); 236 | if (oldState == State.RECORDING) { 237 | try { 238 | connection.stopRecording(id); 239 | connection.closeRecording(id); 240 | } catch (Throwable ignored) { 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/RecordingConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import javax.management.*; 4 | import javax.management.openmbean.OpenDataException; 5 | import javax.management.openmbean.TabularData; 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | /** 14 | * A flight recorder configuration controls the amount of data that is collected. 15 | */ 16 | public abstract class RecordingConfiguration { 17 | 18 | /** 19 | * Convenience for selecting the pre-defined 'default' configuration that is standard with the JDK. 20 | * The default configuration is suitable for continuous recordings. 21 | */ 22 | public static final RecordingConfiguration DEFAULT_CONFIGURATION = new PredefinedConfiguration("default"); 23 | 24 | /** 25 | * Convenience for referencing the 'profile' configuration that is standard with the JDK. 26 | * The profile configuration collects more events and is suitable for profiling an application. 27 | */ 28 | public static final RecordingConfiguration PROFILE_CONFIGURATION = new PredefinedConfiguration("profile"); 29 | 30 | 31 | 32 | /** 33 | * A pre-defined configuration is one which you could select with the 'settings' option 34 | * of the JVM option 'StartFlightRecording', for example {@code -XX:StartFlightRecording:settings=default.jfc}. 35 | */ 36 | public static class PredefinedConfiguration extends RecordingConfiguration { 37 | private final String configurationName; 38 | 39 | @Override 40 | void invokeSetConfiguration(long id, MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException { 41 | invokeSetConfiguration(configurationName, "setPredefinedConfiguration", id, mBeanServerConnection, objectName); 42 | } 43 | 44 | /** 45 | * Sets a pre-defined configuration to use with a {@code Recording}. 46 | * 47 | * @param configurationName The name of the pre-defined configuration, not {@code null}. 48 | * @throws NullPointerException if predefinedConfiguration is {@code null} 49 | */ 50 | public PredefinedConfiguration(String configurationName) { 51 | this.configurationName = configurationName; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return configurationName; 57 | } 58 | } 59 | 60 | /** 61 | * A configuration that is read from a jfc file 62 | */ 63 | public static class JfcFileConfiguration extends RecordingConfiguration { 64 | 65 | private final String configuration; 66 | 67 | /** 68 | * Sets a configuration from a jfc file to use with a {@code Recording}. 69 | * 70 | * @param configurationFile An InputStream containing the configuration file, not {@code null}. 71 | * @throws NullPointerException if predefinedConfiguration is {@code null} 72 | */ 73 | public JfcFileConfiguration(InputStream configurationFile) { 74 | this.configuration = readConfigurationFile(configurationFile); 75 | } 76 | 77 | private static String readConfigurationFile(InputStream inputStream) { 78 | if (inputStream != null) { 79 | return new BufferedReader(new InputStreamReader(inputStream)) 80 | .lines() 81 | .collect(Collectors.joining()); 82 | } else { 83 | throw new IllegalArgumentException("Null configuration provided"); 84 | } 85 | } 86 | 87 | @Override 88 | void invokeSetConfiguration(long id, MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException { 89 | invokeSetConfiguration(configuration, "setConfiguration", id, mBeanServerConnection, objectName); 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return configuration; 95 | } 96 | } 97 | 98 | /** 99 | * A configuration defined from a map. 100 | */ 101 | public static class MapConfiguration extends RecordingConfiguration { 102 | 103 | private final Map configuration; 104 | 105 | /** 106 | * Sets a configuration from a Map 107 | * @param configuration A map defining the JFR events to register. 108 | * For example: {jdk.ObjectAllocationInNewTLAB#enabled=true, jdk.ObjectAllocationOutsideTLAB#enabled=true} 109 | */ 110 | public MapConfiguration(Map configuration) { 111 | this.configuration = configuration; 112 | } 113 | 114 | @Override 115 | void invokeSetConfiguration(long id, MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException, OpenDataException { 116 | if (!configuration.isEmpty()) { 117 | TabularData configAsTabular = OpenDataUtils.makeOpenData(configuration); 118 | Object[] args = new Object[]{id, configAsTabular}; 119 | String[] argTypes = new String[]{long.class.getName(), TabularData.class.getName()}; 120 | mBeanServerConnection.invoke(objectName, "setRecordingSettings", args, argTypes); 121 | } 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return configuration.toString(); 127 | } 128 | } 129 | 130 | abstract void invokeSetConfiguration(long id, MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException, OpenDataException; 131 | 132 | static void invokeSetConfiguration(String configurationName, String getMbeanSetterFunction, long id, MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws InstanceNotFoundException, MBeanException, ReflectionException, IOException { 133 | if (configurationName.trim().length() > 0) { 134 | Object[] args = new Object[]{id, configurationName}; 135 | String[] argTypes = new String[]{long.class.getName(), String.class.getName()}; 136 | mBeanServerConnection.invoke(objectName, getMbeanSetterFunction, args, argTypes); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/RecordingOptions.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | package com.microsoft.jfr; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | /** 14 | * Options for the recording that control maximum recording size, age and duration, 15 | * and whether to store data to disk as it is recorded. To create {@code RecordingOptions}, 16 | * use {@link Builder}. 17 | *

18 | * The options must be set at the time the {@code Recording} is created via 19 | * {@link FlightRecorderConnection#newRecording(RecordingOptions, RecordingConfiguration)}. 20 | * A {@code RecordingOptions} is immutable which prevents attempts at changing the options 21 | * while a recording is in progress. 22 | *

23 | * A note on the API 24 | * It is enticing to want the Builder to take a {@code java.time.Duration} instead of a String for 25 | * the {@code maxAge} or {@code duration} API, or have the {@code maxAge} API take a long, or pass 26 | * a boolean for the others. The problem with this is twofold. First, it makes it difficult for the user of 27 | * the library to set values through system properties since the String value has to be converted. This is 28 | * not really an issue for a long or boolean arg, but it adds a lot of code for handling a Duration. And 29 | * it makes it difficult to document what value a user should provide for a system property. These are not 30 | * insurmountable problems. But they do add error-prone complexity. Secondly, the arguments to 31 | * FlightRecorderMXBean are String, so there would need to be a conversion from the Duration/boolean/long to 32 | * a String anyway. And then what API would you have for Recording? If Recording#getDuration returns a 33 | * Duration and this is called from the underlying code, then the underlying code has to do the conversion 34 | * and this creates a tight coupling between Recording and the underlying code. If Builder takes a Duration 35 | * and Recording returns a String, then the two APIs are not parallel (violates the rule of least surprise). 36 | * Sticking to a String based API resolves these issues. But it does mean that the Builder needs to validate 37 | * the args and potentially throw IllegalArgumentException. String makes the overall code so much simpler. 38 | */ 39 | public class RecordingOptions { 40 | 41 | /* Default values of some options. */ 42 | private static final String EMPTY_STRING = ""; 43 | private static final String NO_LIMIT = "0"; 44 | 45 | /* Options hash table keys. */ 46 | private enum Option { 47 | NAME("name", EMPTY_STRING), 48 | MAX_AGE("maxAge", NO_LIMIT), 49 | MAX_SIZE("maxSize", NO_LIMIT), 50 | DUMP_ON_EXIT("dumpOnExit", "false"), 51 | DESTINATION("destination", EMPTY_STRING), 52 | DISK("disk", "false"), 53 | DURATION("duration", NO_LIMIT); 54 | 55 | Option(String name, String defaultValue) { 56 | this.name = name; 57 | this.defaultValue = defaultValue; 58 | } 59 | private final String name; 60 | private final String defaultValue; /* not null! */ 61 | } 62 | 63 | /* If the arg is null or an empty String, return the Option's default. */ 64 | private static String normalize(String arg, Option option) { 65 | return arg == null || (arg = arg.trim()).isEmpty() ? option.defaultValue : arg; 66 | } 67 | 68 | /** 69 | * Builder for {@link RecordingOptions}. The builder builds up a hash table of options. 70 | * These options correspond to the recording options for a 71 | * {@code jdk.management.jfr.FlightRecorderMXBean}. 72 | */ 73 | public static class Builder { 74 | 75 | private final Map options = new HashMap<>(); 76 | 77 | /** 78 | * Constructor for a {@code Builder}. 79 | */ 80 | public Builder() {} 81 | 82 | /** 83 | * Sets the name for the recording file. 84 | * If the {@code name} is {@code null}, {@code name} will be set to the default value, 85 | * which an empty String. If {@code name} is the default value, the JVM will use the 86 | * recording id. 87 | * @param name The name for the recording. 88 | * @return {@code this} 89 | */ 90 | public Builder name(String name) { 91 | options.put( 92 | Option.NAME, 93 | normalize(name,Option.NAME) 94 | ); 95 | return this; 96 | } 97 | 98 | /** 99 | * Sets the length of time that data is kept on disk before a recording may 100 | * be deleted. If the value is "0", no limit is imposed. Otherwise, the value is the 101 | * string representation of a positive long value followed by an empty space and one 102 | * of the following units: 103 | *

    104 | *
  • "ns" (nanoseconds)
  • 105 | *
  • "us" (microseconds)
  • 106 | *
  • "ms" (milliseconds)
  • 107 | *
  • "s" (seconds)
  • 108 | *
  • "m" (minutes)
  • 109 | *
  • "h" (hours)
  • 110 | *
  • "d" (days)
  • 111 | *
112 | * For example, {@code "2 h"}, {@code "3 m"}. 113 | * 114 | * If the value is {@code null}, {@code maxAge} will be set to the default value, 115 | * which is "no limit". The JVM will ignore this setting if {@link #disk(String) disk} 116 | * is set to false. 117 | * 118 | * @param maxAge The maximum length of time that data is kept on disk. 119 | * @return {@code this} 120 | * @throws IllegalArgumentException The {@code maxAge} parameter is not formatted correctly, 121 | * or represents a negative value 122 | */ 123 | public Builder maxAge(String maxAge) throws IllegalArgumentException { 124 | options.put( 125 | Option.MAX_AGE, 126 | validateDuration(Option.MAX_AGE, maxAge) 127 | ); 128 | return this; 129 | } 130 | 131 | /** 132 | * Sets the size, measured in bytes, at which data is kept on disk. 133 | * If the value is {@code null}, {@code maxSize} will be set to the default value, 134 | * which is "no limit". The JVM will ignore this setting if {@link #disk(String) disk} 135 | * is set to false. 136 | * @param maxSize The maximum size, in bytes. 137 | * @return {@code this} 138 | * @throws IllegalArgumentException The {@code maxSize} parameter is not a positive long value. 139 | */ 140 | public Builder maxSize(String maxSize) throws IllegalArgumentException { 141 | long value = 0L; 142 | try { 143 | String numVal = normalize(maxSize, Option.MAX_SIZE); 144 | value = Long.parseLong(numVal); 145 | if (value < 0L) { 146 | throw new IllegalArgumentException("maxSize: " + value + " < 0"); 147 | } 148 | } catch (NumberFormatException e) { 149 | throw new IllegalArgumentException(e); 150 | } 151 | options.put( 152 | Option.MAX_SIZE, 153 | Long.toString(value) 154 | ); 155 | return this; 156 | } 157 | 158 | /** 159 | * If set to {@code "true"}, the JVM will dump recording data to disk on exit. 160 | * The default value is {@code "false"}. The {@code dumpOnExit} argument follows 161 | * the semantics of the {@code Boolean#valueOf(String)} method. 162 | * @param dumpOnExit Whether to dump recording data when the JVM exits. 163 | * @return {@code this} 164 | */ 165 | public Builder dumpOnExit(String dumpOnExit) { 166 | options.put( 167 | Option.DUMP_ON_EXIT, 168 | Boolean.valueOf(dumpOnExit).toString() 169 | ); 170 | return this; 171 | } 172 | 173 | /** 174 | * Set the path where recording data is written when the recording stops. 175 | * The path is relevant to the machine where the JVM is running. 176 | * If the path is relative, it is relative to the working directory from which the JVM 177 | * was started. 178 | * If the {@code destination} is {@code null}, {@code destination} will be set to the 179 | * default value, which an empty String, and the JVM will use the directory from which 180 | * the JVM was started. 181 | * @param destination A path to where recording data will be written. 182 | * @return {@code this} 183 | */ 184 | public Builder destination(String destination) { 185 | options.put( 186 | Option.DESTINATION, 187 | normalize(destination,Option.DESTINATION) 188 | ); 189 | return this; 190 | } 191 | 192 | /** 193 | * If {@code "true"}, data will be stored to disk as it is recorded. 194 | * The default value is {@code "false"}. The {@code disk} argument follows 195 | * the semantics of the {@code Boolean#valueOf(String)} method. 196 | * @param disk Whether to store data as it is recorded. 197 | * @return {@code this} 198 | */ 199 | public Builder disk(String disk) { 200 | options.put( 201 | Option.DISK, 202 | Boolean.valueOf(normalize(disk, Option.DISK)).toString() 203 | ); 204 | return this; 205 | } 206 | 207 | /** 208 | * Sets how long the recording should be running. The default is to have no limit. 209 | * If the value is "0", no limit is imposed. Otherwise, the value is the string 210 | * representation of a positive long value followed by an empty space and one 211 | * of the following units: 212 | *
    213 | *
  • "ns" (nanoseconds)
  • 214 | *
  • "us" (microseconds)
  • 215 | *
  • "ms" (milliseconds)
  • 216 | *
  • "s" (seconds)
  • 217 | *
  • "m" (minutes)
  • 218 | *
  • "h" (hours)
  • 219 | *
  • "d" (days)
  • 220 | *
221 | * For example, {@code "2 h"}, {@code "3 m"}. 222 | * 223 | * If the value is {@code null}, {@code duration} will be set to the default value, 224 | * which is "no limit". 225 | * 226 | * @param duration The maximum length of time a recording should be running. 227 | * @return {@code this} 228 | * @throws IllegalArgumentException The {@code duration} parameter is not formatted correctly, 229 | * or represents a negative value 230 | */ 231 | public Builder duration(String duration) throws IllegalArgumentException { 232 | options.put( 233 | Option.DURATION, 234 | validateDuration(Option.DURATION, duration) 235 | ); 236 | return this; 237 | } 238 | 239 | /** 240 | * Construct a {@code RecordingOptions} from the options that were set on this builder. 241 | * The {@code Builder} state is not reset by calling this method. 242 | * @return A {@code RecordingOptions}, never {@code null}. 243 | */ 244 | public RecordingOptions build() { 245 | return new RecordingOptions(this); 246 | } 247 | 248 | } 249 | 250 | /** 251 | * Constructor is private. Only the Builder can construct a RecordingOptions. 252 | * @param builder The builder that was used to parameterize the options. 253 | */ 254 | private RecordingOptions(Builder builder) { 255 | 256 | // Note we're converting from Map,String> to Map 257 | final Map options = builder.options; 258 | 259 | // 260 | // For each option, 261 | // if the option is not the default value 262 | // add the option name and value to recordingOptions 263 | // An option is the default value if 264 | // it was not set in the builder, 265 | // it was not set as a system property, 266 | // or was set in the builder, but to a default value (builder.setDuration(null), for example) 267 | // This stream does not modify builder.options. 268 | Map initRecordingOptions = 269 | Stream.of(Option.values()) 270 | .filter(option -> !option.defaultValue.equals(options.getOrDefault(option, option.defaultValue))) 271 | .collect(Collectors.toMap(option -> option.name, options::get)); 272 | 273 | // Due to a bug, some JVMs default "disk=true". So include "disk=false" (the documented default) 274 | // to insure consistent behaviour. 275 | if (!initRecordingOptions.containsKey(Option.DISK.name)) { 276 | initRecordingOptions.put(Option.DISK.name, Option.DISK.defaultValue); 277 | } 278 | this.recordingOptions = Collections.unmodifiableMap(initRecordingOptions); 279 | } 280 | 281 | /** 282 | * Get the recording name. The return value will be an empty String if 283 | * the option has not been set. 284 | * @return The {@code name} recording option, or an empty String. 285 | */ 286 | public String getName() { 287 | return recordingOptions.getOrDefault(Option.NAME.name, Option.NAME.defaultValue); 288 | } 289 | 290 | /** 291 | * Get the maximum length of time that data is kept on disk before it may 292 | * be deleted. The return value will be {@code "0""} if the option has not been set. 293 | * @return The {@code maxAge} recording option. 294 | */ 295 | public String getMaxAge() { 296 | return recordingOptions.getOrDefault(Option.MAX_AGE.name, Option.MAX_AGE.defaultValue); 297 | } 298 | 299 | /** 300 | * Get the maximum size, in bytes, at which data is kept on disk. The return 301 | * value will be {@code "0"} if the option has not been set. 302 | * @return The {@code maxSize} recording option. 303 | */ 304 | public String getMaxSize() { 305 | return recordingOptions.getOrDefault(Option.MAX_SIZE.name, Option.MAX_SIZE.defaultValue); 306 | } 307 | 308 | /** 309 | * Get whether to dump recording data to disk when the JVM exits. 310 | * @return {@code "true"} if recording data is dumped to disk on JVM exit. 311 | */ 312 | public String getDumpOnExit() { 313 | return recordingOptions.getOrDefault(Option.DUMP_ON_EXIT.name, Option.DUMP_ON_EXIT.defaultValue); 314 | } 315 | 316 | /** 317 | * Get the path to where recording data is written. The path is relevant to the 318 | * machine on which the JVM is running. The return value will be an empty String 319 | * if the option has not been set. 320 | * @return The path to where recording data is written. 321 | */ 322 | public String getDestination() { 323 | return recordingOptions.getOrDefault(Option.DESTINATION.name, Option.DESTINATION.defaultValue); 324 | } 325 | 326 | /** 327 | * Get whether to save flight recordings to disk. 328 | * @return {@code "true"} if flight recordings are saved to disk. 329 | */ 330 | public String getDisk() { 331 | return recordingOptions.getOrDefault(Option.DISK.name, Option.DISK.defaultValue); 332 | } 333 | 334 | /** 335 | * Get how long the recording should be running. The return value will be 336 | * {@code "0"} if the option has not been set. 337 | * @return The {@code duration} for a recording. 338 | */ 339 | public String getDuration() { 340 | return recordingOptions.getOrDefault(Option.DURATION.name, Option.DURATION.defaultValue); 341 | } 342 | 343 | /** 344 | * Get the read-only recording options. The keys are names of recording options 345 | * according to FlightRecorderMXBean. 346 | * @return A read-only map of the recording options. 347 | */ 348 | public Map getRecordingOptions() { 349 | return recordingOptions; 350 | } 351 | 352 | // The recording options. The keys are names of recording options 353 | // according to FlightRecorderMXBean. The value is a valid value for 354 | // the option. Options that take on default values should be absent. 355 | private final Map recordingOptions; 356 | 357 | // format for FlightRecorderMXBean maxAge and duration recording options 358 | private static final Pattern durationPattern = Pattern.compile("([-+]?\\d+)\\s*(\\w*)"); 359 | 360 | // If the durationString arg is a valid format, return the arg properly formatted for FlightRecorderMXBean. 361 | // The expected format is a positive number, followed by a space (optional), 362 | // followed by the units (one of ns, us, ms, s, m, h, d). 363 | private static String validateDuration(Option option, String durationString) throws IllegalArgumentException { 364 | 365 | if (durationString == null || durationString.trim().isEmpty()) { 366 | return option.defaultValue; 367 | } 368 | 369 | Matcher durationStringMatcher = durationPattern.matcher(durationString); 370 | if (durationStringMatcher.matches()) { 371 | 372 | final long value; 373 | try { 374 | value = Long.parseLong(durationStringMatcher.group(1)); 375 | } catch (NumberFormatException e) { 376 | throw new IllegalArgumentException(e); 377 | } 378 | 379 | if (value >= 0L) { 380 | 381 | final String units = durationStringMatcher.group(2); 382 | switch (units) { 383 | case "": 384 | return Long.toString(value); 385 | case "ns": 386 | case "us": 387 | case "ms": 388 | case "s": 389 | case "m": 390 | case "h": 391 | case "d": 392 | return Long.toString(value) + " " + units; 393 | } 394 | } 395 | } 396 | // TODO: i18n 397 | throw new IllegalArgumentException("bad format: " + option.name + " = \""+durationString+"\""); 398 | } 399 | 400 | } 401 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/dcmd/FlightRecorderDiagnosticCommandConnection.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr.dcmd; 2 | 3 | import com.microsoft.jfr.FlightRecorderConnection; 4 | import com.microsoft.jfr.JfrStreamingException; 5 | import com.microsoft.jfr.Recording; 6 | import com.microsoft.jfr.RecordingConfiguration; 7 | import com.microsoft.jfr.RecordingOptions; 8 | 9 | import javax.management.InstanceNotFoundException; 10 | import javax.management.MBeanException; 11 | import javax.management.MBeanServerConnection; 12 | import javax.management.MalformedObjectNameException; 13 | import javax.management.ObjectInstance; 14 | import javax.management.ObjectName; 15 | import javax.management.ReflectionException; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.time.Instant; 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | import java.util.Objects; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * Represents a connection to the {@code com.sun.management DiagnosticCommand} MBean of a JVM. 29 | * This is the same mechanism used by the Java jcmd tool. 30 | * {@code FlightRecorderDiagnosticCommandConnection} provides 31 | * {@link #newRecording(RecordingOptions, RecordingConfiguration) API} to create 32 | * Java flight {@link Recording recordings}. More than one {@code Recording} can be created. 33 | *

34 | * To use this class, a {@code javax.management.MBeanServerConnection} is needed. 35 | * This class uses the connection to make calls to the MBean server and does not change 36 | * the state of the connection. Management of the connection is the concern of the caller 37 | * and use of a {@code FlightRecorderConnection} for an MBean server connection that is no 38 | * longer valid will result in {@code IOException} being thrown. 39 | *

40 | * The {@code MBeanServerConnection} can be a connection to any MBean server. 41 | * Typically, the connection is to the platform MBean server obtained by calling 42 | * {@code java.lang.management.ManagementFactory.getPlatformMBeanServer()}. The connection can 43 | * also be to a remote MBean server via {@code javax.management.remote.JMXConnector}. 44 | * Refer to the summary in the javadoc of the {@code javax.management} package and of the 45 | * {@code javax.management.remote} package for details. 46 | * 47 | */ 48 | public class FlightRecorderDiagnosticCommandConnection extends FlightRecorderConnection { 49 | private static final String DIAGNOSTIC_COMMAND_OBJECT_NAME = "com.sun.management:type=DiagnosticCommand"; 50 | private static final String JFR_START_REGEX = "Started recording (.+?)\\. .*"; 51 | private static final Pattern JFR_START_PATTERN = Pattern.compile(JFR_START_REGEX, Pattern.DOTALL); 52 | 53 | // All JFR commands take String[] parameters 54 | private static final String[] signature = new String[]{"[Ljava.lang.String;"}; 55 | 56 | /** 57 | * Create a connection to the {@code FlightRecorder} via JMX. This method either returns a 58 | * {@code FlightRecorderConnection}, or throws an exception. An {@code IOException} 59 | * indicates a problem with the connection to the MBean server. An {@code InstanceNotFoundException} 60 | * indicates that the FlightRecorder MBean is not registered on the target JVM. This could happen 61 | * if the target JVM does not support Java Flight Recorder, or if expermental features need to be 62 | * enabled on the target JVM. 63 | * 64 | * @param mBeanServerConnection The {@code MBeanServerConnection} to the JVM. 65 | * @return A {@code FlightRecorderConnection}. 66 | * @throws IOException A communication problem occurred when talking to the MBean server. 67 | * @throws InstanceNotFoundException The FlightRecorder MBean is not registered on the target JVM. 68 | * @throws JfrStreamingException Wraps a {@code javax.management.MalformedObjectNameException} 69 | * and indicates a bug in this class. 70 | * @throws NullPointerException The {@code mBeanServerConnection} parameter is {@code null}. 71 | */ 72 | public static FlightRecorderConnection connect(MBeanServerConnection mBeanServerConnection) 73 | throws IOException, InstanceNotFoundException, JfrStreamingException { 74 | Objects.requireNonNull(mBeanServerConnection); 75 | try { 76 | ObjectInstance objectInstance = mBeanServerConnection.getObjectInstance(new ObjectName(DIAGNOSTIC_COMMAND_OBJECT_NAME)); 77 | ObjectName objectName = objectInstance.getObjectName(); 78 | assertCommercialFeaturesUnlocked(mBeanServerConnection, objectName); 79 | return new FlightRecorderDiagnosticCommandConnection(mBeanServerConnection, objectInstance.getObjectName()); 80 | } catch (MalformedObjectNameException e) { 81 | // Not expected to happen. This exception comes from the ObjectName constructor. If 82 | // DIAGNOSTIC_COMMAND_OBJECT_NAME is malformed, then this is an internal bug. 83 | throw new JfrStreamingException(DIAGNOSTIC_COMMAND_OBJECT_NAME, e); 84 | } 85 | } 86 | 87 | /* package scope for testing */ FlightRecorderDiagnosticCommandConnection(MBeanServerConnection mBeanServerConnection, ObjectName objectName) { 88 | super(mBeanServerConnection, objectName); 89 | } 90 | 91 | /** 92 | * Start a recording. This method creates a new recording, sets the configuration, and then starts the recording. 93 | * This method is called from the {@link Recording#start()} method. 94 | * 95 | * @param recordingOptions The {@code RecordingOptions} which was passed to 96 | * the {@link #newRecording(RecordingOptions, RecordingConfiguration)} method 97 | * @param recordingConfiguration The {@code RecordingConfiguration} which was passed to 98 | * the {@link #newRecording(RecordingOptions, RecordingConfiguration)} method 99 | * @return The id of the recording. 100 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 101 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 102 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 103 | * The cause may also be a {@code javax.management.openmbean.OpenDataException} 104 | * which indicates a bug in the code of this class. 105 | */ 106 | @Override 107 | public long startRecording(RecordingOptions recordingOptions, RecordingConfiguration recordingConfiguration) throws JfrStreamingException { 108 | 109 | if(recordingConfiguration instanceof RecordingConfiguration.MapConfiguration) { 110 | throw new JfrStreamingException("Map configuration not available for " + FlightRecorderDiagnosticCommandConnection.class.getSimpleName() + "."); 111 | } 112 | 113 | Object[] params = formOptions(recordingOptions, recordingConfiguration); 114 | 115 | // jfrStart returns "Started recording 2." and some more stuff, but all we care about is the name of the recording. 116 | try { 117 | String jfrStart = (String) mBeanServerConnection.invoke(objectName, "jfrStart", params, signature); 118 | String name; 119 | Matcher matcher = JFR_START_PATTERN.matcher(jfrStart); 120 | if (matcher.find()) { 121 | name = matcher.group(1); 122 | return Long.parseLong(name); 123 | } 124 | } catch (InstanceNotFoundException | IOException | ReflectionException | MBeanException e) { 125 | throw new JfrStreamingException("Failed to start recording", e); 126 | } 127 | throw new JfrStreamingException("Failed to start recording"); 128 | } 129 | 130 | private Object[] formOptions(RecordingOptions recordingOptions, RecordingConfiguration recordingConfiguration) throws JfrStreamingException { 131 | List options = recordingOptions.getRecordingOptions() 132 | .entrySet() 133 | .stream() 134 | .filter(kv -> !kv.getKey().equals("disk")) // not supported on Java 8 135 | .map(kv -> kv.getKey() + "=" + kv.getValue()) 136 | .collect(Collectors.toList()); 137 | 138 | if (!(recordingConfiguration instanceof RecordingConfiguration.PredefinedConfiguration)) { 139 | throw new JfrStreamingException("Java 8 currently only supports predefined configurations (default/profile)"); 140 | } 141 | 142 | List settings = Collections.singletonList( 143 | "settings=" + recordingConfiguration 144 | ); 145 | 146 | List params = new ArrayList<>(); 147 | params.addAll(settings); 148 | params.addAll(options); 149 | return mkParamsArray(params); 150 | } 151 | 152 | /** 153 | * Stop a recording. This method is called from the {@link Recording#stop()} method. 154 | * 155 | * @param id The id of the recording. 156 | * @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException}, 157 | * a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException} 158 | * and indicates an issue with the FlightRecorderMXBean in the JVM. 159 | */ 160 | @Override 161 | public void stopRecording(long id) throws JfrStreamingException { 162 | try { 163 | Object[] params = mkParams("name=" + id); 164 | mBeanServerConnection.invoke(objectName, "jfrStop", params, signature); 165 | } catch (InstanceNotFoundException | MBeanException | ReflectionException | IOException e) { 166 | throw new JfrStreamingException("Failed to stop recording", e); 167 | } 168 | } 169 | 170 | /** 171 | * Writes recording data to the specified file. The recording must be started, but not necessarily stopped. 172 | * The {@code outputFile} argument is relevant to the machine where the JVM is running. 173 | * 174 | * @param id The id of the recording. 175 | * @param outputFile the system-dependent file name where data is written, not {@code null} 176 | * @throws JfrStreamingException Wraps a {@code javax.management.JMException}. 177 | */ 178 | @Override 179 | public void dumpRecording(long id, String outputFile) throws JfrStreamingException { 180 | try { 181 | Object[] params = mkParams( 182 | "filename=" + outputFile, 183 | "recording=" + id, 184 | "compress=true" 185 | ); 186 | mBeanServerConnection.invoke(objectName, "jfrDump", params, signature); 187 | } catch (InstanceNotFoundException | MBeanException | ReflectionException | IOException e) { 188 | throw new JfrStreamingException("Failed to dump recording", e); 189 | } 190 | } 191 | 192 | /** 193 | * Not supported on Java 8 194 | * 195 | * @param id The id of the recording being cloned. 196 | * @param stop Whether to stop the cloned recording. 197 | * @return id of the recording 198 | */ 199 | @Override 200 | public long cloneRecording(long id, boolean stop) { 201 | throw new UnsupportedOperationException("Clone not supported on Java 8"); 202 | } 203 | 204 | /** 205 | * Not supported on Java 8 206 | */ 207 | @Override 208 | public InputStream getStream(long id, Instant startTime, Instant endTime, long blockSize) { 209 | throw new UnsupportedOperationException("getStream not supported on Java 8"); 210 | } 211 | 212 | /** 213 | * Not supported on Java 8 214 | */ 215 | @Override 216 | public void closeRecording(long id) { 217 | throw new UnsupportedOperationException("closeRecording not supported on Java 8"); 218 | } 219 | 220 | 221 | // visible for testing 222 | static void assertCommercialFeaturesUnlocked(MBeanServerConnection mBeanServerConnection, ObjectName objectName) throws JfrStreamingException { 223 | try { 224 | Object unlockedMessage = mBeanServerConnection.invoke(objectName, "vmCheckCommercialFeatures", null, null); 225 | if (unlockedMessage instanceof String) { 226 | boolean unlocked = ((String) unlockedMessage).contains("unlocked"); 227 | if (!unlocked) { 228 | throw new JfrStreamingException("Unlocking commercial features may be required. This must be explicitly enabled by adding -XX:+UnlockCommercialFeatures"); 229 | } 230 | } 231 | } catch (InstanceNotFoundException | MBeanException | ReflectionException | IOException e) { 232 | throw new JfrStreamingException("Unable to determine if commercial features are unlocked", e); 233 | } 234 | } 235 | 236 | private static Object[] mkParamsArray(List args) { 237 | return new Object[]{args.toArray(new String[0])}; 238 | } 239 | 240 | private static Object[] mkParams(String... args) { 241 | return new Object[]{args}; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/dcmd/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | /** 4 | * This package provides API for controlling Java Flight Recordings (JFR) 5 | * through the DiagnosticCommand MBean. The code in this package is meant to 6 | * provide a fallback for starting, stopping, and dumping a Java Flight Recording if 7 | * {@link com.microsoft.jfr.FlightRecorderConnection#connect(javax.management.MBeanServerConnection)} 8 | * throws an {@code InstanceNotFoundException} on a Java 8 JVM. 9 | */ 10 | package com.microsoft.jfr.dcmd; 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/microsoft/jfr/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | /** 4 | * This package provides API for controlling Java Flight Recordings (JFR) through Java Management Extensions (JMX). 5 | * 6 | * JDK 9 introduced the {@code jdk.jfr} API which is not available in JDK 8. The 7 | * {@code jdk.management.jfr.FlightRecorderMXBean} is available in OpenJDK 8u262 and higher. 8 | * By relying on JMX and the {@code jdk.management.jfr.FlightRecorderMXBean}, 9 | * the {@code com.microsoft.jfr} package provides access to JFR on local or remote JVMs. 10 | */ 11 | package com.microsoft.jfr; 12 | -------------------------------------------------------------------------------- /core/src/test/java/com/microsoft/jfr/RecordingConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import org.openjdk.jmc.common.item.IItem; 4 | import org.openjdk.jmc.common.item.IItemCollection; 5 | import org.openjdk.jmc.common.item.IItemIterable; 6 | import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException; 7 | import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit; 8 | import org.testng.annotations.AfterTest; 9 | import org.testng.annotations.BeforeTest; 10 | import org.testng.annotations.Test; 11 | 12 | import javax.management.RuntimeMBeanException; 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.time.Instant; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import static org.testng.Assert.assertTrue; 22 | import static org.testng.Assert.fail; 23 | 24 | public class RecordingConfigurationTest { 25 | 26 | FlightRecorderConnection flightRecorderConnection = null; 27 | 28 | @BeforeTest 29 | public void setup() { 30 | flightRecorderConnection = RecordingTest.getFlightRecorderConnection(); 31 | } 32 | 33 | @AfterTest 34 | public static void tearDown() { 35 | RecordingTest.tearDown(); 36 | } 37 | 38 | @Test(expectedExceptions = {IllegalArgumentException.class}) 39 | public void nullConfigThrows() { 40 | new RecordingConfiguration.JfcFileConfiguration(null); 41 | } 42 | 43 | @Test(expectedExceptions = RuntimeMBeanException.class) 44 | public void brokenJfcConfigFileThrowsError() { 45 | executeRecording("brokenJfcFile.jfc"); 46 | } 47 | 48 | @Test 49 | public void jfcFileFromInputStreamCanBeRead() { 50 | IItemCollection recordingContent = executeRecording("sampleJfcFile.jfc"); 51 | assertTrue(containsEvent(recordingContent, "jdk.ThreadAllocationStatistics")); 52 | } 53 | 54 | @Test 55 | public void mapConfiguration() { 56 | 57 | Map recordingConfigAsMap = new HashMap<>(); 58 | recordingConfigAsMap.put("jdk.ObjectAllocationInNewTLAB#enabled", "true"); 59 | recordingConfigAsMap.put("jdk.ObjectAllocationOutsideTLAB#enabled", "true"); 60 | 61 | RecordingConfiguration recordingConfiguration = new RecordingConfiguration.MapConfiguration(recordingConfigAsMap); 62 | 63 | IItemCollection recordingContent = excecuteRecordingWithConfig(recordingConfiguration); 64 | assertTrue(containsEvent(recordingContent, "jdk.ObjectAllocationInNewTLAB")); 65 | assertTrue(containsEvent(recordingContent, "jdk.ObjectAllocationOutsideTLAB")); 66 | } 67 | 68 | private boolean containsEvent(IItemCollection recordingContent, String eventName) { 69 | for (IItemIterable iItemIterable : recordingContent) { 70 | for (IItem iItem : iItemIterable) { 71 | String currentEvent = iItem.getType().getIdentifier(); 72 | if (currentEvent.equals(eventName)) { 73 | return true; 74 | } 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | private IItemCollection executeRecording(String configFile) { 81 | RecordingConfiguration.JfcFileConfiguration configuration = new RecordingConfiguration.JfcFileConfiguration(RecordingConfigurationTest.class.getClassLoader().getResourceAsStream(configFile)); 82 | return excecuteRecordingWithConfig(configuration); 83 | } 84 | 85 | private IItemCollection excecuteRecordingWithConfig(RecordingConfiguration configuration) { 86 | Path dumpFile = null; 87 | try { 88 | dumpFile = Paths.get(System.getProperty("user.dir"), "testRecordingDump_dumped.jfr"); 89 | Files.deleteIfExists(dumpFile); 90 | 91 | Recording recording = flightRecorderConnection.newRecording(null, configuration); 92 | long id = recording.start(); 93 | Instant now = Instant.now(); 94 | Instant then = now.plusSeconds(1); 95 | while (Instant.now().compareTo(then) < 0) { 96 | RecordingTest.fib(Short.MAX_VALUE); // do something 97 | } 98 | recording.stop(); 99 | recording.dump(dumpFile.toString()); 100 | assertTrue(Files.exists(dumpFile)); 101 | 102 | try { 103 | return JfrLoaderToolkit.loadEvents(dumpFile.toFile()); 104 | } catch (CouldNotLoadRecordingException e) { 105 | fail("Unable to load JFR data: ", e); 106 | } 107 | 108 | } catch (IllegalArgumentException badData) { 109 | fail("Issue in test data: " + badData.getMessage()); 110 | } catch (IOException ioe) { 111 | // possible that this can be thrown, but should not happen in this context 112 | fail("IOException not expected: ", ioe); 113 | } catch (JfrStreamingException badBean) { 114 | fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean); 115 | } finally { 116 | if (dumpFile != null) { 117 | try { 118 | Files.deleteIfExists(dumpFile); 119 | } catch (IOException ignore) { 120 | } 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /core/src/test/java/com/microsoft/jfr/RecordingOptionsTest.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import org.testng.annotations.DataProvider; 4 | import org.testng.annotations.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.testng.Assert.*; 10 | 11 | public class RecordingOptionsTest { 12 | 13 | @DataProvider(name="nameValues") 14 | public static Object[][] nameValues() { 15 | return new Object[][] { 16 | {"test", "test"}, 17 | {" test", "test"}, 18 | {" test ", "test"}, 19 | {"", ""}, 20 | {null, ""} 21 | }; 22 | } 23 | @Test(dataProvider = "nameValues") 24 | public void testGetName(String[] args) { 25 | String expected = args[1]; 26 | RecordingOptions opts = new RecordingOptions.Builder().name(args[0]).build(); 27 | assertEquals(opts.getName(), expected); 28 | } 29 | 30 | @Test 31 | public void testGetNameDefault() { 32 | String expected = ""; 33 | RecordingOptions opts = new RecordingOptions.Builder().build(); 34 | assertEquals(opts.getName(), expected); 35 | } 36 | 37 | @DataProvider(name="maxAgeGoodValues") 38 | public static Object[][] maxAgeGoodValues() { 39 | return new Object[][] { 40 | {"3 ns", "3 ns"}, 41 | {"3 us", "3 us"}, 42 | {"3 ms", "3 ms"}, 43 | {"3 s", "3 s"}, 44 | {"3 m", "3 m"}, 45 | {"3 h", "3 h"}, 46 | {"3 h", "3 h"}, 47 | {"+3 d", "3 d"}, 48 | {"3ms", "3 ms"}, 49 | {"0", "0"}, 50 | {"", "0"}, 51 | {null, "0"} 52 | }; 53 | } 54 | @Test(dataProvider = "maxAgeGoodValues") 55 | public void testGetMaxAge(String[] args) { 56 | RecordingOptions opts = new RecordingOptions.Builder().maxAge(args[0]).build(); 57 | assertEquals(opts.getMaxAge(), args[1]); 58 | } 59 | 60 | @Test 61 | public void testGetMaxAgeDefault() { 62 | String expected = "0"; 63 | RecordingOptions opts = new RecordingOptions.Builder().build(); 64 | assertEquals(opts.getMaxAge(), expected); 65 | } 66 | 67 | @DataProvider(name="maxAgeBadValues") 68 | public static Object[][] maxAgeBadValues() { 69 | return new Object[][] { 70 | {"-3 ms"}, 71 | {"3 ps"}, 72 | {"3.0 ms"}, 73 | {"3-ms"}, 74 | {"us"}, 75 | {"3_ms"}, 76 | {"3 _ms"}, 77 | {"3_ ms"}, 78 | {"3 _ ms"} 79 | }; 80 | } 81 | @Test(dataProvider = "maxAgeBadValues", expectedExceptions = {IllegalArgumentException.class}) 82 | public void testGetMaxAgeNegative(String[] args) { 83 | RecordingOptions opts = new RecordingOptions.Builder().maxAge(args[0]).build(); 84 | } 85 | 86 | @DataProvider(name="maxSizeGoodValues") 87 | public static Object[][] maxSizeGoodValues() { 88 | return new Object[][] { 89 | {"12345", "12345"}, 90 | {"+54321", "54321"}, 91 | {" 6789", "6789"}, 92 | {" 6789 ", "6789"}, 93 | {" 06789 ", "6789"}, 94 | {"0", "0"}, 95 | {"", "0"}, 96 | {null, "0"} 97 | }; 98 | } 99 | @Test(dataProvider = "maxSizeGoodValues") 100 | public void testGetMaxSize(String[] args) { 101 | RecordingOptions opts = new RecordingOptions.Builder().maxSize(args[0]).build(); 102 | assertEquals(opts.getMaxSize(), args[1]); 103 | } 104 | 105 | @Test 106 | public void testGetMaxSizeDefault() { 107 | String expected = "0"; 108 | RecordingOptions opts = new RecordingOptions.Builder().build(); 109 | assertEquals(opts.getMaxSize(), expected); 110 | } 111 | 112 | @DataProvider(name="maxSizeBadValues") 113 | public static Object[][] maxSizeBadValues() { 114 | return new Object[][] { 115 | {"-12345"}, 116 | {"5.4321"}, 117 | {"BAD"}, 118 | {"0xBEEF"} 119 | }; 120 | } 121 | @Test(dataProvider = "maxSizeBadValues", expectedExceptions = {IllegalArgumentException.class}) 122 | public void testGetMaxSizeNegative(String[] args) { 123 | RecordingOptions opts = new RecordingOptions.Builder().maxSize(args[0]).build(); 124 | } 125 | 126 | @Test 127 | public void testGetDumpOnExit() { 128 | String expected = "true"; 129 | RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit(expected).build(); 130 | assertEquals(opts.getDumpOnExit(), expected); 131 | } 132 | 133 | @Test 134 | public void testGetDumpOnExitDefault() { 135 | String expected = "false"; 136 | RecordingOptions opts = new RecordingOptions.Builder().build(); 137 | assertEquals(opts.getDumpOnExit(), expected); 138 | } 139 | 140 | @Test 141 | public void testGetDumpOnExitBadValue() { 142 | String expected = "false"; 143 | RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit("BAD_VALUE").build(); 144 | assertEquals(opts.getDumpOnExit(), expected); 145 | } 146 | 147 | @DataProvider(name="destinationValues") 148 | public static Object[][] destinationValues() { 149 | return new Object[][] { 150 | {"./destination", "./destination"}, 151 | {" ./destination", "./destination"}, 152 | {" ./destination ", "./destination"}, 153 | {"", ""}, 154 | {null, ""} 155 | }; 156 | } 157 | @Test(dataProvider = "destinationValues") 158 | public void testGetDestination(String[] args) { 159 | String expected = args[1]; 160 | RecordingOptions opts = new RecordingOptions.Builder().destination(args[0]).build(); 161 | assertEquals(opts.getDestination(), expected); 162 | } 163 | 164 | @Test 165 | public void testGetDestinationDefault() { 166 | String expected = ""; 167 | RecordingOptions opts = new RecordingOptions.Builder().build(); 168 | assertEquals(opts.getDestination(), expected); 169 | } 170 | 171 | @Test 172 | public void testGetDisk() { 173 | String expected = "true"; 174 | RecordingOptions opts = new RecordingOptions.Builder().disk(expected).build(); 175 | assertEquals(opts.getDisk(), expected); 176 | } 177 | 178 | @Test 179 | public void testGetDiskDefault() { 180 | String expected = "false"; 181 | RecordingOptions opts = new RecordingOptions.Builder().build(); 182 | assertEquals(opts.getDisk(), expected); 183 | } 184 | 185 | @Test 186 | public void testGetDiskBadValue() { 187 | String expected = "false"; 188 | RecordingOptions opts = new RecordingOptions.Builder().disk("BAD_VALUE").build(); 189 | assertEquals(opts.getDisk(), expected); 190 | } 191 | 192 | @DataProvider(name="durationGoodValues") 193 | public static Object[][] durationGoodValues() { 194 | return new Object[][] { 195 | {"3 ns", "3 ns"}, 196 | {"3 us", "3 us"}, 197 | {"3 ms", "3 ms"}, 198 | {"3 s", "3 s"}, 199 | {"3 m", "3 m"}, 200 | {"3 h", "3 h"}, 201 | {"3 h", "3 h"}, 202 | {"+3 d", "3 d"}, 203 | {"3ms", "3 ms"}, 204 | {"0", "0"}, 205 | {"", "0"}, 206 | {null, "0"} 207 | }; 208 | } 209 | @Test(dataProvider = "durationGoodValues") 210 | public void testGetDuration(String[] args) { 211 | String expected = args[1]; 212 | RecordingOptions opts = new RecordingOptions.Builder().duration(args[0]).build(); 213 | assertEquals(opts.getDuration(), expected); 214 | } 215 | 216 | @Test 217 | public void testGetDurationDefault() { 218 | String expected = "0"; 219 | RecordingOptions opts = new RecordingOptions.Builder().build(); 220 | assertEquals(opts.getDuration(), expected); 221 | } 222 | 223 | @DataProvider(name="durationBadValues") 224 | public static Object[][] durationBadValues() { 225 | return new Object[][] { 226 | {"-3 ms"}, 227 | {"3 ps"}, 228 | {"3.0 ms"}, 229 | {"3-ms"}, 230 | {"us"}, 231 | {"3_ms"}, 232 | {"3 _ms"}, 233 | {"3_ ms"}, 234 | {"3 _ ms"} 235 | }; 236 | } 237 | @Test(dataProvider = "durationBadValues", expectedExceptions = {IllegalArgumentException.class}) 238 | public void testGetDurationNegative(String[] args) { 239 | RecordingOptions opts = new RecordingOptions.Builder().duration(args[0]).build(); 240 | } 241 | 242 | @Test 243 | public void testGetRecordingOptions() { 244 | Map expected = new HashMap<>(); 245 | expected.put("name", "test"); 246 | expected.put("maxAge", "3 m"); 247 | expected.put("maxSize", "1048576"); 248 | expected.put("dumpOnExit", "true"); 249 | expected.put("destination", "test.jfr"); 250 | expected.put("disk", "true"); 251 | expected.put("duration", "120 s"); 252 | RecordingOptions opts = new RecordingOptions.Builder() 253 | .name("test") 254 | .maxAge("3 m") 255 | .maxSize("1048576") 256 | .dumpOnExit("true") 257 | .destination("test.jfr") 258 | .disk("true") 259 | .duration("120 s") 260 | .build(); 261 | assertEquals(opts.getRecordingOptions(), expected); 262 | } 263 | 264 | @Test 265 | public void testGetRecordingOptionsDefaults() { 266 | Map expected = new HashMap<>(); 267 | // Due to a bug, some JVMs default "disk=true". So include "disk=false" (the documented default) 268 | // to insure consistent behaviour. 269 | expected.put("disk", "false"); 270 | RecordingOptions opts = new RecordingOptions.Builder().build(); 271 | assertEquals(opts.getRecordingOptions(), expected); 272 | } 273 | } -------------------------------------------------------------------------------- /core/src/test/java/com/microsoft/jfr/RecordingTest.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.jfr; 2 | 3 | import org.testng.Reporter; 4 | import org.testng.annotations.AfterTest; 5 | import org.testng.annotations.BeforeTest; 6 | import org.testng.annotations.DataProvider; 7 | import org.testng.annotations.Test; 8 | 9 | import javax.management.InstanceNotFoundException; 10 | import javax.management.MBeanException; 11 | import javax.management.MBeanServerConnection; 12 | import javax.management.MalformedObjectNameException; 13 | import javax.management.ObjectInstance; 14 | import javax.management.ObjectName; 15 | import javax.management.ReflectionException; 16 | import javax.management.RuntimeMBeanException; 17 | import javax.management.openmbean.CompositeData; 18 | import javax.management.openmbean.TabularData; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | import java.lang.management.ManagementFactory; 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.lang.reflect.Method; 27 | import java.math.BigDecimal; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | import java.nio.file.Paths; 31 | import java.time.Instant; 32 | import java.util.Collection; 33 | import java.util.Locale; 34 | 35 | import static org.testng.Assert.assertEquals; 36 | import static org.testng.Assert.assertTrue; 37 | import static org.testng.Assert.fail; 38 | 39 | public class RecordingTest { 40 | 41 | FlightRecorderConnection flightRecorderConnection = null; 42 | 43 | @BeforeTest 44 | public void setup() { 45 | flightRecorderConnection = getFlightRecorderConnection(); 46 | } 47 | 48 | public static FlightRecorderConnection getFlightRecorderConnection() { 49 | MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer(); 50 | try { 51 | return FlightRecorderConnection.connect(mBeanServer); 52 | } catch (InstanceNotFoundException e) { 53 | fail("Either JVM does not support JFR, or experimental options need to be enabled", e); 54 | } catch (IOException e) { 55 | // possible that this can be thrown, but should not happen in this context 56 | fail("IOException not expected", e); 57 | } catch (JfrStreamingException reallyBad) { 58 | fail ("something really bad happened", reallyBad); 59 | } 60 | return null; 61 | } 62 | 63 | @AfterTest 64 | public static void tearDown() { 65 | try { 66 | Path userDir = Paths.get(System.getProperty("user.dir")); 67 | Files.list(userDir) 68 | .filter(Files::isRegularFile) 69 | .filter(path -> path.toString().endsWith(".jfr")) 70 | .forEach(jfrFile -> { 71 | try { 72 | Files.delete(jfrFile); 73 | } catch (IOException ignored) { 74 | Reporter.log(ignored.getMessage()); 75 | } 76 | }); 77 | } catch (IOException ignored) { 78 | Reporter.log(ignored.getMessage()); 79 | } 80 | } 81 | 82 | @Test 83 | public void assertNewRecordingInitialValues() { 84 | assert flightRecorderConnection != null; 85 | Recording recording = flightRecorderConnection.newRecording(null, null); 86 | assertEquals(recording.getState(), Recording.State.NEW); 87 | assertEquals(recording.getId(), -1); 88 | } 89 | 90 | 91 | @Test 92 | public void assertRecordingStartIdAndState() { 93 | assert flightRecorderConnection != null; 94 | Recording recording = flightRecorderConnection.newRecording(null, null); 95 | try { 96 | long id = recording.start(); 97 | assertEquals(recording.getId(), id); 98 | assertEquals(recording.getState(), Recording.State.RECORDING); 99 | } catch (IOException|IllegalStateException| JfrStreamingException e) { 100 | fail("assertRecordingStartIdAndState caught exception", e); 101 | } 102 | } 103 | 104 | @Test 105 | public void assertRecordingStopState() { 106 | assert flightRecorderConnection != null; 107 | Recording recording = flightRecorderConnection.newRecording(null, null); 108 | try { 109 | long id = recording.start(); 110 | assertEquals(recording.getId(), id); 111 | recording.stop(); 112 | assertEquals(recording.getState(), Recording.State.STOPPED); 113 | } catch (IOException|IllegalStateException| JfrStreamingException e) { 114 | fail("assertRecordingStopState caught exception", e); 115 | } 116 | } 117 | 118 | @Test 119 | public void assertRecordingCloseState() { 120 | assert flightRecorderConnection != null; 121 | Recording recording = flightRecorderConnection.newRecording(null, null); 122 | try { 123 | long id = recording.start(); 124 | assertEquals(recording.getId(), id); 125 | recording.close(); 126 | assertEquals(recording.getState(), Recording.State.CLOSED); 127 | } catch (IOException|IllegalStateException| JfrStreamingException e) { 128 | fail("assertRecordingCloseState caught exception", e); 129 | } 130 | } 131 | 132 | static void reflectivelyInvokeMethods(Recording recording, Object[] args) throws Exception { 133 | Class clazz = (Class) recording.getClass(); 134 | Method[] methods = clazz.getDeclaredMethods(); 135 | for (int argc = 0; argc < args.length; ) { 136 | String methodName = (String) args[argc++]; 137 | Method method = null; 138 | for (Method m : methods) { 139 | if (m.getName().equals(methodName)) { 140 | if ("getStream".equals(m.getName())) { 141 | if (m.getParameterTypes().length < 3) { 142 | // Always pick getStream(Instant,Instant,long) 143 | continue; 144 | } 145 | } 146 | method = m; 147 | break; 148 | } 149 | } 150 | if (method == null) { 151 | throw new NoSuchMethodException(methodName + " not found in declared methods of " + clazz.getName()); 152 | } 153 | Class[] parameterTypes = method.getParameterTypes(); 154 | Object [] methodArgs = new Object[parameterTypes.length]; 155 | int index = 0; 156 | for(Class type : parameterTypes) { 157 | if ("boolean".equals(type.getName())) { 158 | methodArgs[index++] = ((Boolean)args[argc++]).booleanValue(); 159 | } else if ("long".equals(type.getName())) { 160 | methodArgs[index++] = ((Long)args[argc++]).longValue(); 161 | } else { 162 | methodArgs[index++] = type.cast(args[argc++]); 163 | } 164 | } 165 | method.invoke(recording, methodArgs); 166 | } 167 | } 168 | 169 | @DataProvider(name="validStateChanges") 170 | public static Object[][] validStateChanges() { 171 | return new Object[][]{ 172 | {"start"}, 173 | {"start", "start"}, 174 | {"stop"}, 175 | {"stop", "stop"}, 176 | {"close"}, 177 | {"close", "close"}, 178 | {"start", "stop"}, 179 | {"start", "stop", "start"}, 180 | {"start", "stop", "start", "close"}, 181 | {"start", "close"}, 182 | {"start", "stop", "close"}, 183 | {"start", "clone", false}, 184 | {"start", "stop", "clone", false}, 185 | {"start", "stop", "getStream", null, null, 500000L}, 186 | {"start", "dump", "test.jfr", "stop"}, 187 | {"start", "stop", "dump", "test.jfr", "stop"}, 188 | {"start", "stop", "dump", "test.jfr", "close"} 189 | }; 190 | }; 191 | 192 | @Test(dataProvider = "validStateChanges") 193 | public void assertValidStateChangeNoException(Object[] args) { 194 | assert flightRecorderConnection != null; 195 | Recording recording = flightRecorderConnection.newRecording(null, null); 196 | try { 197 | reflectivelyInvokeMethods(recording, args); 198 | } catch (Exception e) { 199 | if (e instanceof InvocationTargetException && e.getCause() instanceof IllegalStateException) { 200 | fail("IllegalStateException was not expected"); 201 | } 202 | fail("Bad test code", e); 203 | } 204 | } 205 | 206 | @DataProvider(name="invalidStateChanges") 207 | public static Object[][] invalidStateChanges() { 208 | return new Object[][]{ 209 | {"getStream", null, null, 500000L}, 210 | {"dump", "test.jfr"}, 211 | {"close", "start"}, 212 | {"close", "stop"}, 213 | {"start", "close", "stop"}, 214 | {"start", "close", "clone", false}, 215 | {"start", "close", "dump", "test.jfr"}, 216 | {"start", "getStream", null, null, 500000L}, 217 | {"start", "close", "getStream", null, null, 500000L} 218 | }; 219 | }; 220 | 221 | @Test(dataProvider = "invalidStateChanges", expectedExceptions = {IllegalStateException.class}) 222 | public void assertInvalidStateChangeThrowsIllegalStateException(Object[] args) { 223 | assert flightRecorderConnection != null; 224 | Recording recording = flightRecorderConnection.newRecording(null, null); 225 | try { 226 | reflectivelyInvokeMethods(recording, args); 227 | } catch (Exception e) { 228 | if (e instanceof InvocationTargetException && e.getCause() instanceof IllegalStateException) { 229 | throw (IllegalStateException)e.getCause(); 230 | } 231 | fail("Bad test code", e); 232 | } 233 | } 234 | 235 | @DataProvider(name="options") 236 | public static Object[][] options() { 237 | return new Object[][] { 238 | {""}, 239 | {"name=test"}, 240 | {"maxAge=30 s", "disk=true"}, 241 | {"maxSize=1048576","disk=true"}, 242 | {"dumpOnExit=true"}, 243 | {"destination=temp.jfr","disk=true"}, 244 | {"duration=30 s"}, 245 | {"name=test", "maxAge=30 s", "maxSize=1048576","dumpOnExit=true","destination=temp.jfr","disk=true","duration=30 s"}, 246 | }; 247 | } 248 | 249 | @Test(dataProvider = "options") 250 | public void assertRecordingOptionsAreSetInFlightRecorderMXBean(String[] options) { 251 | try { 252 | MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer(); 253 | ObjectName flightRecorder = new ObjectName("jdk.management.jfr:type=FlightRecorder"); 254 | ObjectInstance objectInstance = mBeanServer.getObjectInstance(flightRecorder); 255 | RecordingOptions.Builder builder = new RecordingOptions.Builder(); 256 | for(String opt : options) { 257 | String[] keyValue = opt.split("="); 258 | if (keyValue.length < 2) continue; 259 | String key = keyValue[0]; 260 | String value = keyValue[1]; 261 | Method method = RecordingOptions.Builder.class.getMethod(key, String.class); 262 | method.invoke(builder, value); 263 | } 264 | RecordingOptions recordingOptions = builder.build(); 265 | Recording recording = flightRecorderConnection.newRecording(recordingOptions, null); 266 | long id = recording.start(); 267 | TabularData flightRecorderMXBeanOptions = 268 | (TabularData)mBeanServer.invoke(flightRecorder, "getRecordingOptions", new Object[]{id}, new String[]{long.class.getName()}); 269 | ((Collection)flightRecorderMXBeanOptions.values()) 270 | .forEach(compositeData -> { 271 | String key = (String)compositeData.get("key"); 272 | String getter = "get" + key.substring(0,1).toUpperCase(Locale.ROOT) + key.substring(1); 273 | String expected = (String)compositeData.get("value"); 274 | try { 275 | Method method = RecordingOptions.class.getMethod(getter); 276 | String actual = (String) method.invoke(recordingOptions); 277 | // special case for name since FlightRecorderMXBean returns id as default 278 | // and for destination since FlightRecorderMXBean returns null as default 279 | if (!("name".equals(key) && "".equals(actual)) 280 | && !("destination".equals(key) && "".equals(actual))) { 281 | assertEquals(actual, expected, getter); 282 | } 283 | } catch (NoSuchMethodException|IllegalArgumentException|IllegalAccessException|InvocationTargetException badAPI) { 284 | fail("Issue in RecordingOptions API: " + badAPI.getMessage()); 285 | } 286 | } 287 | ); 288 | recording.stop(); 289 | recording.close(); 290 | } catch (NoSuchMethodException|IllegalArgumentException badData) { 291 | fail("Issue in test data: " + badData.getMessage()); 292 | } catch (InvocationTargetException|IllegalAccessException badCode) { 293 | fail("Issue in code: " + badCode); 294 | } catch (IOException ioe) { 295 | // possible that this can be thrown, but should not happen in this context 296 | fail("IOException not expected: ", ioe); 297 | } catch (JfrStreamingException | ReflectionException| MBeanException badBean) { 298 | fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean); 299 | } catch (MalformedObjectNameException badTest) { 300 | fail("Error internal to the test: ", badTest); 301 | } catch (InstanceNotFoundException badJvm) { 302 | fail("Either JVM does not support JFR, or experimental options need to be enabled"); 303 | } catch (RuntimeMBeanException ex) { 304 | // some versions of java don't support the 'destination' option 305 | if (!(ex.getCause() instanceof IllegalArgumentException)) { 306 | fail("Something bad happened", ex); 307 | } 308 | } 309 | } 310 | 311 | // something to do 312 | protected static void fib(int limit) { 313 | BigDecimal[] fibs = new BigDecimal[limit]; 314 | fibs[0] = new BigDecimal(0); 315 | fibs[1] = new BigDecimal(1); 316 | for(int i=2; i 2 | 3 | 4 | true 5 | everyChunk 6 | 7 | -------------------------------------------------------------------------------- /core/src/test/resources/sampleJfcFile.jfc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | everyChunk 6 | 7 | 8 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.1.1 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "`uname`" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=`java-config --jre-home` 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && 89 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="`which javac`" 94 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=`which readlink` 97 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 98 | if $darwin ; then 99 | javaHome="`dirname \"$javaExecutable\"`" 100 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 101 | else 102 | javaExecutable="`readlink -f \"$javaExecutable\"`" 103 | fi 104 | javaHome="`dirname \"$javaExecutable\"`" 105 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="`\\unset -f command; \\command -v java`" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=`cd "$wdir/.."; pwd` 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir"; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | echo "$(tr -s '\n' ' ' < "$1")" 164 | fi 165 | } 166 | 167 | BASE_DIR=$(find_maven_basedir "$(dirname $0)") 168 | if [ -z "$BASE_DIR" ]; then 169 | exit 1; 170 | fi 171 | 172 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | echo $MAVEN_PROJECTBASEDIR 175 | fi 176 | 177 | ########################################################################################## 178 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 179 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 180 | ########################################################################################## 181 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 182 | if [ "$MVNW_VERBOSE" = true ]; then 183 | echo "Found .mvn/wrapper/maven-wrapper.jar" 184 | fi 185 | else 186 | if [ "$MVNW_VERBOSE" = true ]; then 187 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 188 | fi 189 | if [ -n "$MVNW_REPOURL" ]; then 190 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 191 | else 192 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 193 | fi 194 | while IFS="=" read key value; do 195 | case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; 196 | esac 197 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 198 | if [ "$MVNW_VERBOSE" = true ]; then 199 | echo "Downloading from: $wrapperUrl" 200 | fi 201 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 202 | if $cygwin; then 203 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 204 | fi 205 | 206 | if command -v wget > /dev/null; then 207 | QUIET="--quiet" 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found wget ... using wget" 210 | QUIET="" 211 | fi 212 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 213 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" 214 | else 215 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" 216 | fi 217 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 218 | elif command -v curl > /dev/null; then 219 | QUIET="--silent" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Found curl ... using curl" 222 | QUIET="" 223 | fi 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L 228 | fi 229 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 230 | else 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Falling back to using Java to download" 233 | fi 234 | javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 235 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" 236 | # For Cygwin, switch paths to Windows format before running javac 237 | if $cygwin; then 238 | javaSource=`cygpath --path --windows "$javaSource"` 239 | javaClass=`cygpath --path --windows "$javaClass"` 240 | fi 241 | if [ -e "$javaSource" ]; then 242 | if [ ! -e "$javaClass" ]; then 243 | if [ "$MVNW_VERBOSE" = true ]; then 244 | echo " - Compiling MavenWrapperDownloader.java ..." 245 | fi 246 | # Compiling the Java class 247 | ("$JAVA_HOME/bin/javac" "$javaSource") 248 | fi 249 | if [ -e "$javaClass" ]; then 250 | # Running the downloader 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo " - Running MavenWrapperDownloader.java ..." 253 | fi 254 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 255 | fi 256 | fi 257 | fi 258 | fi 259 | ########################################################################################## 260 | # End of extension 261 | ########################################################################################## 262 | 263 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 264 | 265 | # For Cygwin, switch paths to Windows format before running java 266 | if $cygwin; then 267 | [ -n "$JAVA_HOME" ] && 268 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 269 | [ -n "$CLASSPATH" ] && 270 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 271 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 272 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 273 | fi 274 | 275 | # Provide a "standardized" way to retrieve the CLI args that will 276 | # work with both Windows and non-Windows executions. 277 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 278 | export MAVEN_CMD_LINE_ARGS 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | $MAVEN_DEBUG_OPTS \ 285 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 286 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 287 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 288 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.1.1 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM Provide a "standardized" way to retrieve the CLI args that will 157 | @REM work with both Windows and non-Windows executions. 158 | set MAVEN_CMD_LINE_ARGS=%* 159 | 160 | %MAVEN_JAVA_EXE% ^ 161 | %JVM_CONFIG_MAVEN_PROPS% ^ 162 | %MAVEN_OPTS% ^ 163 | %MAVEN_DEBUG_OPTS% ^ 164 | -classpath %WRAPPER_JAR% ^ 165 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 166 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 167 | if ERRORLEVEL 1 goto error 168 | goto end 169 | 170 | :error 171 | set ERROR_CODE=1 172 | 173 | :end 174 | @endlocal & set ERROR_CODE=%ERROR_CODE% 175 | 176 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 177 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 178 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 179 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 180 | :skipRcPost 181 | 182 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 183 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 184 | 185 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 186 | 187 | cmd /C exit /B %ERROR_CODE% 188 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 4.0.0 9 | com.microsoft.jfr 10 | microsoft-jfr 11 | pom 12 | 1.2.1-SNAPSHOT 13 | 14 | Java Flight Recorder Streaming Library 15 | A library for streaming JFR files via the FlightRecorderMXBean 16 | https://github.com/Microsoft/jfr-streaming 17 | 18 | Microsoft Corporation 19 | http://microsoft.com 20 | 21 | 22 | 23 | 24 | MIT License 25 | http://opensource.org/licenses/MIT 26 | repo 27 | 28 | 29 | 30 | 31 | scm:git:git@github.com:Microsoft/jfr-streaming.git 32 | scm:git:git@github.com:Microsoft/jfr-streaming.git 33 | https://github.com/Microsoft/jfr-streaming 34 | HEAD 35 | 36 | 37 | 38 | 39 | microsoft 40 | Microsoft Corporation 41 | 42 | 43 | 44 | 45 | GitHub 46 | https://github.com/microsoft/jfr-streaming/issues 47 | 48 | 49 | 50 | 51 | ossrh 52 | https://oss.sonatype.org/content/repositories/snapshots 53 | 54 | 55 | ossrh 56 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 57 | 58 | 59 | 60 | 61 | 62 | release 63 | 64 | 65 | release 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-gpg-plugin 73 | 74 | 75 | org.sonatype.plugins 76 | nexus-staging-maven-plugin 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | core 85 | 86 | 87 | 88 | 10.6.0 89 | 1.4.0 90 | 8 91 | 8 92 | 8 93 | 8 94 | UTF-8 95 | 3.1.0 96 | 2.12.1 97 | 3.2.0 98 | 3.2.0 99 | 3.10.1 100 | 3.3.0 101 | 3.0.0 102 | 1.0 103 | 3.1.0 104 | 3.1.0 105 | 3.0.1 106 | 3.1.0 107 | 0.8.8 108 | 3.3.0 109 | 3.4.1 110 | 3.3.0 111 | 112 | 1.20 113 | 1.6.13 114 | 3.19.0 115 | 3.4.1 116 | 0.15 117 | 3.3.0 118 | 4.0.0-M4 119 | 3.2.1 120 | 4.7.2.1 121 | 3.0.0-M7 122 | 3.8.7 123 | 2.14.2 124 | 0.9.1 125 | UTF-8 126 | microsoft/gctoolkit 127 | git@github.com:${project.github.repository}.git 128 | 4.7.3 129 | 130 | 131 | 132 | 133 | 134 | 135 | org.testng 136 | testng 137 | 7.5.1 138 | test 139 | 140 | 141 | org.mockito 142 | mockito-testng 143 | 0.4.31 144 | test 145 | 146 | 147 | org.openjdk.jmc 148 | flightrecorder 149 | 8.3.0 150 | test 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | com.github.spotbugs 162 | spotbugs-maven-plugin 163 | ${maven.spotbugs-plugin.version} 164 | 165 | 166 | com.github.spotbugs 167 | spotbugs 168 | ${spotbugs.version} 169 | 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-changes-plugin 175 | ${maven.changes-plugin.version} 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-checkstyle-plugin 180 | ${maven.checkstyle-plugin.version} 181 | 182 | 183 | com.puppycrawl.tools 184 | checkstyle 185 | ${checkstyle.version} 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-clean-plugin 192 | ${maven.clean-plugin.version} 193 | 194 | 195 | org.apache.maven.plugins 196 | maven-compiler-plugin 197 | ${maven.compiler-plugin.version} 198 | 199 | ${maven.compiler.source} 200 | ${maven.compiler.testSource} 201 | ${maven.compiler.target} 202 | ${maven.compiler.testTarget} 203 | true 204 | true 205 | 206 | 207 | 208 | org.apache.maven.plugins 209 | maven-dependency-plugin 210 | ${maven.dependency-plugin.version} 211 | 212 | 213 | org.apache.maven.plugins 214 | maven-deploy-plugin 215 | ${maven.deploy-plugin.version} 216 | 217 | 218 | org.apache.maven.plugins 219 | maven-enforcer-plugin 220 | ${maven.enforcer-plugin.version} 221 | 222 | 223 | enforce-maven 224 | 225 | enforce 226 | 227 | 228 | 229 | 230 | ${maven.version} 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | org.apache.maven.plugins 239 | maven-gpg-plugin 240 | ${maven.gpg-plugin.version} 241 | 242 | 243 | org.apache.maven.plugins 244 | maven-install-plugin 245 | ${maven.install-plugin.version} 246 | 247 | 248 | org.apache.maven.plugins 249 | maven-jar-plugin 250 | ${maven.jar-plugin.version} 251 | 252 | 253 | org.apache.maven.plugins 254 | maven-javadoc-plugin 255 | ${maven.javadoc-plugin.version} 256 | 257 | 258 | org.apache.maven.plugins 259 | maven-jxr-plugin 260 | ${maven.jxr-plugin.version} 261 | 262 | 263 | org.apache.maven.plugins 264 | maven-pmd-plugin 265 | ${maven.pmd-plugin.version} 266 | 267 | 268 | org.apache.maven.plugins 269 | maven-source-plugin 270 | ${maven.source-plugin.version} 271 | 272 | 273 | org.apache.maven.plugins 274 | maven-surefire-plugin 275 | ${maven.surefire-plugin.version} 276 | 277 | 278 | org.apache.maven.plugins 279 | maven-site-plugin 280 | ${maven.site-plugin.version} 281 | 282 | 283 | org.apache.maven.plugins 284 | maven-project-info-reports-plugin 285 | ${maven.project-info-reports-plugin.version} 286 | 287 | 288 | org.apache.maven.plugins 289 | maven-resources-plugin 290 | ${maven.resources-plugin.version} 291 | 292 | 293 | org.apache.maven.plugins 294 | maven-antrun-plugin 295 | ${maven.antrun-plugin.version} 296 | 297 | 298 | org.apache.rat 299 | apache-rat-plugin 300 | ${maven.rat-plugin.version} 301 | 302 | 303 | org.codehaus.mojo 304 | exec-maven-plugin 305 | ${maven.exec-plugin.version} 306 | 307 | 308 | org.codehaus.mojo 309 | license-maven-plugin 310 | ${maven.license-plugin.version} 311 | 312 | 313 | org.codehaus.mojo 314 | versions-maven-plugin 315 | ${maven.versions-plugin.version} 316 | 317 | 318 | org.commonjava.maven.plugins 319 | directory-maven-plugin 320 | ${maven.directory-plugin.version} 321 | 322 | 323 | org.jacoco 324 | jacoco-maven-plugin 325 | ${maven.jacoco-plugin.version} 326 | 327 | 328 | 329 | prepare-agent 330 | 331 | 332 | 333 | report 334 | prepare-package 335 | 336 | report 337 | 338 | 339 | 340 | 341 | 342 | org.sonatype.plugins 343 | nexus-staging-maven-plugin 344 | ${maven.nexus-staging-plugin.version} 345 | true 346 | 347 | ossrh 348 | https://oss.sonatype.org/ 349 | false 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | com.github.spotbugs 358 | spotbugs-maven-plugin 359 | 360 | 361 | org.apache.maven.plugins 362 | maven-enforcer-plugin 363 | 364 | 365 | org.apache.maven.plugins 366 | maven-jar-plugin 367 | 368 | 369 | false 370 | 371 | 372 | 373 | 374 | org.apache.maven.plugins 375 | maven-javadoc-plugin 376 | 377 | 378 | ${maven.compiler.source} 379 | false 380 | 381 | 382 | 383 | attach-javadocs 384 | 385 | jar 386 | 387 | 388 | 389 | 390 | 391 | org.apache.maven.plugins 392 | maven-source-plugin 393 | 394 | 395 | attach-sources 396 | 397 | jar-no-fork 398 | 399 | 400 | 401 | 402 | 403 | org.apache.maven.plugins 404 | maven-surefire-plugin 405 | 406 | 407 | org.apache.maven.plugins 408 | maven-gpg-plugin 409 | 410 | 411 | sign-artifacts 412 | deploy 413 | 414 | sign 415 | 416 | 417 | 418 | 419 | 420 | org.sonatype.plugins 421 | nexus-staging-maven-plugin 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | com.github.spotbugs 431 | spotbugs-maven-plugin 432 | 433 | 434 | 435 | org.mutabilitydetector 436 | MutabilityDetector4FindBugs 437 | ${mutability.detector.version} 438 | 439 | 440 | 441 | 442 | 443 | org.apache.maven.plugins 444 | maven-changes-plugin 445 | 446 | 447 | https 448 | 443 449 | false 450 | false 451 | 452 | 453 | 454 | 455 | changes-report 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | org.apache.maven.plugins 464 | maven-checkstyle-plugin 465 | 466 | 467 | 468 | checkstyle 469 | 470 | 471 | 472 | 473 | 474 | org.apache.maven.plugins 475 | maven-dependency-plugin 476 | 477 | 478 | org.apache.maven.plugins 479 | maven-jxr-plugin 480 | 481 | 482 | org.apache.maven.plugins 483 | maven-pmd-plugin 484 | 485 | ${maven.compiler.target} 486 | 487 | 488 | 489 | org.apache.maven.plugins 490 | maven-project-info-reports-plugin 491 | 492 | 493 | org.apache.rat 494 | apache-rat-plugin 495 | 496 | 497 | org.codehaus.mojo 498 | license-maven-plugin 499 | 500 | 501 | org.codehaus.mojo 502 | versions-maven-plugin 503 | 504 | 505 | 506 | dependency-updates-report 507 | plugin-updates-report 508 | property-updates-report 509 | 510 | 511 | 512 | 513 | 514 | org.jacoco 515 | jacoco-maven-plugin 516 | ${maven.jacoco-plugin.version} 517 | 518 | 519 | 520 | 521 | 522 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | *.jfr -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # jfr-streaming Samples 2 | 3 | This directory contains sample maven apps that uses `jfr-streaming` API. 4 | See the README file in each sample for details. 5 | 6 | 7 | To build the samples: 8 | ```shell 9 | $ mvn clean compile 10 | ``` 11 | To run a sample, provide the Maven `--project` argument, or run from the sample's directory: 12 | ```shell 13 | $ mvn exec:java --projects introductory 14 | ``` 15 | -------------------------------------------------------------------------------- /samples/introductory/README.md: -------------------------------------------------------------------------------- 1 | # jfr-streaming introductory sample 2 | 3 | This sample uses jfr-streaming to capture a flight recording file. The sample creates the file `recording.jfr` which 4 | can be opened in JDK Mission Control to visualize the stats. 5 | 6 | ### Execute 7 | 8 | To build the sample from the 'introductory' directory: 9 | ```shell 10 | $ mvn clean compile 11 | ``` 12 | To run the sample: 13 | ```shell 14 | $ mvn exec:java 15 | ``` 16 | The sample simulates load by generating numbers of a Fibonacci sequence. The number of 17 | values to generate can be specified by passing it as an argument to this command, for 18 | example `-Dexec.arguments="1000"` -------------------------------------------------------------------------------- /samples/introductory/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.microsoft.jfr 8 | jfr-streaming-samples 9 | 1.1.0-SNAPSHOT 10 | ../pom.xml 11 | 12 | 13 | introductory 14 | 1.1.0-SNAPSHOT 15 | 16 | introductory 17 | A sample for Java Flight Recorder Streaming Library using Fibonacci number generator 18 | https://github.com/Microsoft/jfr-streaming.git 19 | 20 | 21 | 22 | 23 | org.codehaus.mojo 24 | exec-maven-plugin 25 | 26 | com.microsoft.jfr.Main 27 | false 28 | 29 | 100 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /samples/introductory/src/main/java/com/microsoft/jfr/LoadGenerator.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License.package com.microsoft.jfr; 3 | package com.microsoft.jfr; 4 | 5 | import java.math.BigInteger; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.stream.IntStream; 9 | 10 | /** 11 | * This class uses Fibonacci number generation to produce some load on the CPU. 12 | */ 13 | public class LoadGenerator { 14 | 15 | // The result of the calculations are kept in this cache. This helps drive up 16 | // some memory use, especially if the range is significant. Each time 17 | // generateLoad() is called, the cache is recreated which may cause some 18 | // garbage collection activity (depending on the heap size). 19 | private ArrayList cache; 20 | 21 | // This just keeps track of the index of the last Fibbonacci number in cache. 22 | private int lastFibonacciIndex; 23 | 24 | // How many Fibbonacci numbers to calculate. This can be set as an command line argument. 25 | private int range = 100; 26 | 27 | /** 28 | * In order to keep Main simple, the constructor just takes the array of 29 | * arguments passed on the command line. 30 | * @param args The command line arguments passed from Main. 31 | */ 32 | public LoadGenerator (String[] args) { 33 | try { 34 | range = Integer.parseInt(args[0]); 35 | } catch (IndexOutOfBoundsException | NumberFormatException e) { 36 | } 37 | } 38 | 39 | /** 40 | * This is the method that generates the load. It uses fork-join generate load on the system. 41 | * Each time this method is called, a new cache for the Fibonacci numbers is created. Calling 42 | * this method repeatedly cause garabage collections if the heap is small enough. 43 | */ 44 | public void generateLoad () { 45 | cache = new ArrayList<>(Collections.nCopies(range + 1, BigInteger.ZERO)); 46 | cache.set(0, BigInteger.ZERO); 47 | cache.set(1, BigInteger.ONE); 48 | lastFibonacciIndex = 1; 49 | 50 | // Each value in `stream` gets its own thread to run 51 | IntStream.range(0, range) 52 | .parallel() 53 | .forEach(this::nthFibonacci); 54 | } 55 | 56 | private BigInteger nthFibonacci (int n) { 57 | if (lastFibonacciIndex >= n) return cache.get(n); 58 | for(int i = lastFibonacciIndex + 1; i <= n; i++){ 59 | cache.set(i, cache.get(i - 1).add(cache.get(i - 2))); 60 | } 61 | lastFibonacciIndex = n; 62 | return cache.get(n); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /samples/introductory/src/main/java/com/microsoft/jfr/Main.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License.package com.microsoft.jfr; 3 | package com.microsoft.jfr; 4 | 5 | import javax.management.InstanceNotFoundException; 6 | import javax.management.MBeanServerConnection; 7 | import java.io.IOException; 8 | import java.lang.management.ManagementFactory; 9 | import java.nio.file.Paths; 10 | 11 | /** 12 | * This sample is meant to show the basic usage of the jfr-streaming library. This library provides access 13 | * to most of the FlightRecorderMXBean API. There are a few steps to using the library. 14 | *

15 | * First, an MBeanServerConnection is needed. The MBeanServerConnection can be to a local or 16 | * remote MBean server. For simplicity, this sample makes a connection to the MBean server of 17 | * the JVM that is running the sample. The library only uses the MBeanServerConnection. Creating, 18 | * managing and closing the MBeanServerConnection is the responsibility of the caller. 19 | *

20 | * Next, a connection to the FlightRecorderMXBean is made using the 21 | * {@code com.microsoft.jfr.FlightRecorderConnection#connect(javax.management.MBeanServerConnection)} 22 | * method. This call returns a {@code com.microsoft.jfr.FlightRecorderConnection} which can be used 23 | * to create Java flight recordings. Only one FlightRecorderConnection is needed for a given 24 | * MBeanServerConnection. 25 | *

26 | * To create a flight recording, call the method 27 | * {@code com.microsoft.jfr.FlightRecorderConnection#newRecording(com.microsoft.jfr.RecordingOptions, com.microsoft.jfr.RecordingConfiguration)}. 28 | * This method takes two parameters. The first parameter specifies the options that control the 29 | * flight recording, such as maximum recording size. The second parameter configures what events are 30 | * recorded, typically the 'default' or 'profiling' configurations that are built-in to the JVM. 31 | *

32 | * The {@code newRecording} method returns a {@code com.microsoft.jfr.Recording} object that is used to 33 | * start, stop and stream a recording file. The {@code Recording} can be used repeatedly provided the 34 | * {@code Recording} has not been closed. 35 | *

36 | */ 37 | public class Main { 38 | 39 | /** 40 | * An integer value can be passed as an argument on the command line. This integer 41 | * argument specifies the end of a range of Fibonacci numbers to generate. If no 42 | * argument is given, a default value (1000) is used. The larger the number, the more 43 | * load will be placed on the system and the more events that will be generated for the 44 | * flight recording. But a larger number also means a longer runtime. 45 | * @param args 46 | */ 47 | public static void main( String[] args ) { 48 | // This sample uses the local MBean server, but this could also be a remote MBean server. 49 | MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer(); 50 | 51 | try { 52 | // In order to upload a JFR file, we need to create a FlightRecorderConnection. 53 | // This gives us a connection the the FlightRecorderMXBean on the MBean server. 54 | FlightRecorderConnection flightRecorderConnection = FlightRecorderConnection.connect(mBeanServer); 55 | 56 | // To create a recording, we need recording options, and a recording configuration. 57 | // This sample uses the 'profile' configuration. Refer to the section on 58 | // "Flight Recorder Configurations" in the Flight Recorder API Programmer's Guide 59 | // for more information. 60 | RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build(); 61 | RecordingConfiguration recordingConfiguration = RecordingConfiguration.PROFILE_CONFIGURATION; 62 | 63 | // Note that a Recording is AutoCloseable, so we can use it in a try-with-resources block. 64 | try (Recording recording = flightRecorderConnection.newRecording(recordingOptions, recordingConfiguration)) { 65 | 66 | // To create a flight recording, the Recording has to be started. Once the 67 | // recording is started, flight recording events are collected by the JVM. 68 | recording.start(); 69 | 70 | // LoadGenerator does some busy work to drive up CPU usage. 71 | new LoadGenerator(args).generateLoad(); 72 | 73 | // It isn't always necessary to stop the recording. Check the {@code com.microsoft.jfr.Recording} API. 74 | recording.stop(); 75 | 76 | // The dump method writes the file to the given path on the host where the JVM is running. 77 | // The 'getStream' method is most useful for reading a flight recording file from a remote JVM. 78 | // This sample saves the flight recording to 'recording.jfr', which can be used in JDK Mission control 79 | recording.dump(Paths.get(System.getProperty("user.dir"), "recording.jfr").toString()); 80 | } catch (IOException ioe) { 81 | // IOException can occur when we try to start/stop recording 82 | ioe.printStackTrace(); 83 | } 84 | } catch (InstanceNotFoundException | IOException | JfrStreamingException e) { 85 | // `InstanceNotFoundException` means that you may need to enable commercial 86 | // features by providing `-XX:+UnlockCommercialFeatures` on the command line. 87 | // `IOException` means there is a communication problem when talking to the MBean server. 88 | // `JfrStreamingException` wraps an exception that may be thrown by the MBean server or from 89 | // the FlightRecorderMXBean. 90 | e.printStackTrace(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /samples/jbang/Sample.java: -------------------------------------------------------------------------------- 1 | ///usr/bin/env jbang "$0" "$@" ; exit $? 2 | //DEPS com.microsoft.jfr:jfr-streaming:1.1.0 3 | 4 | import java.io.IOException; 5 | import java.nio.file.Paths; 6 | import java.util.concurrent.TimeUnit; 7 | import java.lang.management.ManagementFactory; 8 | 9 | import javax.management.*; 10 | import com.microsoft.jfr.*; 11 | 12 | public class Sample { 13 | 14 | public static void main(String[] args) { 15 | MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer(); 16 | 17 | try { 18 | FlightRecorderConnection flightRecorderConnection = FlightRecorderConnection.connect(mBeanServer); 19 | RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build(); 20 | RecordingConfiguration recordingConfiguration = RecordingConfiguration.PROFILE_CONFIGURATION; 21 | 22 | try (Recording recording = flightRecorderConnection.newRecording(recordingOptions, recordingConfiguration)) { 23 | recording.start(); 24 | TimeUnit.SECONDS.sleep(10); 25 | recording.stop(); 26 | 27 | recording.dump(Paths.get(System.getProperty("user.dir"), "recording.jfr").toString()); 28 | System.out.println("JFR recording ready: recording.jfr"); 29 | } 30 | } catch (InstanceNotFoundException | IOException | JfrStreamingException | InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /samples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | com.microsoft.jfr 7 | jfr-streaming-samples 8 | 1.1.0-SNAPSHOT 9 | pom 10 | 11 | JFR Streaming library sample apps 12 | Sample maven apps to showcase the usage of jfr-streaming library API 13 | https://github.com/Microsoft/jfr-streaming 14 | 15 | Microsoft Corporation 16 | http://microsoft.com 17 | 18 | 19 | 20 | 21 | MIT License 22 | http://opensource.org/licenses/MIT 23 | repo 24 | 25 | 26 | 27 | 28 | 29 | microsoft 30 | Microsoft Corporation 31 | 32 | 33 | 34 | 35 | introductory 36 | 37 | 38 | 39 | 11 40 | 11 41 | 11 42 | 11 43 | UTF-8 44 | 45 | 46 | 47 | 48 | junit 49 | junit 50 | 4.13.2 51 | test 52 | 53 | 54 | com.microsoft.jfr 55 | jfr-streaming 56 | 1.2.0 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | maven-clean-plugin 66 | 3.2.0 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 3.10.1 72 | 73 | ${maven.compiler.source} 74 | ${maven.compiler.target} 75 | 76 | 77 | 78 | org.codehaus.mojo 79 | exec-maven-plugin 80 | 3.1.0 81 | 82 | 83 | 84 | 85 | 86 | --------------------------------------------------------------------------------