├── .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 | [](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