1 /* 2 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr; 27 28 import java.io.Closeable; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.nio.file.Path; 32 import java.time.Duration; 33 import java.time.Instant; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.Objects; 37 38 import jdk.jfr.internal.PlatformRecorder; 39 import jdk.jfr.internal.PlatformRecording; 40 import jdk.jfr.internal.Type; 41 import jdk.jfr.internal.Utils; 42 import jdk.jfr.internal.WriteableUserPath; 43 44 /** 45 * Provides means to configure, start, stop and dump recording data to disk. 46 * <p> 47 * The following example shows how configure, start, stop and dump recording data to disk. 48 * 49 * <pre> 50 * <code> 51 * Configuration c = Configuration.getConfiguration("default"); 52 * Recording r = new Recording(c); 53 * r.start(); 54 * System.gc(); 55 * Thread.sleep(5000); 56 * r.stop(); 57 * r.dump(Files.createTempFile("my-recording", ".jfr")); 58 * </code> 59 * </pre> 60 * 61 * @since 9 62 */ 63 public final class Recording implements Closeable { 64 65 private static class RecordingSettings extends EventSettings { 66 67 private final Recording recording; 68 private final String identifier; 69 RecordingSettings(Recording r, String identifier)70 RecordingSettings(Recording r, String identifier) { 71 this.recording = r; 72 this.identifier = identifier; 73 } 74 RecordingSettings(Recording r, Class<? extends Event> eventClass)75 RecordingSettings(Recording r, Class<? extends Event> eventClass) { 76 Utils.ensureValidEventSubclass(eventClass); 77 this.recording = r; 78 this.identifier = String.valueOf(Type.getTypeId(eventClass)); 79 } 80 81 @Override with(String name, String value)82 public EventSettings with(String name, String value) { 83 Objects.requireNonNull(value); 84 recording.setSetting(identifier + "#" + name, value); 85 return this; 86 } 87 88 @Override toMap()89 public Map<String, String> toMap() { 90 return recording.getSettings(); 91 } 92 } 93 94 private final PlatformRecording internal; 95 Recording(Map<String, String> settings)96 public Recording(Map<String, String> settings) { 97 PlatformRecorder r = FlightRecorder.getFlightRecorder().getInternal(); 98 synchronized (r) { 99 this.internal = r.newRecording(settings); 100 this.internal.setRecording(this); 101 if (internal.getRecording() != this) { 102 throw new InternalError("Internal recording not properly setup"); 103 } 104 } 105 } 106 107 /** 108 * Creates a recording without any settings. 109 * <p> 110 * A newly created recording is in the {@link RecordingState#NEW} state. To start 111 * the recording, invoke the {@link Recording#start()} method. 112 * 113 * @throws IllegalStateException if Flight Recorder can't be created (for 114 * example, if the Java Virtual Machine (JVM) lacks Flight Recorder 115 * support, or if the file repository can't be created or accessed) 116 * 117 * @throws SecurityException If a security manager is used and 118 * FlightRecorderPermission "accessFlightRecorder" is not set. 119 */ Recording()120 public Recording() { 121 this(new HashMap<String, String>()); 122 } 123 124 /** 125 * Creates a recording with settings from a configuration. 126 * <p> 127 * The following example shows how create a recording that uses a predefined configuration. 128 * 129 * <pre> 130 * <code> 131 * Recording r = new Recording(Configuration.getConfiguration("default")); 132 * </code> 133 * </pre> 134 * 135 * The newly created recording is in the {@link RecordingState#NEW} state. To 136 * start the recording, invoke the {@link Recording#start()} method. 137 * 138 * @param configuration configuration that contains the settings to be use, not 139 * {@code null} 140 * 141 * @throws IllegalStateException if Flight Recorder can't be created (for 142 * example, if the Java Virtual Machine (JVM) lacks Flight Recorder 143 * support, or if the file repository can't be created or accessed) 144 * 145 * @throws SecurityException if a security manager is used and 146 * FlightRecorderPermission "accessFlightRecorder" is not set. 147 * 148 * @see Configuration 149 */ Recording(Configuration configuration)150 public Recording(Configuration configuration) { 151 this(configuration.getSettings()); 152 } 153 154 /** 155 * Starts this recording. 156 * <p> 157 * It's recommended that the recording options and event settings are configured 158 * before calling this method. The benefits of doing so are a more consistent 159 * state when analyzing the recorded data, and improved performance because the 160 * configuration can be applied atomically. 161 * <p> 162 * After a successful invocation of this method, this recording is in the 163 * {@code RUNNING} state. 164 * 165 * @throws IllegalStateException if recording is already started or is in the 166 * {@code CLOSED} state 167 */ start()168 public void start() { 169 internal.start(); 170 } 171 172 /** 173 * Starts this recording after a delay. 174 * <p> 175 * After a successful invocation of this method, this recording is in the 176 * {@code DELAYED} state. 177 * 178 * @param delay the time to wait before starting this recording, not 179 * {@code null} 180 * @throws IllegalStateException if the recording is not it the {@code NEW} state 181 */ scheduleStart(Duration delay)182 public void scheduleStart(Duration delay) { 183 Objects.requireNonNull(delay); 184 internal.scheduleStart(delay); 185 } 186 187 /** 188 * Stops this recording. 189 * <p> 190 * When a recording is stopped it can't be restarted. If this 191 * recording has a destination, data is written to that destination and 192 * the recording is closed. After a recording is closed, the data is no longer 193 * available. 194 * <p> 195 * After a successful invocation of this method, this recording will be 196 * in the {@code STOPPED} state. 197 * 198 * @return {@code true} if recording is stopped, {@code false} otherwise 199 * 200 * @throws IllegalStateException if the recording is not started or is already stopped 201 * 202 * @throws SecurityException if a security manager exists and the caller 203 * doesn't have {@code FilePermission} to write to the destination 204 * path 205 * 206 * @see #setDestination(Path) 207 * 208 */ stop()209 public boolean stop() { 210 return internal.stop("Stopped by user"); 211 } 212 213 /** 214 * Returns settings used by this recording. 215 * <p> 216 * Modifying the returned {@code Map} will not change the settings for this recording. 217 * <p> 218 * If no settings are set for this recording, an empty {@code Map} is 219 * returned. 220 * 221 * @return recording settings, not {@code null} 222 */ getSettings()223 public Map<String, String> getSettings() { 224 return new HashMap<>(internal.getSettings()); 225 } 226 227 /** 228 * Returns the current size of this recording in the disk repository, 229 * measured in bytes. 230 * <p> 231 * The size is updated when recording buffers are flushed. If the recording is 232 * not written to the disk repository the returned size is always {@code 0}. 233 * 234 * @return amount of recorded data, measured in bytes, or {@code 0} if the 235 * recording is not written to the disk repository 236 */ getSize()237 public long getSize() { 238 return internal.getSize(); 239 } 240 241 /** 242 * Returns the time when this recording was stopped. 243 * 244 * @return the time, or {@code null} if this recording is not stopped 245 */ getStopTime()246 public Instant getStopTime() { 247 return internal.getStopTime(); 248 } 249 250 /** 251 * Returns the time when this recording was started. 252 * 253 * @return the the time, or {@code null} if this recording is not started 254 */ getStartTime()255 public Instant getStartTime() { 256 return internal.getStartTime(); 257 } 258 259 /** 260 * Returns the maximum size, measured in bytes, at which data is no longer kept in the disk repository. 261 * 262 * @return maximum size in bytes, or {@code 0} if no maximum size is set 263 */ getMaxSize()264 public long getMaxSize() { 265 return internal.getMaxSize(); 266 } 267 268 /** 269 * Returns the length of time that the data is kept in the disk repository 270 * before it is removed. 271 * 272 * @return maximum length of time, or {@code null} if no maximum length of time 273 * has been set 274 */ getMaxAge()275 public Duration getMaxAge() { 276 return internal.getMaxAge(); 277 } 278 279 /** 280 * Returns the name of this recording. 281 * <p> 282 * By default, the name is the same as the recording ID. 283 * 284 * @return the recording name, not {@code null} 285 */ getName()286 public String getName() { 287 return internal.getName(); 288 } 289 290 /** 291 * Replaces all settings for this recording. 292 * <p> 293 * The following example shows how to set event settings for a recording. 294 * 295 * <pre> 296 * <code> 297 * Map{@literal <}String, String{@literal >} settings = new HashMap{@literal <}{@literal >}(); 298 * settings.putAll(EventSettings.enabled("jdk.CPUSample").withPeriod(Duration.ofSeconds(2)).toMap()); 299 * settings.putAll(EventSettings.enabled(MyEvent.class).withThreshold(Duration.ofSeconds(2)).withoutStackTrace().toMap()); 300 * settings.put("jdk.ExecutionSample#period", "10 ms"); 301 * recording.setSettings(settings); 302 * </code> 303 * </pre> 304 * 305 * The following example shows how to merge settings. 306 * 307 * <pre> 308 * {@code 309 * Map<String, String> settings = recording.getSettings(); 310 * settings.putAll(additionalSettings); 311 * recording.setSettings(settings); 312 * } 313 * </pre> 314 * 315 * @param settings the settings to set, not {@code null} 316 */ setSettings(Map<String, String> settings)317 public void setSettings(Map<String, String> settings) { 318 Objects.requireNonNull(settings); 319 Map<String, String> sanitized = Utils.sanitizeNullFreeStringMap(settings); 320 internal.setSettings(sanitized); 321 } 322 323 /** 324 * Returns the recording state that this recording is currently in. 325 * 326 * @return the recording state, not {@code null} 327 * 328 * @see RecordingState 329 */ getState()330 public RecordingState getState() { 331 return internal.getState(); 332 } 333 334 /** 335 * Releases all data that is associated with this recording. 336 * <p> 337 * After a successful invocation of this method, this recording is in the 338 * {@code CLOSED} state. 339 */ 340 @Override close()341 public void close() { 342 internal.close(); 343 } 344 345 /** 346 * Returns a clone of this recording, with a new recording ID and name. 347 * 348 * Clones are useful for dumping data without stopping the recording. After 349 * a clone is created, the amount of data to copy is constrained 350 * with the {@link #setMaxAge(Duration)} method and the {@link #setMaxSize(long)}method. 351 * 352 * @param stop {@code true} if the newly created copy should be stopped 353 * immediately, {@code false} otherwise 354 * @return the recording copy, not {@code null} 355 */ copy(boolean stop)356 public Recording copy(boolean stop) { 357 return internal.newCopy(stop); 358 } 359 360 /** 361 * Writes recording data to a file. 362 * <p> 363 * Recording must be started, but not necessarily stopped. 364 * 365 * @param destination the location where recording data is written, not 366 * {@code null} 367 * 368 * @throws IOException if the recording can't be copied to the specified 369 * location 370 * 371 * @throws SecurityException if a security manager exists and the caller doesn't 372 * have {@code FilePermission} to write to the destination path 373 */ dump(Path destination)374 public void dump(Path destination) throws IOException { 375 Objects.requireNonNull(destination); 376 internal.dump(new WriteableUserPath(destination)); 377 378 } 379 380 /** 381 * Returns {@code true} if this recording uses the disk repository, {@code false} otherwise. 382 * <p> 383 * If no value is set, {@code true} is returned. 384 * 385 * @return {@code true} if the recording uses the disk repository, {@code false} 386 * otherwise 387 */ isToDisk()388 public boolean isToDisk() { 389 return internal.isToDisk(); 390 } 391 392 /** 393 * Determines how much data is kept in the disk repository. 394 * <p> 395 * To control the amount of recording data that is stored on disk, the maximum 396 * amount of data to retain can be specified. When the maximum limit is 397 * exceeded, the Java Virtual Machine (JVM) removes the oldest chunk to make 398 * room for a more recent chunk. 399 * <p> 400 * If neither maximum limit or the maximum age is set, the size of the 401 * recording may grow indefinitely. 402 * 403 * @param maxSize the amount of data to retain, {@code 0} if infinite 404 * 405 * @throws IllegalArgumentException if <code>maxSize</code> is negative 406 * 407 * @throws IllegalStateException if the recording is in {@code CLOSED} state 408 */ setMaxSize(long maxSize)409 public void setMaxSize(long maxSize) { 410 if (maxSize < 0) { 411 throw new IllegalArgumentException("Max size of recording can't be negative"); 412 } 413 internal.setMaxSize(maxSize); 414 } 415 416 /** 417 * Determines how far back data is kept in the disk repository. 418 * <p> 419 * To control the amount of recording data stored on disk, the maximum length of 420 * time to retain the data can be specified. Data stored on disk that is older 421 * than the specified length of time is removed by the Java Virtual Machine (JVM). 422 * <p> 423 * If neither maximum limit or the maximum age is set, the size of the 424 * recording may grow indefinitely. 425 * 426 * @param maxAge the length of time that data is kept, or {@code null} if infinite 427 * 428 * @throws IllegalArgumentException if <code>maxAge</code> is negative 429 * 430 * @throws IllegalStateException if the recording is in the {@code CLOSED} state 431 */ setMaxAge(Duration maxAge)432 public void setMaxAge(Duration maxAge) { 433 if (maxAge != null && maxAge.isNegative()) { 434 throw new IllegalArgumentException("Max age of recording can't be negative"); 435 } 436 internal.setMaxAge(maxAge); 437 } 438 439 /** 440 * Sets a location where data is written on recording stop, or 441 * {@code null} if data is not to be dumped. 442 * <p> 443 * If a destination is set, this recording is automatically closed 444 * after data is successfully copied to the destination path. 445 * <p> 446 * If a destination is <em>not</em> set, Flight Recorder retains the 447 * recording data until this recording is closed. Use the {@link #dump(Path)} method to 448 * manually write data to a file. 449 * 450 * @param destination the destination path, or {@code null} if recording should 451 * not be dumped at stop 452 * 453 * @throws IllegalStateException if recording is in the {@code STOPPED} or 454 * {@code CLOSED} state. 455 * 456 * @throws SecurityException if a security manager exists and the caller 457 * doesn't have {@code FilePermission} to read, write, and delete the 458 * {@code destination} file 459 * 460 * @throws IOException if the path is not writable 461 */ setDestination(Path destination)462 public void setDestination(Path destination) throws IOException { 463 internal.setDestination(destination != null ? new WriteableUserPath(destination) : null); 464 } 465 466 /** 467 * Returns the destination file, where recording data is written when the 468 * recording stops, or {@code null} if no destination is set. 469 * 470 * @return the destination file, or {@code null} if not set. 471 */ getDestination()472 public Path getDestination() { 473 WriteableUserPath usp = internal.getDestination(); 474 if (usp == null) { 475 return null; 476 } else { 477 return usp.getPotentiallyMaliciousOriginal(); 478 } 479 } 480 481 /** 482 * Returns a unique ID for this recording. 483 * 484 * @return the recording ID 485 */ getId()486 public long getId() { 487 return internal.getId(); 488 } 489 490 /** 491 * Sets a human-readable name (for example, {@code "My Recording"}). 492 * 493 * @param name the recording name, not {@code null} 494 * 495 * @throws IllegalStateException if the recording is in {@code CLOSED} state 496 */ setName(String name)497 public void setName(String name) { 498 Objects.requireNonNull(name); 499 internal.setName(name); 500 } 501 502 /** 503 * Sets whether this recording is dumped to disk when the JVM exits. 504 * 505 * @param dumpOnExit if this recording should be dumped when the JVM exits 506 */ setDumpOnExit(boolean dumpOnExit)507 public void setDumpOnExit(boolean dumpOnExit) { 508 internal.setDumpOnExit(dumpOnExit); 509 } 510 511 /** 512 * Returns whether this recording is dumped to disk when the JVM exits. 513 * <p> 514 * If dump on exit is not set, {@code false} is returned. 515 * 516 * @return {@code true} if the recording is dumped on exit, {@code false} 517 * otherwise. 518 */ getDumpOnExit()519 public boolean getDumpOnExit() { 520 return internal.getDumpOnExit(); 521 } 522 523 /** 524 * Determines whether this recording is continuously flushed to the disk 525 * repository or data is constrained to what is available in memory buffers. 526 * 527 * @param disk {@code true} if this recording is written to disk, 528 * {@code false} if in-memory 529 * 530 */ setToDisk(boolean disk)531 public void setToDisk(boolean disk) { 532 internal.setToDisk(disk); 533 } 534 535 /** 536 * Creates a data stream for a specified interval. 537 * <p> 538 * The stream may contain some data outside the specified range. 539 * 540 * @param the start start time for the stream, or {@code null} to get data from 541 * start time of the recording 542 * 543 * @param the end end time for the stream, or {@code null} to get data until the 544 * present time. 545 * 546 * @return an input stream, or {@code null} if no data is available in the 547 * interval. 548 * 549 * @throws IllegalArgumentException if {@code end} happens before 550 * {@code start} 551 * 552 * @throws IOException if a stream can't be opened 553 */ getStream(Instant start, Instant end)554 public InputStream getStream(Instant start, Instant end) throws IOException { 555 if (start != null && end != null && end.isBefore(start)) { 556 throw new IllegalArgumentException("End time of requested stream must not be before start time"); 557 } 558 return internal.open(start, end); 559 } 560 561 /** 562 * Returns the specified duration for this recording, or {@code null} if no 563 * duration is set. 564 * <p> 565 * The duration can be set only when the recording is in the 566 * {@link RecordingState#NEW} state. 567 * 568 * @return the desired duration of the recording, or {@code null} if no duration 569 * has been set. 570 */ getDuration()571 public Duration getDuration() { 572 return internal.getDuration(); 573 } 574 575 /** 576 * Sets a duration for how long a recording runs before it stops. 577 * <p> 578 * By default, a recording has no duration ({@code null}). 579 * 580 * @param duration the duration, or {@code null} if no duration is set 581 * 582 * @throws IllegalStateException if recording is in the {@code STOPPED} or {@code CLOSED} state 583 */ setDuration(Duration duration)584 public void setDuration(Duration duration) { 585 internal.setDuration(duration); 586 } 587 588 /** 589 * Enables the event with the specified name. 590 * <p> 591 * If multiple events have the same name (for example, the same class is loaded 592 * in different class loaders), then all events that match the name are enabled. To 593 * enable a specific class, use the {@link #enable(Class)} method or a {@code String} 594 * representation of the event type ID. 595 * 596 * @param name the settings for the event, not {@code null} 597 * 598 * @return an event setting for further configuration, not {@code null} 599 * 600 * @see EventType 601 */ enable(String name)602 public EventSettings enable(String name) { 603 Objects.requireNonNull(name); 604 RecordingSettings rs = new RecordingSettings(this, name); 605 rs.with("enabled", "true"); 606 return rs; 607 } 608 609 /** 610 * Disables event with the specified name. 611 * <p> 612 * If multiple events with same name (for example, the same class is loaded 613 * in different class loaders), then all events that match the 614 * name is disabled. To disable a specific class, use the 615 * {@link #disable(Class)} method or a {@code String} representation of the event 616 * type ID. 617 * 618 * @param name the settings for the event, not {@code null} 619 * 620 * @return an event setting for further configuration, not {@code null} 621 * 622 */ disable(String name)623 public EventSettings disable(String name) { 624 Objects.requireNonNull(name); 625 RecordingSettings rs = new RecordingSettings(this, name); 626 rs.with("enabled", "false"); 627 return rs; 628 } 629 630 /** 631 * Enables event. 632 * 633 * @param eventClass the event to enable, not {@code null} 634 * 635 * @throws IllegalArgumentException if {@code eventClass} is an abstract 636 * class or not a subclass of {@link Event} 637 * 638 * @return an event setting for further configuration, not {@code null} 639 */ enable(Class<? extends Event> eventClass)640 public EventSettings enable(Class<? extends Event> eventClass) { 641 Objects.requireNonNull(eventClass); 642 RecordingSettings rs = new RecordingSettings(this, eventClass); 643 rs.with("enabled", "true"); 644 return rs; 645 } 646 647 /** 648 * Disables event. 649 * 650 * @param eventClass the event to enable, not {@code null} 651 * 652 * @throws IllegalArgumentException if {@code eventClass} is an abstract 653 * class or not a subclass of {@link Event} 654 * 655 * @return an event setting for further configuration, not {@code null} 656 * 657 */ disable(Class<? extends Event> eventClass)658 public EventSettings disable(Class<? extends Event> eventClass) { 659 Objects.requireNonNull(eventClass); 660 RecordingSettings rs = new RecordingSettings(this, eventClass); 661 rs.with("enabled", "false"); 662 return rs; 663 } 664 665 // package private getInternal()666 PlatformRecording getInternal() { 667 return internal; 668 } 669 setSetting(String id, String value)670 private void setSetting(String id, String value) { 671 Objects.requireNonNull(id); 672 Objects.requireNonNull(value); 673 internal.setSetting(id, value); 674 } 675 676 } 677