aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Shaldybin <olegsh@google.com>2023-06-16 01:15:33 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-06-16 01:15:33 +0000
commit4a4b713b448fa7874945e616ea82dc77062cdd78 (patch)
tree248e54a31421fdcdebfaf7e83aace32f7bc8a8ad
parent68ac043b69bd19a8c64348357861b1df81c627b3 (diff)
parente0ee6365fff539847e9a51b0b44ed1e787872db8 (diff)
downloadperfmark-4a4b713b448fa7874945e616ea82dc77062cdd78.tar.gz
Import perfmark v-0.26 am: 9d6c9c0f44 am: a14e499d34 am: e0ee6365ff
Original change: https://android-review.googlesource.com/c/platform/external/perfmark/+/2627042 Change-Id: I2e0f379d2b43bd84e60734f42b59c20e68ac035e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.github/dependabot.yml10
-rw-r--r--.github/workflows/gradle-wrapper-validation.yml10
-rw-r--r--.github/workflows/gradle.yml27
-rw-r--r--.gitignore35
-rw-r--r--CONTRIBUTING.md12
-rw-r--r--LICENSE201
-rw-r--r--METADATA17
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--NOTICE41
-rw-r--r--OWNERS2
-rw-r--r--README.md128
-rw-r--r--WORKSPACE28
-rw-r--r--agent/build.gradle.kts77
-rw-r--r--agent/src/main/java/io/perfmark/agent/MethodVisitorRecorder.java435
-rw-r--r--agent/src/main/java/io/perfmark/agent/MethodWrappingWriter.java164
-rw-r--r--agent/src/main/java/io/perfmark/agent/NonMergingClassWriter.java39
-rw-r--r--agent/src/main/java/io/perfmark/agent/PerfMarkAgent.java31
-rw-r--r--agent/src/main/java/io/perfmark/agent/PerfMarkClassVisitor.java172
-rw-r--r--agent/src/main/java/io/perfmark/agent/PerfMarkMethodRewriter.java175
-rw-r--r--agent/src/main/java/io/perfmark/agent/PerfMarkTransformer.java184
-rw-r--r--agent/src/main/resources/io/perfmark/agent/third_party/asm/LICENSE27
-rw-r--r--agent/src/test/java/io/perfmark/agent/PerfMarkMethodRewriterTest.java156
-rw-r--r--agent/src/test/java/io/perfmark/agent/PerfMarkTransformerTest.java509
-rw-r--r--agent/src/test/java/io/perfmark/agent/TransformerTestClasses.java152
-rw-r--r--api/BUILD.bazel62
-rw-r--r--api/build.gradle85
-rw-r--r--api/src/jmh/java/io/perfmark/ClassInitBenchmark.java95
-rw-r--r--api/src/jmh/java/io/perfmark/EnabledBenchmark.java134
-rw-r--r--api/src/main/java/io/perfmark/Impl.java109
-rw-r--r--api/src/main/java/io/perfmark/Link.java42
-rw-r--r--api/src/main/java/io/perfmark/PerfMark.java597
-rw-r--r--api/src/main/java/io/perfmark/StringFunction.java37
-rw-r--r--api/src/main/java/io/perfmark/Tag.java31
-rw-r--r--api/src/main/java/io/perfmark/TaskCloseable.java44
-rw-r--r--api/src/main/java/io/perfmark/package-info.java19
-rw-r--r--api/src/test/java/io/perfmark/CompatibilityTest.java452
-rw-r--r--api/src/test/java/io/perfmark/PerfMarkTest.java491
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.13.37.jarbin0 -> 19157 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.14.0.jarbin0 -> 19419 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.15.0.jarbin0 -> 5113 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.16.0.jarbin0 -> 4294 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.17.0.jarbin0 -> 4696 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.19.0.jarbin0 -> 4693 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.20.1.jarbin0 -> 5013 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.21.0.jarbin0 -> 5013 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.22.0.jarbin0 -> 5753 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.23.0.jarbin0 -> 6385 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.24.0.jarbin0 -> 6482 bytes
-rw-r--r--api/src/test/resources/io/perfmark/perfmark-api-0.25.0.jarbin0 -> 6730 bytes
-rw-r--r--api/testing/build.gradle.kts24
-rw-r--r--api/testing/src/test/java/io/perfmark/apitesting/ArtifactTest.java62
-rw-r--r--build.gradle156
-rw-r--r--buildscripts/checkstyle.license16
-rw-r--r--buildscripts/checkstyle.xml28
-rw-r--r--doc/fix-stop-task.md166
-rw-r--r--doc/perfmark.pngbin0 -> 65729 bytes
-rw-r--r--doc/perfmark.svg90
-rw-r--r--doc/screenshot.pngbin0 -> 177633 bytes
-rw-r--r--examples/build.gradle.kts48
-rw-r--r--examples/src/main/java/io/perfmark/examples/perfetto/WebServer.java92
-rw-r--r--examples/src/main/resources/io/perfmark/examples/perfetto/index.html66
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 59821 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties5
-rwxr-xr-xgradlew234
-rw-r--r--gradlew.bat89
-rw-r--r--impl/BUILD.bazel80
-rw-r--r--impl/build.gradle65
-rw-r--r--impl/src/jmh/java/io/perfmark/impl/SecretPerfMarkImplClassInitBenchmark.java99
-rw-r--r--impl/src/main/java/io/perfmark/impl/Generator.java92
-rw-r--r--impl/src/main/java/io/perfmark/impl/LocalMarkHolder.java41
-rw-r--r--impl/src/main/java/io/perfmark/impl/Mark.java505
-rw-r--r--impl/src/main/java/io/perfmark/impl/MarkHolder.java64
-rw-r--r--impl/src/main/java/io/perfmark/impl/MarkHolderProvider.java45
-rw-r--r--impl/src/main/java/io/perfmark/impl/MarkList.java196
-rw-r--r--impl/src/main/java/io/perfmark/impl/MostlyThreadLocal.java155
-rw-r--r--impl/src/main/java/io/perfmark/impl/MostlyThreadLocalMarkHolder.java37
-rw-r--r--impl/src/main/java/io/perfmark/impl/NoopGenerator.java31
-rw-r--r--impl/src/main/java/io/perfmark/impl/NoopMarkHolderProvider.java94
-rw-r--r--impl/src/main/java/io/perfmark/impl/SecretPerfMarkImpl.java443
-rw-r--r--impl/src/main/java/io/perfmark/impl/Storage.java468
-rw-r--r--impl/src/main/java/io/perfmark/impl/package-info.java19
-rw-r--r--impl/src/test/java/io/perfmark/impl/PerfMarkImplTest.java75
-rw-r--r--impl/src/test/java/io/perfmark/impl/StorageTest.java189
-rw-r--r--java15/build.gradle.kts79
-rw-r--r--java15/src/jmh/java/io/perfmark/java15/HiddenClassVarHandleMarkerBenchmarkTest.java87
-rw-r--r--java15/src/main/java/io/perfmark/java15/HiddenClassVarHandleMarkHolder.java393
-rw-r--r--java15/src/main/java/io/perfmark/java15/SecretHiddenClassMarkHolderProvider.java110
-rw-r--r--java15/src/test/java/io/perfmark/java15/HiddenClassVarHandleMarkHolderTest.java64
-rw-r--r--java6/BUILD.bazel33
-rw-r--r--java6/build.gradle68
-rw-r--r--java6/src/jmh/java/io/perfmark/java6/SynchronizedMarkHolderBenchmarkTest.java73
-rw-r--r--java6/src/jmh/java/io/perfmark/java6/VolatileGeneratorBenchmarkTest.java58
-rw-r--r--java6/src/main/java/io/perfmark/java6/Internal.java23
-rw-r--r--java6/src/main/java/io/perfmark/java6/SecretSynchronizedMarkHolderProvider.java43
-rw-r--r--java6/src/main/java/io/perfmark/java6/SecretVolatileGenerator.java55
-rw-r--r--java6/src/main/java/io/perfmark/java6/SynchronizedMarkHolder.java399
-rw-r--r--java6/src/main/java/io/perfmark/java6/package-info.java19
-rw-r--r--java6/src/test/java/io/perfmark/java6/AutoLoadTest.java43
-rw-r--r--java6/src/test/java/io/perfmark/java6/SynchronizedMarkHolderTest.java31
-rw-r--r--java6/src/test/java/io/perfmark/java6/VersionTest.java47
-rw-r--r--java7/BUILD.bazel9
-rw-r--r--java7/build.gradle59
-rw-r--r--java7/src/jmh/java/io/perfmark/java7/MethodHandleGeneratorBenchmarkTest.java60
-rw-r--r--java7/src/main/java/io/perfmark/java7/Internal.java24
-rw-r--r--java7/src/main/java/io/perfmark/java7/SecretMethodHandleGenerator.java68
-rw-r--r--java7/src/main/java/io/perfmark/java7/package-info.java19
-rw-r--r--java7/src/test/java/io/perfmark/java7/VersionTest.java47
-rw-r--r--java9/BUILD.bazel34
-rw-r--r--java9/build.gradle.kts88
-rw-r--r--java9/src/jcstress/java/io/perfmark/java9/PerfMarkStorageStress.java106
-rw-r--r--java9/src/jmh/java/io/perfmark/java9/VarHandleGeneratorBenchmarkTest.java67
-rw-r--r--java9/src/jmh/java/io/perfmark/java9/VarHandleMarkHolderBenchmarkTest.java85
-rw-r--r--java9/src/main/java/io/perfmark/java9/Internal.java23
-rw-r--r--java9/src/main/java/io/perfmark/java9/SecretVarHandleGenerator.java71
-rw-r--r--java9/src/main/java/io/perfmark/java9/SecretVarHandleMarkHolderProvider.java53
-rw-r--r--java9/src/main/java/io/perfmark/java9/VarHandleMarkHolder.java388
-rw-r--r--java9/src/main/java/io/perfmark/java9/package-info.java20
-rw-r--r--java9/src/test/java/io/perfmark/java9/AutoLoadTest.java51
-rw-r--r--java9/src/test/java/io/perfmark/java9/PerfMarkStressTest.java88
-rw-r--r--java9/src/test/java/io/perfmark/java9/VarHandleMarkHolderTest.java64
-rw-r--r--java9/src/test/java/io/perfmark/java9/VersionTest.java47
-rw-r--r--settings.gradle43
-rw-r--r--testing/build.gradle.kts25
-rw-r--r--testing/src/main/java/io/perfmark/testing/GarbageCollector.java41
-rw-r--r--testing/src/main/java/io/perfmark/testing/GeneratorBenchmark.java85
-rw-r--r--testing/src/main/java/io/perfmark/testing/MarkHolderBenchmark.java142
-rw-r--r--testing/src/main/java/io/perfmark/testing/MarkHolderTest.java217
-rw-r--r--traceviewer/BUILD.bazel12
-rw-r--r--traceviewer/build.gradle.kts22
-rw-r--r--traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java226
-rw-r--r--traceviewer/src/main/java/io/perfmark/traceviewer/package-info.java23
-rw-r--r--traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/LICENSE27
-rw-r--r--traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/index.html124
-rw-r--r--traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/trace_viewer_full.html1
-rw-r--r--traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/LICENSE27
-rw-r--r--traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/webcomponents.min.js14
-rw-r--r--traceviewer/src/test/java/io/perfmark/traceviewer/TraceEventViewerTest.java46
-rw-r--r--tracewriter/BUILD.bazel15
-rw-r--r--tracewriter/build.gradle.kts27
-rw-r--r--tracewriter/src/main/java/io/perfmark/tracewriter/MarkListWalker.java251
-rw-r--r--tracewriter/src/main/java/io/perfmark/tracewriter/TraceEvent.java267
-rw-r--r--tracewriter/src/main/java/io/perfmark/tracewriter/TraceEventWriter.java539
-rw-r--r--tracewriter/src/main/java/io/perfmark/tracewriter/package-info.java23
143 files changed, 13897 insertions, 0 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..1ef0730
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "gradle"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml
new file mode 100644
index 0000000..497d1f3
--- /dev/null
+++ b/.github/workflows/gradle-wrapper-validation.yml
@@ -0,0 +1,10 @@
+name: "Gradle Wrapper"
+on: [push, pull_request]
+
+jobs:
+ validation:
+ name: "validation"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: gradle/wrapper-validation-action@v1
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000..ecbdc41
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,27 @@
+# This workflow will build a Java project with Gradle
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
+
+name: Java CI with Gradle
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 11
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+ - name: Build with Gradle
+ run: ./gradlew build
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5f80a97
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Gradle
+build
+gradle.properties
+.gradle
+local.properties
+out
+
+# Maven (examples)
+target
+
+# Bazel
+bazel-bin
+bazel-examples
+bazel-genfiles
+bazel-perfmark
+bazel-out
+bazel-testlogs
+
+# IntelliJ IDEA
+.idea
+*.iml
+
+# Eclipse
+.classpath
+.project
+.settings
+.gitignore
+bin
+
+# OS X
+.DS_Store
+
+# Emacs
+*~
+\#*\#
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..32df005
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,12 @@
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..085e623
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "Perfmark"
+description:
+ "PerfMark is a High Performance Tracing Library"
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "http://www.perfmark.io"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/perfmark/perfmark.git"
+ }
+ version: "0.26.0"
+ last_upgrade_date { year: 2023 month: 6 day: 2 }
+ license_type: PERMISSIVE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..04fbb4e
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,41 @@
+
+Copyright 2019 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-----------------------------------------------------------------------
+
+This product contains a modified portion of 'Catapult', an open source
+Trace Event viewer for Chome, Linux, and Android applications, which can
+be obtained at:
+
+ * LICENSE:
+ * traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/LICENSE (New BSD License)
+ * HOMEPAGE:
+ * https://github.com/catapult-project/catapult
+
+This product contains a modified portion of 'Polymer', a library for Web
+Components, which can be obtained at:
+ * LICENSE:
+ * traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/LICENSE (New BSD License)
+ * HOMEPAGE:
+ * https://github.com/Polymer/polymer
+
+
+This product contains a modified portion of 'ASM', an open source
+Java Bytecode library, which can be obtained at:
+
+ * LICENSE:
+ * agent/src/main/resources/io/perfmark/agent/third_party/asm/LICENSE (BSD style License)
+ * HOMEPAGE:
+ * https://asm.ow2.io/ \ No newline at end of file
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..3b12c1f
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+kirit@google.com
+olegsh@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f4ac57d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,128 @@
+# PerfMark
+
+![PerfMark Hummingbird](doc/perfmark.png "PerfMark")
+
+PerfMark is a low-overhead, manually-instrumented, tracing library for Java. Users can add the
+tracing function calls to their code to see how long each part takes.
+
+## Features
+
+* **Very Low Overhead**: When enabled, tracing a function call adds about **70ns**. Tracing is
+ done in a lock-free, wait-free, thread local buffer, which avoids interfering with your
+ latency-sensitive code.
+
+* **Dynamically Enabled**: PerfMark can be enabled or disabled at runtime. When disabled,
+ PerfMark has *zero overhead*, taking advantage of the JIT compiler to remove the tracing.
+
+* **Inter-thread Communication**: Existing profilers have difficulty expressing which thread
+ wakes up and executes work on another thread. PerfMark allows users to express this
+ relationship explicitly, making for a clear picture of how code flows.
+
+* **Small Library Size**: The PerfMark tracing API is only *5 KB* in size, and has minimal
+ dependencies making it easy to include in other projects. If no backend for recording the trace
+ is present, the library safely disables itself.
+
+* **Multiple Java Versions**: The PerfMark API supports Java 6, making it easy to include on
+ older or constrained environments. Additionally, PerfMark includes optimized backends for
+ Java 6, Java 7, and Java 9. Each of these backends is automatically loaded at runtime
+ (if possible) and uses advanced JVM features for maximum speed.
+
+* **Chrome Trace Viewer Integration**: PerfMark can export to the Chrome Trace Event Format,
+ making it easy to view in your Web Browser.
+
+## Usage
+
+To use PerfMark, add the following dependencies to your `build.gradle`:
+```
+dependencies {
+ implementation 'io.perfmark:perfmark-api:0.25.0'
+ // Only needed for applications, not libraries.
+ implementation 'io.perfmark:perfmark-traceviewer:0.25.0'
+}
+```
+
+Or in your `pom.xml`:
+
+```
+ <dependency>
+ <groupId>io.perfmark</groupId>
+ <artifactId>perfmark-api</artifactId>
+ <version>0.25.0</version>
+ </dependency>
+```
+
+In your code, add the PerfMark tracing calls like so:
+
+```java
+Map<String, Header> parseHeaders(List<String> rawHeaders) {
+ try (TaskCloseable task = PerfMark.traceTask("Parse HTTP headers")) {
+ Map<String, String> headers = new HashMap<>();
+ for (String rawHeader : rawHeaders) {
+ Header header = parseHeader(rawHeader);
+ headers.put(header.name(), header);
+ }
+ return headers;
+ }
+}
+
+```
+
+PerfMark can also be used to record asynchronous work:
+
+```java
+Future<Response> buildResponse() {
+ try (TaskCloseable task = PerfMark.traceTask("Build Response")) {
+ Link link = PerfMark.linkOut();
+ return executor.submit(() -> {
+ try (TaskCloseable task2 = PerfMark.traceTask("Async Response")) {
+ PerfMark.linkIn(link);
+ return new Response(/* ... */);
+ }
+ });
+ }
+}
+```
+
+To view the traces in your browser, generate the HTML:
+
+```java
+ PerfMark.setEnabled(true);
+ PerfMark.event("My Task");
+ TraceEventViewer.writeTraceHtml();
+```
+
+The output looks like:
+
+![PerfMark Hummingbird](doc/screenshot.png "PerfMark")
+
+## Configuration
+PerfMark provides some System Properties that allow controlling how it initializes. These can be set
+by providing them as JVM arguments. (e.g. `-Dio.perfmark.PerfMark.startEnabled=true`)
+
+* `io.perfmark.PerfMark.startEnabled` controls if PerfMark starts enabled. This boolean property
+ makes it possible to start tracing calls immediately. This is helpful when it's difficult
+ to invoke `setEnabled()` on PerfMark before task tracing calls have started.
+
+* `io.perfmark.PerfMark.debug` controls if PerfMark can log initializing steps. This property
+ exists to disable class loading of the logger package (currently `java.util.logging`). If
+ the debug property is set, the logger settings still need to be configured to report the logs.
+ By default, all PerfMark logs use level `FINE` (SLF4J `DEBUG`) or lower, which means that they
+ usually need additional setup to print.
+
+ In addition to initialization, the debug property controls if other tracing failures can be
+ logged. When calls involving deferred execution are used (e.g.
+ `startTask(T, StringFunction<T>)`), the String function provided may throw an exception. In
+ these cases, the exception is silently ignored. This makes it easy to ensure the start/stop
+ call parity is maintained. To view these failures, the debug property can be set to log such
+ problems. As above, the PerfMark logger should be configured as well to report these.
+
+## Versioning and API Stability
+
+PerfMark uses Semantic Versioning, and thus will not break existing APIs within a minor version
+update. PerfMark may need to disable some functionality, and thus may need to make some tracing
+calls become No-ops. In such cases, it will remain safe to call these functions being recorded.
+
+## Users
+
+PerfMark was designed originally for [gRPC](https://github.com/grpc/grpc-java). It is also used
+by [Zuul](https://github.com/Netflix/zuul).
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..6ae91aa
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,28 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+RULES_JVM_EXTERNAL_TAG = "4.0"
+
+RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169"
+
+http_archive(
+ name = "rules_jvm_external",
+ sha256 = RULES_JVM_EXTERNAL_SHA,
+ strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
+ url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
+)
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+
+maven_install(
+ artifacts = [
+ "junit:junit:4.13.2",
+ "com.google.errorprone:error_prone_annotations:2.7.1",
+ "com.google.code.findbugs:jsr305:3.0.2",
+ "com.google.code.gson:gson:2.8.7",
+ "com.google.truth:truth:1.1.3",
+ ],
+ repositories = [
+ "https://maven.google.com",
+ "https://repo1.maven.org/maven2",
+ ],
+)
diff --git a/agent/build.gradle.kts b/agent/build.gradle.kts
new file mode 100644
index 0000000..eb572fd
--- /dev/null
+++ b/agent/build.gradle.kts
@@ -0,0 +1,77 @@
+import groovy.util.Node
+
+buildscript {
+ extra.apply {
+ set("moduleName", "io.perfmark.agent")
+ }
+}
+
+plugins {
+ id("com.github.johnrengelman.shadow") version "7.0.0"
+}
+
+val jdkVersion = JavaVersion.VERSION_1_6
+
+dependencies {
+ compileOnly(libs.jsr305)
+ compileOnly(libs.errorprone)
+
+ implementation("org.ow2.asm:asm:9.1")
+ implementation("org.ow2.asm:asm-commons:9.1")
+
+ testImplementation(project(":perfmark-api"))
+ testImplementation(project(":perfmark-impl"))
+ testImplementation(libs.truth)
+ testRuntimeOnly(project(":perfmark-java6"))
+}
+
+tasks.named<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+
+ javaCompiler.set(javaToolchains.compilerFor {
+ languageVersion.set(JavaLanguageVersion.of(11))
+ })
+
+ options.compilerArgs.add("-Xlint:-options")
+}
+
+tasks.named<JavaCompile>("compileTestJava") {
+ sourceCompatibility = JavaVersion.VERSION_17.toString()
+ targetCompatibility = JavaVersion.VERSION_17.toString()
+}
+
+tasks.named<Jar>("jar") {
+ // Make this not the default
+ archiveClassifier.value("original")
+ manifest {
+ attributes(mapOf(
+ "Premain-Class" to "io.perfmark.agent.PerfMarkAgent",
+ ))
+ }
+}
+
+tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
+ // make sure this is THE jar, which removes the suffix.
+ archiveClassifier.value(null as String?)
+
+ relocate("org.objectweb.asm", "io.perfmark.agent.shaded.org.objectweb.asm")
+}
+
+publishing {
+ publications {
+ named<MavenPublication>("maven") {
+ pom.withXml {
+ val root = asNode()
+
+ for (child in root.children()) {
+ val c = child as Node
+ if (c.name().toString().endsWith("dependencies")) {
+ root.remove(c)
+ break
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/io/perfmark/agent/MethodVisitorRecorder.java b/agent/src/main/java/io/perfmark/agent/MethodVisitorRecorder.java
new file mode 100644
index 0000000..960d0f7
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/MethodVisitorRecorder.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.TypePath;
+
+/**
+ * This class records the "header" portion of the method visitor.
+ */
+class MethodVisitorRecorder extends MethodVisitor {
+
+ private final int VISIT_PARAMETER = 1;
+ private final int VISIT_ANNOTATION_DEFAULT = 2;
+
+ private final int ANNOTATION_VISIT = 3;
+ private final int ANNOTATION_VISIT_ENUM = 4;
+ private final int ANNOTATION_VISIT_ANNOTATION = 5;
+ private final int ANNOTATION_VISIT_ARRAY = 6;
+ private final int ANNOTATION_VISIT_END = 7;
+
+ private final int VISIT_ANNOTATION = 8;
+ private final int VISIT_ANNOTABLE_PARAMETER_COUNT = 9;
+ private final int VISIT_PARAMETER_ANNOTATION = 10;
+ private final int VISIT_TYPE_ANNOTATION = 11;
+ private final int VISIT_ATTRIBUTE = 12;
+
+ private final AnnotationVisitorRecorder annotationVisitorRecorder = new AnnotationVisitorRecorder(null);
+
+ private int opsWidx;
+ private int intsWidx;
+ private int stringsWidx;
+ private int objectsWidx;
+ private int booleansWidx;
+
+ private int[] ops = new int[0];
+ private String[] strings = new String[0];
+ private int[] ints = new int[0];
+ private Object[] objects = new Object[0];
+ private boolean[] booleans = new boolean[0];
+
+ int firstLine = -1;
+ int lastLine = -1;
+
+ MethodVisitorRecorder(MethodVisitor delegate) {
+ // Have to pin to a specific version, since the method invocations may be different in a later release
+ super(Opcodes.ASM9, delegate);
+ }
+
+ /*
+ * Docs for Method Visitor Say:
+ *
+ * A visitor to visit a Java method. The methods of this class must be called in the following order:
+ * ( visitParameter )*
+ * [ visitAnnotationDefault ]
+ * ( visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation visitTypeAnnotation | visitAttribute )*
+ * [ visitCode (
+ * visitFrame | visit<i>X</i>Insn | visitLabel | visitInsnAnnotation | visitTryCatchBlock
+ * | visitTryCatchAnnotation | visitLocalVariable | visitLocalVariableAnnotation | visitLineNumber )*
+ * visitMaxs ]
+ * visitEnd.
+ *
+ *
+ * In addition, the visit<i>X</i>Insn and visitLabel methods must be called in the sequential order of the bytecode
+ * instructions of the visited code, visitInsnAnnotation must be called after the annotated instruction,
+ * visitTryCatchBlock must be called before the labels passed as arguments have been visited,
+ * visitTryCatchBlockAnnotation must be called after the corresponding try catch block has been visited, and the
+ * visitLocalVariable, visitLocalVariableAnnotation and visitLineNumber methods must be called after the labels passed
+ * as arguments have been visited.
+ */
+
+ @Override
+ public final void visitParameter(String name, int access) {
+ addOp(VISIT_PARAMETER);
+ addString(name);
+ addInt(access);
+ super.visitParameter(name, access);
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotationDefault() {
+ addOp(VISIT_ANNOTATION_DEFAULT);
+ if (mv == null) {
+ return annotationVisitorRecorder;
+ }
+ return new AnnotationVisitorRecorder(super.visitAnnotationDefault());
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ addOp(VISIT_ANNOTATION);
+ addString(descriptor);
+ addBoolean(visible);
+ if (mv == null) {
+ return annotationVisitorRecorder;
+ }
+ return new AnnotationVisitorRecorder(super.visitAnnotation(descriptor, visible));
+ }
+
+ @Override
+ public final void visitAnnotableParameterCount(int parameterCount, boolean visible) {
+ addOp(VISIT_ANNOTABLE_PARAMETER_COUNT);
+ addInt(parameterCount);
+ addBoolean(visible);
+ super.visitAnnotableParameterCount(parameterCount, visible);
+ }
+
+ @Override
+ public final AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {
+ addOp(VISIT_PARAMETER_ANNOTATION);
+ addInt(parameter);
+ addString(descriptor);
+ addBoolean(visible);
+ if (mv == null) {
+ return annotationVisitorRecorder;
+ }
+ return new AnnotationVisitorRecorder(super.visitParameterAnnotation(parameter, descriptor, visible));
+ }
+
+ @Override
+ public final AnnotationVisitor visitTypeAnnotation(
+ int typeRef, TypePath typePath, String descriptor, boolean visible) {
+ addOp(VISIT_TYPE_ANNOTATION);
+ addInt(typeRef);
+ addObject(typePath);
+ addString(descriptor);
+ addBoolean(visible);
+ if (mv == null) {
+ return annotationVisitorRecorder;
+ }
+ return new AnnotationVisitorRecorder(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));
+ }
+
+ @Override
+ public final void visitAttribute(Attribute attribute) {
+ addOp(VISIT_ATTRIBUTE);
+ addObject(attribute);
+ super.visitAttribute(attribute);
+ }
+
+ @Override
+ public final void visitLineNumber(int line, Label start) {
+ if (firstLine == -1) {
+ firstLine = line;
+ }
+ lastLine = line;
+ super.visitLineNumber(line, start);
+ }
+
+ final void replay() {
+ if (mv != null) {
+ replay(mv);
+ }
+ }
+
+ final void replay(MethodVisitor delegate) {
+ if (delegate == null) {
+ return;
+ }
+ int stringsRidx = 0;
+ int intsRidx = 0;
+ int objectsRidx = 0;
+ int booleansRidx = 0;
+ int annoWidx = 0;
+ AnnotationVisitor[] visitorStack = new AnnotationVisitor[0];
+ for (int opsRidx = 0; opsRidx < opsWidx; opsRidx++) {
+ int op = ops[opsRidx];
+ switch (op) {
+ case VISIT_PARAMETER: {
+ String name = getString(stringsRidx++);
+ int access = getInt(intsRidx++);
+ delegate.visitParameter(name, access);
+ break;
+ }
+ case VISIT_ANNOTATION_DEFAULT: {
+ AnnotationVisitor visitor = delegate.visitAnnotationDefault();
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, visitor);
+ break;
+ }
+ case ANNOTATION_VISIT: {
+ AnnotationVisitor currentVisitor = visitorStack[annoWidx - 1];
+ String name = getString(stringsRidx++);
+ Object value = getObject(objectsRidx++);
+ if (currentVisitor != null) {
+ currentVisitor.visit(name, value);
+ }
+ break;
+ }
+ case ANNOTATION_VISIT_ENUM: {
+ AnnotationVisitor currentVisitor = visitorStack[annoWidx - 1];
+ String name = getString(stringsRidx++);
+ String descriptor = getString(stringsRidx++);
+ String value = getString(stringsRidx++);
+ if (currentVisitor != null) {
+ currentVisitor.visitEnum(name, descriptor, value);
+ }
+ break;
+ }
+ case ANNOTATION_VISIT_ANNOTATION: {
+ AnnotationVisitor currentVisitor = visitorStack[annoWidx - 1];
+ String name = getString(stringsRidx++);
+ String descriptor = getString(stringsRidx++);
+ AnnotationVisitor newVisitor = null;
+ if (currentVisitor != null) {
+ newVisitor = currentVisitor.visitAnnotation(name, descriptor);
+ }
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, newVisitor);
+ break;
+ }
+ case ANNOTATION_VISIT_ARRAY: {
+ AnnotationVisitor currentVisitor = visitorStack[annoWidx - 1];
+ String name = getString(stringsRidx++);
+ AnnotationVisitor newVisitor = null;
+ if (currentVisitor != null) {
+ newVisitor = currentVisitor.visitArray(name);
+ }
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, newVisitor);
+ break;
+ }
+ case ANNOTATION_VISIT_END: {
+ AnnotationVisitor currentVisitor = visitorStack[annoWidx - 1];
+ visitorStack[--annoWidx] = null;
+ if (currentVisitor != null) {
+ currentVisitor.visitEnd();
+ }
+ break;
+ }
+ case VISIT_ANNOTATION: {
+ String descriptor = getString(stringsRidx++);
+ boolean visible = getBoolean(booleansRidx++);
+ AnnotationVisitor newVisitor = delegate.visitAnnotation(descriptor, visible);
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, newVisitor);
+ break;
+ }
+ case VISIT_ANNOTABLE_PARAMETER_COUNT: {
+ int parameterCount = getInt(intsRidx++);
+ boolean visible = getBoolean(booleansRidx++);
+ delegate.visitAnnotableParameterCount(parameterCount, visible);
+ break;
+ }
+ case VISIT_PARAMETER_ANNOTATION: {
+ int parameter = getInt(intsRidx++);
+ String descriptor = getString(stringsRidx++);
+ boolean visible = getBoolean(booleansRidx++);
+ AnnotationVisitor newVisitor = delegate.visitParameterAnnotation(parameter, descriptor, visible);
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, newVisitor);
+ break;
+ }
+ case VISIT_TYPE_ANNOTATION: {
+ // (int typeRef, TypePath typePath, String descriptor, boolean visible)
+ int typeRef = getInt(intsRidx++);
+ TypePath typePath = (TypePath) getObject(objectsRidx++);
+ String descriptor = getString(stringsRidx++);
+ boolean visible = getBoolean(booleansRidx++);
+ AnnotationVisitor newVisitor = delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
+ visitorStack = addAnnotationVisitor(visitorStack, annoWidx++, newVisitor);
+ break;
+ }
+ case VISIT_ATTRIBUTE: {
+ Attribute attribute = (Attribute) getObject(objectsRidx++);
+ delegate.visitAttribute(attribute);
+ break;
+ }
+ default:
+ throw new AssertionError("Bad op " + op);
+ }
+ }
+ }
+
+ private void addOp(int op) {
+ ops = addInt(ops, opsWidx++, op);
+ }
+
+ private void addInt(int value) {
+ ints = addInt(ints, intsWidx++, value);
+ }
+
+ private static int[] addInt(int[] dest, int pos, int value) {
+ if (dest.length == pos){
+ int[] newDest = new int[1 + dest.length * 2];
+ System.arraycopy(dest, 0, newDest, 0, dest.length);
+ dest = newDest;
+ }
+ dest[pos] = value;
+ return dest;
+ }
+
+ private void addString(String value) {
+ strings = addString(strings, stringsWidx++, value);
+ }
+
+ private static String[] addString(String[] dest, int pos, String value) {
+ if (dest.length == pos){
+ String[] newDest = new String[1 + dest.length * 2];
+ System.arraycopy(dest, 0, newDest, 0, dest.length);
+ dest = newDest;
+ }
+ dest[pos] = value;
+ return dest;
+ }
+
+ private void addObject(Object value) {
+ objects = addObject(objects, objectsWidx++, value);
+ }
+
+ private static Object[] addObject(Object[] dest, int pos, Object value) {
+ if (dest.length == pos){
+ Object[] newDest = new Object[1 + dest.length * 2];
+ System.arraycopy(dest, 0, newDest, 0, dest.length);
+ dest = newDest;
+ }
+ dest[pos] = value;
+ return dest;
+ }
+
+ private void addBoolean(boolean value) {
+ booleans = addBoolean(booleans, booleansWidx++, value);
+ }
+
+ private static boolean[] addBoolean(boolean[] dest, int pos, boolean value) {
+ if (dest.length == pos){
+ boolean[] newDest = new boolean[1 + dest.length * 2];
+ System.arraycopy(dest, 0, newDest, 0, dest.length);
+ dest = newDest;
+ }
+ dest[pos] = value;
+ return dest;
+ }
+
+ private static AnnotationVisitor[] addAnnotationVisitor(AnnotationVisitor[] dest, int pos, AnnotationVisitor value) {
+ if (dest.length == pos){
+ AnnotationVisitor[] newDest = new AnnotationVisitor[1 + dest.length * 2];
+ System.arraycopy(dest, 0, newDest, 0, dest.length);
+ dest = newDest;
+ }
+ dest[pos] = value;
+ return dest;
+ }
+
+ private int getInt(int ridx) {
+ assert ridx < intsWidx;
+ return ints[ridx];
+ }
+
+ private String getString(int ridx) {
+ assert ridx < stringsWidx;
+ return strings[ridx];
+ }
+
+ private Object getObject(int ridx) {
+ assert ridx < objectsWidx;
+ return objects[ridx];
+ }
+
+ private boolean getBoolean(int ridx) {
+ assert ridx < booleansWidx;
+ return booleans[ridx];
+ }
+
+ private final class AnnotationVisitorRecorder extends AnnotationVisitor {
+
+ AnnotationVisitorRecorder(AnnotationVisitor delegate) {
+ super(MethodVisitorRecorder.this.api, delegate);
+ }
+
+ /*
+ * Docs for AnnotationVisitor say
+ *
+ * A visitor to visit a Java annotation. The methods of this class must be called in the following order:
+ * ( visit | visitEnum | visitAnnotation | visitArray )* visitEnd.
+ */
+
+ @Override
+ public void visit(String name, Object value) {
+ addOp(ANNOTATION_VISIT);
+ addString(name);
+ addObject(value);
+ super.visit(name, value);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ addOp(ANNOTATION_VISIT_ENUM);
+ addString(name);
+ addString(descriptor);
+ addString(value);
+ super.visitEnum(name, descriptor, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ addOp(ANNOTATION_VISIT_ANNOTATION);
+ addString(name);
+ addString(descriptor);
+ if (av == null) {
+ return this;
+ }
+ return new AnnotationVisitorRecorder(super.visitAnnotation(name, descriptor));
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ addOp(ANNOTATION_VISIT_ARRAY);
+ addString(name);
+ if (av == null) {
+ return this;
+ }
+ return new AnnotationVisitorRecorder(super.visitArray(name));
+ }
+
+ @Override
+ public void visitEnd() {
+ addOp(ANNOTATION_VISIT_END);
+ super.visitEnd();
+ }
+ }
+}
diff --git a/agent/src/main/java/io/perfmark/agent/MethodWrappingWriter.java b/agent/src/main/java/io/perfmark/agent/MethodWrappingWriter.java
new file mode 100644
index 0000000..46dd6e2
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/MethodWrappingWriter.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+final class MethodWrappingWriter {
+
+ private final MethodVisitorRecorder recorder;
+ private final int access;
+ private final String methodName;
+ private final String descriptor;
+ private final String signature;
+ private final String[] exceptions;
+ private final String className;
+ private final String bodyMethodName;
+ private final ClassVisitor classVisitor;
+ private final boolean isInterface;
+
+ MethodWrappingWriter(
+ MethodVisitorRecorder recorder, int access, String methodName, String descriptor, String signature,
+ String[] exceptions, boolean isInterface, String className, String bodyMethodName, ClassVisitor cv) {
+ this.recorder = recorder;
+ this.access = access;
+ this.methodName = methodName;
+ this.descriptor = descriptor;
+ this.signature = signature;
+ this.exceptions = exceptions;
+ this.isInterface = isInterface;
+ this.className = className;
+ this.bodyMethodName = bodyMethodName;
+ this.classVisitor = cv;
+ }
+
+ void visit() {
+ MethodVisitor mv = classVisitor.visitMethod(access, methodName, descriptor, signature, exceptions);
+ if (mv == null) {
+ return;
+ }
+ // TODO(carl-mastrangelo): add start / stop call tags here
+ recorder.replay(mv);
+
+ mv.visitCode();
+ Label start = new Label();
+ Label end = new Label();
+ mv.visitLabel(start);
+ mv.visitLineNumber(recorder.firstLine, start);
+ mv.visitLdcInsn(className + "::" + methodName);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "io/perfmark/PerfMark", "startTask", "(Ljava/lang/String;)V", false);
+
+ if (((access & Opcodes.ACC_STATIC) == 0)) {
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ }
+
+ int params = 1;
+ char ret = 0;
+ assert descriptor.charAt(0) == '(';
+ out: for (int i = 1; i < descriptor.length(); i++) {
+ char c = descriptor.charAt(i);
+ switch (c) {
+ case ')':
+ ret = descriptor.charAt(i + 1);
+ break out;
+ case 'L':
+ mv.visitVarInsn(Opcodes.ALOAD, params++);
+ i = descriptor.indexOf(';', i);
+ assert i > 0;
+ break;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ mv.visitVarInsn(Opcodes.ILOAD, params++);
+ break;
+ case 'F':
+ mv.visitVarInsn(Opcodes.FLOAD, params++);
+ break;
+ case 'J':
+ mv.visitVarInsn(Opcodes.LLOAD, params++);
+ break;
+ case 'D':
+ mv.visitVarInsn(Opcodes.DLOAD, params++);
+ break;
+ case '[':
+ mv.visitVarInsn(Opcodes.ALOAD, params++);
+ while (descriptor.charAt(++i) == '[') {
+ // empty body on purpose.
+ }
+ if (descriptor.charAt(i) == 'L') {
+ i = descriptor.indexOf(';', i);
+ }
+
+ break;
+ default:
+ throw new RuntimeException("Bad descriptor " + c + " in " + descriptor);
+ }
+ }
+
+ int invoke;
+ if ((access & Opcodes.ACC_STATIC) != 0) {
+ invoke = Opcodes.INVOKESTATIC;
+ } else if (isInterface) {
+ invoke = Opcodes.INVOKEINTERFACE;
+ } else {
+ invoke = Opcodes.INVOKESPECIAL;
+ }
+
+ mv.visitMethodInsn(invoke, className.replace(".", "/"), bodyMethodName, descriptor, isInterface);
+
+ mv.visitLabel(end);
+ mv.visitLineNumber(recorder.lastLine, end);
+ mv.visitLdcInsn(className + ":::" + methodName);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, "io/perfmark/PerfMark", "stopTask", "(Ljava/lang/String;)V", false);
+
+ switch (ret) {
+ case 'V':
+ mv.visitInsn(Opcodes.RETURN);
+ break;
+ case 'L':
+ case '[':
+ mv.visitInsn(Opcodes.ARETURN);
+ break;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ mv.visitInsn(Opcodes.IRETURN);
+ break;
+ case 'F':
+ mv.visitInsn(Opcodes.FRETURN);
+ break;
+ case 'J':
+ mv.visitInsn(Opcodes.LRETURN);
+ break;
+ case 'D':
+ mv.visitInsn(Opcodes.DRETURN);
+ break;
+ default:
+ throw new RuntimeException("Bad Descriptor " + ret);
+ }
+
+ mv.visitMaxs(params, params + 1);
+ mv.visitEnd();
+ }
+}
diff --git a/agent/src/main/java/io/perfmark/agent/NonMergingClassWriter.java b/agent/src/main/java/io/perfmark/agent/NonMergingClassWriter.java
new file mode 100644
index 0000000..c93c648
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/NonMergingClassWriter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+/**
+ * This class exists to avoid the runtime lookup of classes when doing flow analysis. When
+ * computing frames, the default implementation needs to merge types together, which involves
+ * finding their common super class. This makes the transformer accidentally load a bunch of
+ * classes before they can be transformed. To avoid the possibility of this happening this
+ * class writer intentionally fails any such attempts. This ensures flags like {@link
+ * ClassWriter#COMPUTE_FRAMES} don't trigger merge logic.
+ */
+final class NonMergingClassWriter extends ClassWriter {
+ NonMergingClassWriter(ClassReader classReader, int flags) {
+ super(classReader, flags);
+ }
+
+ @Override
+ protected String getCommonSuperClass(String type1, String type2) {
+ throw new UnsupportedOperationException("avoiding reflective lookup of classes");
+ }
+}
diff --git a/agent/src/main/java/io/perfmark/agent/PerfMarkAgent.java b/agent/src/main/java/io/perfmark/agent/PerfMarkAgent.java
new file mode 100644
index 0000000..ab4b2e9
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/PerfMarkAgent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import java.lang.instrument.Instrumentation;
+
+public final class PerfMarkAgent {
+
+ /**
+ * Entry point as an agent.
+ */
+ public static void premain(String agentArgs, Instrumentation inst) {
+ inst.addTransformer(new PerfMarkTransformer(inst));
+ }
+
+ private PerfMarkAgent() {}
+}
diff --git a/agent/src/main/java/io/perfmark/agent/PerfMarkClassVisitor.java b/agent/src/main/java/io/perfmark/agent/PerfMarkClassVisitor.java
new file mode 100644
index 0000000..3833068
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/PerfMarkClassVisitor.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.ModuleVisitor;
+import org.objectweb.asm.Opcodes;
+
+final class PerfMarkClassVisitor extends ClassVisitor {
+
+ static final String[] ALL_METHODS = new String[0];
+
+ private final Runnable changeDetector = new ChangeDetector();
+
+ private final String classLoaderName;
+ private final String className;
+ private final String[] methodsToRewrite;
+ private final String[] methodsToWrap;
+
+ private String fileName;
+ private String moduleName;
+ private String moduleVersion;
+ private boolean isInterface;
+ @SuppressWarnings("unused")
+ private boolean madeChanges;
+
+ PerfMarkClassVisitor(
+ String classLoaderName, String className, String[] methodsToRewrite, String[] methodsToWrap,
+ ClassVisitor classVisitor) {
+ super(Opcodes.ASM9, classVisitor);
+ this.classLoaderName = classLoaderName;
+ this.className = className;
+ this.methodsToRewrite = methodsToRewrite == ALL_METHODS ? ALL_METHODS : methodsToRewrite.clone();
+ this.methodsToWrap = methodsToWrap == ALL_METHODS ? ALL_METHODS : methodsToWrap.clone();
+ }
+
+ @Override
+ public ModuleVisitor visitModule(String name, int access, String version) {
+ this.moduleName = name;
+ this.moduleVersion = version;
+ return super.visitModule(name, access, version);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ this.fileName = source;
+ super.visitSource(source, debug);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String methodName, String descriptor, String signature, String[] exceptions) {
+ final MethodVisitor superDelegate;
+ final MethodWrappingWriter methodWrapper;
+ if (shouldWrap(methodName, access)) {
+ if (className.startsWith("io/perfmark/") || className.startsWith("jdk/")) {
+ // Avoid recursion for now.
+ return super.visitMethod(access, methodName, descriptor, signature, exceptions);
+ }
+ String bodyMethodName = methodName + "$perfmark";
+ int newAccess;
+ if (!isInterface) {
+ newAccess = (access | Opcodes.ACC_PRIVATE) & ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
+ } else {
+ newAccess = access;
+ }
+ MethodVisitor visitor = super.visitMethod(newAccess, bodyMethodName, descriptor, signature, exceptions);
+ MethodVisitorRecorder recorder = new MethodVisitorRecorder(visitor);
+ superDelegate = recorder;
+ methodWrapper =
+ new MethodWrappingWriter(
+ recorder, access, methodName, descriptor, signature, exceptions, isInterface, className, bodyMethodName,
+ cv);
+ } else {
+ superDelegate = super.visitMethod(access, methodName, descriptor, signature, exceptions);
+ methodWrapper = null;
+ }
+
+ final MethodVisitor visitor;
+ if (shouldRewrite(methodName)) {
+ visitor = new PerfMarkMethodRewriter(
+ classLoaderName, moduleName, moduleVersion, className, methodName, fileName, changeDetector, superDelegate);
+ } else {
+ visitor = superDelegate;
+ }
+
+ return new MethodVisitorWrapper(methodWrapper, visitor);
+ }
+
+ boolean shouldWrap(String methodName, int access) {
+ if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) {
+ return false;
+ }
+ if (methodsToWrap == ALL_METHODS) {
+ // not yet supported
+ if (methodName.equals("<init>") || methodName.equals("<clinit>")) {
+ return false;
+ }
+ return true;
+ }
+ for (String method : methodsToWrap) {
+ if (method.equals(methodName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean shouldRewrite(String methodName) {
+ if (methodsToRewrite == ALL_METHODS) {
+ return true;
+ }
+ for (String method : methodsToRewrite) {
+ if (method.equals(methodName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private final class MethodVisitorWrapper extends MethodVisitor {
+
+ private final MethodWrappingWriter methodWrapper;
+
+ /**
+ *
+ * @param methodWrapper the writer of the "wrapper" method. May be {@code null}.
+ * @param delegate method visitor to inject in the additional method. May be {@code null}.
+ */
+ MethodVisitorWrapper(MethodWrappingWriter methodWrapper, MethodVisitor delegate) {
+ super(PerfMarkClassVisitor.this.api, delegate);
+ this.methodWrapper = methodWrapper;
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+ if (methodWrapper != null) {
+ madeChanges = true;
+ methodWrapper.visit();
+ }
+ }
+ }
+
+ private final class ChangeDetector implements Runnable {
+ @Override
+ public void run() {
+ madeChanges = true;
+ }
+ }
+}
diff --git a/agent/src/main/java/io/perfmark/agent/PerfMarkMethodRewriter.java b/agent/src/main/java/io/perfmark/agent/PerfMarkMethodRewriter.java
new file mode 100644
index 0000000..8a876b6
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/PerfMarkMethodRewriter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Modified PerfMark startTask and stopTask call sits to include tags about where in code they came from.
+ */
+final class PerfMarkMethodRewriter extends MethodVisitor {
+
+ private static final String PERFMARK_CLZ = "io/perfmark/PerfMark";
+ private static final String TASKCLOSEABLE_CLZ = "io/perfmark/TaskCloseable";
+
+ /**
+ * May be {@code null} if absent.
+ */
+ private static final Constructor<? extends StackTraceElement> MODULE_CTOR = getStackTraceElementCtorSafe();
+
+ private final Runnable onChange;
+ private final String classLoaderName;
+ private final String moduleName;
+ private final String moduleVersion;
+ // The internal fully qualified name. (e.g. java/lang/String)
+ private final String className;
+ private final String methodName;
+ private final String fileName;
+
+ private int lineNumber = -1;
+
+ /**
+ * Builds the rewriter to add debug into to trace calls.
+ *
+ * @param classLoaderName the loader of this class. May be {@code null}.
+ * @param moduleName the module of this class. May be {@code null}.
+ * @param moduleVersion the module version of this class. May be {@code null}.
+ * @param className the class that contains this method. Must be non-{@code null}.
+ * @param methodName the method currently being visited. Must be non-{@code null}.
+ * @param fileName the class that contains this method. May be {@code null}.
+ * @param onChange runnable to invoke if any changes are made to the class definition. May be {@code null}.
+ * @param methodVisitor the delegate to call. May be {@code null}.
+ */
+ PerfMarkMethodRewriter(
+ String classLoaderName, String moduleName, String moduleVersion, String className, String methodName,
+ String fileName, Runnable onChange, MethodVisitor methodVisitor) {
+ super(Opcodes.ASM9, methodVisitor);
+ this.classLoaderName = classLoaderName;
+ this.moduleName = moduleName;
+ this.moduleVersion = moduleVersion;
+ this.className = className;
+ this.methodName = methodName;
+ this.fileName = fileName;
+ this.onChange = onChange;
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ lineNumber = line;
+ super.visitLineNumber(line, start);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
+ if (shouldPreTag(opcode, owner, name)) {
+ visitLdcInsn("PerfMark.stopCallSite");
+ visitLdcInsn(callSite());
+ super.visitMethodInsn(INVOKESTATIC, PERFMARK_CLZ, "attachTag", "(Ljava/lang/String;Ljava/lang/String;)V", false);
+ if (onChange != null) {
+ onChange.run();
+ }
+ }
+
+ super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+
+ if (shouldPostTag(opcode, owner, name)) {
+ visitLdcInsn("PerfMark.startCallSite");
+ visitLdcInsn(callSite());
+ super.visitMethodInsn(INVOKESTATIC, PERFMARK_CLZ, "attachTag", "(Ljava/lang/String;Ljava/lang/String;)V", false);
+ if (onChange != null) {
+ onChange.run();
+ }
+ }
+ }
+
+ private boolean shouldPreTag(int opcode, String owner, String methodName) {
+ switch (opcode) {
+ case INVOKESTATIC:
+ return PERFMARK_CLZ.equals(owner) && methodName.equals("stopTask") && !TASKCLOSEABLE_CLZ.equals(className);
+ case INVOKEVIRTUAL:
+ return TASKCLOSEABLE_CLZ.equals(owner) && methodName.equals("close");
+ default:
+ return false;
+ }
+ }
+
+ private boolean shouldPostTag(int opcode, String owner, String methodName) {
+ return opcode == INVOKESTATIC
+ && PERFMARK_CLZ.equals(owner)
+ && (methodName.equals("startTask") || methodName.equals("traceTask"));
+ }
+
+ private String callSite() {
+ StackTraceElement elem = null;
+ try {
+ elem = moduleElement();
+ } catch (Throwable t) {
+ // TODO(carl-mastrangelo): this should log.
+ }
+ if (elem == null) {
+ elem = new StackTraceElement(className, methodName, fileName, lineNumber);
+ }
+ return elem.toString();
+ }
+
+ /**
+ * Builds the current callsite. Visible for testing.
+ *
+ * @return stack trace element using the modern constructor, or {@code null} if absent.
+ */
+ StackTraceElement moduleElement() throws InvocationTargetException, InstantiationException, IllegalAccessException {
+ StackTraceElement elem = null;
+ if (MODULE_CTOR != null) {
+ elem = MODULE_CTOR.newInstance(
+ classLoaderName, moduleName, moduleVersion, className, methodName, fileName, lineNumber);
+ }
+ return elem;
+ }
+
+ private static Constructor<StackTraceElement> getStackTraceElementCtorSafe() {
+ try {
+ return getStackTraceElementCtor();
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the more modern constructor. Visible for testing.
+ *
+ * @return the module-based constructor, or {@code null} if it is absent.
+ */
+ static Constructor<StackTraceElement> getStackTraceElementCtor() {
+ Constructor<StackTraceElement> ctor = null;
+ try {
+ ctor = StackTraceElement.class.getConstructor(
+ String.class, String.class, String.class, String.class, String.class, String.class, int.class);
+ } catch (NoSuchMethodException e) {
+ // normal on JDK 8 and below, but include an assert in case the descriptor was wrong.
+ assert StackTraceElement.class.getConstructors().length < 2 : e;
+ }
+ return ctor;
+ }
+}
diff --git a/agent/src/main/java/io/perfmark/agent/PerfMarkTransformer.java b/agent/src/main/java/io/perfmark/agent/PerfMarkTransformer.java
new file mode 100644
index 0000000..e5ce204
--- /dev/null
+++ b/agent/src/main/java/io/perfmark/agent/PerfMarkTransformer.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package io.perfmark.agent;
+
+import java.io.IOException;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.jar.JarFile;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+final class PerfMarkTransformer implements ClassFileTransformer {
+
+ /** May be {@code null}. */
+ private static final Method CLASS_LOADER_GET_NAME = getClassLoaderNameMethodSafe();
+
+ @SuppressWarnings("unused")
+ private final Instrumentation instrumentation;
+
+ /**
+ * @param instrumentation may be {@code null}.
+ */
+ PerfMarkTransformer(Instrumentation instrumentation) {
+ this.instrumentation = instrumentation;
+ if (instrumentation != null) {
+ try {
+ URL url = getClass().getClassLoader().getResource("io/perfmark/PerfMark.class");
+ int index;
+ if (url != null && "jar".equals(url.getProtocol()) && (index = url.getFile().indexOf("!/")) != -1) {
+ // The "file" is what appendToBootstrapClassLoaderSearch uses, so we will too. The jar format
+ // looks like "file:/home/jars/perfmark-api.jar:/io/perfmark/PerfMark.class", so we need to strip
+ // off the "file:" and the bangslash.
+ JarFile jf = new JarFile(url.getFile().substring(5, index));
+ jf.close();
+ instrumentation.appendToBootstrapClassLoaderSearch(jf);
+ }
+ } catch (IOException e ) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+
+ @Override
+ public byte[] transform(
+ ClassLoader loader,
+ final String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ System.err.println(" Attempting " + className);
+ try {
+ return transformInternal(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
+ } catch (Exception e) {
+ System.err.println(e.toString());
+ e.printStackTrace(System.err);
+ throw new RuntimeException(e);
+ }
+ }
+
+ byte[] transformInternal(
+ ClassLoader loader,
+ final String className,
+ Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ assert !className.contains(".") : "Binary name with `.` detected rather than internal name";
+ String classLoaderName = getClassLoaderName(loader);
+ return transform(classLoaderName, className, classfileBuffer);
+ }
+
+ private static byte[] transform(String classLoaderName, String className, byte[] classfileBuffer) {
+ ClassReader cr = new ClassReader(classfileBuffer);
+ if (true) {
+ ClassWriter cw = new NonMergingClassWriter(cr, ClassWriter.COMPUTE_MAXS);
+ PerfMarkClassVisitor perfMarkClassVisitor =
+ new PerfMarkClassVisitor(
+ classLoaderName, className, PerfMarkClassVisitor.ALL_METHODS, new String[0], cw);
+ cr.accept(perfMarkClassVisitor, 0);
+ return cw.toByteArray();
+ }
+ return null;
+ }
+
+ static String deriveFileName(String className) {
+ String clzName = className.replace('/', '.');
+ int dollar = clzName.indexOf('$');
+ String fileName;
+ if (dollar == -1) {
+ fileName = clzName;
+ } else {
+ fileName = clzName.substring(0, dollar);
+ }
+ if (!fileName.isEmpty()) {
+ int dot = fileName.lastIndexOf('.');
+ if (dot != -1) {
+ fileName = fileName.substring(dot + 1);
+ }
+ }
+ // TODO: this is broken for private top level classes.
+ if (!fileName.isEmpty()) {
+ fileName += ".java";
+ } else {
+ fileName = null;
+ }
+ return fileName;
+ }
+
+ /**
+ * Returns the name for the class loader. Visible for testing.
+ *
+ * @param loader the class loader. May be {@code null}.
+ * @return The name of the class loader, or {@code null} if unavailable
+ */
+ static String getClassLoaderName(ClassLoader loader) {
+ if (loader == null) {
+ return null;
+ }
+ if (CLASS_LOADER_GET_NAME != null) {
+ try {
+ return (String) CLASS_LOADER_GET_NAME.invoke(loader);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof Error) {
+ throw (Error) e.getCause();
+ } else if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ } else {
+ throw new RuntimeException(e.getCause());
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return null;
+ }
+
+ private static Method getClassLoaderNameMethodSafe() {
+ try {
+ return getClassLoaderNameMethod();
+ } catch (Throwable t) {
+ safeLog(t, "Can't get loader method");
+ }
+ return null;
+ }
+
+ /**
+ * Gets name method for the Class loader for JDK9+. Visible for testing.
+ *
+ * @return the {@code getName} method, or {@code null} if it is absent.
+ */
+ static Method getClassLoaderNameMethod() {
+ try {
+ return ClassLoader.class.getMethod("getName");
+ } catch (NoSuchMethodException e) {
+ safeLog(e, "getName method missing");
+ // expected
+ }
+ return null;
+ }
+
+ @SuppressWarnings("UnusedVariable")
+ private static void safeLog(Throwable t, String message, Object... args) {
+ // TODO(carl-mastrangelo): implement.
+ }
+}
diff --git a/agent/src/main/resources/io/perfmark/agent/third_party/asm/LICENSE b/agent/src/main/resources/io/perfmark/agent/third_party/asm/LICENSE
new file mode 100644
index 0000000..c71bb7b
--- /dev/null
+++ b/agent/src/main/resources/io/perfmark/agent/third_party/asm/LICENSE
@@ -0,0 +1,27 @@
+ASM: a very small and fast Java bytecode manipulation framework
+Copyright (c) 2000-2011 INRIA, France Telecom
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/agent/src/test/java/io/perfmark/agent/PerfMarkMethodRewriterTest.java b/agent/src/test/java/io/perfmark/agent/PerfMarkMethodRewriterTest.java
new file mode 100644
index 0000000..3a3e1bc
--- /dev/null
+++ b/agent/src/test/java/io/perfmark/agent/PerfMarkMethodRewriterTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.truth.Truth;
+import io.perfmark.PerfMark;
+import io.perfmark.TaskCloseable;
+import io.perfmark.impl.MarkList;
+import io.perfmark.impl.Storage;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(JUnit4.class)
+public class PerfMarkMethodRewriterTest {
+
+ @Test
+ public void getStackTraceElementCtor_present() {
+ Constructor<StackTraceElement> ctor = PerfMarkMethodRewriter.getStackTraceElementCtor();
+ assertNotNull(ctor);
+ }
+
+ @Test
+ public void moduleElement() throws Exception {
+ PerfMarkMethodRewriter rewriter =
+ new PerfMarkMethodRewriter(
+ "loadername", "modulename", "moduleversion", "classname", "methodname", "filename", () -> {}, null);
+ rewriter.visitLineNumber(1234, new Label());
+ StackTraceElement expected =
+ new StackTraceElement("loadername", "modulename", "moduleversion", "classname", "methodname", "filename", 1234);
+
+ StackTraceElement ste = rewriter.moduleElement();
+
+ assertEquals(expected, ste);
+ }
+
+ @Test
+ public void moduleElement_oldClass() throws Exception {
+ PerfMarkMethodRewriter rewriter =
+ new PerfMarkMethodRewriter(
+ null, null, null, "classname", "methodname", null, () -> {}, null);
+ rewriter.visitLineNumber(1234, new Label());
+ StackTraceElement expected =
+ new StackTraceElement("classname", "methodname", null, 1234);
+
+ StackTraceElement ste = rewriter.moduleElement();
+
+ assertEquals(expected, ste);
+ }
+
+ @Test
+ public void rewriteClass_task() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+ String expectedValue =
+ new StackTraceElement("loadername", "modulename", "moduleversion", "classname", "methodname", "filename", -1)
+ .toString();
+ // remove the trailing paren ) to make string match easier.
+ expectedValue = expectedValue.substring(0, expectedValue.length() - 1);
+ ClassReader reader = new ClassReader(ClzToRewrite.class.getName());
+ ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
+ reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor delegate = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return new PerfMarkMethodRewriter(
+ "loadername", "modulename", "moduleversion", "classname", "methodname", "filename", () -> {}, delegate);
+ }
+ }, 0);
+ Class<?> clz = MethodHandles.lookup().defineHiddenClass(writer.toByteArray(), false).lookupClass();
+
+ clz.getMethod("task").invoke(null);
+
+ MarkList out = Storage.readForTest();
+ Truth.assertThat(out).hasSize(4);
+ String start = out.get(1).getTagStringValue();
+ Truth.assertThat(start).startsWith(expectedValue);
+ String end = out.get(2).getTagStringValue();
+ // Hack to check the line number changed.
+ Truth.assertThat(start).isNotEqualTo(end);
+ }
+
+ @Test
+ public void rewriteClass_closeable() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+ String expectedValue =
+ new StackTraceElement("loadername", "modulename", "moduleversion", "classname", "methodname", "filename", -1)
+ .toString();
+ // remove the trailing paren ) to make string match easier.
+ expectedValue = expectedValue.substring(0, expectedValue.length() - 1);
+ ClassReader reader = new ClassReader(ClzToRewrite.class.getName());
+ ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
+ reader.accept(new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor delegate = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return new PerfMarkMethodRewriter(
+ "loadername", "modulename", "moduleversion", "classname", "methodname", "filename", () -> {}, delegate);
+ }
+ }, 0);
+ Class<?> clz = MethodHandles.lookup().defineHiddenClass(writer.toByteArray(), false).lookupClass();
+
+ clz.getMethod("closeable").invoke(null);
+
+ MarkList out = Storage.readForTest();
+ Truth.assertThat(out).hasSize(4);
+ String start = out.get(1).getTagStringValue();
+ Truth.assertThat(start).startsWith(expectedValue);
+ String end = out.get(2).getTagStringValue();
+ // Hack to check the line number changed.
+ Truth.assertThat(start).isNotEqualTo(end);
+ }
+
+ @SuppressWarnings("UnusedMethod")
+ private static final class ClzToRewrite {
+ public static void task() {
+ // These two calls MUST happen on separate lines to ensure line reading works.
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ }
+
+ public static void closeable() {
+ try (TaskCloseable closeable = PerfMark.traceTask("task")) {
+ // extra line to ensure line numbers are different.
+ }
+ }
+ }
+}
diff --git a/agent/src/test/java/io/perfmark/agent/PerfMarkTransformerTest.java b/agent/src/test/java/io/perfmark/agent/PerfMarkTransformerTest.java
new file mode 100644
index 0000000..df5ea58
--- /dev/null
+++ b/agent/src/test/java/io/perfmark/agent/PerfMarkTransformerTest.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.truth.Truth;
+import io.perfmark.PerfMark;
+import io.perfmark.TaskCloseable;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.Storage;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PerfMarkTransformerTest {
+
+ @Test
+ public void deriveFileName() {
+ String file = PerfMarkTransformer.deriveFileName("io/perfmark/Clz");
+
+ assertEquals("Clz.java", file);
+ }
+
+ @Test
+ public void deriveFileName_innerClass() {
+ String file = PerfMarkTransformer.deriveFileName("io/perfmark/Clz$Inner");
+
+ assertEquals("Clz.java", file);
+ }
+
+ @Test
+ @Ignore
+ public void transform_autoAnnotate() throws Exception {
+ // This test currently depends on the transformer treating this test class specially.
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzAutoRecord.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(2);
+ }
+
+ @Test
+ public void transform_record() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.SomeRecord.class);
+ Constructor<?> ctor = clz.getConstructor(int.class);
+ ctor.setAccessible(true);
+ ctor.newInstance(2);
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(4);
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$SomeRecord");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("<init>");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$SomeRecord");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("<init>");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+ }
+
+ @Test
+ public void transform_lambda() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzCtorLambda.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(4);
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzCtorLambda");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("lambda");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzCtorLambda");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("lambda");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+ }
+
+ @Test
+ public void transform_methodRef() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithMethodRefs.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(2);
+ // I'm not sure what to do with methodrefs, so just leave it alone for now.
+ }
+
+ @Test
+ public void transform_interface() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz =
+ transformAndLoad(TransformerTestClasses.Bar.class, TransformerTestClasses.InterfaceWithDefaults.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(10);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("record");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("record");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+
+ assertEquals(marks.get(4).withTaskName("task"), marks.get(4));
+
+ // Ignore the regular tag at 5
+
+ assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("record");
+
+ assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$InterfaceWithDefaults");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("record");
+
+ // Ignore the regular tag at 8
+
+ assertEquals(marks.get(9).withTaskName("task"), marks.get(9));
+ }
+
+ @Test
+ public void transform_link() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithLinks.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(6);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithLinks");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ // assume links have not been modified
+
+ assertEquals(marks.get(4).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(4).getTagStringValue()).contains("TransformerTestClasses$ClzWithLinks");
+ Truth.assertThat(marks.get(4).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(5).withTaskName("task"), marks.get(5));
+ }
+
+ @Test
+ public void transform_closeable() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithCloseable.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(4);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithCloseable");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithCloseable");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("init");
+
+ assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(3).getOperation());
+ }
+
+ @Test
+ public void transform_wrongCloseable() throws Exception {
+ // If the wrong static type is used, the agent won't be able to instrument it. Add a test to document this
+ // behavior.
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithWrongCloseable.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(3);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithWrongCloseable");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(2).getOperation());
+ }
+
+ @Test
+ public void transform_wrongCloseable_autoCloseable() throws Exception {
+ // If the wrong static type is used, the agent won't be able to instrument it. Add a test to document this
+ // behavior.
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<? extends Closeable> clz = transformAndLoad(TaskCloseable.class).asSubclass(Closeable.class);
+ Constructor<? extends Closeable> ctor = clz.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ Closeable closeable = ctor.newInstance();
+ closeable.close();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(1);
+
+ assertEquals(Mark.Operation.TASK_END_N1S0, marks.get(0).getOperation());
+ }
+
+ @Test
+ public void transform_ctor() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithCtor.class);
+ Constructor<?> ctor = clz.getConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(10);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+
+ assertEquals(marks.get(4).withTaskName("task"), marks.get(4));
+
+ // Ignore the regular tag at 5
+
+ assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithCtor");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("init");
+
+ // Ignore the regular tag at 8
+
+ assertEquals(marks.get(9).withTaskName("task"), marks.get(9));
+ }
+
+ @Test
+ public void transform_init() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithInit.class);
+ Constructor<?> ctor = clz.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(10);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+
+ assertEquals(marks.get(4).withTaskName("task"), marks.get(4));
+
+ // Ignore the regular tag at 5
+
+ assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithInit");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("init");
+
+ // Ignore the regular tag at 8
+
+ assertEquals(marks.get(9).withTaskName("task"), marks.get(9));
+ }
+
+ @Test
+ public void transform_clinit() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(TransformerTestClasses.ClzWithClinit.class);
+ Constructor<?> ctor = clz.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(10);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("clinit");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("clinit");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+
+ assertEquals(marks.get(4).withTaskName("task"), marks.get(4));
+
+ // Ignore the regular tag at 5
+
+ assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("clinit");
+
+ assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("TransformerTestClasses$ClzWithClinit");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("clinit");
+
+ // Ignore the regular tag at 8
+
+ assertEquals(marks.get(9).withTaskName("task"), marks.get(9));
+ }
+
+ @Test
+ public void transform_toplevel() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(ClzFooter.class);
+ Constructor<?> ctor = clz.getDeclaredConstructor();
+ ctor.setAccessible(true);
+ ctor.newInstance();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(10);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("ClzFooter");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("ClzFooter");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+
+ assertEquals(marks.get(4).withTaskName("task"), marks.get(4));
+
+ // Ignore the regular tag at 5
+
+ assertEquals(marks.get(6).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("ClzFooter");
+ Truth.assertThat(marks.get(6).getTagStringValue()).contains("init");
+
+ assertEquals(marks.get(7).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("ClzFooter");
+ Truth.assertThat(marks.get(7).getTagStringValue()).contains("init");
+
+ // Ignore the regular tag at 8
+
+ assertEquals(marks.get(9).withTaskName("task"), marks.get(9));
+ }
+
+ @Test
+ public void transform_anonymousClass() throws Exception {
+ PerfMark.setEnabled(true);
+ Storage.clearLocalStorage();
+
+ Class<?> clz = transformAndLoad(new Runnable() {
+ // avoid IntelliJ thinking this should be a lambda.
+ public volatile int a;
+ @Override
+ public void run() {
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ }
+ }.getClass());
+ Constructor<?> ctor = clz.getDeclaredConstructor(PerfMarkTransformerTest.class);
+ ctor.setAccessible(true);
+ Runnable instance = (Runnable) ctor.newInstance(this);
+ instance.run();
+ List<Mark> marks = Storage.readForTest();
+ assertThat(marks).hasSize(4);
+
+ assertEquals(marks.get(0).withTaskName("task"), marks.get(0));
+
+ assertEquals(marks.get(1).getTagKey(), "PerfMark.startCallSite");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("PerfMarkTransformerTest$");
+ Truth.assertThat(marks.get(1).getTagStringValue()).contains("run");
+
+ assertEquals(marks.get(2).getTagKey(), "PerfMark.stopCallSite");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("PerfMarkTransformerTest$");
+ Truth.assertThat(marks.get(2).getTagStringValue()).contains("run");
+
+ assertEquals(marks.get(3).withTaskName("task"), marks.get(3));
+ }
+
+ private static Class<?> transformAndLoad(Class<?> toLoad, Class<?> ...extra) throws IOException {
+ Map<String, Class<?>> toTransform = new HashMap<>();
+ for (Class<?> clz : extra) {
+ toTransform.put(clz.getName(), clz);
+ }
+ toTransform.put(toLoad.getName(), toLoad);
+ try {
+ return new ClassLoader(toLoad.getClassLoader()) {
+
+ @Override
+ protected Class<?> loadClass(String binaryClassName, boolean resolve) throws ClassNotFoundException {
+ Class<?> existing = toTransform.get(binaryClassName);
+ if (existing == null) {
+ return super.loadClass(binaryClassName, resolve);
+ }
+ String internalFullyQualifiedClassName = binaryClassName.replace('.', '/');
+ String resourceName = internalFullyQualifiedClassName + ".class";
+ byte[] data;
+ try (InputStream stream = getResourceAsStream(resourceName)) {
+ data = stream.readAllBytes();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ byte[] newClassBytes =
+ new PerfMarkTransformer(null).transformInternal(
+ this, internalFullyQualifiedClassName, existing, null, data);
+ if (newClassBytes == null) {
+ newClassBytes = data;
+ }
+ Class<?> newClass = defineClass(binaryClassName, newClassBytes, 0, newClassBytes.length);
+ if (resolve) {
+ resolveClass(newClass);
+ }
+ return newClass;
+ }
+ }.loadClass(toLoad.getName());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/agent/src/test/java/io/perfmark/agent/TransformerTestClasses.java b/agent/src/test/java/io/perfmark/agent/TransformerTestClasses.java
new file mode 100644
index 0000000..fcfa5ce
--- /dev/null
+++ b/agent/src/test/java/io/perfmark/agent/TransformerTestClasses.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.agent;
+
+import io.perfmark.Link;
+import io.perfmark.PerfMark;
+import io.perfmark.Tag;
+import io.perfmark.TaskCloseable;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+
+final class TransformerTestClasses {
+
+ static final class ClzAutoRecord {
+ public ClzAutoRecord() {
+ recordMe();
+ }
+
+ void recordMe() {
+ // seemingly nothing here.
+ }
+ }
+
+ record SomeRecord(@SuppressWarnings("unused") int hi) {
+ public SomeRecord {
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ }
+ }
+
+ static final class ClzCtorLambda implements Executor {
+ public ClzCtorLambda() {
+ execute(
+ () -> {
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ });
+ }
+
+ @Override
+ public void execute(@Nullable final Runnable command) {
+ command.run();
+ }
+ }
+
+ static final class ClzWithClinit {
+ static {
+ Tag tag = PerfMark.createTag("tag", 1);
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ PerfMark.startTask("task", tag);
+ PerfMark.stopTask("task", tag);
+ }
+ }
+
+ static final class ClzWithInit {
+ {
+ Tag tag = PerfMark.createTag("tag", 1);
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ PerfMark.startTask("task", tag);
+ PerfMark.stopTask("task", tag);
+ }
+ }
+
+ static final class ClzWithCtor {
+ public ClzWithCtor() {
+ Tag tag = PerfMark.createTag("tag", 1);
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ PerfMark.startTask("task", tag);
+ PerfMark.stopTask("task", tag);
+ }
+ }
+
+ static final class ClzWithLinks {
+ public ClzWithLinks() {
+ PerfMark.startTask("task");
+ Link link = PerfMark.linkOut();
+ PerfMark.linkIn(link);
+ PerfMark.stopTask("task");
+ }
+ }
+
+ static final class ClzWithCloseable {
+ public ClzWithCloseable() {
+ try (TaskCloseable discard = PerfMark.traceTask("task")) {}
+ }
+ }
+
+ static final class ClzWithWrongCloseable {
+ public ClzWithWrongCloseable() throws IOException {
+ try (Closeable discard = PerfMark.traceTask("task")) {}
+ }
+ }
+
+ public interface InterfaceWithDefaults {
+ default void record() {
+ Tag tag = PerfMark.createTag("tag", 1);
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ PerfMark.startTask("task", tag);
+ PerfMark.stopTask("task", tag);
+ }
+ }
+
+ static final class Bar implements InterfaceWithDefaults {
+ public Bar() {
+ record();
+ }
+ }
+
+ static final class ClzWithMethodRefs {
+ public ClzWithMethodRefs() {
+ execute(PerfMark::startTask);
+ execute(PerfMark::stopTask);
+ }
+
+ void execute(Consumer<String> method) {
+ method.accept("task");
+ }
+ }
+
+ private TransformerTestClasses() {}
+}
+
+final class ClzFooter {
+ {
+ Tag tag = PerfMark.createTag("tag", 1);
+ PerfMark.startTask("task");
+ PerfMark.stopTask("task");
+ PerfMark.startTask("task", tag);
+ PerfMark.stopTask("task", tag);
+ }
+}
diff --git a/api/BUILD.bazel b/api/BUILD.bazel
new file mode 100644
index 0000000..1b7418a
--- /dev/null
+++ b/api/BUILD.bazel
@@ -0,0 +1,62 @@
+java_library(
+ name = "api",
+ visibility = ["//visibility:public"],
+ exports = [
+ ":link",
+ ":perfmark",
+ ":tag",
+ ":stringfunction",
+ ],
+)
+
+java_library(
+ name = "perfmark",
+ srcs = glob(["src/main/java/io/perfmark/PerfMark.java",
+ "src/main/java/io/perfmark/TaskCloseable.java"]),
+ deps = [
+ ":impl",
+ ":link",
+ ":tag",
+ ":stringfunction",
+ "@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+java_library(
+ name = "tag",
+ srcs = glob(["src/main/java/io/perfmark/Tag.java"]),
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@maven//:com_google_code_findbugs_jsr305",
+ ],
+)
+
+java_library(
+ name = "link",
+ srcs = glob(["src/main/java/io/perfmark/Link.java"]),
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
+
+java_library(
+ name = "stringfunction",
+ visibility = ["//:__subpackages__"],
+ srcs = glob(["src/main/java/io/perfmark/StringFunction.java"]),
+)
+
+java_library(
+ name = "impl",
+ srcs = glob(["src/main/java/io/perfmark/Impl.java"]),
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":link",
+ ":tag",
+ ":stringfunction",
+ "@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 0000000..1b69f2e
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,85 @@
+plugins {
+ id "me.champeau.jmh"
+}
+
+description = "PerfMark API"
+ext.moduleName = "io.perfmark"
+ext.jdkVersion = JavaVersion.VERSION_1_6
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+}
+
+compileJava {
+ sourceCompatibility = jdkVersion
+ targetCompatibility = jdkVersion
+
+ options.compilerArgs.add("-Xlint:-options")
+}
+
+dependencies {
+ compileOnly libs.jsr305,
+ libs.errorprone
+ testImplementation project(':perfmark-impl'),
+ libs.truth
+ testRuntimeOnly project(':perfmark-java6')
+
+ jmh project(':perfmark-java9'),
+ project(':perfmark-java7')
+}
+
+compileTestJava {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+compileJmhJava {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+java {
+ disableAutoTargetJvm()
+}
+
+javadoc {
+ exclude 'io/perfmark/Impl**'
+}
+
+jmh {
+
+ timeOnIteration = "1s"
+ warmup = "1s"
+ fork = 400
+ warmupIterations = 0
+
+ includes = ["ClassInit"]
+ profilers = ["cl"]
+ jvmArgs = ["-Dio.perfmark.debug=true"]
+
+ /*
+ profilers = ["perfasm"]
+
+ jvmArgs = [
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+LogCompilation",
+ "-XX:LogFile=/tmp/blah.txt",
+ "-XX:+PrintAssembly",
+ "-XX:+PrintInterpreter",
+ "-XX:+PrintNMethods",
+ "-XX:+PrintNativeNMethods",
+ "-XX:+PrintSignatureHandlers",
+ "-XX:+PrintAdapterHandlers",
+ "-XX:+PrintStubCode",
+ "-XX:+PrintCompilation",
+ "-XX:+PrintInlining",
+ "-XX:+TraceClassLoading",
+ "-XX:PrintAssemblyOptions=syntax",
+ "-XX:PrintAssemblyOptions=intel"
+ ]
+ */
+
+ //duplicateClassesStrategy DuplicatesStrategy.INCLUDE
+} \ No newline at end of file
diff --git a/api/src/jmh/java/io/perfmark/ClassInitBenchmark.java b/api/src/jmh/java/io/perfmark/ClassInitBenchmark.java
new file mode 100644
index 0000000..0944b41
--- /dev/null
+++ b/api/src/jmh/java/io/perfmark/ClassInitBenchmark.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+@State(Scope.Benchmark)
+@Fork(1000)
+@Warmup(iterations = 0)
+@Measurement(iterations = 1)
+public class ClassInitBenchmark {
+
+ private ClassLoader loader;
+
+ @Setup
+ public void setup() {
+ loader = new TestClassLoader(getClass().getClassLoader(), "io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl");
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.SingleShotTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public Object forName_noinit() throws Exception {
+ return Class.forName(PerfMark.class.getName(), false, loader);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.SingleShotTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public Object forName_init() throws Exception {
+ return Class.forName(PerfMark.class.getName(), true, loader);
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+
+ private final List<String> classesToDrop;
+
+ TestClassLoader(ClassLoader parent, String ... classesToDrop) {
+ super(parent);
+ this.classesToDrop = Arrays.asList(classesToDrop);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (classesToDrop.contains(name)) {
+ // Throw an exception, just like the real one would.
+ throw new ClassNotFoundException();
+ }
+ if (!name.startsWith("io.perfmark.")) {
+ return super.loadClass(name, resolve);
+ }
+ try (InputStream is = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (is == null) {
+ throw new ClassNotFoundException(name);
+ }
+ byte[] data = is.readAllBytes();
+ Class<?> clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/api/src/jmh/java/io/perfmark/EnabledBenchmark.java b/api/src/jmh/java/io/perfmark/EnabledBenchmark.java
new file mode 100644
index 0000000..07ca90b
--- /dev/null
+++ b/api/src/jmh/java/io/perfmark/EnabledBenchmark.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+@State(Scope.Benchmark)
+public class EnabledBenchmark {
+ private final Tag TAG = new Tag("tag", 2);
+
+ @Param({"true", "false"})
+ public boolean enabled;
+
+ final String there = "there";
+
+ @Setup
+ public void setup() {
+ PerfMark.setEnabled(enabled);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void startStop() {
+ PerfMark.startTask("hi", TAG);
+ PerfMark.stopTask("hi", TAG);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void startStop_method() {
+ PerfMark.startTask("hi", String::valueOf);
+ PerfMark.stopTask();
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public Tag createTag() {
+ return PerfMark.createTag("tag", 2);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void link() {
+ Link link = PerfMark.linkOut();
+ PerfMark.linkIn(link);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void event() {
+ PerfMark.event("hi", TAG);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_ss() {
+ PerfMark.attachTag("hi", "there");
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_sn() {
+ PerfMark.attachTag("hi", 934);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_snn() {
+ PerfMark.attachTag("hi", 934, 5);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_ss_methodRef() {
+ PerfMark.attachTag("hi", this, EnabledBenchmark::getStringValue);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_ss_ctor() {
+ PerfMark.attachTag("hi", there, String::new);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_ss_globalRef() {
+ PerfMark.attachTag("hi", this, ignore -> this.there);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void attachKeyedTag_ss_localRef() {
+ String bar = there;
+ PerfMark.attachTag("hi", this, ignore -> bar);
+ }
+
+ static String getStringValue(EnabledBenchmark self) {
+ return self.there;
+ }
+}
diff --git a/api/src/main/java/io/perfmark/Impl.java b/api/src/main/java/io/perfmark/Impl.java
new file mode 100644
index 0000000..ddd65ad
--- /dev/null
+++ b/api/src/main/java/io/perfmark/Impl.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import javax.annotation.Nullable;
+
+public class Impl {
+ static final String NO_TAG_NAME = "";
+ static final long NO_TAG_ID = Long.MIN_VALUE;
+ /**
+ * This value is current {@link Long#MIN_VALUE}, but it could also be {@code 0}. The invariant
+ * {@code NO_LINK_ID == -NO_LINK_ID} must be maintained to work when PerfMark is disabled.
+ */
+ private static final long NO_LINK_ID = Long.MIN_VALUE;
+
+ static final Tag NO_TAG = new Tag(Impl.NO_TAG_NAME, Impl.NO_TAG_ID);
+ static final Link NO_LINK = new Link(Impl.NO_LINK_ID);
+
+ /** The Noop implementation */
+ protected Impl(Tag key) {
+ if (key != NO_TAG) {
+ throw new AssertionError("nope");
+ }
+ }
+
+ protected void setEnabled(boolean value) {}
+
+ protected boolean setEnabled(boolean value, boolean overload) {
+ return false;
+ }
+
+ protected <T> void startTask(T taskNameObject, StringFunction<? super T> taskNameFunc) {}
+
+ protected void startTask(String taskName, Tag tag) {}
+
+ protected void startTask(String taskName) {}
+
+ protected void startTask(String taskName, String subTaskName) {}
+
+ protected void event(String eventName, Tag tag) {}
+
+ protected void event(String eventName) {}
+
+ protected void event(String eventName, String subEventName) {}
+
+ protected void stopTask() {}
+
+ protected void stopTask(String taskName, Tag tag) {}
+
+ protected void stopTask(String taskName) {}
+
+ protected void stopTask(String taskName, String subTaskName) {}
+
+ protected Link linkOut() {
+ return NO_LINK;
+ }
+
+ protected void linkIn(Link link) {}
+
+ protected void attachTag(Tag tag) {}
+
+ protected void attachTag(String tagName, String tagValue) {}
+
+ protected void attachTag(String tagName, long tagValue) {}
+
+ protected void attachTag(String tagName, long tagValue0, long tagValue1) {}
+
+ protected <T> void attachTag(
+ String tagName, T tagObject, StringFunction<? super T> stringFunction) {}
+
+ protected Tag createTag(@Nullable String tagName, long tagId) {
+ return NO_TAG;
+ }
+
+ @Nullable
+ protected static String unpackTagName(Tag tag) {
+ return tag.tagName;
+ }
+
+ protected static long unpackTagId(Tag tag) {
+ return tag.tagId;
+ }
+
+ protected static long unpackLinkId(Link link) {
+ return link.linkId;
+ }
+
+ protected static Tag packTag(@Nullable String tagName, long tagId) {
+ return new Tag(tagName, tagId);
+ }
+
+ protected static Link packLink(long linkId) {
+ return new Link(linkId);
+ }
+}
diff --git a/api/src/main/java/io/perfmark/Link.java b/api/src/main/java/io/perfmark/Link.java
new file mode 100644
index 0000000..de6c9b0
--- /dev/null
+++ b/api/src/main/java/io/perfmark/Link.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import com.google.errorprone.annotations.DoNotCall;
+
+/**
+ * A link represents a linkage between asynchronous tasks. A link is created inside of a started
+ * task. The resulting {@link Link} object can then be passed to other asynchronous tasks to
+ * associate them with the original task.
+ *
+ * <p>A source task may have multiple outbound links pointing to other tasks. However, calling
+ * {@code PerfMark.linkIn(Link)} only work if it is the first such call. Subsequent calls have no
+ * effect.
+ */
+public final class Link {
+
+ final long linkId;
+
+ Link(long linkId) {
+ this.linkId = linkId;
+ }
+
+ /** DO NOT CALL, no longer implemented. Use {@link PerfMark#linkIn} instead. */
+ @Deprecated
+ @DoNotCall
+ public void link() {}
+}
diff --git a/api/src/main/java/io/perfmark/PerfMark.java b/api/src/main/java/io/perfmark/PerfMark.java
new file mode 100644
index 0000000..9ce8044
--- /dev/null
+++ b/api/src/main/java/io/perfmark/PerfMark.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.errorprone.annotations.DoNotCall;
+import com.google.errorprone.annotations.MustBeClosed;
+import java.lang.reflect.Method;
+
+/**
+ * PerfMark is a very low overhead tracing library. To use PerfMark, annotate the code that needs to
+ * be traced using the start and stop methods. For example:
+ *
+ * <pre>{@code
+ * PerfMark.startTask("parseMessage");
+ * try {
+ * message = parse(bytes);
+ * } finally {
+ * PerfMark.stopTask("parseMessage");
+ * }
+ * }</pre>
+ *
+ * <p>When PerfMark is enabled, these tracing calls will record the start and stop times of the
+ * given task. When disabled, PerfMark disables these tracing calls, resulting in no additional
+ * tracing overhead. PerfMark can be enabled or disabled using the {@link #setEnabled(boolean)}. By
+ * default, PerfMark starts off disabled. PerfMark can be automatically enabled by setting the
+ * System property {@code io.perfmark.PerfMark.startEnabled} to true.
+ *
+ * <p>Tasks represent the span of work done by some code, starting and stopping in the same thread.
+ * Each task is started using one of the {@code startTask} methods, and ended using one of
+ * {@code stopTask} methods. Each start must have a corresponding stop. While not required,
+ * it is good practice for the start and stop calls have matching arguments for clarity. Tasks
+ * form a "tree", with each child task starting after the parent has started, and stopping before
+ * the parent has stopped. The most recently started (and not yet stopped) task is used by the
+ * tagging and linking commands described below.
+ *
+ * <p>Tags are metadata about the task. Each {@code Tag} contains a String and/or a long that
+ * describes the task, such as an RPC name, or request ID. When PerfMark is disabled, the Tag
+ * objects are not created, avoiding overhead. Tags are useful for keeping track of metadata
+ * about a task(s) that doesn't change frequently, or needs to be applied at multiple layers.
+ * In addition to Tag objects, named-tags can be added to the current task using the
+ * {@code attachTag} methods. These allow including key-value like metadata with the task.
+ *
+ * <p>Links allow the code to represent relationships between different threads. When one thread
+ * initiates work for another thread (such as a callback), Links express the control flow. For
+ * example:
+ *
+ * <pre>{@code
+ * PerfMark.startTask("handleMessage");
+ * try {
+ * Link link = PerfMark.linkOut();
+ * message = parse(bytes);
+ * executor.execute(() -> {
+ * PerfMark.startTask("processMessage");
+ * try {
+ * PerfMark.linkIn(link);
+ * handle(message);
+ * } finally {
+ * PerfMark.stopTask("processMessage");
+ * }
+ * });
+ * } finally {
+ * PerfMark.stopTask("handleMessage");
+ * }
+ * }</pre>
+ *
+ * <p>Links are created inside the scope of the current task and are linked into the scope of
+ * another task. PerfMark will represent the causal relationship between these two tasks. Links
+ * have a many-many relationship, and can be reused. Like Tasks and Tags, when PerfMark is
+ * disabled, the Links returned are no-op implementations.
+ *
+ * <p>Events are a special kind of Task, which do not have a duration. In effect, they only have
+ * a single timestamp the represents a particular occurrence. Events are slightly more efficient
+ * than tasks while PerfMark is enabled, but cannot be used with Links or named-tags.
+ *
+ * @author Carl Mastrangelo
+ */
+public final class PerfMark {
+ /**
+ * Turns on or off PerfMark recording. Don't call this method too frequently; while neither on nor
+ * off have very high overhead, transitioning between the two may be slow.
+ *
+ * @param value {@code true} to enable PerfMark recording, or {@code false} to disable it.
+ * @return If the enabled value was changed.
+ */
+ @CanIgnoreReturnValue
+ public static boolean setEnabled(boolean value) {
+ return impl.setEnabled(value, false);
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The name of the
+ * task should be a runtime-time constant, usually a string literal. Tasks with the same name can
+ * be grouped together for analysis later, so avoid using too many unique task names.
+ *
+ * <p>The tag is a run-time identifier for the task. It represents the dynamic part of the task,
+ * while the task name is the constant part of the task. While not always enforced, tags should
+ * not be {@code null}.
+ *
+ * @param taskName the name of the task.
+ * @param tag a user provided tag for the task.
+ */
+ public static void startTask(String taskName, Tag tag) {
+ impl.startTask(taskName, tag);
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The name of the
+ * task should be a runtime-time constant, usually a string literal. Tasks with the same name can
+ * be grouped together for analysis later, so avoid using too many unique task names.
+ *
+ * @param taskName the name of the task.
+ */
+ public static void startTask(String taskName) {
+ impl.startTask(taskName);
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The name of the
+ * task should be a runtime-time constant, usually a string literal. Tasks with the same name can
+ * be grouped together for analysis later, so avoid using too many unique task names.
+ *
+ * <p>This function has many more caveats than the {@link #startTask(String)} that accept a
+ * string. See the docs at {@link #attachTag(String, Object, StringFunction)} for a list of risks
+ * associated with passing a function.
+ *
+ * @param taskNameObject the name of the task.
+ * @param taskNameFunction the function that will convert the taskNameObject to a taskName
+ * @param <T> the object type to be stringified
+ * @since 0.22.0
+ */
+ public static <T> void startTask(T taskNameObject, StringFunction<? super T> taskNameFunction) {
+ impl.startTask(taskNameObject, taskNameFunction);
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The names of
+ * the task and subtask should be runtime-time constants, usually a string literal. Tasks with the
+ * same name can be grouped together for analysis later, so avoid using too many unique task
+ * names.
+ *
+ * @param taskName the name of the task.
+ * @param subTaskName the name of the sub task
+ * @since 0.20.0
+ */
+ public static void startTask(String taskName, String subTaskName) {
+ impl.startTask(taskName, subTaskName);
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The name of the
+ * task should be a runtime-time constant, usually a string literal. Tasks with the same name can
+ * be grouped together for analysis later, so avoid using too many unique task names.
+ *
+ * <p>The returned closeable is meant to be used in a try-with-resources block. Callers should not
+ * allow the returned closeable to be used outside of the try block that initiated the call.
+ * Unlike other closeables, it is not safe to call close() more than once.
+ *
+ * @param taskName the name of the task.
+ * @return a closeable that must be closed at the end of the task
+ * @since 0.23.0
+ */
+ @MustBeClosed
+ public static TaskCloseable traceTask(String taskName) {
+ impl.startTask(taskName);
+ return TaskCloseable.INSTANCE;
+ }
+
+ /**
+ * Marks the beginning of a task. If PerfMark is disabled, this method is a no-op. The name of the
+ * task should be a runtime-time constant, usually a string literal. Tasks with the same name can
+ * be grouped together for analysis later, so avoid using too many unique task names.
+ *
+ * <p>This function has many more caveats than the {@link #traceTask(String)} that accept a
+ * string. See the docs at {@link #attachTag(String, Object, StringFunction)} for a list of risks
+ * associated with passing a function. Unlike other closeables, it is not safe to call close()
+ * more than once.
+ *
+ * @param taskNameObject the name of the task.
+ * @param taskNameFunction the function that will convert the taskNameObject to a taskName
+ * @param <T> the object type to be stringified
+ * @return a closeable that must be closed at the end of the task
+ * @since 0.23.0
+ */
+ @MustBeClosed
+ public static <T> TaskCloseable traceTask(
+ T taskNameObject, StringFunction<? super T> taskNameFunction) {
+ impl.startTask(taskNameObject, taskNameFunction);
+ return TaskCloseable.INSTANCE;
+ }
+
+ /**
+ * Marks an event. Events are logically both a task start and a task end. Events have no duration
+ * associated. Events still represent the instant something occurs. If PerfMark is disabled, this
+ * method is a no-op.
+ *
+ * <p>The tag is a run-time identifier for the event. It represents the dynamic part of the event,
+ * while the event name is the constant part of the event. While not always enforced, tags should
+ * not be {@code null}.
+ *
+ * @param eventName the name of the event.
+ * @param tag a user provided tag for the event.
+ */
+ public static void event(String eventName, Tag tag) {
+ impl.event(eventName, tag);
+ }
+
+ /**
+ * Marks an event. Events are logically both a task start and a task end. Events have no duration
+ * associated. Events still represent the instant something occurs. If PerfMark is disabled, this
+ * method is a no-op.
+ *
+ * @param eventName the name of the event.
+ */
+ public static void event(String eventName) {
+ impl.event(eventName);
+ }
+
+ /**
+ * Marks an event. Events are logically both a task start and a task end. Events have no duration
+ * associated. Events still represent the instant something occurs. If PerfMark is disabled, this
+ * method is a no-op.
+ *
+ * @param eventName the name of the event.
+ * @param subEventName the name of the sub event.
+ * @since 0.20.0
+ */
+ public static void event(String eventName, String subEventName) {
+ impl.event(eventName, subEventName);
+ }
+
+ /**
+ * Marks the end of a task. If PerfMark is disabled, this method is a no-op.
+ *
+ * <p>It is important that {@link #stopTask} always be called after starting a task, even in case
+ * of exceptions. Failing to do so may result in corrupted results.
+ *
+ * @since 0.22.0
+ */
+ public static void stopTask() {
+ impl.stopTask();
+ }
+
+ /**
+ * Marks the end of a task. If PerfMark is disabled, this method is a no-op. The task name and tag
+ * should match the ones provided to the corresponding {@link #startTask(String, Tag)}, if
+ * provided. If the task name or tag do not match, the implementation will prefer the starting
+ * name and tag. The name and tag help identify the task if PerfMark is enabled mid way through
+ * the task, or if the previous results have been overwritten. The name of the task should be a
+ * runtime-time constant, usually a string literal. Consider using {@link #stopTask()} instead.
+ *
+ * <p>It is important that {@link #stopTask} always be called after starting a task, even in case
+ * of exceptions. Failing to do so may result in corrupted results.
+ *
+ * @param taskName the name of the task being ended.
+ * @param tag the tag of the task being ended.
+ */
+ public static void stopTask(String taskName, Tag tag) {
+ impl.stopTask(taskName, tag);
+ }
+
+ /**
+ * Marks the end of a task. If PerfMark is disabled, this method is a no-op. The task name should
+ * match the ones provided to the corresponding {@link #startTask(String)}, if provided. If the
+ * task name does not match, the implementation will prefer the starting name. The name helps
+ * identify the task if PerfMark is enabled mid way through the task, or if the previous results
+ * have been overwritten. The name of the task should be a runtime-time constant, usually a string
+ * literal. Consider using {@link #stopTask()} instead.
+ *
+ * <p>It is important that {@link #stopTask} always be called after starting a task, even in case
+ * of exceptions. Failing to do so may result in corrupted results.
+ *
+ * @param taskName the name of the task being ended.
+ */
+ public static void stopTask(String taskName) {
+ impl.stopTask(taskName);
+ }
+
+ /**
+ * Marks the end of a task. If PerfMark is disabled, this method is a no-op. The task name should
+ * match the ones provided to the corresponding {@link #startTask(String, String)}, if provided.
+ * If the task name does not match, the implementation will prefer the starting name. The name
+ * helps identify the task if PerfMark is enabled mid way through the task, or if the previous
+ * results have been overwritten. The name of the task should be a runtime-time constant, usually
+ * a string literal. Consider using {@link #stopTask()} instead.
+ *
+ * <p>It is important that {@link #stopTask} always be called after starting a task, even in case
+ * of exceptions. Failing to do so may result in corrupted results.
+ *
+ * @param taskName the name of the task being ended.
+ * @param subTaskName the name of the sub task being ended.
+ * @since 0.20.0
+ */
+ public static void stopTask(String taskName, String subTaskName) {
+ impl.stopTask(taskName, subTaskName);
+ }
+
+ /**
+ * Creates a tag with no name or numeric identifier. The returned instance is different based on
+ * if PerfMark is enabled or not.
+ *
+ * <p>This method is seldomly useful; users should generally prefer to use the overloads of
+ * methods that don't need a tag. An empty tag may be useful though when the tag of a group of
+ * tasks may change over time.
+ *
+ * @return a Tag that has no name or id.
+ */
+ public static Tag createTag() {
+ return Impl.NO_TAG;
+ }
+
+ /**
+ * Creates a tag with no name. The returned instance is different based on if PerfMark is enabled
+ * or not. The provided id does not have to be globally unique, but is instead meant to give
+ * context to a task.
+ *
+ * @param id a user provided identifier for this Tag.
+ * @return a Tag that has no name.
+ */
+ public static Tag createTag(long id) {
+ return impl.createTag(Impl.NO_TAG_NAME, id);
+ }
+
+ /**
+ * Creates a tag with no numeric identifier. The returned instance is different based on if
+ * PerfMark is enabled or not. The provided name does not have to be globally unique, but is
+ * instead meant to give context to a task.
+ *
+ * @param name a user provided name for this Tag.
+ * @return a Tag that has no numeric identifier.
+ */
+ public static Tag createTag(String name) {
+ return impl.createTag(name, Impl.NO_TAG_ID);
+ }
+
+ /**
+ * Creates a tag with both a name and a numeric identifier. The returned instance is different
+ * based on if PerfMark is enabled or not. Neither the provided name nor id has to be globally
+ * unique, but are instead meant to give context to a task.
+ *
+ * @param id a user provided identifier for this Tag.
+ * @param name a user provided name for this Tag.
+ * @return a Tag that has both a name and id.
+ */
+ public static Tag createTag(String name, long id) {
+ return impl.createTag(name, id);
+ }
+
+ /**
+ * DO NOT CALL, no longer implemented. Use {@link #linkOut} instead.
+ *
+ * @return a no-op link that
+ */
+ @Deprecated
+ @DoNotCall
+ public static Link link() {
+ return Impl.NO_LINK;
+ }
+
+ /**
+ * A link connects between two tasks that start asynchronously. When {@link #linkOut()} is called,
+ * an association between the most recently started task and a yet-to-be named task on another
+ * thread, is created. Links are a one-to-many relationship. A single started task can have
+ * multiple associated tasks on other threads.
+ *
+ * @since 0.17.0
+ * @return A Link to be used in other tasks.
+ */
+ public static Link linkOut() {
+ return impl.linkOut();
+ }
+
+ /**
+ * Associate this link with the most recently started task. There may be at most one inbound
+ * linkage per task: the first call to {@link #linkIn} decides which outbound task is the origin.
+ *
+ * @param link a link created inside of another task.
+ * @since 0.17.0
+ */
+ public static void linkIn(Link link) {
+ impl.linkIn(link);
+ }
+
+ /**
+ * Attaches an additional tag to the current active task. The tag provided is independent of the
+ * tag used with {@link #startTask(String, Tag)} and {@link #stopTask(String, Tag)}. Unlike the
+ * two previous two task overloads, the tag provided to {@link #attachTag(Tag)} does not have to
+ * match any other tags in use. This method is useful for when you have the tag information after
+ * the task is started.
+ *
+ * <p>Here are some example usages:
+ *
+ * <p>Recording the amount of work done in a task:
+ *
+ * <pre>
+ * PerfMark.startTask("read");
+ * byte[] data = file.read();
+ * PerfMark.attachTag(PerfMark.createTag("bytes read", data.length));
+ * PerfMark.stopTask("read");
+ * </pre>
+ *
+ * <p>Recording a tag which may be absent on an exception:
+ *
+ * <pre>
+ * Socket s;
+ * Tag remoteTag = PerfMark.createTag(remoteAddress.toString());
+ * PerfMark.startTask("connect", remoteTag);
+ * try {
+ * s = connect(remoteAddress);
+ * PerfMark.attachTag(PerfMark.createTag(s.getLocalAddress().toString());
+ * } finally {
+ * PerfMark.stopTask("connect", remoteTag);
+ * }
+ * </pre>
+ *
+ * @since 0.18.0
+ * @param tag the Tag to attach.
+ */
+ public static void attachTag(Tag tag) {
+ impl.attachTag(tag);
+ }
+
+ /**
+ * Attaches an additional keyed tag to the current active task. The tag provided is independent of
+ * the tag used with {@code startTask} and {@code stopTask}. This tag operation is different than
+ * {@link Tag} in that the tag value has an associated name (also called a key). The tag name and
+ * value are attached to the most recently started task, and don't have to match any other tags.
+ * This method is useful for when you have the tag information after the task is started.
+ *
+ * @param tagName The name of the value being attached
+ * @param tagValue The value to attach to the current task.
+ * @since 0.20.0
+ */
+ public static void attachTag(String tagName, String tagValue) {
+ impl.attachTag(tagName, tagValue);
+ }
+
+ /**
+ * Attaches an additional keyed tag to the current active task. The tag provided is independent of
+ * the tag used with {@code startTask} and {@code stopTask}. This tag operation is different than
+ * {@link Tag} in that the tag value has an associated name (also called a key). The tag name and
+ * value are attached to the most recently started task, and don't have to match any other tags.
+ * This method is useful for when you have the tag information after the task is started.
+ *
+ * @param tagName The name of the value being attached
+ * @param tagValue The value to attach to the current task.
+ * @since 0.20.0
+ */
+ public static void attachTag(String tagName, long tagValue) {
+ impl.attachTag(tagName, tagValue);
+ }
+
+ /**
+ * Attaches an additional keyed tag to the current active task. The tag provided is independent of
+ * the tag used with {@code startTask} and {@code stopTask}. This tag operation is different than
+ * {@link Tag} in that the tag values have an associated name (also called a key). The tag name
+ * and values are attached to the most recently started task, and don't have to match any other
+ * tags. This method is useful for when you have the tag information after the task is started.
+ *
+ * <p>This method may treat the given two longs as special. If the tag name contains the string
+ * "uuid" (case insensitive), the value may be treated as a single 128 bit value. An example
+ * usage:
+ *
+ * <pre>
+ * RPC rpc = ...
+ * PerfMark.startTask("sendRPC");
+ * try {
+ * UUID u = rpc.uuid();
+ * PerfMark.attachTag("rpc uuid", u.getMostSignificantBits(), u.getLeastSignificantBits());
+ * send(rpc);
+ * } finally {
+ * PerfMark.stopTask("sendRPC");
+ * }
+ * </pre>
+ *
+ * @param tagName The name of the value being attached
+ * @param tagValue0 The first value to attach to the current task.
+ * @param tagValue1 The second value to attach to the current task.
+ * @since 0.20.0
+ */
+ public static void attachTag(String tagName, long tagValue0, long tagValue1) {
+ impl.attachTag(tagName, tagValue0, tagValue1);
+ }
+
+ /**
+ * Attaches an additional keyed tag to the current active task. The tag provided is independent of
+ * the tag used with {@code startTask} and {@code stopTask}. This tag operation is different than
+ * {@link Tag} in that the tag value has an associated name (also called a key). The tag name and
+ * value are attached to the most recently started task, and don't have to match any other tags.
+ * This method is useful for when you have the tag information after the task is started.
+ *
+ * <p>Unlike {@link #attachTag(String, String)}, this defers constructing the tagValue String
+ * until later, and avoids doing any work while PerfMark is disabled. Callers are expected to
+ * provide a method handle that can consume the {@code tagObject}, and produce a tagValue. For
+ * example:
+ *
+ * <pre>{@code
+ * Response resp = client.makeCall(request);
+ * PerfMark.attachTag("httpServerHeader", resp, r -> r.getHeaders().get("Server"));
+ * }</pre>
+ *
+ * <p>Also unlike {@link #attachTag(String, String)}, this function is easier to misuse. Prefer
+ * using the other attachTag methods unless you are confident you need this one. Be familiar with
+ * following issues:
+ *
+ * <ul>
+ * <li>Callers should be careful to not capture the {@code tagObject}, and instead use the
+ * argument to {@code stringFunction}. This avoids a memory allocation and possibly holding
+ * the tagObject alive longer than necessary.
+ * <li>The {@code stringFunction} should be idempotent, have no side effects, and be safe to
+ * invoke from other threads. If the string function references state that may be changed,
+ * callers must synchronize access. The string function may be called multiple times for the
+ * same tag object. Additionally, if {@code attachTag()} is called with the same tag object
+ * and string function multiple times, PerfMark may invoke the function only once.
+ * <li>The tag object may kept alive longer than normal, and prevent garbage collection from
+ * reclaiming it. If the tag object retains a large amount of resources, this may appear as
+ * a memory leak. The risk of this memory increase will need to be balanced with the cost of
+ * eagerly constructing the tag value string. Additionally, if the string function is a
+ * capturing lambda (refers to local or global state), the function itself may appear as a
+ * leak.
+ * <li>If the stringFunction is {@code null}, or if it throws an exception when called, the tag
+ * value will not be attached. It is implementation defined if such problems are reported
+ * (e.g. logged). Note that exceptions are expensive compared to PerfMark calls, and thus
+ * may slow down tracing. If an exception is thrown, or if the stringFunction is {@code
+ * null}, PerfMark may invoke other methods on the tag object or string function, such as
+ * {@code toString()} and {@code getClass()}.
+ * </ul>
+ *
+ * @param tagName The name of the value being attached
+ * @param tagObject The tag object which will passed to the stringFunction.
+ * @param stringFunction The function that will convert the object to
+ * @param <T> the type of tag object to be stringified
+ * @since 0.22.0
+ */
+ public static <T> void attachTag(
+ String tagName, T tagObject, StringFunction<? super T> stringFunction) {
+ impl.attachTag(tagName, tagObject, stringFunction);
+ }
+
+ private static final Impl impl;
+
+ static {
+ Impl instance = null;
+ Throwable err = null;
+ Class<?> clz = null;
+ try {
+ clz = Class.forName("io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl");
+ } catch (Throwable t) {
+ err = t;
+ }
+ if (clz != null) {
+ try {
+ instance = clz.asSubclass(Impl.class).getConstructor(Tag.class).newInstance(Impl.NO_TAG);
+ } catch (Throwable t) {
+ err = t;
+ }
+ }
+ if (instance != null) {
+ impl = instance;
+ } else {
+ impl = new Impl(Impl.NO_TAG);
+ }
+ if (err != null) {
+ try {
+ if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) {
+ // We need to be careful here, as it's easy to accidentally cause a class load. Logger is loaded
+ // reflectively to avoid accidentally pulling it in.
+ // TODO(carl-mastrangelo): Maybe make this load SLF4J instead?
+ Class<?> logClass = Class.forName("java.util.logging.Logger");
+ Object logger = logClass.getMethod("getLogger", String.class).invoke(null, PerfMark.class.getName());
+ Class<?> levelClass = Class.forName("java.util.logging.Level");
+ Object level = levelClass.getField("FINE").get(null);
+ Method logMethod = logClass.getMethod("log", levelClass, String.class, Throwable.class);
+ logMethod.invoke(logger, level, "Error during PerfMark.<clinit>", err);
+ }
+ } catch (Throwable e) {
+ // ignored.
+ }
+ }
+ }
+
+ private PerfMark() {}
+}
diff --git a/api/src/main/java/io/perfmark/StringFunction.java b/api/src/main/java/io/perfmark/StringFunction.java
new file mode 100644
index 0000000..19506e0
--- /dev/null
+++ b/api/src/main/java/io/perfmark/StringFunction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+/**
+ * This interface is equivalent to {@code java.util.function.Function}. It is here as a
+ * compatibility shim to make PerfMark compatible with Java 6. This will likely be removed if
+ * PerfMark picks Java 8 as the minimum support version. See {@link PerfMark} for expected usage.
+ *
+ * @since 0.22.0
+ * @param <T> The type to turn into a String.
+ */
+public interface StringFunction<T> {
+
+ /**
+ * Takes the given argument and produces a String.
+ *
+ * @since 0.22.0
+ * @param t the subject to Stringify
+ * @return the String
+ */
+ String apply(T t);
+}
diff --git a/api/src/main/java/io/perfmark/Tag.java b/api/src/main/java/io/perfmark/Tag.java
new file mode 100644
index 0000000..f054a7e
--- /dev/null
+++ b/api/src/main/java/io/perfmark/Tag.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import javax.annotation.Nullable;
+
+/** Tag is a dynamic, runtime created identifier (such as an RPC id). */
+public final class Tag {
+ @Nullable final String tagName;
+ final long tagId;
+
+ Tag(@Nullable String tagName, long tagId) {
+ // tagName should be non-null, but checking is expensive
+ this.tagName = tagName;
+ this.tagId = tagId;
+ }
+}
diff --git a/api/src/main/java/io/perfmark/TaskCloseable.java b/api/src/main/java/io/perfmark/TaskCloseable.java
new file mode 100644
index 0000000..50c763b
--- /dev/null
+++ b/api/src/main/java/io/perfmark/TaskCloseable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import java.io.Closeable;
+
+/**
+ * TaskCloseable is a helper class to simplify the closing of PerfMark tasks. It should be used in a
+ * try-with-resources block so that PerfMark tasks are recorded even in the event of exceptions.
+ *
+ * <p>Implementation note: This would normally implement {@code AutoCloseable}, but that is not
+ * available in Java 6. A future version of PerfMark may implement the parent interface instead.
+ *
+ *
+ * @since 0.23.0
+ */
+public final class TaskCloseable implements Closeable {
+
+ static final TaskCloseable INSTANCE = new TaskCloseable();
+
+ /**
+ * Stops the opened task. See {@link PerfMark#traceTask(String)}.
+ */
+ @Override
+ public void close() {
+ PerfMark.stopTask();
+ }
+
+ private TaskCloseable() {}
+}
diff --git a/api/src/main/java/io/perfmark/package-info.java b/api/src/main/java/io/perfmark/package-info.java
new file mode 100644
index 0000000..1c4301e
--- /dev/null
+++ b/api/src/main/java/io/perfmark/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark;
diff --git a/api/src/test/java/io/perfmark/CompatibilityTest.java b/api/src/test/java/io/perfmark/CompatibilityTest.java
new file mode 100644
index 0000000..51b383d
--- /dev/null
+++ b/api/src/test/java/io/perfmark/CompatibilityTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompatibilityTest {
+ private static final int STABLE_VERSION = 15;
+
+ private static final List<String> VERSIONS =
+ List.of(
+ "0.13.37",
+ "0.14.0",
+ "0.15.0",
+ "0.16.0",
+ "0.17.0",
+ // "0.18.0", missing, not sure why
+ "0.19.0",
+ "0.20.1",
+ "0.21.0",
+ "0.22.0",
+ "0.23.0",
+ "0.24.0",
+ "0.25.0");
+
+ @Parameterized.Parameters(name = "version v{0}")
+ @SuppressWarnings("StringSplitter")
+ public static Iterable<Object[]> params() {
+ List<Object[]> params = new ArrayList<>();
+ for (var version : VERSIONS) {
+ String fileName = "perfmark-api-" + version + ".jar";
+ URL jarPath = CompatibilityTest.class.getResource(fileName);
+ if (jarPath == null) {
+ throw new AssertionError("Can't load version " + version);
+ }
+ params.add(new Object[]{version, Integer.valueOf(version.split("\\.")[1]), jarPath});
+ }
+
+ return params;
+ }
+
+ @Parameterized.Parameter(0)
+ public String semanticVersion;
+
+ @Parameterized.Parameter(1)
+ public int minorVersion;
+
+ @Parameterized.Parameter(2)
+ public URL jarPath;
+
+ private final Class<?> currentPerfMarkClz = PerfMark.class;
+
+ private Class<?> perfMarkClz;
+ private Class<?> storageClz;
+
+ @Before
+ public void setUp() throws Exception {
+ ClassLoader loader = new ApiOverrideClassLoader();
+
+ perfMarkClz = Class.forName("io.perfmark.PerfMark", false, loader);
+ assertNotEquals(currentPerfMarkClz, perfMarkClz);
+
+ storageClz = Class.forName("io.perfmark.impl.Storage", false, loader);
+ var marks = (List) storageClz.getMethod("readForTest").invoke(null);
+ assertThat(marks).isNull();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (storageClz != null) {
+ storageClz.getMethod("clearLocalStorage").invoke(null);
+ }
+ }
+
+ @Test
+ public void checkPublicMethods_disabledOnMissingImpl() throws Exception {
+ Assume.assumeTrue(minorVersion >= STABLE_VERSION);
+
+ ClassLoader loader = new ApiOverrideNoImplClassLoader();
+
+ Class<?> perfMarkClz = Class.forName("io.perfmark.PerfMark", false, loader);
+ assertNotEquals(currentPerfMarkClz, perfMarkClz);
+ assertNotEquals(this.perfMarkClz, perfMarkClz);
+
+ var tag = perfMarkClz.getMethod("createTag").invoke(null);
+ var link = perfMarkClz.getMethod("link").invoke(null);
+
+ for (Method method : perfMarkClz.getMethods()) {
+ if (!Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+ var paramTypes = method.getParameterTypes();
+ Object[] args = new Object[paramTypes.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (paramTypes[i].getName().startsWith("io.perfmark.")) {
+ paramTypes[i] = Class.forName(paramTypes[i].getName(), false, currentPerfMarkClz.getClassLoader());
+ }
+ if (paramTypes[i] == long.class) {
+ args[i] = 0L;
+ } else if (paramTypes[i] == boolean.class) {
+ args[i] = true;
+ } else if (paramTypes[i].getSimpleName().equals("Link")) {
+ args[i] = link;
+ } else if (paramTypes[i].getSimpleName().equals("Tag")) {
+ args[i] = tag;
+ } else if (Object.class.isAssignableFrom(paramTypes[i])) {
+ args[i] = null;
+ } else {
+ throw new AssertionError("unknown param");
+ }
+ }
+
+ method.invoke(null, args);
+ }
+ }
+
+ @Test
+ public void checkPublicMethods() throws Exception {
+ Assume.assumeTrue(minorVersion >= STABLE_VERSION);
+ for (Method method : perfMarkClz.getMethods()) {
+ var paramTypes = method.getParameterTypes();
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (paramTypes[i].getName().startsWith("io.perfmark.")) {
+ paramTypes[i] = Class.forName(paramTypes[i].getName(), false, currentPerfMarkClz.getClassLoader());
+ }
+ }
+ Class<?> returnType = method.getReturnType();
+ if (returnType.getName().startsWith("io.perfmark.")) {
+ returnType = Class.forName(returnType.getName(), false, currentPerfMarkClz.getClassLoader());
+ }
+
+ var m = currentPerfMarkClz.getMethod(method.getName(), paramTypes);
+ assertNotNull(method.getName(), m);
+ if (returnType == void.class) {
+ return;
+ }
+ assertEquals(m.getReturnType(), returnType);
+ }
+ }
+
+ @Test
+ public void startStopTaskWorks() throws Exception {
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task1");
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task1");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ // 13 - 16 should safely disable themselves, nag, but ultimately produce no data.
+ if (minorVersion >= 17) {
+ assertThat(marks).hasSize(2);
+ } else {
+ assertThat(marks).isNull();
+ }
+ }
+
+ @Test
+ public void startStopTaskWorks_tag() throws Exception {
+ Class<?> tagClz = Class.forName("io.perfmark.Tag", false, perfMarkClz.getClassLoader());
+ Object tag = perfMarkClz.getMethod("createTag").invoke(null);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("startTask", String.class, tagClz).invoke(null, "task1", tag);
+ perfMarkClz.getMethod("stopTask", String.class, tagClz).invoke(null, "task1", tag);
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ // 13 - 16 should safely disable themselves, nag, but ultimately produce no data.
+ if (minorVersion >= 17) {
+ assertThat(marks).hasSize(4);
+ } else {
+ assertThat(marks).isNull();
+ }
+ }
+
+ @Test
+ public void startStopTaskWorks_namedFunction() throws Exception {
+ Assume.assumeTrue(minorVersion >= 22);
+ Class<?> fnClz = Class.forName("io.perfmark.StringFunction", false, perfMarkClz.getClassLoader());
+ Object fn =
+ Proxy.newProxyInstance(perfMarkClz.getClassLoader(), new Class<?>[]{fnClz}, (proxy, method, args) -> "hi");
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("startTask", Object.class, fnClz).invoke(null, new Object(), fn);
+ perfMarkClz.getMethod("stopTask").invoke(null);
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(2);
+ }
+
+ @Test
+ public void startStopTaskWorks_subTask() throws Exception {
+ Assume.assumeTrue(minorVersion >= 20);
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("startTask", String.class, String.class).invoke(null, "task1", "sub");
+ perfMarkClz.getMethod("stopTask", String.class, String.class).invoke(null, "task1", "sub");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(2);
+ }
+
+ @Test
+ public void traceTask() throws Exception {
+ Assume.assumeTrue(minorVersion >= 23);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ try (var c = (Closeable) perfMarkClz.getMethod("traceTask", String.class).invoke(null, "task1")) {}
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(2);
+ }
+
+ @Test
+ public void traceTask_namedFunction() throws Exception {
+ Assume.assumeTrue(minorVersion >= 23);
+
+ Class<?> fnClz = Class.forName("io.perfmark.StringFunction", false, perfMarkClz.getClassLoader());
+ Object fn =
+ Proxy.newProxyInstance(perfMarkClz.getClassLoader(), new Class<?>[]{fnClz}, (proxy, method, args) -> "hi");
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ try (var c = (Closeable) perfMarkClz.getMethod("traceTask", Object.class, fnClz).invoke(null, new Object(), fn)) {}
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(2);
+ }
+
+ @Test
+ public void event_tag() throws Exception {
+ // Versions Prior to 15 Had a duration associated with an event that was removed before stability. This means
+ // Classloading in PerfMark init can't find the MarkHolder.event() incompatibility between older and later versions.
+ Assume.assumeTrue(minorVersion >= STABLE_VERSION);
+
+ Class<?> tagClz = Class.forName("io.perfmark.Tag", false, perfMarkClz.getClassLoader());
+ Object tag = perfMarkClz.getMethod("createTag").invoke(null);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("event", String.class, tagClz).invoke(null, "event1", tag);
+ perfMarkClz.getMethod("event", String.class).invoke(null, "event2");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ // 13 - 16 should safely disable themselves, nag, but ultimately produce no data.
+ if (minorVersion >= 17) {
+ assertThat(marks).hasSize(2);
+ } else {
+ assertThat(marks).isNull();
+ }
+ }
+
+ @Test
+ public void event_subEvent() throws Exception {
+ Assume.assumeTrue(minorVersion >= 20);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("event", String.class, String.class).invoke(null, "event1", "subevent");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void createTags() throws Exception {
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("createTag", long.class).invoke(null, 2);
+ perfMarkClz.getMethod("createTag", String.class).invoke(null, "tag2");
+ perfMarkClz.getMethod("createTag", String.class, long.class).invoke(null, "tag2", 2);
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).isNull();
+ }
+
+ @Test
+ public void link_doesNothing() throws Exception {
+ // This is broken in early versions, sorry.
+ Assume.assumeTrue(minorVersion >= STABLE_VERSION);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ perfMarkClz.getMethod("link").invoke(null);
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).isNull();
+ }
+
+ @Test
+ public void linkInLinkOut() throws Exception {
+ Assume.assumeTrue(minorVersion >= 17);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task1");
+ Object link = perfMarkClz.getMethod("linkOut").invoke(null);
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task1");
+
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task2");
+ perfMarkClz.getMethod("linkIn", link.getClass()).invoke(null, link);
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task2");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(6);
+ }
+
+ @Test
+ public void attachTag_tag() throws Exception {
+ Assume.assumeTrue(minorVersion >= 18);
+
+ Class<?> tagClz = Class.forName("io.perfmark.Tag", false, perfMarkClz.getClassLoader());
+ Object tag = perfMarkClz.getMethod("createTag").invoke(null);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task1");
+ perfMarkClz.getMethod("attachTag", tagClz).invoke(null, tag);
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task1");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(3);
+ }
+
+ @Test
+ public void attachTag_namedTag() throws Exception {
+ Assume.assumeTrue(minorVersion >= 20);
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task1");
+ perfMarkClz.getMethod("attachTag", String.class, String.class).invoke(null, "name1", "val");
+ perfMarkClz.getMethod("attachTag", String.class, long.class).invoke(null, "name2", 22L);
+ perfMarkClz.getMethod("attachTag", String.class, long.class, long.class).invoke(null, "uuid", 22L, 55L);
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task1");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(5);
+ }
+
+ @Test
+ public void attachTag_namedFunction() throws Exception {
+ Assume.assumeTrue(minorVersion >= 22);
+
+ Class<?> fnClz = Class.forName("io.perfmark.StringFunction", false, perfMarkClz.getClassLoader());
+ Object fn =
+ Proxy.newProxyInstance(perfMarkClz.getClassLoader(), new Class<?>[]{fnClz}, (proxy, method, args) -> "hi");
+
+ perfMarkClz.getMethod("setEnabled", boolean.class).invoke(null, true);
+
+ perfMarkClz.getMethod("startTask", String.class).invoke(null, "task1");
+ perfMarkClz.getMethod("attachTag", String.class, Object.class, fnClz).invoke(null, "name1", new Object(), fn);
+ perfMarkClz.getMethod("stopTask", String.class).invoke(null, "task1");
+
+ List marks = (List) storageClz.getMethod("readForTest").invoke(null);
+
+ assertThat(marks).hasSize(3);
+ }
+
+ private final class ApiOverrideClassLoader extends ClassLoader {
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (name.startsWith("io.perfmark.")) {
+ for (var loader : List.of(new URLClassLoader(new URL[] {jarPath}, null), getClass().getClassLoader())) {
+ try (var stream = loader.getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (stream == null) {
+ continue;
+ }
+ var data = stream.readAllBytes();
+ var clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw (ClassNotFoundException) new ClassNotFoundException().initCause(e);
+ }
+ }
+ throw new ClassNotFoundException("not in here: " + name);
+ }
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ private final class ApiOverrideNoImplClassLoader extends ClassLoader {
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (name.startsWith("io.perfmark.")) {
+ if (name.startsWith("io.perfmark.impl.")) {
+ throw new ClassNotFoundException(name);
+ }
+ for (var loader : List.of(new URLClassLoader(new URL[] {jarPath}, null), getClass().getClassLoader())) {
+ try (var stream = loader.getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (stream == null) {
+ continue;
+ }
+ var data = stream.readAllBytes();
+ var clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw (ClassNotFoundException) new ClassNotFoundException().initCause(e);
+ }
+ }
+ throw new ClassNotFoundException("not in here: " + name);
+ }
+ return super.loadClass(name, resolve);
+ }
+ }
+}
diff --git a/api/src/test/java/io/perfmark/PerfMarkTest.java b/api/src/test/java/io/perfmark/PerfMarkTest.java
new file mode 100644
index 0000000..5397b3f
--- /dev/null
+++ b/api/src/test/java/io/perfmark/PerfMarkTest.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark;
+
+import static io.perfmark.impl.Mark.NO_TAG_ID;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.truth.Truth;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.Storage;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.Permission;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.PropertyPermission;
+import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Filter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.LoggingPermission;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PerfMarkTest {
+
+ /**
+ * This test checks to see if PerfMark can be used from a Logger, which is used for recording if there is trouble
+ * turning on. PerfMark should set a noop implementation before recording any problems with boot.
+ */
+ @Test
+ public void noBootCycle() throws Exception {
+ AtomicReference<LogRecord> ref = new AtomicReference<>();
+ ClassLoader loader =
+ new TestClassLoader(
+ getClass().getClassLoader(), "io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl");
+ Class<?> clz = Class.forName(PerfMark.class.getName(), false, loader);
+
+ Class<?> filterClz = Class.forName(TracingFilter.class.getName(), false, loader);
+ Constructor<? extends Filter> ctor = filterClz.asSubclass(Filter.class)
+ .getDeclaredConstructor(Class.class, AtomicReference.class);
+ ctor.setAccessible(true);
+ Filter filter = ctor.newInstance(clz, ref);
+ Logger logger = Logger.getLogger(PerfMark.class.getName());
+ Level oldLevel = logger.getLevel();
+ Filter oldFilter = logger.getFilter();
+ logger.setLevel(Level.ALL);
+ logger.setFilter(filter);
+ try {
+ runWithProperty(System.getProperties(), "io.perfmark.PerfMark.debug", "true", () -> {
+ try {
+ // Force Initialization.
+ Class.forName(PerfMark.class.getName(), true, loader);
+ } finally{
+ logger.setFilter(oldFilter);
+ }
+ return null;
+ });
+ } finally{
+ logger.setFilter(oldFilter);
+ logger.setLevel(oldLevel);
+ }
+
+ // The actual SecretPerfMarkImpl is not part of the custom class loader above, so it will be a class mismatch when
+ // it tries to implement Impl.
+ // The message will be the default still, so check for that, to prove it did something.
+ Truth.assertThat(ref.get()).isNotNull();
+ Truth.assertThat(ref.get().getMessage()).contains("Error during PerfMark.<clinit>");
+ }
+
+ private static class HesitantSecurityManager extends SecurityManager {
+ boolean unload;
+
+ @Override
+ public void checkPermission(Permission perm) {
+ if (unload && perm.getName().equals("setSecurityManager")) {
+ return;
+ }
+ if (perm instanceof FilePermission) {
+ FilePermission fp = (FilePermission) perm;
+ if ("read".equals(fp.getActions())) {
+ if (fp.getName().endsWith(".class") && fp.getName().contains("io/perfmark/")) {
+ return;
+ }
+ if (fp.getName().endsWith(".jar") && fp.getName().contains("/perfmark/")) {
+ return;
+ }
+ }
+ }
+ if (perm instanceof PropertyPermission) {
+ if (perm.getName().equals("java.util.logging.manager") && perm.getActions().equals("read")) {
+ return;
+ }
+ }
+ for (StackTraceElement element : new Throwable().getStackTrace()) {
+ if (element.getClassName().equals(TestClassLoader.class.getName())) {
+ if (perm.getName().equals("suppressAccessChecks")) {
+ return;
+ }
+ if (perm.getName().equals("accessSystemModules")) {
+ return;
+ }
+ }
+ if (element.getClassName().equals("java.util.logging.Level")) {
+ if (perm.getName().equals("suppressAccessChecks")) {
+ return;
+ }
+ if (perm.getName().equals("accessSystemModules")) {
+ return;
+ }
+ }
+ if (element.getClassName().equals("java.util.logging.LogManager")) {
+ if (perm.getName().equals("shutdownHooks")) {
+ return;
+ }
+ if (perm.getName().equals("setContextClassLoader")) {
+ return;
+ }
+ if (perm instanceof LoggingPermission && perm.getName().equals("control")) {
+ return;
+ }
+ }
+ if (element.getClassName().equals("java.util.logging.Logger")) {
+ if (perm.getName().equals("sun.util.logging.disableCallerCheck")) {
+ return;
+ }
+ if (perm.getName().equals("getClassLoader")) {
+ return;
+ }
+ }
+ }
+
+ super.checkPermission(perm);
+ }
+ }
+
+ @Test
+ public void worksWithSecurityManager_noStartEnabled_noDebug() throws Exception {
+ ClassLoader loader = new TestClassLoader(getClass().getClassLoader());
+
+ SecurityManager oldMgr = System.getSecurityManager();
+ HesitantSecurityManager newMgr = new HesitantSecurityManager();
+ Class<?> clz;
+ try {
+ System.setSecurityManager(newMgr);
+ clz = Class.forName(PerfMark.class.getName(), true, loader);
+ clz.getMethod("setEnabled", boolean.class).invoke(null, true);
+ clz.getMethod("event", String.class).invoke(null, "event");
+ } finally {
+ newMgr.unload = true;
+ System.setSecurityManager(oldMgr);
+ }
+
+ Class<?> storageClass = Class.forName(Storage.class.getName(), true, clz.getClassLoader());
+ List<Mark> marks = (List<Mark>) storageClass.getMethod("readForTest").invoke(null);
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void worksWithSecurityManager_startEnabled_noDebug() throws Exception {
+ ClassLoader loader = new TestClassLoader(getClass().getClassLoader());
+
+ SecurityManager oldMgr = System.getSecurityManager();
+ HesitantSecurityManager newMgr = new HesitantSecurityManager() {
+ @Override
+ public void checkPermission(Permission perm) {
+ if (perm instanceof PropertyPermission) {
+ if (perm.getName().equals("*")) {
+ return;
+ }
+ if (perm.getName().equals("io.perfmark.PerfMark.startEnabled") && perm.getActions().equals("read")) {
+ return;
+ }
+ }
+ super.checkPermission(perm);
+ }
+ };
+
+ Class<?> clz = runWithProperty(System.getProperties(), "io.perfmark.PerfMark.startEnabled", "true", () -> {
+ try {
+ System.setSecurityManager(newMgr);
+ Class<?> clz2 = Class.forName(PerfMark.class.getName(), true, loader);
+ clz2.getMethod("event", String.class).invoke(null, "event");
+ return clz2;
+ } finally {
+ newMgr.unload = true;
+ System.setSecurityManager(oldMgr);
+ }
+ });
+
+ Class<?> storageClass = Class.forName(Storage.class.getName(), true, clz.getClassLoader());
+ List<Mark> marks = (List<Mark>) storageClass.getMethod("readForTest").invoke(null);
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void worksWithSecurityManager_noStartEnabled_debug() throws Exception {
+ ClassLoader loader = new TestClassLoader(getClass().getClassLoader());
+
+ SecurityManager oldMgr = System.getSecurityManager();
+ HesitantSecurityManager newMgr = new HesitantSecurityManager() {
+ @Override
+ public void checkPermission(Permission perm) {
+ if (perm instanceof PropertyPermission) {
+ if (perm.getName().equals("*")) {
+ return;
+ }
+ if (perm.getName().equals("io.perfmark.PerfMark.debug") && perm.getActions().contains("read")) {
+ return;
+ }
+ }
+ super.checkPermission(perm);
+ }
+ };
+
+ // TODO check logging occurred.
+
+ Class<?> clz = runWithProperty(System.getProperties(), "io.perfmark.PerfMark.debug", "true", () -> {
+ try {
+ System.setSecurityManager(newMgr);
+ Class<?> clz2 = Class.forName(PerfMark.class.getName(), true, loader);
+ clz2.getMethod("setEnabled", boolean.class).invoke(null, true);
+ clz2.getMethod("event", String.class).invoke(null, "event");
+ return clz2;
+ } finally {
+ newMgr.unload = true;
+ System.setSecurityManager(oldMgr);
+ }
+ });
+
+ Class<?> storageClass = Class.forName(Storage.class.getName(), true, clz.getClassLoader());
+ List<Mark> marks = (List<Mark>) storageClass.getMethod("readForTest").invoke(null);
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void allMethodForward_taskName() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+
+ long gen = getGen();
+
+ Tag tag1 = PerfMark.createTag(1);
+ Tag tag2 = PerfMark.createTag("two");
+ Tag tag3 = PerfMark.createTag("three", 3);
+ PerfMark.startTask("task1", tag1);
+ PerfMark.startTask("task2", tag2);
+ PerfMark.startTask("task3", tag3);
+ PerfMark.startTask("task4");
+ PerfMark.startTask("task5", String::valueOf);
+ PerfMark.attachTag(PerfMark.createTag("extra"));
+ PerfMark.attachTag("name", "extra2", String::valueOf);
+ Link link = PerfMark.linkOut();
+ PerfMark.linkIn(link);
+ PerfMark.stopTask();
+ PerfMark.stopTask("task4");
+ PerfMark.stopTask("task3", tag3);
+ PerfMark.stopTask("task2", tag2);
+ PerfMark.stopTask("task1", tag1);
+ try (TaskCloseable task6 = PerfMark.traceTask("task6")) {
+ try (TaskCloseable task7 = PerfMark.traceTask("task7", String::valueOf)) {}
+ }
+
+ List<Mark> marks = Storage.readForTest();
+
+ Truth.assertThat(marks).hasSize(24);
+ List<Mark> expected =
+ Arrays.asList(
+ Mark.taskStart(gen, marks.get(0).getNanoTime(), "task1"),
+ Mark.tag(gen, tag1.tagName, tag1.tagId),
+ Mark.taskStart(gen, marks.get(2).getNanoTime(), "task2"),
+ Mark.tag(gen, tag2.tagName, tag2.tagId),
+ Mark.taskStart(gen, marks.get(4).getNanoTime(), "task3"),
+ Mark.tag(gen, tag3.tagName, tag3.tagId),
+ Mark.taskStart(gen, marks.get(6).getNanoTime(), "task4"),
+ Mark.taskStart(gen, marks.get(7).getNanoTime(), "task5"),
+ Mark.tag(gen, "extra", NO_TAG_ID),
+ Mark.keyedTag(gen, "name", "extra2"),
+ Mark.link(gen, link.linkId),
+ Mark.link(gen, -link.linkId),
+ Mark.taskEnd(gen, marks.get(12).getNanoTime()),
+ Mark.taskEnd(gen, marks.get(13).getNanoTime(), "task4"),
+ Mark.tag(gen, tag3.tagName, tag3.tagId),
+ Mark.taskEnd(gen, marks.get(15).getNanoTime(), "task3"),
+ Mark.tag(gen, tag2.tagName, tag2.tagId),
+ Mark.taskEnd(gen, marks.get(17).getNanoTime(), "task2"),
+ Mark.tag(gen, tag1.tagName, tag1.tagId),
+ Mark.taskEnd(gen, marks.get(19).getNanoTime(), "task1"),
+ Mark.taskStart(gen, marks.get(20).getNanoTime(), "task6"),
+ Mark.taskStart(gen, marks.get(21).getNanoTime(), "task7"),
+ Mark.taskEnd(gen, marks.get(22).getNanoTime()),
+ Mark.taskEnd(gen, marks.get(23).getNanoTime()));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void attachTag_nullFunctionFailsSilently() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+
+ PerfMark.attachTag("name", "extra2", null);
+
+ List<Mark> marks = Storage.readForTest();
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void attachTag_functionFailureSucceeds() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+
+ PerfMark.attachTag(
+ "name",
+ "extra2",
+ v -> {
+ throw new RuntimeException("bad");
+ });
+
+ List<Mark> marks = Storage.readForTest();
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void attachTag_functionFailureObjectFailureSucceeds() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+ Object o =
+ new Object() {
+ @Override
+ public String toString() {
+ throw new RuntimeException("worse");
+ }
+ };
+
+ PerfMark.attachTag(
+ "name",
+ o,
+ v -> {
+ throw new RuntimeException("bad");
+ });
+
+ List<Mark> marks = Storage.readForTest();
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ @Test
+ public void attachTag_doubleFunctionFailureSucceeds() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+
+ PerfMark.attachTag(
+ "name",
+ "extra2",
+ v -> {
+ throw new RuntimeException("bad") {
+ @Override
+ public String getMessage() {
+ throw new RuntimeException("worse");
+ }
+ };
+ });
+
+ List<Mark> marks = Storage.readForTest();
+ Truth.assertThat(marks).hasSize(1);
+ }
+
+ public static final class FakeGenerator extends Generator {
+
+ long generation;
+
+ @Override
+ public void setGeneration(long generation) {
+ this.generation = generation;
+ }
+
+ @Override
+ public long getGeneration() {
+ return generation;
+ }
+ }
+
+ private static long getGen() {
+ try {
+ Class<?> implClz = Class.forName("io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl");
+ Method method = implClz.getDeclaredMethod("getGen");
+ method.setAccessible(true);
+ return (long) method.invoke(null);
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @CanIgnoreReturnValue
+ private static <T> T runWithProperty(Properties properties, String name, String value, Callable<T> runnable)
+ throws Exception {
+ if (properties.containsKey(name)) {
+ String oldProp;
+ oldProp = properties.getProperty(name);
+ try {
+ System.setProperty(name, value);
+ return runnable.call();
+ } finally{
+ properties.setProperty(name, oldProp);
+ }
+ } else {
+ try {
+ System.setProperty(name, value);
+ return runnable.call();
+ } finally{
+ properties.remove(name);
+ }
+ }
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+
+ private final List<String> classesToDrop;
+
+ TestClassLoader(ClassLoader parent, String ... classesToDrop) {
+ super(parent);
+ this.classesToDrop = Arrays.asList(classesToDrop);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (classesToDrop.contains(name)) {
+ throw new ClassNotFoundException();
+ }
+ if (!name.startsWith("io.perfmark.")) {
+ return super.loadClass(name, resolve);
+ }
+ try (InputStream is = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (is == null) {
+ throw new ClassNotFoundException(name);
+ }
+ byte[] data = is.readAllBytes();
+ Class<?> clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static final class TracingFilter implements Filter {
+ private final AtomicReference<LogRecord> ref;
+
+ TracingFilter(Class<?> clz, AtomicReference<LogRecord> ref) {
+ assertEquals(PerfMark.class, clz);
+ this.ref = ref;
+ }
+
+ @Override
+ public boolean isLoggable(LogRecord record) {
+ PerfMark.startTask("isLoggable");
+ try {
+ ref.compareAndExchange(null, record);
+ return false;
+ } finally {
+ PerfMark.stopTask("isLoggable");
+ }
+ }
+ }
+}
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.13.37.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.13.37.jar
new file mode 100644
index 0000000..9f2f6c0
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.13.37.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.14.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.14.0.jar
new file mode 100644
index 0000000..5e76ef6
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.14.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.15.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.15.0.jar
new file mode 100644
index 0000000..94f200d
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.15.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.16.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.16.0.jar
new file mode 100644
index 0000000..1e9fe2f
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.16.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.17.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.17.0.jar
new file mode 100644
index 0000000..8f07afa
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.17.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.19.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.19.0.jar
new file mode 100644
index 0000000..557a056
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.19.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.20.1.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.20.1.jar
new file mode 100644
index 0000000..be6b1bb
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.20.1.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.21.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.21.0.jar
new file mode 100644
index 0000000..8d9fd07
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.21.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.22.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.22.0.jar
new file mode 100644
index 0000000..fc4e3f8
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.22.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.23.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.23.0.jar
new file mode 100644
index 0000000..690ce83
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.23.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.24.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.24.0.jar
new file mode 100644
index 0000000..dab4c46
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.24.0.jar
Binary files differ
diff --git a/api/src/test/resources/io/perfmark/perfmark-api-0.25.0.jar b/api/src/test/resources/io/perfmark/perfmark-api-0.25.0.jar
new file mode 100644
index 0000000..5189d23
--- /dev/null
+++ b/api/src/test/resources/io/perfmark/perfmark-api-0.25.0.jar
Binary files differ
diff --git a/api/testing/build.gradle.kts b/api/testing/build.gradle.kts
new file mode 100644
index 0000000..fdcfb77
--- /dev/null
+++ b/api/testing/build.gradle.kts
@@ -0,0 +1,24 @@
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.apitesting")
+ }
+}
+
+description = "PerfMark API Tests"
+
+dependencies {
+ testImplementation(libs.truth)
+
+ testImplementation(project(":perfmark-api"))
+ testImplementation(project(":perfmark-tracewriter"))
+ testImplementation(project(":perfmark-traceviewer"))
+ testImplementation(project(":perfmark-agent"))
+ testRuntimeOnly(project(":perfmark-java6"))
+ testRuntimeOnly(project(":perfmark-java7"))
+ testRuntimeOnly(project(":perfmark-java9"))
+}
+
+tasks.named<JavaCompile>("compileTestJava") {
+ sourceCompatibility = JavaVersion.VERSION_17.toString()
+ targetCompatibility = JavaVersion.VERSION_17.toString()
+}
diff --git a/api/testing/src/test/java/io/perfmark/apitesting/ArtifactTest.java b/api/testing/src/test/java/io/perfmark/apitesting/ArtifactTest.java
new file mode 100644
index 0000000..f6e4155
--- /dev/null
+++ b/api/testing/src/test/java/io/perfmark/apitesting/ArtifactTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.apitesting;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.truth.Truth;
+import io.perfmark.PerfMark;
+import io.perfmark.traceviewer.TraceEventViewer;
+import io.perfmark.tracewriter.TraceEventWriter;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ArtifactTest {
+ @Test
+ public void hasPackage() throws Exception {
+ List<String> reflectiveClasses =
+ List.of(
+ "io.perfmark.impl.SecretPerfMarkImpl$PerfMarkImpl",
+ "io.perfmark.java9.SecretVarHandleMarkHolderProvider$VarHandleMarkHolderProvider",
+ "io.perfmark.java6.SecretSynchronizedMarkHolderProvider$SynchronizedMarkHolderProvider",
+ "io.perfmark.java7.SecretMethodHandleGenerator$MethodHandleGenerator"
+ );
+ for (String reflectiveClass : reflectiveClasses) {
+ Class<?> clz = Class.forName(reflectiveClass, false, getClass().getClassLoader());
+ checkPackage(clz.getPackage());
+ }
+ checkPackage(PerfMark.class.getPackage());
+ checkPackage(TraceEventWriter.class.getPackage());
+ checkPackage(TraceEventViewer.class.getPackage());
+ }
+
+ private static void checkPackage(Package pkg) {
+ Truth.assertWithMessage(pkg.toString()).that(pkg.getImplementationTitle()).contains("PerfMark");
+
+ String vers = pkg.getImplementationVersion();
+ assertNotNull(vers);
+ String[] path = vers.split("\\.", 3);
+ Truth.assertThat(path).hasLength(3);
+ Truth.assertThat(Long.parseLong(path[0])).isAtLeast(0);
+ Truth.assertThat(Long.parseLong(path[1])).isAtLeast(1);
+
+ Truth.assertThat(pkg.getImplementationVendor()).isNotEmpty();
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..143473a
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,156 @@
+plugins {
+ id "net.ltgt.errorprone" version "3.0.1" apply false
+ id "me.champeau.jmh" version "0.6.8" apply false
+ id "io.github.reyerizo.gradle.jcstress" version "0.8.14" apply false
+}
+
+subprojects {
+ apply plugin: "checkstyle"
+ apply plugin: "java-library"
+ apply plugin: 'maven-publish'
+ apply plugin: "idea"
+ apply plugin: "signing"
+ apply plugin: "net.ltgt.errorprone"
+
+ repositories {
+ maven {
+ url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+ mavenCentral()
+ }
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ }
+
+ task javadocJar(type: Jar) {
+ classifier = 'javadoc'
+ from javadoc
+ }
+
+ task sourcesJar(type: Jar) {
+ classifier = 'sources'
+ from sourceSets.main.allSource
+ }
+
+ checkstyle {
+ configDirectory = file("$rootDir/buildscripts")
+ toolVersion = "6.17"
+ ignoreFailures = false
+ if (rootProject.hasProperty("checkstyle.ignoreFailures")) {
+ ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean()
+ }
+ }
+
+ afterEvaluate {
+ jar {
+ manifest {
+ attributes ('Automatic-Module-Name': moduleName,
+ "Implementation-Version": archiveVersion.get(),
+ "Implementation-Title": "PerfMark",
+ "Implementation-Vendor": "Carl Mastrangelo",
+ "Implementation-URL": "https://www.perfmark.io/",
+ "Carl-Is-Awesome": "true")
+ }
+
+ }
+ }
+
+
+ publishing {
+ publications {
+ maven(MavenPublication) {
+ from components.java
+
+ artifact javadocJar
+ artifact sourcesJar
+
+ pom {
+ name = project.group + ":" + project.name
+ url = 'https://github.com/perfmark/perfmark'
+ afterEvaluate {
+ // description is not available until evaluated.
+ description = project.description
+ }
+
+ scm {
+ connection = 'scm:git:https://github.com/perfmark/perfmark.git'
+ developerConnection = 'scm:git@github.com:perfmark/perfmark.git'
+ url = 'https://github.com/perfmark/perfmark'
+ }
+
+ licenses {
+ license {
+ name = 'Apache 2.0'
+ url = 'https://opensource.org/licenses/Apache-2.0'
+ }
+ }
+
+ developers {
+ developer {
+ id = "carl-mastrangelo"
+ name = "Carl Mastrangelo"
+ email = "carl@carlmastrangelo.com"
+ url = "https://www.perfmark.io/"
+ }
+ }
+ }
+ }
+ }
+
+ repositories {
+ maven {
+ def stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
+ def releaseUrl = stagingUrl
+ def snapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
+ url = version.endsWith('SNAPSHOT') ? snapshotUrl : releaseUrl
+ credentials {
+ if (rootProject.hasProperty('ossrhUsername')
+ && rootProject.hasProperty('ossrhPassword')) {
+ username = rootProject.ossrhUsername
+ password = rootProject.ossrhPassword
+ }
+ }
+ }
+ }
+ }
+
+ signing {
+ required false
+ sign publishing.publications.maven
+ }
+
+ [publishMavenPublicationToMavenRepository, publishMavenPublicationToMavenLocal]*.onlyIf {
+ !name.contains("perfmark-examples") && !name.contains("perfmark-api-testing")
+ && !name.contains("perfmark-testing") && !name.contains("perfmark-agent")
+ }
+
+ [javadoc]*.onlyIf {
+ !name.contains("perfmark-java9") && !name.contains("perfmark-examples")
+ && !name.contains("perfmark-api-testing") && !name.contains("perfmark-testing")
+ }
+
+ if (rootProject.properties.get('errorProne', true)) {
+ dependencies {
+ errorprone 'com.google.errorprone:error_prone_core:2.16'
+ errorproneJavac 'com.google.errorprone:javac:9+181-r4173-1'
+ }
+ } else {
+ // Disable Error Prone
+ allprojects {
+ afterEvaluate { project ->
+ project.tasks.withType(JavaCompile) {
+ options.errorprone.enabled = false
+ }
+ }
+ }
+ }
+
+ group = "io.perfmark"
+ version = "0.26.0"
+
+ dependencies {
+ testImplementation libs.junit
+ }
+}
diff --git a/buildscripts/checkstyle.license b/buildscripts/checkstyle.license
new file mode 100644
index 0000000..38a5192
--- /dev/null
+++ b/buildscripts/checkstyle.license
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
diff --git a/buildscripts/checkstyle.xml b/buildscripts/checkstyle.xml
new file mode 100644
index 0000000..7d44717
--- /dev/null
+++ b/buildscripts/checkstyle.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+ "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
+ "https://checkstyle.org/dtds/configuration_1_3.dtd">
+
+<!--
+ Checkstyle configuration that checks the Google coding conventions from Google Java Style
+ that can be found at https://google.github.io/styleguide/javaguide.html.
+
+ Checkstyle is very configurable. Be sure to read the documentation at
+ http://checkstyle.sf.net (or in your downloaded distribution).
+
+ To completely disable a check, just comment it out or delete it from the file.
+
+ Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
+ -->
+
+<module name = "Checker">
+ <property name="charset" value="UTF-8"/>
+
+ <property name="severity" value="error"/>
+
+ <module name="Header">
+ <property name="headerFile" value="${config_loc}/checkstyle.license"/>
+ <property name="ignoreLines" value="2"/>
+ <property name="fileExtensions" value="java"/>
+ </module>
+</module>
diff --git a/doc/fix-stop-task.md b/doc/fix-stop-task.md
new file mode 100644
index 0000000..d50a01c
--- /dev/null
+++ b/doc/fix-stop-task.md
@@ -0,0 +1,166 @@
+# Unbalancing PerfMark Calls
+
+Tl;Dr: `PerfMark.stopTask()` will no longer use task name or tags.
+
+# Background
+
+PerfMark was designed as a replacement for an existing tracing library called
+JEndoscope. JEndoscope used annotations to indicate which methods to trace.
+When the classes were loaded, a Java agent would rewrite the methods to include
+the tracing calls if JEndoscope was enabled. One of the benefits of this API
+is that it isn't possible to forget to add the closing trace call, which
+indicates a span is complete. Thus, the danger of mismatched start and stop
+trace calls was not a problem.
+
+PerfMark was designed to not rely on an agent for tracing, and thus could not
+use annotations to indicate which calls to trace. To avoid the risk for API
+users, PerfMark offered matching stop trace calls that included the relevant
+task name and tag. An example usage:
+
+```java
+PerfMark.startTask("makeRPC");
+invokeSomeRPC();
+PerfMark.stopTask("makeRPC");
+```
+
+When PerfMark returned the traces collected, it was expected that the
+start task name matched the stop task name. It could verify these to ensure
+the trace calls were properly balanced. It was expected that a warning could
+be emitted to aid the user in fixing the mismatch.
+
+There is an additional benefit to this API that was not needed in the previous
+JEndoscope model. PerfMark allows tracing to be dynamically enabled and
+disable at runtime. This means that tracing may be enabled in the middle of a
+start-stop pair. This would mean that only the stop would be recorded,
+without knowing the starting task name. Even if the calls were properly
+balanced, if only the stop edge is present, there isn't a way to reconstruct the
+name of the task. Having the task name in both the start and the stop solves
+this problem. (The same problem exists on the opposite side too, if the
+traces are read before the task completes.)
+
+Finally, having the task name in both the start and stop has a symmetry to it
+that made it look correct. It was clear which task was being stopped, even if
+the start trace was many lines above.
+
+# Problems with stopTask().
+
+At the time of the design, the aforementioned choices made sense. However,
+several problems arose from this pattern that can't easily be worked around.
+
+## Stuttering Arguments Makes Code Verbose
+
+The PerfMark API is very fast, but it accomplishes this by trusting the
+programmer to be careful. Because of this, a safe invocation of the trace
+involves Wrapping the code in a try-finally block:
+
+```java
+PerfMark.startTask("makeRPC");
+try {
+ invokeSomeRPC();
+} finally {
+ PerfMark.stopTask("makeRPC");
+}
+```
+
+This costs an indent block, as well as 3 extra lines per trace call. This
+puts us into verbosity debt. Having multiple such calls makes the code pyramid
+off the page, decreasing the readability of the code.
+
+In order to repay this debt, dropping the redundant task name (and tag) makes
+the code less unpleasant to look at.
+
+While the duplicated names do have technical reasons (mentioned above), users
+feedback indicated the verbosity was more of a problem. Given the relatively
+rare problems with mismatched tags and split traces, addressing verbosity is
+more important.
+
+## try-with-resources Imposes an Allocation cost
+
+One of the ways Java helps programmers clean up resources is the
+try-with-resources idiom. PerfMark explored using this as an alternative to
+bare start-stop calls with the following usage:
+
+```java
+try (PerfMarkTask task = PerfMark.task("makeRPC")) {
+ invokeSomeRPC();
+}
+```
+
+In an ideal world, `PerfMarkTask` would be a no-op object when PerfMark is
+disabled, and be a singleton object when enabled. This would in turn call the
+appropriate start-stop methods. However, because the preferred start-stop calls
+*both* require the task name, a new object must be allocated to capture the name
+(and tag). This forces the user to choose between a runtime cost and safe
+programming practice.
+
+## Lazy Task Names complicate API.
+
+Another way PerfMark explored making trace calls cheaper was to use a lambda
+base task naming call. Sometimes task names are not compile time constant, and
+in fact may have a significant runtime cost. Eagerly calculating these imposes
+a runtime cost, even when PerfMark is disabled. The following API shows the
+proposed usage:
+
+```java
+PerfMark.startTask(rpc, RPC::nameAndId);
+// Same as PerfMark.startTask(rpc.nameAndId());
+```
+
+The use of a method handle avoids the unwanted calculation of the `nameAndId`
+string, which the JVM may not be able to eliminate otherwise.
+
+The problem becomes more awkward when encouraging users to use matching names
+for start and stop:
+
+```java
+PerfMark.startTask(rpc, RPC::nameAndId);
+invokeSomeRPC();
+PerfMark.stopTask(rpc, RPC::nameAndId);
+```
+
+Will the expensive function be calculated twice? What happens if the function
+is not idempotent? The user has used the API to the best of their ability, but
+the skew problems are still present. Trying to solve this is difficult without
+imposing other costs, and being hard to reason about.
+
+### Permutations in API size.
+
+PerfMark has several overloads of the start - stop calls:
+
+* `startTask(String name);`
+* `startTask(String name, Tag tag);`
+* `startTask(String name, String subName);`
+
+
+While exploring lambdas to make lazy variants of these methods, the API grew
+substantially. As the number of start methods grows, so too must the stop
+methods. This makes usage more error prone since there may be 10s of possible
+overloads to invoke. This is an unnecessary burden on the user, since the
+library already must deal with the possibility of mismatched names.
+
+
+## Agent Byte Code is more difficult
+
+While JEndoscope required an agent to work, PerfMark can be optionally enhanced
+by using one. It can add line and file information about the calls
+when instrumenting classes. However, it's difficult to rewrite this byte code
+when it depends on the possibly changing task name. JEndoscope worked around
+this problem by forcing all tracing calls to be compile time constants, and
+forcing rewritten calls to use the `ldc` JVM instruction.
+
+# Proposed Change: one stopTask()
+
+To avoid the problems above, a singular `stopTask()` method overload is
+proposed. The existing stopTask overloads will no longer be recommended, and
+behave as the single `stopTask()` method with extra info. The tracing library
+will be updated to not expect this info.
+
+This solves each of the problems above. The usage of PerfMark becomes less
+verbose. There is no longer a runtime cost to using the
+try-with-resources idiom. There is no possibility of skew between start and
+stop names and tags. The API will grow slightly to accommodate this new method,
+but will prevent the doubling of methods.
+
+The benefits brought by having the name twice are seldom seen, and are paid
+at runtime. The names passed to stopTask() are stored, but are never used
+when visualizing trace diagrams.
diff --git a/doc/perfmark.png b/doc/perfmark.png
new file mode 100644
index 0000000..9cb68d2
--- /dev/null
+++ b/doc/perfmark.png
Binary files differ
diff --git a/doc/perfmark.svg b/doc/perfmark.svg
new file mode 100644
index 0000000..27df238
--- /dev/null
+++ b/doc/perfmark.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ id="svg64"
+ xml:space="preserve"
+ width="440.94666"
+ height="519.77332"
+ viewBox="0 0 440.94666 519.77332"
+ style="shape-rendering:crispEdges"><defs
+ id="defs68" /><g
+ id="g72"
+ transform="matrix(1.3333333,0,0,-1.3333333,0,519.77333)"><g
+ id="g74"
+ transform="scale(0.1)"><path
+ d="m 1196.91,1582.06 c 194.03,-147.55 436.15,-235.14 698.72,-235.14 638.12,0 1155.43,517.31 1155.43,1155.43 0,638.13 -517.31,1155.44 -1155.43,1155.44 -323.86,0 -616.6,-133.25 -826.39,-347.91"
+ style="fill:none;stroke:#393536;stroke-width:110;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path76"
+ /><path
+ d="m 2426.78,3299.61 c 9.07,33.86 -11.03,68.66 -44.88,77.74 -33.86,9.07 -68.66,-11.02 -77.74,-44.88 -9.07,-33.86 11.03,-68.66 44.89,-77.73 33.85,-9.08 68.65,11.01 77.73,44.87"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path78"
+ /><path
+ d="m 2892.67,2471.47 c 9.07,33.85 -11.02,68.65 -44.87,77.73 -33.86,9.07 -68.67,-11.02 -77.74,-44.88 -9.08,-33.86 11.02,-68.66 44.87,-77.73 33.86,-9.07 68.67,11.02 77.74,44.88"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path80"
+ /><path
+ d="m 2768.41,2007.82 c 9.07,33.86 -11.01,68.66 -44.87,77.74 -33.86,9.08 -68.66,-11.02 -77.74,-44.88 -9.08,-33.85 11.02,-68.65 44.88,-77.73 33.85,-9.07 68.66,11.02 77.73,44.87"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path82"
+ /><path
+ d="m 2426.69,1672.19 c 9.07,33.86 -11.01,68.66 -44.87,77.73 -33.87,9.07 -68.66,-11.01 -77.74,-44.87 -9.08,-33.86 11.02,-68.66 44.88,-77.74 33.86,-9.07 68.65,11.02 77.73,44.88"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path84"
+ /><path
+ d="m 1956.89,1546.33 c 9.07,33.86 -11.02,68.66 -44.88,77.73 -33.85,9.08 -68.66,-11.01 -77.73,-44.87 -9.08,-33.86 11.02,-68.66 44.87,-77.73 33.86,-9.08 68.67,11.01 77.74,44.87"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path86"
+ /><path
+ d="m 1364.57,3332.52 c -9.07,-33.85 11.02,-68.66 44.88,-77.73 33.85,-9.08 68.66,11.01 77.73,44.87 9.08,33.86 -11.02,68.67 -44.87,77.74 -33.86,9.07 -68.66,-11.02 -77.74,-44.88"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path88"
+ /><path
+ d="m 1834.38,3458.38 c -9.07,-33.86 11.01,-68.66 44.87,-77.74 33.86,-9.07 68.66,11.02 77.74,44.88 9.07,33.86 -11.02,68.66 -44.88,77.73 -33.86,9.08 -68.66,-11.01 -77.73,-44.87"
+ style="fill:#393536;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path90"
+ /><path
+ d="m 1047.51,3287.03 c -3.35,-3.62 -6.68,-7.26 -9.98,-10.92"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path92"
+ /><path
+ d="m 1069.24,3309.88 c -7.35,-7.52 -14.59,-15.13 -21.73,-22.85"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path94"
+ /><path
+ d="M 1934.23,2265.15 C 1796.25,1945.29 1520.14,1698.7 1182.48,1597.86 l -1,-0.59 c -79.43,-96.51 -124.5,-220.06 -124.5,-354.82 0,-262.239 180.56,-482.22 424.12,-542.641 C 1417.4,515.648 1302.6,355.41 1153.8,235.738 925.363,510.07 787.906,862.809 787.906,1247.71 c 0,255.88 60.813,497.53 168.653,711.42"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path96"
+ /><path
+ d="m 999.555,2038.65 c -15.067,-26.04 -29.407,-52.56 -42.996,-79.52"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path98"
+ /><path
+ d="m 1462,2543.61 c 70.27,49.36 144.55,93 221.89,130.31 55.89,26.95 114.16,52.43 177.2,58.94 62.96,6.51 129.04,-13.52 196.88,-20.85 25.17,-2.72 50.87,-1.69 75.99,1.05 52.78,5.76 104.28,20.56 153.95,38.91 140.65,51.97 271.79,129.57 400.73,205.08 8.92,5.22 17.83,10.45 26.73,15.7"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path100"
+ /><path
+ d="m 689.426,3164.17 c 24.027,137.51 76.707,265.16 151.652,376.59"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path102"
+ /><path
+ d="m 1498.49,2005.92 c -60.6,-11.72 -123.18,-17.85 -187.21,-17.85 -108.97,0 -213.8,17.77 -311.725,50.58 -388.731,130.26 -668.844,497.41 -668.844,929.99 0,146.11 31.957,284.75 89.258,409.32 l 2.668,0.51 1039.363,-834.86 113.42,-91.09"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path104"
+ /><path
+ d="M 841.082,3540.76 1533.76,2591.15"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path106"
+ /><path
+ d="m 2707.1,2968.01 c -133.04,-76.35 -268.85,-153.15 -388.34,-250.05 -42.91,-34.8 -84.81,-73.88 -118.25,-118.85 -43.66,-58.74 -57.77,-132.06 -89.47,-196.85 -32.67,-66.76 -89.53,-117.17 -162.9,-134.2 -17.21,-4 -34.86,-6.02 -52.51,-6.02 -36.8,0 -71.67,8.28 -102.84,23.07"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path108"
+ /><path
+ d="m 1044.65,1586.39 c 46.37,-3.66 97.03,-0.82 137.83,11.47"
+ style="fill:none;stroke:#393536;stroke-width:120;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
+ id="path110"
+ /></g></g></svg>
diff --git a/doc/screenshot.png b/doc/screenshot.png
new file mode 100644
index 0000000..4c1d52e
--- /dev/null
+++ b/doc/screenshot.png
Binary files differ
diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts
new file mode 100644
index 0000000..44d3fa3
--- /dev/null
+++ b/examples/build.gradle.kts
@@ -0,0 +1,48 @@
+plugins {
+ application
+}
+
+buildscript {
+ extra.apply {
+ set("moduleName", "io.perfmark.examples")
+ }
+}
+
+val jdkVersion = JavaVersion.VERSION_1_8
+
+
+configurations {
+ create("perfmarkAgent")
+}
+
+dependencies {
+ implementation(project(":perfmark-api"))
+ implementation(project(":perfmark-tracewriter"))
+ runtimeOnly(project(":perfmark-java7"))
+ runtimeOnly(project(":perfmark-java6"))
+
+ add("perfmarkAgent", project(":perfmark-agent", configuration = "shadow"))
+}
+
+tasks.named<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+}
+
+tasks.named<JavaExec>("run") {
+ dependsOn(":perfmark-agent:shadowJar")
+}
+
+application {
+ mainClass.set("io.perfmark.examples.perfetto.WebServer")
+ applicationDefaultJvmArgs = mutableListOf(
+ "-javaagent:" + configurations.getByName("perfmarkAgent").singleFile.path,
+ "-Xlog:class+load=info",
+ "-XX:StartFlightRecording",
+ "-Dio.perfmark.PerfMark.startEnabled=true",
+ )
+}
+
+tasks.named<Javadoc>("javadoc") {
+ exclude("io/perfmark/examples/**")
+}
diff --git a/examples/src/main/java/io/perfmark/examples/perfetto/WebServer.java b/examples/src/main/java/io/perfmark/examples/perfetto/WebServer.java
new file mode 100644
index 0000000..97f60a9
--- /dev/null
+++ b/examples/src/main/java/io/perfmark/examples/perfetto/WebServer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.examples.perfetto;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import io.perfmark.PerfMark;
+import io.perfmark.TaskCloseable;
+import io.perfmark.tracewriter.TraceEventWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class shows how to set up a basic HTTP server that can display PerfMark traces in the
+ * browser. The code here has some minor error cases ignored for the sake of brevity.
+ */
+public final class WebServer {
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+ PerfMark.setEnabled(true);
+
+ HttpServer res = HttpServer.create(new InetSocketAddress("localhost", 0), 5);
+
+ res.createContext("/", new IndexHandler());
+ res.createContext("/trace.json", new JsonHandler());
+ res.start();
+ try {
+ InetSocketAddress listen = res.getAddress();
+ System.err.println("Listening at http://localhost:" + listen.getPort() + '/');
+
+ while (true) {
+ Thread.sleep(10);
+ }
+ } finally {
+ res.stop(5);
+ }
+ }
+
+ private static final class IndexHandler implements HttpHandler {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ exchange.getResponseHeaders().set("Content-Type", "text/html");
+ exchange.sendResponseHeaders(200, 0);
+ try (TaskCloseable ignored = PerfMark.traceTask("IndexHandler.handle");
+ InputStream is = getClass().getResourceAsStream("index.html");
+ OutputStream os = exchange.getResponseBody()) {
+ byte[] data = new byte[is.available()];
+ int total = is.read(data);
+ if (total != data.length) {
+ throw new IOException("didn't read");
+ }
+ os.write(data);
+ os.flush();
+ }
+ }
+ }
+
+ private static final class JsonHandler implements HttpHandler {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ exchange.getResponseHeaders().set("Content-Type", "application/json");
+ exchange.sendResponseHeaders(200, 0);
+ try (TaskCloseable ignored = PerfMark.traceTask("JsonHandler.handle");
+ OutputStream os = exchange.getResponseBody();
+ OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
+ TraceEventWriter.writeTraceEvents(osw);
+ osw.flush();
+ }
+ }
+ }
+}
diff --git a/examples/src/main/resources/io/perfmark/examples/perfetto/index.html b/examples/src/main/resources/io/perfmark/examples/perfetto/index.html
new file mode 100644
index 0000000..2576d73
--- /dev/null
+++ b/examples/src/main/resources/io/perfmark/examples/perfetto/index.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name=viewport content="width=device-width, initial-scale=1">
+ <title>PerfMark Trace UI</title>
+ <base href="/">
+</head>
+<body>
+ <h1>PerfMark Trace Viewer</h1>
+ <p>
+ This is an example Trace Viewer, using the <a href="https://ui.perfetto.dev">Perfetto UI</a>.
+ </p>
+ <button id="thebutton">Load</button>
+ <br />
+ <textarea id="logs" cols="80" rows="24">
+ </textarea>
+ <script>
+ (function() {
+ let origin = "https://ui.perfetto.dev";
+ let logs = document.getElementById("logs");
+ let thebutton = document.getElementById("thebutton");
+ let thewindow = undefined;
+
+ thebutton.addEventListener("click", function (evt) {
+ logs.innerText += "Loading ...\n";
+ fetch("trace.json").then(function(result) {
+ result.blob().then(function(blob) {
+ blob.arrayBuffer().then(arrayBuffer => {
+ logs.innerText += "Trace JSON fetched.\n";
+ if (thewindow) {
+ thewindow.postMessage(arrayBuffer, origin);
+ return;
+ }
+ loadWindow(arrayBuffer);
+ });
+ });
+ });
+ });
+
+ function loadWindow(arrayBuffer) {
+ if (thewindow === undefined) {
+ thewindow = null;
+ } else {
+ return;
+ }
+ logs.innerText += "Loading Perfetto Window\n";
+ let win = window.open(origin);
+ let thetimer = null;
+ window.addEventListener("message", function(evt) {
+ if (evt.data !== "PONG") {
+ return;
+ }
+ window.clearInterval(thetimer);
+ logs.innerText += "Perfetto Window Ready\n";
+ thewindow = win;
+ thewindow.postMessage(arrayBuffer, origin);
+ });
+ thetimer = setInterval(function() {
+ win.postMessage("PING", origin);
+ }, 250);
+ }
+ })();
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..41d9927
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ae04661
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..1b6c787
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/impl/BUILD.bazel b/impl/BUILD.bazel
new file mode 100644
index 0000000..68ba7e1
--- /dev/null
+++ b/impl/BUILD.bazel
@@ -0,0 +1,80 @@
+java_library(
+ name = "impl",
+ srcs = [
+ "src/main/java/io/perfmark/impl/NoopGenerator.java",
+ "src/main/java/io/perfmark/impl/SecretPerfMarkImpl.java",
+ ],
+ deps = [
+ ":generator",
+ ":mark",
+ ":storage",
+ "//api:impl",
+ "//api:link",
+ "//api:tag",
+ "//api:stringfunction",
+ "@maven//:com_google_code_findbugs_jsr305",
+ ],
+)
+
+java_library(
+ name = "generator",
+ srcs = ["src/main/java/io/perfmark/impl/Generator.java"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@maven//:com_google_code_findbugs_jsr305",
+ ],
+)
+
+java_library(
+ name = "storage",
+ srcs = [
+ "src/main/java/io/perfmark/impl/NoopMarkHolderProvider.java",
+ "src/main/java/io/perfmark/impl/Storage.java",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":generator",
+ ":mark",
+ ":mark-holder",
+ ":mark-holder-provider",
+ ":mark-list",
+ "@maven//:com_google_code_findbugs_jsr305",
+ ],
+)
+
+java_library(
+ name = "mark-holder",
+ srcs = ["src/main/java/io/perfmark/impl/MarkHolder.java"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":mark",
+ ],
+)
+
+java_library(
+ name = "mark-holder-provider",
+ srcs = ["src/main/java/io/perfmark/impl/MarkHolderProvider.java"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":mark-holder",
+ ],
+)
+
+java_library(
+ name = "mark",
+ srcs = ["src/main/java/io/perfmark/impl/Mark.java"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":generator",
+ "@maven//:com_google_code_findbugs_jsr305",
+ ],
+)
+
+java_library(
+ name = "mark-list",
+ srcs = ["src/main/java/io/perfmark/impl/MarkList.java"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":mark",
+ ],
+)
diff --git a/impl/build.gradle b/impl/build.gradle
new file mode 100644
index 0000000..7f7fddb
--- /dev/null
+++ b/impl/build.gradle
@@ -0,0 +1,65 @@
+plugins {
+ id "me.champeau.jmh"
+}
+
+description = "PerfMark Implementation API"
+ext.moduleName = "io.perfmark.impl"
+ext.jdkVersion = JavaVersion.VERSION_1_6
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+}
+
+compileJava {
+ sourceCompatibility = jdkVersion
+ targetCompatibility = jdkVersion
+
+ options.compilerArgs.add("-Xlint:-options")
+}
+
+dependencies {
+ implementation project(':perfmark-api')
+ compileOnly libs.jsr305,
+ libs.errorprone
+ testImplementation libs.truth
+ testCompileOnly libs.errorprone
+}
+
+
+jmh {
+
+ timeOnIteration = "1s"
+ warmup = "1s"
+ fork = 400
+ warmupIterations = 0
+
+ includes = ["ClassInit"]
+ profilers = ["cl"]
+ jvmArgs = ["-Dio.perfmark.PerfMark.debug=true"]
+
+ /*
+ profilers = ["perfasm"]
+
+ jvmArgs = [
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+LogCompilation",
+ "-XX:LogFile=/tmp/blah.txt",
+ "-XX:+PrintAssembly",
+ "-XX:+PrintInterpreter",
+ "-XX:+PrintNMethods",
+ "-XX:+PrintNativeNMethods",
+ "-XX:+PrintSignatureHandlers",
+ "-XX:+PrintAdapterHandlers",
+ "-XX:+PrintStubCode",
+ "-XX:+PrintCompilation",
+ "-XX:+PrintInlining",
+ "-XX:+TraceClassLoading",
+ "-XX:PrintAssemblyOptions=syntax",
+ "-XX:PrintAssemblyOptions=intel"
+ ]
+ */
+
+ //duplicateClassesStrategy DuplicatesStrategy.INCLUDE
+} \ No newline at end of file
diff --git a/impl/src/jmh/java/io/perfmark/impl/SecretPerfMarkImplClassInitBenchmark.java b/impl/src/jmh/java/io/perfmark/impl/SecretPerfMarkImplClassInitBenchmark.java
new file mode 100644
index 0000000..77e9786
--- /dev/null
+++ b/impl/src/jmh/java/io/perfmark/impl/SecretPerfMarkImplClassInitBenchmark.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+@State(Scope.Benchmark)
+@Fork(1000)
+@Warmup(iterations = 0)
+@Measurement(iterations = 1)
+public class SecretPerfMarkImplClassInitBenchmark {
+
+ private ClassLoader loader;
+
+ @Setup
+ public void setup() {
+ loader =
+ new TestClassLoader(getClass().getClassLoader(),
+ "io.perfmark.java7.SecretMethodHandleGenerator$MethodHandleGenerator",
+ "io.perfmark.java9.SecretVarHandleGenerator$VarHandleGenerator",
+ "io.perfmark.java6.SecretVolatileGenerator$VolatileGenerator");
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.SingleShotTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public Object forName_noinit() throws Exception {
+ return Class.forName(SecretPerfMarkImpl.PerfMarkImpl.class.getName(), false, loader);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.SingleShotTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public Object forName_init() throws Exception {
+ return Class.forName(SecretPerfMarkImpl.PerfMarkImpl.class.getName(), true, loader);
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+
+ private final List<String> classesToDrop;
+
+ TestClassLoader(ClassLoader parent, String ... classesToDrop) {
+ super(parent);
+ this.classesToDrop = Arrays.asList(classesToDrop);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (classesToDrop.contains(name)) {
+ // Throw an exception, just like the real one would.
+ throw new ClassNotFoundException();
+ }
+ if (!name.startsWith("io.perfmark.")) {
+ return super.loadClass(name, resolve);
+ }
+ try (InputStream is = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (is == null) {
+ throw new ClassNotFoundException(name);
+ }
+ byte[] data = is.readAllBytes();
+ Class<?> clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/Generator.java b/impl/src/main/java/io/perfmark/impl/Generator.java
new file mode 100644
index 0000000..f2a717e
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/Generator.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+/**
+ * A Generator keeps track of what generation the PerfMark library is on. A generation is a way to
+ * group PerfMark recorded tasks and links together. This allows dynamically enabling and disabling
+ * PerfMark. Each time the library is enabled, a new generation is started and associated with all
+ * the recorded data.
+ *
+ * <p>This class is not threadsafe. Synchronization is handled externally.
+ *
+ * <p>Normal users are not expected to use this class.
+ */
+public abstract class Generator {
+ /**
+ * This field is here as a hack. This class is a shared dependency of both {@link
+ * SecretPerfMarkImpl} and {@link Storage}. The impl needs to record the first time an event
+ * occurs, but doesn't call Storage#clinit until PerfMark is enabled. This leads to the timings
+ * being off in the trace event viewer, since the "start" time is since it was enabled, rather
+ * than when the first PerfMark call happens.
+ */
+ static final long INIT_NANO_TIME = System.nanoTime();
+
+ /**
+ * This field is also here as a hack, capturing the time the program stated.
+ */
+ static final long INIT_CURRENT_TIME_MILLIS = System.currentTimeMillis();
+
+ /**
+ * The number of reserved bits at the bottom of the generation. All generations should be
+ * left-shifted by this amount.
+ */
+ public static final int GEN_OFFSET = 8;
+ /**
+ * Represents a failure to enable PerfMark library. This can be used by subclasses to indicate a
+ * previous failure to set the generation. It can also be used to indicate the generation count
+ * has overflowed.
+ */
+ public static final long FAILURE = -2L << GEN_OFFSET;
+
+ protected Generator() {}
+
+ /**
+ * Sets the current generation count. This should be eventually noticeable for callers of {@link
+ * #getGeneration()}. An odd number means the library is enabled, while an even number means the
+ * library is disabled.
+ *
+ * @param generation the generation id, shifted left by {@link #GEN_OFFSET}.
+ */
+ public abstract void setGeneration(long generation);
+
+ /**
+ * Gets the current generation, shifted left by {@link #GEN_OFFSET}. An odd number means the
+ * library is enabled, while an even number means the library is disabled.
+ *
+ * @return the current generation or {@link #FAILURE}.
+ */
+ public abstract long getGeneration();
+
+ /**
+ * Returns the approximate cost to change the generation.
+ *
+ * @return an approximate number of nanoseconds needed to change the generator value.
+ */
+ public long costOfSetNanos() {
+ return 1000000;
+ }
+
+ /**
+ * Returns the approximate cost to read the generation.
+ *
+ * @return an approximate number of nanoseconds needed to read the generator value.
+ */
+ public long costOfGetNanos() {
+ return 10;
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/LocalMarkHolder.java b/impl/src/main/java/io/perfmark/impl/LocalMarkHolder.java
new file mode 100644
index 0000000..ecfd0b4
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/LocalMarkHolder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2022 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+/**
+ * A local MarkHolder is a class that gets the "current" MarkHolder based on context. For example, a thread local
+ * MarkHolder could use this class to pull the local MarkHolder from the threadlocal variable. Other implementations
+ * are possible as well.
+ */
+public abstract class LocalMarkHolder {
+
+ /**
+ * Removes the local markholder storage.
+ */
+ public abstract void clear();
+
+ /**
+ * Get's the current MarkHolder for mutation. Only called from a tracing thread.
+ */
+ public abstract MarkHolder acquire();
+
+ /**
+ * Releases the MarkHolder from being written too. Usually called very shortly after {@link #acquire()}.
+ * This method is meant to be overridden and should not be called from subclasses.
+ */
+ public void release(MarkHolder markHolder) {}
+}
diff --git a/impl/src/main/java/io/perfmark/impl/Mark.java b/impl/src/main/java/io/perfmark/impl/Mark.java
new file mode 100644
index 0000000..a07c194
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/Mark.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+public final class Mark {
+ // TODO: make sure these match the values in Impl
+ public static final String NO_TAG_NAME = "";
+ public static final long NO_TAG_ID = Long.MIN_VALUE;
+ public static final long NO_LINK_ID = Long.MIN_VALUE;
+
+ public static final long NO_NANOTIME = 0;
+
+ private static final long N0 = 0;
+ private static final String S0 = null;
+
+ private final long generation;
+
+ private final long n1;
+ private final long n2;
+ private final long n3;
+
+ @Nullable private final String s1;
+ @Nullable private final String s2;
+ @Nullable private final String s3;
+
+ private final Operation operation;
+
+ public static Mark taskStart(long generation, long nanoTime, String name) {
+ return new Mark(nanoTime, N0, N0, name, S0, S0, generation, Operation.TASK_START_N1S1);
+ }
+
+ public static Mark taskStart(long generation, long nanoTime, String name, String subName) {
+ return new Mark(nanoTime, N0, N0, name, subName, S0, generation, Operation.TASK_START_N1S2);
+ }
+
+ public static Mark taskEnd(long generation, long nanoTime) {
+ return new Mark(nanoTime, N0, N0, S0, S0, S0, generation, Operation.TASK_END_N1S0);
+ }
+
+ public static Mark taskEnd(long generation, long nanoTime, String name) {
+ return new Mark(nanoTime, N0, N0, name, S0, S0, generation, Operation.TASK_END_N1S1);
+ }
+
+ public static Mark taskEnd(long generation, long nanoTime, String name, String subName) {
+ return new Mark(nanoTime, N0, N0, name, subName, S0, generation, Operation.TASK_END_N1S2);
+ }
+
+ public static Mark event(long generation, long nanoTime, String name) {
+ return new Mark(nanoTime, N0, N0, name, S0, S0, generation, Operation.EVENT_N1S1);
+ }
+
+ public static Mark event(long generation, long nanoTime, String name, String subName) {
+ return new Mark(nanoTime, N0, N0, name, subName, S0, generation, Operation.EVENT_N1S2);
+ }
+
+ public static Mark event(
+ long generation, long nanoTime, String taskName, String tagName, long tagId) {
+ return new Mark(
+ nanoTime, tagId, N0, taskName, tagName, S0, generation, Operation.EVENT_N2S2);
+ }
+
+ public static Mark event(
+ long generation,
+ long nanoTime,
+ String taskName,
+ String subTaskName,
+ String tagName,
+ long tagId) {
+ return new Mark(
+ nanoTime, tagId, N0, taskName, subTaskName, tagName, generation, Operation.EVENT_N2S3);
+ }
+
+ public static Mark tag(long generation, String tagName, long tagId) {
+ return new Mark(tagId, N0, N0, tagName, S0, S0, generation, Operation.TAG_N1S1);
+ }
+
+ public static Mark tag(long generation, long tagId) {
+ return new Mark(tagId, N0, N0, S0, S0, S0, generation, Operation.TAG_N1S0);
+ }
+
+ public static Mark tag(long generation, String tagName) {
+ return new Mark(N0, N0, N0, tagName, S0, S0, generation, Operation.TAG_N0S1);
+ }
+
+ public static Mark keyedTag(long generation, String tagName, String value) {
+ return new Mark(N0, N0, N0, tagName, value, S0, generation, Operation.TAG_KEYED_N0S2);
+ }
+
+ public static Mark keyedTag(long generation, String tagName, long value) {
+ return new Mark(value, N0, N0, tagName, S0, S0, generation, Operation.TAG_KEYED_N1S1);
+ }
+
+ public static Mark keyedTag(long generation, String tagName, long value0, long value1) {
+ return new Mark(value0, value1, N0, tagName, S0, S0, generation, Operation.TAG_KEYED_N2S1);
+ }
+
+ public static Mark link(long generation, long linkId) {
+ return new Mark(linkId, N0, N0, S0, S0, S0, generation, Operation.LINK);
+ }
+
+ public Mark withTaskName(String name) {
+ switch (operation) {
+ case EVENT_N1S1:
+ case TASK_END_N1S1:
+ case TASK_START_N1S1:
+ return new Mark(n1, n2, n3, name, s2, s3, generation, operation);
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case NONE:
+ case TASK_END_N1S2:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case LINK:
+ case TAG_N0S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ case TAG_KEYED_N0S2:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError();
+ }
+
+ private Mark(
+ long n1,
+ long n2,
+ long n3,
+ @Nullable String s1,
+ @Nullable String s2,
+ @Nullable String s3,
+ long generation,
+ Operation operation) {
+ if (operation == null) {
+ throw new NullPointerException("operation");
+ }
+ this.operation = operation;
+ this.generation = generation;
+
+ if (operation == Operation.NONE) {
+ throw new IllegalArgumentException("bad operation");
+ }
+ this.n1 = n1;
+ this.n2 = n2;
+ this.n3 = n3;
+
+ this.s1 = s1;
+ this.s2 = s2;
+ this.s3 = s3;
+ }
+
+ public enum OperationType {
+ NONE,
+ TASK_START,
+ TASK_END,
+ EVENT,
+ LINK,
+ TAG,
+ ;
+ }
+
+ public enum Operation {
+ NONE(OperationType.NONE, 0, 0),
+ /** startTask(String taskName) 1 long for nanoTime. */
+ TASK_START_N1S1(OperationType.TASK_START, 1, 1),
+ /** startTask(String name, String subTaskName) 1 long for nanoTime. */
+ TASK_START_N1S2(OperationType.TASK_START, 1, 2),
+
+ TASK_END_N1S0(OperationType.TASK_END, 1, 0),
+ TASK_END_N1S1(OperationType.TASK_END, 1, 1),
+ TASK_END_N1S2(OperationType.TASK_END, 1, 2),
+
+ EVENT_N1S1(OperationType.EVENT, 1, 1),
+ EVENT_N1S2(OperationType.EVENT, 1, 2),
+ /** Tagged event, since attach tags can't apply to events */
+ EVENT_N2S2(OperationType.EVENT, 2, 2),
+ /** Tagged event, since attach tags can't apply to events */
+ EVENT_N2S3(OperationType.EVENT, 2, 3),
+
+ LINK(OperationType.LINK, 1, 0),
+
+ /** An unkeyed tag that has a single string value. */
+ TAG_N0S1(OperationType.TAG, 0, 1),
+ /** An unkeyed tag that has a single numeric value. */
+ TAG_N1S0(OperationType.TAG, 1, 0),
+ /**
+ * An unkeyed tag that has a string and numeric value. The values are unrelated to each other.
+ */
+ TAG_N1S1(OperationType.TAG, 1, 1),
+
+ TAG_KEYED_N1S1(OperationType.TAG, 1, 1),
+
+ TAG_KEYED_N2S1(OperationType.TAG, 2, 1),
+
+ TAG_KEYED_N0S2(OperationType.TAG, 0, 2),
+ ;
+
+ private final OperationType opType;
+ private final int longs;
+ private final int strings;
+
+ Operation(OperationType opType, int longs, int strings) {
+ this.opType = opType;
+ this.longs = longs;
+ this.strings = strings;
+ assert longs <= maxNumbers();
+ assert strings <= maxStrings();
+ }
+
+ private static final Operation[] values = Operation.values();
+
+ static {
+ assert values.length <= (1 << Generator.GEN_OFFSET);
+ }
+
+ public OperationType getOpType() {
+ return opType;
+ }
+
+ public int getNumbers() {
+ return longs;
+ }
+
+ public int getStrings() {
+ return strings;
+ }
+
+ public static int maxNumbers() {
+ return 2;
+ }
+
+ public static int maxStrings() {
+ return 3;
+ }
+
+ public static int maxMarkers() {
+ return 1;
+ }
+
+ public static Operation valueOf(int code) {
+ return values[code];
+ }
+ }
+
+ public long getNanoTime() {
+ switch (operation.opType) {
+ case TASK_START:
+ case TASK_END:
+ case EVENT:
+ return n1;
+ case NONE:
+ case LINK:
+ case TAG:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ public long getGeneration() {
+ return generation;
+ }
+
+ public Operation getOperation() {
+ return operation;
+ }
+
+ public String getTagStringValue() {
+ switch (operation) {
+ case TAG_N0S1:
+ case TAG_N1S1:
+ return s1;
+ case TAG_KEYED_N0S2:
+ case EVENT_N2S2:
+ return s2;
+ case EVENT_N2S3:
+ return s3;
+ case TAG_N1S0:
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ case LINK:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ public long getTagFirstNumeric() {
+ switch (operation) {
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ return n1;
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ return n2;
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case LINK:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ public long getTagSecondNumeric() {
+ switch (operation) {
+ case TAG_KEYED_N2S1:
+ return n2;
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case TAG_KEYED_N1S1:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case LINK:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ public String getTagKey() {
+ switch (operation) {
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ return s1;
+ case TAG_N1S1:
+ case TAG_N0S1:
+ case TAG_N1S0:
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case LINK:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ public String getTaskName() {
+ switch (operation) {
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ return s1;
+ case NONE:
+ case LINK:
+ case TASK_END_N1S0:
+ case TAG_N0S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation);
+ }
+
+ public String getSubTaskName() {
+ switch (operation) {
+ case TASK_END_N1S2:
+ case TASK_START_N1S2:
+ case EVENT_N1S2:
+ case EVENT_N2S3:
+ return s2;
+ case TASK_START_N1S1:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case EVENT_N1S1:
+ case EVENT_N2S2:
+ case NONE:
+ case LINK:
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation);
+ }
+
+ public long getLinkId() {
+ switch (operation.opType) {
+ case LINK:
+ return n1;
+ case TASK_START:
+ case TASK_END:
+ case EVENT:
+ case NONE:
+ case TAG:
+ throw new UnsupportedOperationException();
+ }
+ throw new AssertionError(operation.opType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Mark)) {
+ return false;
+ }
+ Mark that = (Mark) obj;
+ return equal(this.s1, that.s1)
+ && equal(this.s2, that.s2)
+ && equal(this.s3, that.s3)
+ && this.n1 == that.n1
+ && this.n2 == that.n2
+ && this.n3 == that.n3
+ && this.operation == that.operation
+ && this.generation == that.generation;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] {operation, s1, s2, s3, n1, n2, n3, generation});
+ }
+
+ @Override
+ public String toString() {
+ return "Mark{"
+ + "operation="
+ + operation
+ + ", "
+ + "s1="
+ + s1
+ + ", "
+ + "s2="
+ + s2
+ + ", "
+ + "s3="
+ + s3
+ + ", "
+ + "n1="
+ + n1
+ + ", "
+ + "n2="
+ + n2
+ + ", "
+ + "n3="
+ + n3
+ + ", "
+ + "generation="
+ + generation
+ + "}";
+ }
+
+ static <T> boolean equal(T a, T b) {
+ return a == b || a.equals(b);
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/MarkHolder.java b/impl/src/main/java/io/perfmark/impl/MarkHolder.java
new file mode 100644
index 0000000..a18fdfd
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/MarkHolder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.util.List;
+
+public abstract class MarkHolder {
+
+ public static final int NO_MAX_MARKS = -1;
+
+ public abstract void start(long gen, String taskName, String tagName, long tagId, long nanoTime);
+
+ public abstract void start(long gen, String taskName, long nanoTime);
+
+ public abstract void start(long gen, String taskName, String subTaskName, long nanoTime);
+
+ public abstract void link(long gen, long linkId);
+
+ public abstract void stop(long gen, long nanoTime);
+
+ public abstract void stop(long gen, String taskName, String tagName, long tagId, long nanoTime);
+
+ public abstract void stop(long gen, String taskName, long nanoTime);
+
+ public abstract void stop(long gen, String taskName, String subTaskName, long nanoTime);
+
+ public abstract void event(long gen, String eventName, String tagName, long tagId, long nanoTime);
+
+ public abstract void event(long gen, String eventName, long nanoTime);
+
+ public abstract void event(long gen, String eventName, String subEventName, long nanoTime);
+
+ public abstract void attachTag(long gen, String tagName, long tagId);
+
+ public abstract void attachKeyedTag(long gen, String name, String value);
+
+ public abstract void attachKeyedTag(long gen, String name, long value0);
+
+ public abstract void attachKeyedTag(long gen, String name, long value0, long value1);
+
+ public abstract void resetForTest();
+
+ public abstract List<Mark> read(boolean concurrentWrites);
+
+ public int maxMarks() {
+ return NO_MAX_MARKS;
+ }
+
+ protected MarkHolder() {}
+}
diff --git a/impl/src/main/java/io/perfmark/impl/MarkHolderProvider.java b/impl/src/main/java/io/perfmark/impl/MarkHolderProvider.java
new file mode 100644
index 0000000..be11451
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/MarkHolderProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+public abstract class MarkHolderProvider {
+
+ protected MarkHolderProvider() {}
+
+ /**
+ * To be removed in 0.26.0
+ *
+ * @return the new MarkHolder for the current thread.
+ */
+ @Deprecated
+ public MarkHolder create() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Creates a new MarkHolder. Mark holders are always mutated by the thread that created them, (e.g. THIS thread),
+ * but may be read by other threads.
+ *
+ * @param markHolderId the Unique ID associated with the Mark Holder. This exists as a work around to Java's
+ * thread ID, which does not guarantee they will not be reused.
+ * @return the new MarkHolder for the current thread.
+ * @since 0.24.0
+ */
+ public MarkHolder create(long markHolderId) {
+ return create();
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/MarkList.java b/impl/src/main/java/io/perfmark/impl/MarkList.java
new file mode 100644
index 0000000..dc195de
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/MarkList.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/** MarkList is collection of Marks, in the order they were recorded. */
+public final class MarkList extends AbstractList<Mark> {
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private final List<Mark> marks;
+ private final long threadId;
+ private final long markListId;
+ private final String threadName;
+
+ MarkList(Builder builder) {
+ if (builder.marks == null) {
+ throw new NullPointerException("marks");
+ }
+ this.marks = builder.marks;
+ if (builder.threadName == null) {
+ throw new NullPointerException("threadName");
+ }
+ this.threadName = builder.threadName;
+ this.threadId = builder.threadId;
+ this.markListId = builder.markListId;
+ }
+
+ /**
+ * Gets the Thread name of the thread that recorded the Marks.
+ *
+ * @return the Thread name.
+ */
+ public String getThreadName() {
+ return threadName;
+ }
+
+ /**
+ * Thread IDs can be recycled, so this is not unique.
+ *
+ * @return the id of the thread, as returned by {@link Thread#getId()}.
+ */
+ public long getThreadId() {
+ return threadId;
+ }
+
+ /**
+ * The globally unique ID for this Mark list. Unlike {@link #getThreadId()}, this value is never
+ * recycled.
+ *
+ * @return the id of this list.
+ */
+ public long getMarkListId() {
+ return markListId;
+ }
+
+ @Override
+ public Mark get(int index) {
+ return marks.get(index);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MarkList)) {
+ return false;
+ }
+ MarkList that = (MarkList) obj;
+ return Mark.equal(this.marks, that.marks)
+ && this.threadId == that.threadId
+ && this.markListId == that.markListId
+ && Mark.equal(this.threadName, that.threadName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] {marks, threadId, markListId, threadName});
+ }
+
+ @Override
+ public int size() {
+ return marks.size();
+ }
+
+ @Override
+ public String toString() {
+ return "MarkList{"
+ + "marks="
+ + marks
+ + ", "
+ + "threadId="
+ + threadId
+ + ", "
+ + "markListId="
+ + markListId
+ + ", "
+ + "threadName="
+ + threadName
+ + "}";
+ }
+
+ public Builder toBuilder() {
+ Builder builder = newBuilder();
+ builder.marks = marks;
+ return builder.setThreadName(threadName).setThreadId(threadId).setMarkListId(markListId);
+ }
+
+ public static final class Builder {
+
+ List<Mark> marks;
+ String threadName;
+ long threadId;
+ long markListId;
+
+ public MarkList build() {
+ return new MarkList(this);
+ }
+
+ /**
+ * Sets the marks for this MarkList builder.
+ *
+ * @throws NullPointerException if any element in this list is {@code null}.
+ * @param marks the marks to set.
+ * @return this
+ */
+ public Builder setMarks(List<Mark> marks) {
+ if (marks == null) {
+ throw new NullPointerException("marks");
+ }
+ ArrayList<Mark> copy = new ArrayList<Mark>(marks.size());
+ ListIterator<Mark> it = marks.listIterator();
+ while (it.hasNext()) {
+ Mark mark = it.next();
+ if (mark == null) {
+ throw new NullPointerException("mark is null at pos " + (it.nextIndex() - 1));
+ }
+ copy.add(mark);
+ }
+ this.marks = Collections.unmodifiableList(copy);
+ return this;
+ }
+
+ /**
+ * Sets the thread name for this MarkList builder.
+ *
+ * @param threadName the Thread Name
+ * @return this
+ */
+ public Builder setThreadName(String threadName) {
+ this.threadName = threadName;
+ return this;
+ }
+
+ /**
+ * Sets the thread ID for this MarkList builder.
+ *
+ * @param threadId the Thread Id
+ * @return this
+ */
+ public Builder setThreadId(long threadId) {
+ this.threadId = threadId;
+ return this;
+ }
+
+ /**
+ * Sets the mark list ID for this MarkList builder.
+ *
+ * @param markListId the mark list ID
+ * @return this
+ */
+ public Builder setMarkListId(long markListId) {
+ this.markListId = markListId;
+ return this;
+ }
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/MostlyThreadLocal.java b/impl/src/main/java/io/perfmark/impl/MostlyThreadLocal.java
new file mode 100644
index 0000000..1c72cfa
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/MostlyThreadLocal.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+
+/**
+ * This class tries to access the mark holder in a thread local. If the class is unable to store the created
+ * MarkHolder in the thread local, a concurrent map of Thread to MarkHolders is used for lookup. In Java 19
+ * Thread Locals can be disabled due to use of Virtual threads.
+ *
+ * <p>
+ * Note that this class avoids using {@link #initialValue()} because it may be called multiple times, in the
+ * case that ThreadLocals are unsettable.
+ */
+final class MostlyThreadLocal extends ThreadLocal<MarkHolder> {
+ private static final int BITS = 11;
+ private static final int SIZE = 1 << BITS;
+ private static final int MASK = SIZE - 1;
+ private static final AtomicReferenceArray<CopyOnWriteArrayList<Storage.MarkHolderHandle>> threadToHandles =
+ new AtomicReferenceArray<CopyOnWriteArrayList<Storage.MarkHolderHandle>>(SIZE);
+
+ MostlyThreadLocal() {}
+
+ @Override
+ public MarkHolder get() {
+ MarkHolder markHolder = super.get();
+ if (markHolder != null) {
+ return markHolder;
+ }
+ return getAndSetSlow();
+ }
+
+ private MarkHolder getAndSetSlow() {
+ assert super.get() == null;
+ Storage.MarkHolderHandle markHolderHandle;
+ MarkHolder markHolder;
+ Storage.MarkHolderAndHandle markHolderAndHandle;
+ Thread thread = Thread.currentThread();
+ CopyOnWriteArrayList<Storage.MarkHolderHandle> handles = getHandles(thread);
+ if (handles == null) {
+ markHolderAndHandle = Storage.allocateMarkHolder();
+ markHolder = markHolderAndHandle.markHolder();
+ assert markHolder != null;
+ markHolderHandle = markHolderAndHandle.handle();
+ try {
+ set(markHolder);
+ return markHolder;
+ } catch (UnsupportedOperationException e) {
+ // ignore.
+ }
+ handles = getOrCreateHandles(thread);
+ } else {
+ if ((markHolderHandle = getConcurrent(handles)) != null) {
+ if ((markHolder = markHolderHandle.markHolder()) != null) {
+ return markHolder;
+ }
+ }
+ markHolderAndHandle = Storage.allocateMarkHolder();
+ markHolder = markHolderAndHandle.markHolder();
+ assert markHolder != null;
+ markHolderHandle = markHolderAndHandle.handle();
+ }
+ handles.add(markHolderHandle);
+ return markHolder;
+ }
+
+ @Override
+ public void remove() {
+ Thread thread = Thread.currentThread();
+ CopyOnWriteArrayList<Storage.MarkHolderHandle> handles = getHandles(thread);
+ if (handles == null) {
+ super.remove();
+ } else {
+ assert super.get() == null;
+ for (Storage.MarkHolderHandle handle : handles) {
+ Thread t = handle.threadRef().get();
+ if (t == null || t == thread) {
+ handles.remove(handle);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the handles for the given thread index bucket, or {@code null}.
+ */
+ private static CopyOnWriteArrayList<Storage.MarkHolderHandle> getHandles(Thread thread) {
+ int hashCode = System.identityHashCode(thread);
+ int index = hashCode & MASK;
+ return threadToHandles.get(index);
+ }
+
+ private static CopyOnWriteArrayList<Storage.MarkHolderHandle> getOrCreateHandles(Thread thread) {
+ int hashCode = System.identityHashCode(thread);
+ int index = hashCode & MASK;
+ CopyOnWriteArrayList<Storage.MarkHolderHandle> handles;
+ do {
+ if ((handles = threadToHandles.get(index)) != null) {
+ break;
+ }
+ } while (
+ !threadToHandles.compareAndSet(index, null, (handles = new CopyOnWriteArrayList<Storage.MarkHolderHandle>())));
+ return handles;
+ }
+
+ /**
+ * May return {@code null} if not found.
+ */
+ private static Storage.MarkHolderHandle getConcurrent(CopyOnWriteArrayList<Storage.MarkHolderHandle> handles) {
+ if (!handles.isEmpty()) {
+ Storage.MarkHolderHandle handle;
+ try {
+ handle = handles.get(0);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+ if (handle.threadRef().get() == Thread.currentThread()) {
+ return handle;
+ }
+ return slowGetConcurrent(handles);
+ }
+ return null;
+ }
+
+ /**
+ * May return {@code null} if not found.
+ */
+ private static Storage.MarkHolderHandle slowGetConcurrent(CopyOnWriteArrayList<Storage.MarkHolderHandle> handles) {
+ for (Storage.MarkHolderHandle handle : handles) {
+ Thread thread = handle.threadRef().get();
+ if (thread == null) {
+ handles.remove(handle);
+ } else if (thread == Thread.currentThread()) {
+ return handle;
+ }
+ }
+ return null;
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/MostlyThreadLocalMarkHolder.java b/impl/src/main/java/io/perfmark/impl/MostlyThreadLocalMarkHolder.java
new file mode 100644
index 0000000..a17c8c0
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/MostlyThreadLocalMarkHolder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2022 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+final class MostlyThreadLocalMarkHolder extends LocalMarkHolder {
+
+ private static final MostlyThreadLocal localMarkHolder = new MostlyThreadLocal();
+
+ MostlyThreadLocalMarkHolder() {}
+
+ @Override
+ public MarkHolder acquire() {
+ return localMarkHolder.get();
+ }
+
+ @Override
+ public void release(MarkHolder markHolder) {}
+
+ @Override
+ public void clear() {
+ localMarkHolder.remove();
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/NoopGenerator.java b/impl/src/main/java/io/perfmark/impl/NoopGenerator.java
new file mode 100644
index 0000000..d28089d
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/NoopGenerator.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+/** Noop Generator for use when no other generator can be used. */
+final class NoopGenerator extends Generator {
+
+ NoopGenerator() {}
+
+ @Override
+ public void setGeneration(long generation) {}
+
+ @Override
+ public long getGeneration() {
+ return FAILURE;
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/NoopMarkHolderProvider.java b/impl/src/main/java/io/perfmark/impl/NoopMarkHolderProvider.java
new file mode 100644
index 0000000..aa44356
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/NoopMarkHolderProvider.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.util.Collections;
+import java.util.List;
+
+final class NoopMarkHolderProvider extends MarkHolderProvider {
+ NoopMarkHolderProvider() {}
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public MarkHolder create() {
+ return new NoopMarkHolder();
+ }
+
+ @Override
+ public MarkHolder create(long markHolderId) {
+ return new NoopMarkHolder();
+ }
+
+
+ private static final class NoopMarkHolder extends MarkHolder {
+
+ NoopMarkHolder() {}
+
+ @Override
+ public void start(long gen, String taskName, String tagName, long tagId, long nanoTime) {}
+
+ @Override
+ public void start(long gen, String taskName, long nanoTime) {}
+
+ @Override
+ public void start(long gen, String taskName, String subTaskName, long nanoTime) {}
+
+ @Override
+ public void link(long gen, long linkId) {}
+
+ @Override
+ public void stop(long gen, long nanoTime) {}
+
+ @Override
+ public void stop(long gen, String taskName, String tagName, long tagId, long nanoTime) {}
+
+ @Override
+ public void stop(long gen, String taskName, long nanoTime) {}
+
+ @Override
+ public void stop(long gen, String taskName, String subTaskName, long nanoTime) {}
+
+ @Override
+ public void event(long gen, String eventName, String tagName, long tagId, long nanoTime) {}
+
+ @Override
+ public void event(long gen, String eventName, long nanoTime) {}
+
+ @Override
+ public void event(long gen, String eventName, String subEventName, long nanoTime) {}
+
+ @Override
+ public void attachTag(long gen, String tagName, long tagId) {}
+
+ @Override
+ public void attachKeyedTag(long gen, String name, String value) {}
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value0) {}
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value0, long value1) {}
+
+ @Override
+ public void resetForTest() {}
+
+ @Override
+ public List<Mark> read(boolean readerIsWriter) {
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/impl/src/main/java/io/perfmark/impl/SecretPerfMarkImpl.java b/impl/src/main/java/io/perfmark/impl/SecretPerfMarkImpl.java
new file mode 100644
index 0000000..0f11a72
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/SecretPerfMarkImpl.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import io.perfmark.Impl;
+import io.perfmark.Link;
+import io.perfmark.StringFunction;
+import io.perfmark.Tag;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+final class SecretPerfMarkImpl {
+
+ public static final class PerfMarkImpl extends Impl {
+ private static final int ENABLED_BIT_SPACE = 2;
+ private static final int GEN_TIMESTAMP_SPACE = 54;
+ private static final long MAX_MIBROS = (1L << GEN_TIMESTAMP_SPACE) - 1;
+ private static final Tag NO_TAG = packTag(Mark.NO_TAG_NAME, Mark.NO_TAG_ID);
+ private static final Link NO_LINK = packLink(Mark.NO_LINK_ID);
+ private static final long INCREMENT = 1L << Generator.GEN_OFFSET;
+
+ private static final AtomicLong linkIdAlloc = new AtomicLong(1);
+ private static final Generator generator;
+
+ // May be null if debugging is disabled.
+ private static final Object logger;
+
+ /**
+ * This is the generation of the recorded tasks. The bottom 8 bits [0-7] are reserved for opcode packing.
+ * Bit 9 [8] is used for detecting if PerfMark is enabled or not. Bit 10 [9] is unused. Bits 11-64 [10-647]
+ * are used for storing the time since Perfmark Was last / enabled or disabled. The units are in nanoseconds/1024,
+ * or (inaccurately) called mibros (like micros, but power of 2 based).
+ */
+ private static long actualGeneration;
+
+ static {
+ assert ENABLED_BIT_SPACE + Generator.GEN_OFFSET + GEN_TIMESTAMP_SPACE <= 64;
+ Generator gen = null;
+ Throwable[] problems = new Throwable[4];
+ // Avoid using a for-loop for this code, as it makes it easier for tools like Proguard to rewrite.
+ try {
+ Class<?> clz = Class.forName("io.perfmark.java7.SecretMethodHandleGenerator$MethodHandleGenerator");
+ gen = clz.asSubclass(Generator.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[0] = t;
+ }
+ if (gen == null) {
+ try {
+ Class<?> clz = Class.forName("io.perfmark.java9.SecretVarHandleGenerator$VarHandleGenerator");
+ gen = clz.asSubclass(Generator.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[1] = t;
+ }
+ }
+ if (gen == null) {
+ try {
+ Class<?> clz = Class.forName("io.perfmark.java6.SecretVolatileGenerator$VolatileGenerator");
+ gen = clz.asSubclass(Generator.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[2] = t;
+ }
+ }
+ if (gen == null) {
+ generator = new NoopGenerator();
+ } else {
+ generator = gen;
+ }
+
+ boolean startEnabled = false;
+ boolean startEnabledSuccess = false;
+ try {
+ if ((startEnabled = Boolean.getBoolean("io.perfmark.PerfMark.startEnabled"))) {
+ startEnabledSuccess = setEnabledQuiet(startEnabled, Generator.INIT_NANO_TIME);
+ }
+ } catch (Throwable t) {
+ problems[3] = t;
+ }
+
+ Object log = null;
+ try {
+ if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) {
+ Logger localLogger = Logger.getLogger(PerfMarkImpl.class.getName());
+ log = localLogger;
+ for (Throwable problem : problems) {
+ if (problem == null) {
+ continue;
+ }
+ localLogger.log(Level.FINE, "Error loading Generator", problem);
+ }
+ localLogger.log(Level.FINE, "Using {0}", new Object[] {generator.getClass().getName()});
+ logEnabledChange(startEnabled, startEnabledSuccess);
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+ logger = log;
+ }
+
+ public PerfMarkImpl(Tag key) {
+ super(key);
+ }
+
+ @Override
+ protected synchronized void setEnabled(boolean value) {
+ boolean changed = setEnabledQuiet(value, System.nanoTime());
+ logEnabledChange(value, changed);
+ }
+
+ @Override
+ protected synchronized boolean setEnabled(boolean value, boolean overload) {
+ boolean changed = setEnabledQuiet(value, System.nanoTime());
+ logEnabledChange(value, changed);
+ return changed;
+ }
+
+ private static synchronized void logEnabledChange(boolean value, boolean success) {
+ if (success && logger != null) {
+ Logger localLogger = (Logger) logger;
+ if (localLogger.isLoggable(Level.FINE)) {
+ localLogger.fine((value ? "Enabling" : "Disabling") + " PerfMark recorder");
+ }
+ }
+ }
+
+ /** Returns true if successfully changed. */
+ private static synchronized boolean setEnabledQuiet(boolean value, long now) {
+ if (isEnabled(actualGeneration) == value) {
+ return false;
+ }
+ if (actualGeneration == Generator.FAILURE) {
+ return false;
+ }
+ long nanoDiff = now - Generator.INIT_NANO_TIME;
+ generator.setGeneration(actualGeneration = nextGeneration(actualGeneration, nanoDiff));
+ return true;
+ }
+
+ // VisibleForTesting
+ static long nextGeneration(final long currentGeneration, final long nanosSinceInit) {
+ assert currentGeneration != Generator.FAILURE;
+ long currentMibros = mibrosFromGeneration(currentGeneration);
+ long mibrosSinceInit = Math.min(mibrosFromNanos(nanosSinceInit), MAX_MIBROS); // 54bits
+ boolean nextEnabled = !isEnabled(currentGeneration);
+ long nextMibros;
+ if (mibrosSinceInit > currentMibros) {
+ nextMibros = mibrosSinceInit;
+ } else {
+ nextMibros = currentMibros + (nextEnabled ? 1 : 0);
+ }
+ if (nextMibros > MAX_MIBROS || nextMibros < 0) {
+ return Generator.FAILURE;
+ }
+ long enabledMask = nextEnabled ? INCREMENT : 0;
+ long mibroMask = (nextMibros << (Generator.GEN_OFFSET + ENABLED_BIT_SPACE));
+ assert (enabledMask & mibroMask) == 0;
+ return mibroMask | enabledMask;
+ }
+
+ private static long mibrosFromGeneration(long currentGeneration) {
+ if (currentGeneration == Generator.FAILURE) {
+ throw new IllegalArgumentException();
+ }
+ return currentGeneration >>> (Generator.GEN_OFFSET + ENABLED_BIT_SPACE);
+ }
+
+ private static long mibrosFromNanos(long nanos) {
+ long remainder = ((1L<<(64 - GEN_TIMESTAMP_SPACE)) - 1) & nanos;
+ return (nanos >>> (64 - GEN_TIMESTAMP_SPACE))
+ + (remainder >= (1L<<(64 - GEN_TIMESTAMP_SPACE - 1)) ? 1 : 0);
+ }
+
+ @Override
+ protected void startTask(String taskName, Tag tag) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.startAnyway(gen, taskName, unpackTagName(tag), unpackTagId(tag));
+ }
+
+ @Override
+ protected void startTask(String taskName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.startAnyway(gen, taskName);
+ }
+
+ @Override
+ protected void startTask(String taskName, String subTaskName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.startAnyway(gen, taskName, subTaskName);
+ }
+
+ @Override
+ protected <T> void startTask(T taskNameObject, StringFunction<? super T> stringFunction) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ String taskName = deriveTaskValue(taskNameObject, stringFunction);
+ Storage.startAnyway(gen, taskName);
+ }
+
+ @Override
+ protected void stopTask() {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.stopAnyway(gen);
+ }
+
+ @Override
+ protected void stopTask(String taskName, Tag tag) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.stopAnyway(gen, taskName, unpackTagName(tag), unpackTagId(tag));
+ }
+
+ @Override
+ protected void stopTask(String taskName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.stopAnyway(gen, taskName);
+ }
+
+ @Override
+ protected void stopTask(String taskName, String subTaskName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.stopAnyway(gen, taskName, subTaskName);
+ }
+
+ @Override
+ protected void event(String eventName, Tag tag) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.eventAnyway(gen, eventName, unpackTagName(tag), unpackTagId(tag));
+ }
+
+ @Override
+ protected void event(String eventName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.eventAnyway(gen, eventName);
+ }
+
+ @Override
+ protected void event(String eventName, String subEventName) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.eventAnyway(gen, eventName, subEventName);
+ }
+
+ @Override
+ protected void attachTag(Tag tag) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.attachTagAnyway(gen, unpackTagName(tag), unpackTagId(tag));
+ }
+
+ @Override
+ protected void attachTag(String tagName, String tagValue) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.attachKeyedTagAnyway(gen, tagName, tagValue);
+ }
+
+ @Override
+ protected <T> void attachTag(
+ String tagName, T tagObject, StringFunction<? super T> stringFunction) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ String tagValue = deriveTagValue(tagName, tagObject, stringFunction);
+ Storage.attachKeyedTagAnyway(gen, tagName, tagValue);
+ }
+
+ static <T> String deriveTagValue(
+ String tagName, T tagObject, StringFunction<? super T> stringFunction) {
+ try {
+ return stringFunction.apply(tagObject);
+ } catch (Throwable t) {
+ handleTagValueFailure(tagName, tagObject, stringFunction, t);
+ return "PerfMarkTagError:" + t.getClass().getName();
+ }
+ }
+
+ static <T> String deriveTaskValue(T taskNameObject, StringFunction<? super T> stringFunction) {
+ try {
+ return stringFunction.apply(taskNameObject);
+ } catch (Throwable t) {
+ handleTaskNameFailure(taskNameObject, stringFunction, t);
+ return "PerfMarkTaskError:" + t.getClass().getName();
+ }
+ }
+
+ static <T> void handleTagValueFailure(
+ String tagName, T tagObject, StringFunction<? super T> stringFunction, Throwable cause) {
+ if (logger == null) {
+ return;
+ }
+ Logger localLogger = (Logger) logger;
+ try {
+ if (localLogger.isLoggable(Level.FINE)) {
+ LogRecord lr =
+ new LogRecord(
+ Level.FINE,
+ "PerfMark.attachTag failed: tagName={0}, tagObject={1}, stringFunction={2}");
+ lr.setParameters(new Object[] {tagName, tagObject, stringFunction});
+ lr.setThrown(cause);
+ localLogger.log(lr);
+ }
+ } catch (Throwable t) {
+ // Need to be careful here. It's possible that the Exception thrown may itself throw
+ // while trying to convert it to a String. Instead, only pass the class name, which is
+ // safer than passing the whole object.
+ localLogger.log(
+ Level.FINE,
+ "PerfMark.attachTag failed for {0}: {1}",
+ new Object[] {tagName, t.getClass()});
+ }
+ }
+
+ static <T> void handleTaskNameFailure(
+ T taskNameObject, StringFunction<? super T> stringFunction, Throwable cause) {
+ if (logger == null) {
+ return;
+ }
+ Logger localLogger = (Logger) logger;
+ try {
+ if (localLogger.isLoggable(Level.FINE)) {
+ LogRecord lr =
+ new LogRecord(
+ Level.FINE, "PerfMark.startTask failed: taskObject={0}, stringFunction={1}");
+ lr.setParameters(new Object[] {taskNameObject, stringFunction});
+ lr.setThrown(cause);
+ localLogger.log(lr);
+ }
+ } catch (Throwable t) {
+ // Need to be careful here. It's possible that the Exception thrown may itself throw
+ // while trying to convert it to a String. Instead, only pass the class name, which is
+ // safer than passing the whole object.
+ localLogger.log(Level.FINE, "PerfMark.startTask failed for {0}", new Object[] {t.getClass()});
+ }
+ }
+
+ @Override
+ protected void attachTag(String tagName, long tagValue) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.attachKeyedTagAnyway(gen, tagName, tagValue);
+ }
+
+ @Override
+ protected void attachTag(String tagName, long tagValue0, long tagValue1) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.attachKeyedTagAnyway(gen, tagName, tagValue0, tagValue1);
+ }
+
+ @Override
+ protected Tag createTag(@Nullable String tagName, long tagId) {
+ if (!isEnabled(getGen())) {
+ return NO_TAG;
+ }
+ return packTag(tagName, tagId);
+ }
+
+ @Override
+ protected Link linkOut() {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return NO_LINK;
+ }
+ long linkId = linkIdAlloc.getAndIncrement();
+ Storage.linkAnyway(gen, linkId);
+ return packLink(linkId);
+ }
+
+ @Override
+ protected void linkIn(Link link) {
+ final long gen = getGen();
+ if (!isEnabled(gen)) {
+ return;
+ }
+ Storage.linkAnyway(gen, -unpackLinkId(link));
+ }
+
+ private static long getGen() {
+ return generator.getGeneration();
+ }
+
+ private static boolean isEnabled(long gen) {
+ return ((gen >>> Generator.GEN_OFFSET) & 0x1L) != 0L;
+ }
+ }
+
+ private SecretPerfMarkImpl() {}
+}
diff --git a/impl/src/main/java/io/perfmark/impl/Storage.java b/impl/src/main/java/io/perfmark/impl/Storage.java
new file mode 100644
index 0000000..9cbda73
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/Storage.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+
+/**
+ * Storage is responsible for storing and returning recorded marks. This is a low level class and
+ * not intended for use by users. Instead, the {@code TraceEventWriter} and {@code TraceEventViewer}
+ * classes provide easier to use APIs for accessing PerfMark data.
+ *
+ * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed
+ * without notice.
+ */
+public final class Storage {
+ static final AtomicLong markHolderIdAllocator = new AtomicLong(1);
+ // The order of initialization here matters. If a logger invokes PerfMark, it will be re-entrant
+ // and need to use these static variables.
+ static final ConcurrentMap<Reference<Thread>, MarkHolderHandle> allMarkHolders =
+ new ConcurrentHashMap<Reference<Thread>, MarkHolderHandle>();
+ private static final LocalMarkHolder localMarkHolder = new MostlyThreadLocalMarkHolder();
+ private static final MarkHolderProvider markHolderProvider;
+ private static final ReferenceQueue<Thread> threadReferenceQueue = new ReferenceQueue<Thread>();
+ private static volatile long lastGlobalIndexClear = Generator.INIT_NANO_TIME - 1;
+
+ static {
+ MarkHolderProvider provider = null;
+ Throwable[] problems = new Throwable[3];
+ try {
+ String markHolderOverride = System.getProperty("io.perfmark.PerfMark.markHolderProvider");
+ if (markHolderOverride != null && !markHolderOverride.isEmpty()) {
+ Class<?> clz = Class.forName(markHolderOverride);
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ }
+ } catch (Throwable t) {
+ problems[0] = t;
+ }
+ if (provider == null) {
+ try {
+ Class<?> clz =
+ Class.forName(
+ "io.perfmark.java9.SecretVarHandleMarkHolderProvider$VarHandleMarkHolderProvider");
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[1] = t;
+ }
+ }
+ if (provider == null) {
+ try {
+ Class<?> clz =
+ Class.forName(
+ "io.perfmark.java6.SecretSynchronizedMarkHolderProvider$SynchronizedMarkHolderProvider");
+ provider = clz.asSubclass(MarkHolderProvider.class).getConstructor().newInstance();
+ } catch (Throwable t) {
+ problems[2] = t;
+ }
+ }
+ if (provider == null) {
+ markHolderProvider = new NoopMarkHolderProvider();
+ } else {
+ markHolderProvider = provider;
+ }
+ try {
+ if (Boolean.getBoolean("io.perfmark.PerfMark.debug")) {
+ // See the comment in io.perfmark.PerfMark for why this is invoked reflectively.
+ Class<?> logClass = Class.forName("java.util.logging.Logger");
+ Object logger = logClass.getMethod("getLogger", String.class).invoke(null, Storage.class.getName());
+ Class<?> levelClass = Class.forName("java.util.logging.Level");
+ Object level = levelClass.getField("FINE").get(null);
+ Method logProblemMethod = logClass.getMethod("log", levelClass, String.class, Throwable.class);
+
+ for (Throwable problem : problems) {
+ if (problem == null) {
+ continue;
+ }
+ logProblemMethod.invoke(logger, level, "Error loading MarkHolderProvider", problem);
+ }
+ Method logSuccessMethod = logClass.getMethod("log", levelClass, String.class, Object[].class);
+ logSuccessMethod.invoke(logger, level, "Using {0}", new Object[] {markHolderProvider.getClass().getName()});
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+ }
+
+ public static long getInitNanoTime() {
+ return Generator.INIT_NANO_TIME;
+ }
+
+ /**
+ * Returns a list of {@link MarkList}s across all reachable threads.
+ *
+ * @return all reachable MarkLists.
+ */
+ public static List<MarkList> read() {
+ long lastReset = lastGlobalIndexClear;
+ drainThreadQueue();
+ List<MarkList> markLists = new ArrayList<MarkList>(allMarkHolders.size());
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ Thread writer = handle.threadRef().get();
+ if (writer == null) {
+ handle.softenMarkHolderReference();
+ }
+ MarkHolder markHolder = handle.markHolder();
+ if (markHolder == null) {
+ it.remove();
+ handle.clearSoftReference();
+ continue;
+ }
+ String threadName = handle.getAndUpdateThreadName();
+ long threadId = handle.getAndUpdateThreadId();
+ boolean concurrentWrites = !(Thread.currentThread() == writer || writer == null);
+ markLists.add(
+ MarkList.newBuilder()
+ .setMarks(markHolder.read(concurrentWrites))
+ .setThreadName(threadName)
+ .setThreadId(threadId)
+ .setMarkListId(handle.markHolderId)
+ .build());
+ }
+ return Collections.unmodifiableList(markLists);
+ }
+
+ static void startAnyway(long gen, String taskName, @Nullable String tagName, long tagId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, tagName, tagId, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void startAnyway(long gen, String taskName) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void startAnyway(long gen, String taskName, String subTaskName) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.start(gen, taskName, subTaskName, System.nanoTime());
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName, @Nullable String tagName, long tagId) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, tagName, tagId, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void stopAnyway(long gen, String taskName, String subTaskName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.stop(gen, taskName, subTaskName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName, @Nullable String tagName, long tagId) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, tagName, tagId, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void eventAnyway(long gen, String eventName, String subEventName) {
+ long nanoTime = System.nanoTime();
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.event(gen, eventName, subEventName, nanoTime);
+ localMarkHolder.release(mh);
+ }
+
+ static void linkAnyway(long gen, long linkId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.link(gen, linkId);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachTagAnyway(long gen, @Nullable String tagName, long tagId) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachTag(gen, tagName, tagId);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(long gen, @Nullable String tagName, String tagValue) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(long gen, @Nullable String tagName, long tagValue) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue);
+ localMarkHolder.release(mh);
+ }
+
+ static void attachKeyedTagAnyway(
+ long gen, @Nullable String tagName, long tagValue0, long tagValue1) {
+ MarkHolder mh = localMarkHolder.acquire();
+ mh.attachKeyedTag(gen, tagName, tagValue0, tagValue1);
+ localMarkHolder.release(mh);
+ }
+
+ /**
+ * Removes all data for the calling Thread. Other threads may Still have stored data.
+ */
+ public static void clearLocalStorage() {
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ if (handle.threadRef.get() == Thread.currentThread()) {
+ it.remove();
+ handle.threadRef.clearInternal();
+ handle.softenMarkHolderReference();
+ handle.clearSoftReference();
+ }
+ }
+ localMarkHolder.clear();
+ }
+
+ /**
+ * Removes the global Read index on all storage, but leaves local storage in place. Because writer threads may still
+ * be writing to the same buffer (which they have a strong ref to), this function only removed data that is truly
+ * unwritable anymore. In addition, it captures a timestamp to which marks to include when reading. Thus, the data
+ * isn't fully removed. To fully remove all data, each tracing thread must call {@link #clearLocalStorage}.
+ */
+ public static void clearGlobalIndex() {
+ lastGlobalIndexClear = System.nanoTime() - 1;
+ for (Iterator<MarkHolderHandle> it = allMarkHolders.values().iterator(); it.hasNext();) {
+ MarkHolderHandle handle = it.next();
+ handle.softenMarkHolderReference();
+ Thread writer = handle.threadRef().get();
+ if (writer == null) {
+ it.remove();
+ handle.clearSoftReference();
+ }
+ }
+ }
+
+ private static void drainThreadQueue() {
+ while (true) {
+ Reference<?> ref = threadReferenceQueue.poll();
+ if (ref == null) {
+ return;
+ }
+ MarkHolderHandle handle = allMarkHolders.get(ref);
+ if (handle != null) {
+ handle.softenMarkHolderReference();
+ if (handle.markHolder() == null) {
+ allMarkHolders.remove(ref);
+ handle.clearSoftReference();
+ }
+ }
+ }
+ }
+
+ @Nullable
+ public static MarkList readForTest() {
+ List<MarkList> lists = read();
+ for (MarkList list : lists) {
+ // This is slightly wrong as the thread ID could be reused.
+ if (list.getThreadId() == Thread.currentThread().getId()) {
+ return list;
+ }
+ }
+ return null;
+ }
+
+ public static final class MarkHolderHandle {
+ private static final SoftReference<MarkHolder> EMPTY = new SoftReference<MarkHolder>(null);
+
+ private final UnmodifiableWeakReference<Thread> threadRef;
+ private final AtomicReference<MarkHolder> markHolderRef;
+ private volatile SoftReference<MarkHolder> softMarkHolderRef;
+
+ private volatile String threadName;
+ private volatile long threadId;
+ private final long markHolderId;
+
+ MarkHolderHandle(Thread thread, MarkHolder markHolder, long markHolderId) {
+ this.threadRef = new UnmodifiableWeakReference<Thread>(thread, threadReferenceQueue);
+ this.markHolderRef = new AtomicReference<MarkHolder>(markHolder);
+ this.threadName = thread.getName();
+ this.threadId = thread.getId();
+ this.markHolderId = markHolderId;
+ }
+
+ /**
+ * Returns the MarkHolder. May return {@code null} if the Thread is gone. If {@code null} is returned,
+ * then {@code getThreadRef().get() == null}. If a non-{@code null} value is returned, the thread may be dead or
+ * alive. Additionally, since the {@link #threadRef} may be externally cleared, it is not certain that the Thread
+ * is dead.
+ */
+ public MarkHolder markHolder() {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder == null) {
+ markHolder = softMarkHolderRef.get();
+ assert markHolder != null || threadRef.get() == null;
+ }
+ return markHolder;
+ }
+
+ /**
+ * Returns a weak reference to the Thread that created the MarkHolder.
+ */
+ public WeakReference<? extends Thread> threadRef() {
+ return threadRef;
+ }
+
+ void softenMarkHolderReference() {
+ synchronized (markHolderRef) {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder != null) {
+ softMarkHolderRef = new SoftReference<MarkHolder>(markHolder);
+ markHolderRef.set(null);
+ }
+ }
+ }
+
+ void clearSoftReference() {
+ Thread thread = threadRef.get();
+ if (thread != null) {
+ throw new IllegalStateException("Thread still alive " + thread);
+ }
+ synchronized (markHolderRef) {
+ MarkHolder markHolder = markHolderRef.get();
+ if (markHolder != null) {
+ throw new IllegalStateException("Handle not yet softened");
+ }
+ softMarkHolderRef.clear();
+ softMarkHolderRef = EMPTY;
+ threadName = null;
+ threadId = -255;
+ }
+ }
+
+ String getAndUpdateThreadName() {
+ Thread t = threadRef.get();
+ String name;
+ if (t != null) {
+ threadName = (name = t.getName());
+ } else {
+ name = threadName;
+ }
+ return name;
+ }
+
+ /**
+ * Some threads change their id over time, so we need to sync it if available.
+ */
+ long getAndUpdateThreadId() {
+ Thread t = threadRef.get();
+ long id;
+ if (t != null) {
+ threadId = (id = t.getId());
+ } else {
+ id = threadId;
+ }
+ return id;
+ }
+ }
+
+ /**
+ * This class is needed to work around a race condition where a newly created MarkHolder could be GC'd before
+ * the caller of {@link #allocateMarkHolder} can consume the results. The provided MarkHolder is strongly reachable.
+ */
+ public static final class MarkHolderAndHandle {
+ private final MarkHolder markHolder;
+ private final MarkHolderHandle handle;
+
+ MarkHolderAndHandle(MarkHolder markHolder, MarkHolderHandle handle) {
+ this.markHolder = markHolder;
+ this.handle = handle;
+
+ MarkHolder tmp = handle.markHolder();
+ if (markHolder != tmp && tmp != null) {
+ throw new IllegalArgumentException("Holder Handle mismatch");
+ }
+ }
+
+ public MarkHolder markHolder() {
+ return markHolder;
+ }
+
+ public MarkHolderHandle handle() {
+ return handle;
+ }
+ }
+
+ public static MarkHolderAndHandle allocateMarkHolder() {
+ drainThreadQueue();
+ long markHolderId = markHolderIdAllocator.getAndIncrement();
+ MarkHolder holder = markHolderProvider.create(markHolderId);
+ MarkHolderHandle handle = new MarkHolderHandle(Thread.currentThread(), holder, markHolderId);
+ allMarkHolders.put(handle.threadRef, handle);
+ return new MarkHolderAndHandle(holder, handle);
+ }
+
+ private static final class UnmodifiableWeakReference<T> extends WeakReference<T> {
+
+ UnmodifiableWeakReference(T referent, ReferenceQueue<T> q) {
+ super(referent, q);
+ }
+
+ @Override
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public void clear() {}
+
+ @Override
+ @Deprecated
+ @SuppressWarnings("InlineMeSuggester")
+ public boolean enqueue() {
+ return false;
+ }
+
+ void clearInternal() {
+ super.clear();
+ }
+ }
+
+ private Storage() {}
+}
diff --git a/impl/src/main/java/io/perfmark/impl/package-info.java b/impl/src/main/java/io/perfmark/impl/package-info.java
new file mode 100644
index 0000000..a854569
--- /dev/null
+++ b/impl/src/main/java/io/perfmark/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.impl;
diff --git a/impl/src/test/java/io/perfmark/impl/PerfMarkImplTest.java b/impl/src/test/java/io/perfmark/impl/PerfMarkImplTest.java
new file mode 100644
index 0000000..9a9259d
--- /dev/null
+++ b/impl/src/test/java/io/perfmark/impl/PerfMarkImplTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PerfMarkImplTest {
+
+ @Test
+ public void nextGeneration_enable() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(0, 3*1024);
+ assertEquals((3L<<10) + (1<<8), gen);
+ }
+
+ @Test
+ public void nextGeneration_disable() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(1 << Generator.GEN_OFFSET, 3*1024);
+ assertEquals((3L<<10), gen);
+ }
+
+ @Test
+ public void nextGeneration_newStampEnabled() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(
+ (3<< (Generator.GEN_OFFSET + 2))
+ + (1 << Generator.GEN_OFFSET),
+ 3*1024);
+ assertEquals((3L<<10), gen);
+ }
+
+ @Test
+ public void nextGeneration_newStampDisabled() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(
+ (3<< (Generator.GEN_OFFSET + 2)),
+ 3*1024);
+ assertEquals((4L<<10) + (1<<Generator.GEN_OFFSET), gen);
+ }
+
+ @Test
+ public void nextGeneration_maxNanos() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(1 << Generator.GEN_OFFSET, -1);
+ assertNotEquals(Generator.FAILURE, gen);
+ }
+
+ @Test
+ public void nextGeneration_noOverflowOnDisable() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(0xFFFF_FFFF_FFFF_FD00L, -1);
+ assertNotEquals(Generator.FAILURE, gen);
+ }
+
+ @Test
+ public void nextGeneration_overflowOnEnable() {
+ var gen = SecretPerfMarkImpl.PerfMarkImpl.nextGeneration(0xFFFF_FFFF_FFFF_FC00L, -1);
+ assertEquals(Generator.FAILURE, gen);
+ }
+}
diff --git a/impl/src/test/java/io/perfmark/impl/StorageTest.java b/impl/src/test/java/io/perfmark/impl/StorageTest.java
new file mode 100644
index 0000000..ebe9e0f
--- /dev/null
+++ b/impl/src/test/java/io/perfmark/impl/StorageTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.truth.Truth;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Filter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StorageTest {
+
+ @Test
+ public void threadsCleanedUp() throws Exception {
+ Storage.clearLocalStorage();
+ final CountDownLatch latch = new CountDownLatch(1);
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ Storage.clearLocalStorage();
+ Storage.linkAnyway(4096, 1234);
+ latch.countDown();
+ }
+ })
+ .start();
+
+ for (int i = 10; i < 5000; i += i / 2) {
+ latch.await(i, TimeUnit.MILLISECONDS);
+ System.gc();
+ System.runFinalization();
+ }
+
+ assertEquals(0, latch.getCount());
+ List<MarkList> firstRead = Storage.read();
+ assertEquals(1, firstRead.size());
+ // simulate an OOM
+ Storage.clearGlobalIndex();
+ List<MarkList> secondRead = Storage.read();
+ assertEquals(0, secondRead.size());
+ }
+
+ @Test
+ public void customMarkHolderImpl() throws Exception {
+ Class<?> clz = runWithProperty(
+ System.getProperties(),
+ "io.perfmark.PerfMark.markHolderProvider",
+ TestMarkHolderProvider.class.getName(),
+ () -> Class.forName(Storage.class.getName(), true, new TestClassLoader(getClass().getClassLoader())));
+
+ Field field = clz.getDeclaredField("markHolderProvider");
+ field.setAccessible(true);
+ Object value = field.get(null);
+ assertNotNull(value);
+ // Can't do instanceof, since class loaders are different.
+ assertEquals(TestMarkHolderProvider.class.getName(), value.getClass().getName());
+ }
+ @Test
+ public void logEnabled() throws Exception {
+ ClassLoader loader = new TestClassLoader(getClass().getClassLoader());
+ List<LogRecord> logs = new ArrayList<>();
+ Filter filter = record -> {
+ logs.add(record);
+ return true;
+ };
+ Logger logger = Logger.getLogger(Storage.class.getName());
+ Level oldLevel = logger.getLevel();
+ Filter oldFilter = logger.getFilter();
+ logger.setLevel(Level.ALL);
+ logger.setFilter(filter);
+ try {
+ runWithProperty(System.getProperties(), "io.perfmark.PerfMark.debug", "true", () -> {
+ // Force Initialization.
+ Class.forName(Storage.class.getName(), true, loader);
+ return null;
+ });
+ } finally{
+ logger.setFilter(oldFilter);
+ logger.setLevel(oldLevel);
+ }
+
+ // This depends on the the classpath being set up correctly.
+ Truth.assertThat(logs).hasSize(3);
+ Truth.assertThat(logs.get(0).getMessage()).contains("Error loading MarkHolderProvider");
+ Truth.assertThat(logs.get(0).getThrown()).hasMessageThat()
+ .contains("io.perfmark.java9.SecretVarHandleMarkHolderProvider$VarHandleMarkHolderProvider");
+ Truth.assertThat(logs.get(1).getMessage()).contains("Error loading MarkHolderProvider");
+ Truth.assertThat(logs.get(1).getThrown()).hasMessageThat()
+ .contains("io.perfmark.java6.SecretSynchronizedMarkHolderProvider$SynchronizedMarkHolderProvider");
+ Truth.assertThat(new SimpleFormatter().format(logs.get(2)))
+ .contains("Using io.perfmark.impl.NoopMarkHolderProvider");
+ }
+
+ public static final class TestMarkHolderProvider extends MarkHolderProvider {
+ @Override
+ public MarkHolder create(long markHolderId) {
+ throw new AssertionError();
+ }
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+
+ private final List<String> classesToDrop;
+
+ TestClassLoader(ClassLoader parent, String ... classesToDrop) {
+ super(parent);
+ this.classesToDrop = Arrays.asList(classesToDrop);
+ }
+
+ @Override
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (classesToDrop.contains(name)) {
+ throw new ClassNotFoundException();
+ }
+ if (!name.startsWith("io.perfmark.")) {
+ return super.loadClass(name, resolve);
+ }
+ try (InputStream is = getParent().getResourceAsStream(name.replace('.', '/') + ".class")) {
+ if (is == null) {
+ throw new ClassNotFoundException(name);
+ }
+ byte[] data = is.readAllBytes();
+ Class<?> clz = defineClass(name, data, 0, data.length);
+ if (resolve) {
+ resolveClass(clz);
+ }
+ return clz;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @CanIgnoreReturnValue
+ private static <T> T runWithProperty(Properties properties, String name, String value, Callable<T> runnable)
+ throws Exception {
+ if (properties.containsKey(name)) {
+ String oldProp;
+ oldProp = properties.getProperty(name);
+ try {
+ System.setProperty(name, value);
+ return runnable.call();
+ } finally{
+ properties.setProperty(name, oldProp);
+ }
+ } else {
+ try {
+ System.setProperty(name, value);
+ return runnable.call();
+ } finally{
+ properties.remove(name);
+ }
+ }
+ }
+
+}
diff --git a/java15/build.gradle.kts b/java15/build.gradle.kts
new file mode 100644
index 0000000..b6d7e9f
--- /dev/null
+++ b/java15/build.gradle.kts
@@ -0,0 +1,79 @@
+import net.ltgt.gradle.errorprone.errorprone
+
+plugins {
+ id("io.github.reyerizo.gradle.jcstress")
+}
+
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.javafifteen")
+ }
+}
+
+val jdkVersion = JavaVersion.VERSION_15
+
+description = "PerfMark Java15 API"
+
+sourceSets {
+ create("jmh")
+}
+
+val jmhImplementation by configurations.getting {
+ extendsFrom(configurations.implementation.get())
+}
+
+val jmhAnnotationProcessor by configurations.getting {
+ extendsFrom(configurations.annotationProcessor.get())
+}
+
+dependencies {
+ implementation(project(":perfmark-impl"))
+ compileOnly(libs.jsr305)
+
+ testImplementation(project(":perfmark-api"))
+ testImplementation(project(":perfmark-testing"))
+ jcstressImplementation(project(":perfmark-impl"))
+
+ jmhImplementation(project(":perfmark-api"))
+ jmhImplementation(project(":perfmark-impl"))
+ jmhImplementation(project(":perfmark-java15"))
+ jmhImplementation(project(":perfmark-testing"))
+
+ jmhImplementation(libs.junit)
+ jmhImplementation(libs.jmhcore)
+ jmhAnnotationProcessor(libs.jmhanno)
+}
+
+tasks.named<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+}
+
+tasks.named<Javadoc>("javadoc") {
+ exclude("io/perfmark/java15/**")
+}
+
+tasks.register<Test>("jmh") {
+ description = "Runs integration tests."
+ group = "stress"
+
+ testClassesDirs = sourceSets["jmh"].output.classesDirs
+ classpath = sourceSets["jmh"].runtimeClasspath
+}
+
+// ./gradlew --no-daemon clean :perfmark-java9:jcstress
+jcstress {
+ jcstressDependency = "org.openjdk.jcstress:jcstress-core:0.5"
+ // mode "tough"
+ deoptRatio = "2"
+}
+
+
+tasks.named<JavaCompile>("compileJmhJava") {
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+}
+
+
+tasks.named<JavaCompile>("compileJcstressJava") {
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+}
diff --git a/java15/src/jmh/java/io/perfmark/java15/HiddenClassVarHandleMarkerBenchmarkTest.java b/java15/src/jmh/java/io/perfmark/java15/HiddenClassVarHandleMarkerBenchmarkTest.java
new file mode 100644
index 0000000..2836f0b
--- /dev/null
+++ b/java15/src/jmh/java/io/perfmark/java15/HiddenClassVarHandleMarkerBenchmarkTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java15;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.MarkHolderBenchmark;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+@RunWith(JUnit4.class)
+public class HiddenClassVarHandleMarkerBenchmarkTest {
+ @Test
+ public void markHolderBenchmark() throws Exception {
+ Options options = new OptionsBuilder()
+ .include(SecretHiddenClassMarkHolderBenchmark.class.getCanonicalName())
+ .addProfiler("perfasm")
+ .measurementIterations(10)
+ .warmupIterations(10)
+ .forks(1)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .shouldFailOnError(true)
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs(
+ "-da",
+ "-XX:+UnlockExperimentalVMOptions",
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+UseEpsilonGC",
+ "-XX:+LogCompilation",
+ "-XX:LogFile=/dev/null",
+ "-XX:+PrintAssembly",
+ "-XX:+PrintInterpreter",
+ "-XX:+PrintNMethods",
+ "-XX:+PrintNativeNMethods",
+ "-XX:+PrintSignatureHandlers",
+ "-XX:+PrintAdapterHandlers",
+ "-XX:+PrintStubCode",
+ "-XX:+PrintCompilation",
+ "-XX:+PrintInlining",
+ "-XX:PrintAssemblyOptions=syntax",
+ "-XX:PrintAssemblyOptions=intel")
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Thread)
+ public static class SecretHiddenClassMarkHolderBenchmark extends MarkHolderBenchmark {
+ @Override
+ public MarkHolder getMarkHolder() {
+ return new SecretHiddenClassMarkHolderProvider.HiddenClassMarkHolderProvider().create(1234, 16384);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public MarkHolder allocationBenchmark() {
+ return new SecretHiddenClassMarkHolderProvider.HiddenClassMarkHolderProvider().create(1234, 4);
+ }
+ }
+}
diff --git a/java15/src/main/java/io/perfmark/java15/HiddenClassVarHandleMarkHolder.java b/java15/src/main/java/io/perfmark/java15/HiddenClassVarHandleMarkHolder.java
new file mode 100644
index 0000000..c15396d
--- /dev/null
+++ b/java15/src/main/java/io/perfmark/java15/HiddenClassVarHandleMarkHolder.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java15;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.Deque;
+import java.util.List;
+
+/** HiddenClassVarHandleMarkHolder is a MarkHolder optimized for wait free writes and few reads. */
+final class HiddenClassVarHandleMarkHolder extends MarkHolder {
+ private static final long GEN_MASK = (1 << Generator.GEN_OFFSET) - 1;
+ private static final long START_OP = 1; // Mark.Operation.TASK_START.ordinal();
+ private static final long START_S_OP = 2;
+ private static final long START_T_OP = 3; // Mark.Operation.TASK_START_T.ordinal();
+ private static final long STOP_OP = 4; // Mark.Operation.TASK_END.ordinal();
+ private static final long STOP_V_OP = 5;
+ private static final long STOP_T_OP = 6; // Mark.Operation.TASK_END_T.ordinal();
+ private static final long STOP_S_OP = 7;
+ private static final long EVENT_OP = 8; // Mark.Operation.EVENT.ordinal();
+ private static final long EVENT_T_OP = 9; // Mark.Operation.EVENT_T.ordinal();
+ private static final long EVENT_S_OP = 10;
+ private static final long LINK_OP = 11; // Mark.Operation.LINK.ordinal();
+ private static final long ATTACH_T_OP = 12; // Mark.Operation.ATTACH_TAG.ordinal();
+ private static final long ATTACH_SS_OP = 13;
+ private static final long ATTACH_SN_OP = 14;
+ private static final long ATTACH_SNN_OP = 15;
+
+ private static final VarHandle IDX;
+ private static final VarHandle STRINGS;
+ private static final VarHandle LONGS;
+
+ /** This is a magic number, read the top level doc for explanation. */
+ static final int MAX_EVENTS = 0x7e3779b9;
+ static final long MAX_EVENTS_MASK = MAX_EVENTS - 1;
+
+ // where to write to next
+ @SuppressWarnings("unused") // Used Reflectively
+ private static volatile long idx;
+
+ private static final String[] taskNames;
+ private static final String[] tagNames;
+ private static final long[] tagIds;
+ private static final long[] nanoTimes;
+ private static final long[] genOps;
+
+ static {
+ try {
+ IDX = MethodHandles.lookup().findStaticVarHandle(HiddenClassVarHandleMarkHolder.class, "idx", long.class);
+ STRINGS = MethodHandles.arrayElementVarHandle(String[].class);
+ LONGS = MethodHandles.arrayElementVarHandle(long[].class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ int maxEvents = (int) HiddenClassVarHandleMarkHolder.class.getDeclaredField("MAX_EVENTS").get(null);
+ if (((maxEvents - 1) & maxEvents) != 0) {
+ throw new IllegalArgumentException(maxEvents + " is not a power of two");
+ }
+ if (maxEvents <= 0) {
+ throw new IllegalArgumentException(maxEvents + " is not positive");
+ }
+ long maxEventsMask = (long) HiddenClassVarHandleMarkHolder.class.getDeclaredField("MAX_EVENTS_MASK").get(null);
+ if (maxEvents - 1 != maxEventsMask) {
+ throw new IllegalArgumentException(maxEvents + " doesn't match mask " + maxEventsMask);
+ }
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+
+ taskNames = new String[MAX_EVENTS];
+ tagNames = new String[MAX_EVENTS];
+ tagIds = new long[MAX_EVENTS];
+ nanoTimes = new long[MAX_EVENTS];
+ genOps = new long[MAX_EVENTS];
+ }
+
+ HiddenClassVarHandleMarkHolder() {}
+
+ @Override
+ public void start(long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_T_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void start(long gen, String taskName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void start(long gen, String taskName, String subTaskName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, subTaskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_S_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void link(long gen, long linkId) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ LONGS.setOpaque(tagIds, i, linkId);
+ LONGS.setOpaque(genOps, i, gen + LINK_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_V_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_T_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, String subTaskName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, subTaskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_S_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_T_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, String subEventName, long nanoTime) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ STRINGS.setOpaque(tagNames, i, subEventName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_S_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachTag(long gen, String tagName, long tagId) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_T_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(tagNames, i, name);
+ LONGS.setOpaque(tagIds, i, value);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SN_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value0, long value1) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(tagNames, i, name);
+ LONGS.setOpaque(tagIds, i, value0);
+ LONGS.setOpaque(nanoTimes, i, value1);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SNN_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, String value) {
+ long localIdx = (long) IDX.get();
+ int i = (int) (localIdx & MAX_EVENTS_MASK);
+ STRINGS.setOpaque(tagNames, i, name);
+ STRINGS.setOpaque(taskNames, i, value);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SS_OP);
+ IDX.setRelease(localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void resetForTest() {
+ Arrays.fill(taskNames, null);
+ Arrays.fill(tagNames, null);
+ Arrays.fill(tagIds, 0);
+ Arrays.fill(nanoTimes, 0);
+ Arrays.fill(genOps, 0);
+ IDX.setRelease(0L);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public List<Mark> read(boolean concurrentWrites) {
+ final String[] localTaskNames = new String[MAX_EVENTS];
+ final String[] localTagNames = new String[MAX_EVENTS];
+ final long[] localTagIds = new long[MAX_EVENTS];
+ final long[] localNanoTimes = new long[MAX_EVENTS];
+ final long[] localGenOps = new long[MAX_EVENTS];
+ long startIdx = (long) IDX.getOpaque();
+ VarHandle.loadLoadFence();
+ int size = (int) Math.min(startIdx, MAX_EVENTS);
+ for (int i = 0; i < size; i++) {
+ localTaskNames[i] = (String) STRINGS.getOpaque(taskNames, i);
+ localTagNames[i] = (String) STRINGS.getOpaque(tagNames, i);
+ localTagIds[i] = (long) LONGS.getOpaque(tagIds, i);
+ localNanoTimes[i] = (long) LONGS.getOpaque(nanoTimes, i);
+ localGenOps[i] = (long) LONGS.getOpaque(genOps, i);
+ }
+ VarHandle.loadLoadFence();
+ long endIdx = (long) IDX.getOpaque();
+ if (endIdx < startIdx) {
+ throw new AssertionError();
+ }
+ // If we are reading from ourselves (such as in a test), we can assume there isn't an in
+ // progress write modifying the oldest entry. Additionally, if the writer has not yet
+ // wrapped around, the last entry cannot have been corrupted.
+ boolean tailValid = !concurrentWrites || endIdx < MAX_EVENTS - 1;
+ endIdx += !tailValid ? 1 : 0;
+ long eventsToDrop = endIdx - startIdx;
+ final Deque<Mark> marks = new ArrayDeque<>(size);
+ for (int i = 0; i < size - eventsToDrop; i++) {
+ int readIdx = (int) ((startIdx - i - 1) & MAX_EVENTS_MASK);
+ long gen = localGenOps[readIdx] & ~GEN_MASK;
+ int opVal = (int) (localGenOps[readIdx] & GEN_MASK);
+ switch (opVal) {
+ case (int) START_T_OP:
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ // fallthrough
+ case (int) START_OP:
+ marks.addFirst(Mark.taskStart(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) START_S_OP:
+ marks.addFirst(
+ Mark.taskStart(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) STOP_V_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx]));
+ break;
+ case (int) STOP_S_OP:
+ marks.addFirst(
+ Mark.taskEnd(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) STOP_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) STOP_T_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) EVENT_OP:
+ marks.addFirst(Mark.event(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) EVENT_T_OP:
+ marks.addFirst(
+ Mark.event(
+ gen,
+ localNanoTimes[readIdx],
+ localTaskNames[readIdx],
+ localTagNames[readIdx],
+ localTagIds[readIdx]));
+ break;
+ case (int) EVENT_S_OP:
+ marks.addFirst(
+ Mark.event(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) LINK_OP:
+ marks.addFirst(Mark.link(gen, localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_T_OP:
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_SS_OP:
+ marks.addFirst(Mark.keyedTag(gen, localTagNames[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) ATTACH_SN_OP:
+ marks.addFirst(Mark.keyedTag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_SNN_OP:
+ marks.addFirst(
+ Mark.keyedTag(
+ gen, localTagNames[readIdx], localTagIds[readIdx], localNanoTimes[readIdx]));
+ break;
+ default:
+ throw new ConcurrentModificationException("Read of storage was not threadsafe " + opVal);
+ }
+ }
+
+ return Collections.unmodifiableList(new ArrayList<>(marks));
+ }
+
+ @Override
+ public int maxMarks() {
+ return MAX_EVENTS;
+ }
+}
diff --git a/java15/src/main/java/io/perfmark/java15/SecretHiddenClassMarkHolderProvider.java b/java15/src/main/java/io/perfmark/java15/SecretHiddenClassMarkHolderProvider.java
new file mode 100644
index 0000000..a296f40
--- /dev/null
+++ b/java15/src/main/java/io/perfmark/java15/SecretHiddenClassMarkHolderProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java15;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.impl.MarkHolderProvider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+public final class SecretHiddenClassMarkHolderProvider {
+ public static final class HiddenClassMarkHolderProvider extends MarkHolderProvider {
+ private static final int DEFAULT_SIZE = 32768;
+
+ private final int[] maxEventsOffsets;
+ private final int[] maxEventsMaskOffsets;
+
+ private final byte[] markHolderClassData;
+
+ public HiddenClassMarkHolderProvider() {
+ try (InputStream classData = getClass().getResourceAsStream("HiddenClassVarHandleMarkHolder.class")) {
+ markHolderClassData = classData.readAllBytes();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ ByteBuffer expectedMaxEvents = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN);
+ expectedMaxEvents.putInt(HiddenClassVarHandleMarkHolder.MAX_EVENTS);
+ byte[] maxEvents = expectedMaxEvents.array();
+ maxEventsOffsets = findOffsets(markHolderClassData, maxEvents);
+ if (maxEventsOffsets.length != 2) {
+ throw new RuntimeException("hop");
+ }
+
+ ByteBuffer expectedMaxEventsMask = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
+ expectedMaxEventsMask.putLong(HiddenClassVarHandleMarkHolder.MAX_EVENTS_MASK);
+ byte[] maxEventsMax = expectedMaxEventsMask.array();
+ maxEventsMaskOffsets = findOffsets(markHolderClassData, maxEventsMax);
+ if (maxEventsMaskOffsets.length != 1) {
+ throw new RuntimeException("skip");
+ }
+
+ replaceSize(markHolderClassData, DEFAULT_SIZE);
+ }
+
+ @Override
+ public MarkHolder create(long markHolderId) {
+ return create(markHolderId, DEFAULT_SIZE);
+ }
+
+ MarkHolder create(long markHolderId, int size) {
+ final byte[] classData;
+ if (size != DEFAULT_SIZE) {
+ classData = Arrays.copyOf(markHolderClassData, markHolderClassData.length);
+ replaceSize(classData, size);
+ } else {
+ classData = markHolderClassData;
+ }
+ try {
+ Class<?> clz = MethodHandles.lookup().defineHiddenClass(classData, true).lookupClass();
+ return clz.asSubclass(MarkHolder.class).getDeclaredConstructor().newInstance();
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void replaceSize(byte[] haystack, int size) {
+ ByteBuffer buf = ByteBuffer.wrap(haystack).order(ByteOrder.BIG_ENDIAN);
+ for (int off : maxEventsOffsets) {
+ buf.putInt(off, size);
+ }
+ for (int off : maxEventsMaskOffsets) {
+ buf.putLong(off, size - 1);
+ }
+ }
+
+ private static int[] findOffsets(byte[] haystack, byte[] needle) {
+ int[] matches = new int[0];
+ outer: for (int i = 0; i < haystack.length - needle.length; i++) {
+ for (int k = 0; k < needle.length; k++) {
+ if (haystack[i + k] != needle[k]) {
+ continue outer;
+ }
+ }
+ matches = Arrays.copyOf(matches, matches.length + 1);
+ matches[matches.length - 1] = i;
+ }
+ return matches;
+ }
+ }
+
+ private SecretHiddenClassMarkHolderProvider() {}
+}
diff --git a/java15/src/test/java/io/perfmark/java15/HiddenClassVarHandleMarkHolderTest.java b/java15/src/test/java/io/perfmark/java15/HiddenClassVarHandleMarkHolderTest.java
new file mode 100644
index 0000000..73200d5
--- /dev/null
+++ b/java15/src/test/java/io/perfmark/java15/HiddenClassVarHandleMarkHolderTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java15;
+
+import static org.junit.Assert.assertEquals;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.MarkHolderTest;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HiddenClassVarHandleMarkHolderTest extends MarkHolderTest {
+
+ private final long gen = 1L << Generator.GEN_OFFSET;
+
+ @Override
+ protected MarkHolder getMarkHolder() {
+ return new SecretHiddenClassMarkHolderProvider.HiddenClassMarkHolderProvider().create(1234);
+ }
+
+ @Test
+ public void read_getsAllButLastIfNotWriter() {
+ MarkHolder mh = getMarkHolder();
+ int events = mh.maxMarks() - 1;
+ for (int i = 0; i < events; i++) {
+ mh.start(gen, "task", 3);
+ }
+
+ List<Mark> marks = mh.read(true);
+ assertEquals(events - 1, marks.size());
+ }
+
+ @Test
+ public void read_getsAllIfNotWriterButNoWrap() {
+ MarkHolder mh = getMarkHolder();
+
+ int events = mh.maxMarks() - 2;
+ for (int i = 0; i < events; i++) {
+ mh.start(gen, "task", 3);
+ }
+
+ List<Mark> marks = mh.read(true);
+ assertEquals(events, marks.size());
+ }
+}
diff --git a/java6/BUILD.bazel b/java6/BUILD.bazel
new file mode 100644
index 0000000..eb1ac1a
--- /dev/null
+++ b/java6/BUILD.bazel
@@ -0,0 +1,33 @@
+java_library(
+ name = "generator",
+ srcs = [
+ "src/main/java/io/perfmark/java6/SecretVolatileGenerator.java",
+ ],
+ deps = [
+ "//impl:generator",
+ ],
+)
+
+java_library(
+ name = "mark-holder",
+ srcs = [
+ "src/main/java/io/perfmark/java6/SynchronizedMarkHolder.java",
+ ],
+ deps = [
+ "//impl:generator",
+ "//impl:mark",
+ "//impl:mark-holder",
+ ],
+)
+
+java_library(
+ name = "mark-holder-provider",
+ srcs = [
+ "src/main/java/io/perfmark/java6/SecretSynchronizedMarkHolderProvider.java",
+ ],
+ deps = [
+ ":mark-holder",
+ "//impl:mark-holder",
+ "//impl:mark-holder-provider",
+ ],
+)
diff --git a/java6/build.gradle b/java6/build.gradle
new file mode 100644
index 0000000..d4d5dbf
--- /dev/null
+++ b/java6/build.gradle
@@ -0,0 +1,68 @@
+description = "PerfMark Java6 API"
+ext.moduleName = "io.perfmark.javasix"
+ext.jdkVersion = JavaVersion.VERSION_1_6
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
+}
+
+compileJava {
+ sourceCompatibility = jdkVersion
+ targetCompatibility = jdkVersion
+
+ options.compilerArgs.add("-Xlint:-options")
+}
+
+sourceSets {
+ jmh {}
+}
+
+
+compileJmhJava {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ javaCompiler = javaToolchains.compilerFor({
+ languageVersion = JavaLanguageVersion.of("17")
+ })
+ options.errorprone.enabled = true
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+}
+
+dependencies {
+ implementation project(':perfmark-impl')
+ compileOnly libs.jsr305
+
+ testImplementation(project(":perfmark-api"))
+ testImplementation(project(':perfmark-testing'))
+
+ jmhImplementation project(':perfmark-api'),
+ project(':perfmark-impl'),
+ project(':perfmark-java6'),
+ project(':perfmark-testing')
+ jmhImplementation libs.junit
+ jmhImplementation libs.jmhcore
+ jmhAnnotationProcessor libs.jmhanno
+}
+
+tasks.register('jmh', Test) {
+ description = 'Runs integration tests.'
+ group = 'stress'
+
+ testClassesDirs = sourceSets.jmh.output.classesDirs
+ classpath = sourceSets.jmh.runtimeClasspath
+
+ javaLauncher = javaToolchains.launcherFor({
+ languageVersion = JavaLanguageVersion.of("17")
+ })
+ //shouldRunAfter test
+}
+
+javadoc {
+ exclude 'io/perfmark/java6**'
+}
+
+jar {
+ exclude 'io/perfmark/java6/Internal*'
+}
diff --git a/java6/src/jmh/java/io/perfmark/java6/SynchronizedMarkHolderBenchmarkTest.java b/java6/src/jmh/java/io/perfmark/java6/SynchronizedMarkHolderBenchmarkTest.java
new file mode 100644
index 0000000..2da17d1
--- /dev/null
+++ b/java6/src/jmh/java/io/perfmark/java6/SynchronizedMarkHolderBenchmarkTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.MarkHolderBenchmark;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+@RunWith(JUnit4.class)
+public class SynchronizedMarkHolderBenchmarkTest {
+ @Test
+ public void markHolderBenchmark() throws Exception {
+ Options options = new OptionsBuilder()
+ .include(SynchronizedMarkHolderBenchmark.class.getCanonicalName())
+ .addProfiler("perfasm")
+ .measurementIterations(5)
+ .warmupIterations(10)
+ .forks(1)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .shouldFailOnError(true)
+
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs("-da",
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+LogCompilation",
+ "-XX:LogFile=/dev/null",
+ "-XX:+PrintAssembly",
+ "-XX:+PrintInterpreter",
+ "-XX:+PrintNMethods",
+ "-XX:+PrintNativeNMethods",
+ "-XX:+PrintSignatureHandlers",
+ "-XX:+PrintAdapterHandlers",
+ "-XX:+PrintStubCode",
+ "-XX:+PrintCompilation",
+ "-XX:+PrintInlining",
+ "-XX:PrintAssemblyOptions=syntax",
+ "-XX:PrintAssemblyOptions=intel")
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Thread)
+ public static class SynchronizedMarkHolderBenchmark extends MarkHolderBenchmark {
+ @Override
+ public MarkHolder getMarkHolder() {
+ return new SynchronizedMarkHolder(16384);
+ }
+ }
+}
diff --git a/java6/src/jmh/java/io/perfmark/java6/VolatileGeneratorBenchmarkTest.java b/java6/src/jmh/java/io/perfmark/java6/VolatileGeneratorBenchmarkTest.java
new file mode 100644
index 0000000..de7aa6d
--- /dev/null
+++ b/java6/src/jmh/java/io/perfmark/java6/VolatileGeneratorBenchmarkTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.testing.GeneratorBenchmark;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+@RunWith(JUnit4.class)
+public class VolatileGeneratorBenchmarkTest {
+
+ @Test
+ public void generatorBenchmark() throws Exception {
+ Options options = new OptionsBuilder()
+ .include(VolatileGeneratorBenchmark.class.getCanonicalName())
+ .measurementIterations(5)
+ .warmupIterations(10)
+ .forks(1)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .shouldFailOnError(true)
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs("-da")
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Benchmark)
+ public static class VolatileGeneratorBenchmark extends GeneratorBenchmark {
+ @Override
+ protected Generator getGenerator() {
+ return new SecretVolatileGenerator.VolatileGenerator();
+ }
+ }
+}
diff --git a/java6/src/main/java/io/perfmark/java6/Internal.java b/java6/src/main/java/io/perfmark/java6/Internal.java
new file mode 100644
index 0000000..9b7115b
--- /dev/null
+++ b/java6/src/main/java/io/perfmark/java6/Internal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+public final class Internal {
+ private Internal() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java6/src/main/java/io/perfmark/java6/SecretSynchronizedMarkHolderProvider.java b/java6/src/main/java/io/perfmark/java6/SecretSynchronizedMarkHolderProvider.java
new file mode 100644
index 0000000..356820d
--- /dev/null
+++ b/java6/src/main/java/io/perfmark/java6/SecretSynchronizedMarkHolderProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.impl.MarkHolderProvider;
+
+final class SecretSynchronizedMarkHolderProvider {
+
+ public static final class SynchronizedMarkHolderProvider extends MarkHolderProvider {
+
+ public SynchronizedMarkHolderProvider() {}
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public MarkHolder create() {
+ return new SynchronizedMarkHolder();
+ }
+
+ @Override
+ public MarkHolder create(long markHolderId) {
+ return new SynchronizedMarkHolder();
+ }
+ }
+
+ private SecretSynchronizedMarkHolderProvider() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java6/src/main/java/io/perfmark/java6/SecretVolatileGenerator.java b/java6/src/main/java/io/perfmark/java6/SecretVolatileGenerator.java
new file mode 100644
index 0000000..efeb0da
--- /dev/null
+++ b/java6/src/main/java/io/perfmark/java6/SecretVolatileGenerator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.Generator;
+
+final class SecretVolatileGenerator {
+
+ // @UsedReflectively
+ public static final class VolatileGenerator extends Generator {
+
+ // @UsedReflectively
+ public VolatileGenerator() {}
+
+ private volatile long gen;
+
+ @Override
+ public void setGeneration(long generation) {
+ gen = generation;
+ }
+
+ @Override
+ public long getGeneration() {
+ return gen;
+ }
+
+ @Override
+ public long costOfGetNanos() {
+ return 3;
+ }
+
+ @Override
+ public long costOfSetNanos() {
+ return 10;
+ }
+ }
+
+ private SecretVolatileGenerator() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java6/src/main/java/io/perfmark/java6/SynchronizedMarkHolder.java b/java6/src/main/java/io/perfmark/java6/SynchronizedMarkHolder.java
new file mode 100644
index 0000000..4b4afeb
--- /dev/null
+++ b/java6/src/main/java/io/perfmark/java6/SynchronizedMarkHolder.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import java.util.AbstractCollection;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+
+final class SynchronizedMarkHolder extends MarkHolder {
+ private static final long GEN_MASK = (1 << Generator.GEN_OFFSET) - 1;
+
+ private static final long START_N1S1_OP = Mark.Operation.TASK_START_N1S1.ordinal();
+ private static final long START_N1S2_OP = Mark.Operation.TASK_START_N1S2.ordinal();
+ private static final long STOP_N1S0_OP = Mark.Operation.TASK_END_N1S0.ordinal();
+ private static final long STOP_N1S1_OP = Mark.Operation.TASK_END_N1S1.ordinal();
+ private static final long STOP_N1S2_OP = Mark.Operation.TASK_END_N1S2.ordinal();
+ private static final long EVENT_N1S1_OP = Mark.Operation.EVENT_N1S1.ordinal();
+ private static final long EVENT_N1S2_OP = Mark.Operation.EVENT_N1S2.ordinal();
+ private static final long EVENT_N2S2_OP = Mark.Operation.EVENT_N2S2.ordinal();
+ private static final long LINK_OP = Mark.Operation.LINK.ordinal();
+ private static final long TAG_N1S1_OP = Mark.Operation.TAG_N1S1.ordinal();
+ private static final long TAG_KEYED_N0S2_OP = Mark.Operation.TAG_KEYED_N0S2.ordinal();
+ private static final long TAG_KEYED_N1S1_OP = Mark.Operation.TAG_KEYED_N1S1.ordinal();
+ private static final long TAG_KEYED_N2S1_OP = Mark.Operation.TAG_KEYED_N2S1.ordinal();
+
+ private final int maxEvents;
+ private final long maxEventsMask;
+
+ // where to write to next
+ private long nIdx;
+ private long sIdx;
+
+ private final long[] nums;
+ private final String[] strings;
+
+ SynchronizedMarkHolder() {
+ this(32768);
+ }
+
+ SynchronizedMarkHolder(int maxEvents) {
+ if (((maxEvents - 1) & maxEvents) != 0) {
+ throw new IllegalArgumentException(maxEvents + " is not a power of two");
+ }
+ if (maxEvents <= 0) {
+ throw new IllegalArgumentException(maxEvents + " is not positive");
+ }
+ this.maxEvents = maxEvents;
+ this.maxEventsMask = maxEvents - 1L;
+ this.nums = new long[maxEvents];
+ this.strings = new String[maxEvents];
+ }
+
+ private void writeNnss(long genOp, long n0, long n1, String s0, String s1) {
+ nums[(int) (nIdx++ & maxEventsMask)] = n0;
+ nums[(int) (nIdx++ & maxEventsMask)] = n1;
+ strings[(int) (sIdx++ & maxEventsMask)] = s0;
+ strings[(int) (sIdx++ & maxEventsMask)] = s1;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ private void writeNss(long genOp, long n0, String s0, String s1) {
+ nums[(int) (nIdx++ & maxEventsMask)] = n0;
+ strings[(int) (sIdx++ & maxEventsMask)] = s0;
+ strings[(int) (sIdx++ & maxEventsMask)] = s1;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ private void writeNs(long genOp, long n0, String s0) {
+ nums[(int) (nIdx++ & maxEventsMask)] = n0;
+ strings[(int) (sIdx++ & maxEventsMask)] = s0;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ private void writeN(long genOp, long n0) {
+ nums[(int) (nIdx++ & maxEventsMask)] = n0;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ private void writeNns(long genOp, long n0, long n1, String s0) {
+ nums[(int) (nIdx++ & maxEventsMask)] = n0;
+ nums[(int) (nIdx++ & maxEventsMask)] = n1;
+ strings[(int) (sIdx++ & maxEventsMask)] = s0;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ private void writeSs(long genOp, String s0, String s1) {
+ strings[(int) (sIdx++ & maxEventsMask)] = s0;
+ strings[(int) (sIdx++ & maxEventsMask)] = s1;
+ nums[(int) (nIdx++ & maxEventsMask)] = genOp;
+ }
+
+ @Override
+ public synchronized void start(
+ long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ writeNs(gen + START_N1S1_OP, nanoTime, taskName);
+ writeNs(gen + TAG_N1S1_OP, tagId, tagName);
+ }
+
+ @Override
+ public synchronized void start(long gen, String taskName, long nanoTime) {
+ writeNs(gen + START_N1S1_OP, nanoTime, taskName);
+ }
+
+ @Override
+ public synchronized void start(long gen, String taskName, String subTaskName, long nanoTime) {
+ writeNss(gen + START_N1S2_OP, nanoTime, taskName, subTaskName);
+ }
+
+ @Override
+ public synchronized void link(long gen, long linkId) {
+ writeN(gen + LINK_OP, linkId);
+ }
+
+ @Override
+ public synchronized void stop(long gen, long nanoTime) {
+ writeN(gen + STOP_N1S0_OP, nanoTime);
+ }
+
+ @Override
+ public synchronized void stop(
+ long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ writeNs(gen + TAG_N1S1_OP, tagId, tagName);
+ writeNs(gen + STOP_N1S1_OP, nanoTime, taskName);
+ }
+
+ @Override
+ public synchronized void stop(long gen, String taskName, long nanoTime) {
+ writeNs(gen + STOP_N1S1_OP, nanoTime, taskName);
+ }
+
+ @Override
+ public synchronized void stop(long gen, String taskName, String subTaskName, long nanoTime) {
+ writeNss(gen + STOP_N1S2_OP, nanoTime, taskName, subTaskName);
+ }
+
+ @Override
+ public synchronized void event(
+ long gen, String eventName, String tagName, long tagId, long nanoTime) {
+ writeNnss(gen + EVENT_N2S2_OP, nanoTime, tagId, eventName, tagName);
+ }
+
+ @Override
+ public synchronized void event(long gen, String eventName, long nanoTime) {
+ writeNs(gen + EVENT_N1S1_OP, nanoTime, eventName);
+ }
+
+ @Override
+ public synchronized void event(long gen, String eventName, String subEventName, long nanoTime) {
+ writeNss(gen + EVENT_N1S2_OP, nanoTime, eventName, subEventName);
+ }
+
+ @Override
+ public synchronized void attachTag(long gen, String tagName, long tagId) {
+ writeNs(gen + TAG_N1S1_OP, tagId, tagName);
+ }
+
+ @Override
+ public synchronized void attachKeyedTag(long gen, String name, long value0) {
+ writeNs(gen + TAG_KEYED_N1S1_OP, value0, name);
+ }
+
+ @Override
+ public synchronized void attachKeyedTag(long gen, String name, String value) {
+ writeSs(gen + TAG_KEYED_N0S2_OP, name, value);
+ }
+
+ @Override
+ public synchronized void attachKeyedTag(long gen, String name, long value0, long value1) {
+ writeNns(gen + TAG_KEYED_N2S1_OP, value0, value1, name);
+ }
+
+ @Override
+ public synchronized void resetForTest() {
+ Arrays.fill(nums, 0);
+ Arrays.fill(strings, null);
+ nIdx = 0;
+ sIdx = 0;
+ }
+
+ @Override
+ public List<Mark> read(boolean concurrentWrites) {
+ Kyoo<Long> numQ;
+ Kyoo<String> stringQ;
+ {
+ final long[] nums = new long[maxEvents];
+ final String[] strings = new String[maxEvents];
+ final long nIdx;
+ final long sIdx;
+
+ synchronized (this) {
+ System.arraycopy(this.nums, 0, nums, 0, maxEvents);
+ System.arraycopy(this.strings, 0, strings, 0, maxEvents);
+ nIdx = this.nIdx;
+ sIdx = this.sIdx;
+ }
+ Long[] numsBoxed = new Long[nums.length];
+ for (int i = 0; i < nums.length; i++) {
+ numsBoxed[i] = nums[i];
+ }
+ numQ = new Kyoo<Long>(numsBoxed, nIdx, (int) Math.min(nIdx, maxEvents));
+ stringQ = new Kyoo<String>(strings, sIdx, (int) Math.min(sIdx, maxEvents));
+ }
+
+ Deque<Mark> marks = new ArrayDeque<Mark>(maxEvents);
+
+ while (true) {
+ if (numQ.isEmpty()) {
+ break;
+ }
+ long genOp = numQ.remove();
+ long gen = genOp & ~GEN_MASK;
+ Mark.Operation op = Mark.Operation.valueOf((int) (genOp & GEN_MASK));
+
+ if (op.getNumbers() > numQ.size()
+ || op.getStrings() > stringQ.size()) {
+ break;
+ }
+ long n1;
+ String s1;
+ long n2;
+ String s2;
+ switch (op) {
+ case TASK_START_N1S1:
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.taskStart(gen, n1, s1));
+ break;
+ case TASK_START_N1S2:
+ n1 = numQ.remove();
+ s2 = stringQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.taskStart(gen, n1, s1, s2));
+ break;
+ case TASK_END_N1S0:
+ n1 = numQ.remove();
+ marks.addFirst(Mark.taskEnd(gen, n1));
+ break;
+ case TASK_END_N1S1:
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.taskEnd(gen, n1, s1));
+ break;
+ case TASK_END_N1S2:
+ n1 = numQ.remove();
+ s2 = stringQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.taskEnd(gen, n1, s1, s2));
+ break;
+ case EVENT_N1S1:
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.event(gen, n1, s1));
+ break;
+ case EVENT_N1S2:
+ n1 = numQ.remove();
+ s2 = stringQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.event(gen, n1, s1, s2));
+ break;
+ case EVENT_N2S2:
+ n2 = numQ.remove();
+ s2 = stringQ.remove();
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.event(gen, n1, s1, s2, n2));
+ break;
+ case EVENT_N2S3:
+ throw new UnsupportedOperationException();
+ case LINK:
+ n1 = numQ.remove();
+ marks.addFirst(Mark.link(gen, n1));
+ break;
+ case TAG_N0S1:
+ throw new UnsupportedOperationException();
+ case TAG_N1S0:
+ throw new UnsupportedOperationException();
+ case TAG_N1S1:
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.tag(gen, s1, n1));
+ break;
+ case TAG_KEYED_N0S2:
+ s2 = stringQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.keyedTag(gen, s1, s2));
+ break;
+ case TAG_KEYED_N1S1:
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.keyedTag(gen, s1, n1));
+ break;
+ case TAG_KEYED_N2S1:
+ n2 = numQ.remove();
+ n1 = numQ.remove();
+ s1 = stringQ.remove();
+ marks.addFirst(Mark.keyedTag(gen, s1, n1, n2));
+ break;
+ case NONE:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ return Collections.unmodifiableList(new ArrayList<Mark>(marks));
+ }
+
+ @Override
+ public int maxMarks() {
+ return maxEvents;
+ }
+
+ private final class Kyoo<T> extends AbstractCollection<T> implements Queue<T> {
+
+ private final T[] elements;
+ private final long wIdx;
+ private final int size;
+
+ private int ri;
+
+ Kyoo(T[] elements, long wIdx, int size) {
+ this.elements = elements;
+ this.wIdx = wIdx;
+ this.size = size;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ return size - ri;
+ }
+
+ @Override
+ public boolean offer(T t) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T remove() {
+ checkSize();
+ return poll();
+ }
+
+ @Override
+ public T poll() {
+ if (size() == 0) {
+ return null;
+ }
+ int rIdx = (int) (((wIdx - 1) - ri++) & maxEventsMask);
+ return elements[rIdx];
+ }
+
+ @Override
+ public T element() {
+ checkSize();
+ return peek();
+ }
+
+ @Override
+ public T peek() {
+ if (size() == 0) {
+ return null;
+ }
+ int rIdx = (int) (((wIdx - 1) - ri) & maxEventsMask);
+ return elements[rIdx];
+ }
+
+ private void checkSize() {
+ if (size() == 0) {
+ throw new IllegalStateException();
+ }
+ }
+ }
+}
diff --git a/java6/src/main/java/io/perfmark/java6/package-info.java b/java6/src/main/java/io/perfmark/java6/package-info.java
new file mode 100644
index 0000000..824dad2
--- /dev/null
+++ b/java6/src/main/java/io/perfmark/java6/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.java6;
diff --git a/java6/src/test/java/io/perfmark/java6/AutoLoadTest.java b/java6/src/test/java/io/perfmark/java6/AutoLoadTest.java
new file mode 100644
index 0000000..97841be
--- /dev/null
+++ b/java6/src/test/java/io/perfmark/java6/AutoLoadTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import static org.junit.Assert.assertEquals;
+
+import io.perfmark.PerfMark;
+import io.perfmark.impl.MarkList;
+import io.perfmark.impl.Storage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Checks that auto loading this provider works.
+ */
+@RunWith(JUnit4.class)
+public class AutoLoadTest {
+ @Test
+ public void autoLoad() {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+ PerfMark.startTask("hi");
+ PerfMark.stopTask("hi");
+ PerfMark.setEnabled(false);
+ MarkList markList = Storage.readForTest();
+ assertEquals(2, markList.size());
+ }
+}
diff --git a/java6/src/test/java/io/perfmark/java6/SynchronizedMarkHolderTest.java b/java6/src/test/java/io/perfmark/java6/SynchronizedMarkHolderTest.java
new file mode 100644
index 0000000..9f1ad68
--- /dev/null
+++ b/java6/src/test/java/io/perfmark/java6/SynchronizedMarkHolderTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.MarkHolderTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SynchronizedMarkHolderTest extends MarkHolderTest {
+
+ @Override
+ protected MarkHolder getMarkHolder() {
+ return new SynchronizedMarkHolder();
+ }
+}
diff --git a/java6/src/test/java/io/perfmark/java6/VersionTest.java b/java6/src/test/java/io/perfmark/java6/VersionTest.java
new file mode 100644
index 0000000..a6ef6ba
--- /dev/null
+++ b/java6/src/test/java/io/perfmark/java6/VersionTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java6;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VersionTest {
+
+ private static final short JAVA_VERSION_6 = 50;
+
+ @Test
+ public void blah() throws Exception {
+ Class<?> clz = SecretSynchronizedMarkHolderProvider.class;
+ try (InputStream stream =
+ clz.getClassLoader().getResourceAsStream(clz.getName().replace('.', '/') + ".class")) {
+ byte[] data = stream.readAllBytes();
+ ByteBuffer buf = ByteBuffer.wrap(data);
+ // Discard magic int
+ buf.getInt();
+ short major = buf.getShort();
+ short minor = buf.getShort();
+ assertEquals(0, major);
+ assertEquals(JAVA_VERSION_6, minor);
+ }
+ }
+}
diff --git a/java7/BUILD.bazel b/java7/BUILD.bazel
new file mode 100644
index 0000000..7c98084
--- /dev/null
+++ b/java7/BUILD.bazel
@@ -0,0 +1,9 @@
+java_library(
+ name = "generator",
+ srcs = [
+ "src/main/java/io/perfmark/java7/SecretMethodHandleGenerator.java",
+ ],
+ deps = [
+ "//impl:generator",
+ ],
+)
diff --git a/java7/build.gradle b/java7/build.gradle
new file mode 100644
index 0000000..6173dd4
--- /dev/null
+++ b/java7/build.gradle
@@ -0,0 +1,59 @@
+
+description = "PerfMark Java7 API"
+ext.moduleName = "io.perfmark.javaseven"
+ext.jdkVersion = JavaVersion.VERSION_1_7
+
+compileJava {
+ sourceCompatibility = jdkVersion
+ targetCompatibility = jdkVersion
+
+ options.compilerArgs.add("-Xlint:-options")
+}
+
+sourceSets {
+ jmh {}
+}
+
+
+compileJmhJava {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ javaCompiler = javaToolchains.compilerFor({
+ languageVersion = JavaLanguageVersion.of("11")
+ })
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+}
+
+dependencies {
+ implementation project(':perfmark-impl')
+ compileOnly libs.jsr305
+
+ jmhImplementation project(':perfmark-api'),
+ project(':perfmark-impl'),
+ project(':perfmark-java7'),
+ project(':perfmark-testing')
+ jmhImplementation libs.junit
+ jmhImplementation libs.jmhcore
+ jmhAnnotationProcessor libs.jmhanno
+}
+
+javadoc {
+ exclude 'io/perfmark/java7**'
+}
+
+jar {
+ exclude 'io/perfmark/java7/Internal*'
+}
+
+tasks.register('jmh', Test) {
+ description = 'Runs integration tests.'
+ group = 'stress'
+
+ testClassesDirs = sourceSets.jmh.output.classesDirs
+ classpath = sourceSets.jmh.runtimeClasspath
+
+ javaLauncher = javaToolchains.launcherFor({
+ languageVersion = JavaLanguageVersion.of("16")
+ })
+ //shouldRunAfter test
+} \ No newline at end of file
diff --git a/java7/src/jmh/java/io/perfmark/java7/MethodHandleGeneratorBenchmarkTest.java b/java7/src/jmh/java/io/perfmark/java7/MethodHandleGeneratorBenchmarkTest.java
new file mode 100644
index 0000000..e10285f
--- /dev/null
+++ b/java7/src/jmh/java/io/perfmark/java7/MethodHandleGeneratorBenchmarkTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java7;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.testing.GeneratorBenchmark;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+import org.openjdk.jmh.runner.options.VerboseMode;
+
+@RunWith(JUnit4.class)
+public class MethodHandleGeneratorBenchmarkTest {
+
+ @Test
+ public void generatorBenchmark() throws Exception {
+ Options options = new OptionsBuilder()
+ .include(MethodHandleGeneratorBenchmark.class.getCanonicalName())
+ .measurementIterations(5)
+ .warmupIterations(10)
+ .forks(1)
+ .verbosity(VerboseMode.EXTRA)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .shouldFailOnError(true)
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs("-da")
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Benchmark)
+ public static class MethodHandleGeneratorBenchmark extends GeneratorBenchmark {
+ @Override
+ protected Generator getGenerator() {
+ return new SecretMethodHandleGenerator.MethodHandleGenerator();
+ }
+ }
+}
diff --git a/java7/src/main/java/io/perfmark/java7/Internal.java b/java7/src/main/java/io/perfmark/java7/Internal.java
new file mode 100644
index 0000000..5578e2b
--- /dev/null
+++ b/java7/src/main/java/io/perfmark/java7/Internal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java7;
+
+public final class Internal {
+
+ private Internal() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java7/src/main/java/io/perfmark/java7/SecretMethodHandleGenerator.java b/java7/src/main/java/io/perfmark/java7/SecretMethodHandleGenerator.java
new file mode 100644
index 0000000..1ab5d36
--- /dev/null
+++ b/java7/src/main/java/io/perfmark/java7/SecretMethodHandleGenerator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java7;
+
+import io.perfmark.impl.Generator;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MutableCallSite;
+
+final class SecretMethodHandleGenerator {
+
+ // UsedReflectively
+ public static final class MethodHandleGenerator extends Generator {
+ private static final MutableCallSite currentGeneration =
+ new MutableCallSite(MethodHandles.constant(long.class, 0));
+ private static final MutableCallSite[] currentGenerations =
+ new MutableCallSite[] {currentGeneration};
+ private static final MethodHandle currentGenerationGetter = currentGeneration.dynamicInvoker();
+
+ public MethodHandleGenerator() {}
+
+ @Override
+ public long getGeneration() {
+ try {
+ return (long) currentGenerationGetter.invoke();
+ } catch (Throwable throwable) {
+ return FAILURE;
+ }
+ }
+
+ @Override
+ public void setGeneration(long generation) {
+ currentGeneration.setTarget(MethodHandles.constant(long.class, generation));
+ MutableCallSite.syncAll(currentGenerations);
+ }
+
+ @Override
+ public long costOfGetNanos() {
+ // Method handles compile to constants, so this is effectively free.
+ // JMH testing on a Skylake x86_64 processor shows the cost to be about 0.3ns.
+ return 0;
+ }
+
+ @Override
+ public long costOfSetNanos() {
+ // based on JMH testing on a Skylake x86_64 processor.
+ return 2_000_000;
+ }
+ }
+
+ private SecretMethodHandleGenerator() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java7/src/main/java/io/perfmark/java7/package-info.java b/java7/src/main/java/io/perfmark/java7/package-info.java
new file mode 100644
index 0000000..0878c1e
--- /dev/null
+++ b/java7/src/main/java/io/perfmark/java7/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.java7;
diff --git a/java7/src/test/java/io/perfmark/java7/VersionTest.java b/java7/src/test/java/io/perfmark/java7/VersionTest.java
new file mode 100644
index 0000000..03b65aa
--- /dev/null
+++ b/java7/src/test/java/io/perfmark/java7/VersionTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java7;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VersionTest {
+
+ private static final short JAVA_VERSION_7 = 51;
+
+ @Test
+ public void blah() throws Exception {
+ Class<?> clz = SecretMethodHandleGenerator.class;
+ try (InputStream stream =
+ clz.getClassLoader().getResourceAsStream(clz.getName().replace('.', '/') + ".class")) {
+ byte[] data = stream.readAllBytes();
+ ByteBuffer buf = ByteBuffer.wrap(data);
+ // Discard magic int
+ buf.getInt();
+ short major = buf.getShort();
+ short minor = buf.getShort();
+ assertEquals(0, major);
+ assertEquals(JAVA_VERSION_7, minor);
+ }
+ }
+}
diff --git a/java9/BUILD.bazel b/java9/BUILD.bazel
new file mode 100644
index 0000000..7a0fc5f
--- /dev/null
+++ b/java9/BUILD.bazel
@@ -0,0 +1,34 @@
+java_library(
+ name = "generator",
+ srcs = [
+ "src/main/java/io/perfmark/java9/SecretVarHandleGenerator.java",
+ ],
+ deps = [
+ "//impl:generator",
+ ],
+)
+
+java_library(
+ name = "mark-holder",
+ srcs = [
+ "src/main/java/io/perfmark/java9/VarHandleMarkHolder.java",
+ ],
+ deps = [
+ "//impl:generator",
+ "//impl:mark",
+ "//impl:mark-holder",
+ ],
+)
+
+java_library(
+ name = "mark-holder-provider",
+ srcs = [
+ "src/main/java/io/perfmark/java9/SecretVarHandleMarkHolderProvider.java",
+ ],
+ deps = [
+ ":mark-holder",
+ "//impl:generator",
+ "//impl:mark-holder",
+ "//impl:mark-holder-provider",
+ ],
+)
diff --git a/java9/build.gradle.kts b/java9/build.gradle.kts
new file mode 100644
index 0000000..5a163a4
--- /dev/null
+++ b/java9/build.gradle.kts
@@ -0,0 +1,88 @@
+import net.ltgt.gradle.errorprone.errorprone
+
+plugins {
+ id("io.github.reyerizo.gradle.jcstress")
+}
+
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.javanine")
+ }
+}
+
+val jdkVersion = JavaVersion.VERSION_1_9
+
+description = "PerfMark Java9 API"
+
+sourceSets {
+ create("jmh")
+}
+
+val jmhImplementation by configurations.getting {
+ extendsFrom(configurations.implementation.get())
+}
+
+val jmhAnnotationProcessor by configurations.getting {
+ extendsFrom(configurations.annotationProcessor.get())
+}
+
+dependencies {
+ implementation(project(":perfmark-impl"))
+ compileOnly(libs.jsr305)
+
+ testImplementation(project(":perfmark-api"))
+ testImplementation(project(":perfmark-testing"))
+ jcstressImplementation(project(":perfmark-impl"))
+
+ jmhImplementation(project(":perfmark-api"))
+ jmhImplementation(project(":perfmark-impl"))
+ jmhImplementation(project(":perfmark-java9"))
+ jmhImplementation(project(":perfmark-testing"))
+ jmhImplementation(libs.junit)
+ jmhImplementation(libs.jmhcore)
+ jmhAnnotationProcessor(libs.jmhanno)
+}
+
+tasks.named<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+}
+
+tasks.named<JavaCompile>("compileJmhJava") {
+ sourceCompatibility = JavaVersion.VERSION_11.toString()
+ targetCompatibility = JavaVersion.VERSION_11.toString()
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+
+}
+
+tasks.register<Test>("jmh") {
+ description = "Runs integration tests."
+ group = "stress"
+
+ testClassesDirs = sourceSets["jmh"].output.classesDirs
+ classpath = sourceSets["jmh"].runtimeClasspath
+
+ javaLauncher.set(javaToolchains.launcherFor({
+ languageVersion.set(JavaLanguageVersion.of("11"))
+ }))
+}
+
+
+tasks.named<Jar>("jar") {
+ exclude("io/perfmark/java9/Internal*")
+}
+
+tasks.named<Javadoc>("javadoc") {
+ exclude("io/perfmark/java9/**")
+}
+
+// ./gradlew --no-daemon clean :perfmark-java9:jcstress
+jcstress {
+ jcstressDependency = "org.openjdk.jcstress:jcstress-core:0.5"
+ // mode "tough"
+ deoptRatio = "2"
+}
+
+tasks.named<JavaCompile>("compileJcstressJava") {
+ options.errorprone.excludedPaths.set(".*/build/generated/sources/annotationProcessor/.*")
+}
diff --git a/java9/src/jcstress/java/io/perfmark/java9/PerfMarkStorageStress.java b/java9/src/jcstress/java/io/perfmark/java9/PerfMarkStorageStress.java
new file mode 100644
index 0000000..5c80cb9
--- /dev/null
+++ b/java9/src/jcstress/java/io/perfmark/java9/PerfMarkStorageStress.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import java.util.List;
+import org.openjdk.jcstress.annotations.Actor;
+import org.openjdk.jcstress.annotations.Description;
+import org.openjdk.jcstress.annotations.Expect;
+import org.openjdk.jcstress.annotations.JCStressTest;
+import org.openjdk.jcstress.annotations.Outcome;
+import org.openjdk.jcstress.annotations.State;
+import org.openjdk.jcstress.infra.results.L_Result;
+
+/** Simulates the PerfMarkStorage racy reader. */
+@JCStressTest
+@Outcome(id = "0", expect = Expect.ACCEPTABLE, desc = "0 Writes")
+@Outcome(id = "1", expect = Expect.ACCEPTABLE, desc = "1 Write")
+@Outcome(id = "2", expect = Expect.ACCEPTABLE, desc = "2 Writes")
+@Outcome(id = "3", expect = Expect.ACCEPTABLE, desc = "3 Writes")
+@Outcome(id = "4", expect = Expect.ACCEPTABLE, desc = "4 Writes")
+@Outcome(id = "5", expect = Expect.ACCEPTABLE, desc = "5 Writes")
+@Outcome(id = "6", expect = Expect.ACCEPTABLE, desc = "6 Writes")
+@Outcome(id = "7", expect = Expect.ACCEPTABLE, desc = "7 Writes")
+@Outcome(id = "8", expect = Expect.ACCEPTABLE, desc = "8 Writes")
+@Outcome(id = "9", expect = Expect.ACCEPTABLE, desc = "9 Writes")
+@Outcome(id = "10", expect = Expect.ACCEPTABLE, desc = "10 Writes")
+@Outcome(id = "11", expect = Expect.ACCEPTABLE, desc = "11 Writes")
+@Outcome(id = "12", expect = Expect.ACCEPTABLE, desc = "12 Writes")
+@Outcome(id = "13", expect = Expect.ACCEPTABLE, desc = "13 Writes")
+@Outcome(id = "14", expect = Expect.ACCEPTABLE, desc = "14 Writes")
+@Outcome(id = "15", expect = Expect.ACCEPTABLE, desc = "15 Writes")
+@Outcome(id = "16", expect = Expect.ACCEPTABLE, desc = "16 Writes")
+@Outcome(id = "17", expect = Expect.ACCEPTABLE, desc = "17 Writes")
+@Outcome(id = "18", expect = Expect.ACCEPTABLE, desc = "18 Writes")
+@Outcome(id = "19", expect = Expect.ACCEPTABLE, desc = "19 Writes")
+@Outcome(id = "20", expect = Expect.ACCEPTABLE, desc = "20 Writes")
+@Outcome(id = "21", expect = Expect.ACCEPTABLE, desc = "21 Writes")
+@Outcome(id = "22", expect = Expect.ACCEPTABLE, desc = "22 Writes")
+@Outcome(id = "23", expect = Expect.ACCEPTABLE, desc = "23 Writes")
+@Outcome(id = "24", expect = Expect.ACCEPTABLE, desc = "24 Writes")
+@Outcome(id = "25", expect = Expect.ACCEPTABLE, desc = "25 Writes")
+@Outcome(id = "26", expect = Expect.ACCEPTABLE, desc = "26 Writes")
+@Outcome(id = "27", expect = Expect.ACCEPTABLE, desc = "27 Writes")
+@Outcome(id = "28", expect = Expect.ACCEPTABLE, desc = "28 Writes")
+@Outcome(id = "29", expect = Expect.ACCEPTABLE, desc = "29 Writes")
+@Outcome(id = "30", expect = Expect.ACCEPTABLE, desc = "30 Writes")
+@Outcome(id = "31", expect = Expect.ACCEPTABLE, desc = "31 Writes")
+@Outcome(id = "32", expect = Expect.ACCEPTABLE, desc = "32 Writes")
+@Outcome(id = "-1", expect = Expect.FORBIDDEN, desc = "Wrong Type")
+@Outcome(id = "-3", expect = Expect.FORBIDDEN, desc = "Wrong ID")
+@State
+@Description("Simulates the PerfMarkStorage reader.")
+public class PerfMarkStorageStress {
+ private static final int OFFSET;
+ private static final int SIZE = 32;
+
+ static {
+ OFFSET = 31;
+ assert Generator.GEN_OFFSET <= OFFSET;
+ }
+
+ private final VarHandleMarkHolder holder = new VarHandleMarkHolder(SIZE);
+
+ @Actor
+ public void writer() {
+ for (long i = 0; i < SIZE * 4; i++) {
+ holder.link(i << OFFSET, i);
+ }
+ }
+
+ @Actor
+ @SuppressWarnings("ReferenceEquality")
+ public void reader(L_Result r) {
+ List<Mark> marks = holder.read(true);
+ int ret = marks.size();
+ for (int i = 0; i < marks.size(); i++) {
+ Mark mark = marks.get(i);
+ if (mark.getOperation() != Mark.Operation.LINK) {
+ ret = -1;
+ break;
+ } else if (mark.getGeneration() >>> OFFSET != mark.getLinkId()) {
+ ret = -2;
+ break;
+ } else {
+ // keep going
+ }
+ }
+ r.r1 = ret;
+ }
+}
diff --git a/java9/src/jmh/java/io/perfmark/java9/VarHandleGeneratorBenchmarkTest.java b/java9/src/jmh/java/io/perfmark/java9/VarHandleGeneratorBenchmarkTest.java
new file mode 100644
index 0000000..b16267a
--- /dev/null
+++ b/java9/src/jmh/java/io/perfmark/java9/VarHandleGeneratorBenchmarkTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.testing.GarbageCollector;
+import io.perfmark.testing.GeneratorBenchmark;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+import org.openjdk.jmh.runner.options.VerboseMode;
+
+@RunWith(JUnit4.class)
+public class VarHandleGeneratorBenchmarkTest {
+
+ @Test
+ public void generatorBenchmark() throws Exception {
+ Options options = new OptionsBuilder()
+ .include(VarHandleGeneratorBenchmark.class.getCanonicalName())
+ .measurementIterations(5)
+ .warmupIterations(10)
+ .forks(1)
+ .verbosity(VerboseMode.EXTRA)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .shouldFailOnError(true)
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs("-da")
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Benchmark)
+ public static class VarHandleGeneratorBenchmark extends GeneratorBenchmark {
+ @Override
+ protected Generator getGenerator() {
+ return new SecretVarHandleGenerator.VarHandleGenerator();
+ }
+ }
+}
diff --git a/java9/src/jmh/java/io/perfmark/java9/VarHandleMarkHolderBenchmarkTest.java b/java9/src/jmh/java/io/perfmark/java9/VarHandleMarkHolderBenchmarkTest.java
new file mode 100644
index 0000000..b6162fe
--- /dev/null
+++ b/java9/src/jmh/java/io/perfmark/java9/VarHandleMarkHolderBenchmarkTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import static java.util.List.of;
+
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.GarbageCollector;
+import io.perfmark.testing.MarkHolderBenchmark;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+@RunWith(Parameterized.class)
+public class VarHandleMarkHolderBenchmarkTest {
+
+ @Parameterized.Parameter(0)
+ public GarbageCollector gc;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> args() {
+ List<Object[]> cases = new ArrayList<>();
+ for (GarbageCollector gc : GarbageCollector.values()) {
+ if (gc != GarbageCollector.CMS) {
+ continue;
+ }
+ cases.add(List.of(gc).toArray(new Object[0]));
+ }
+
+ return List.copyOf(cases);
+ }
+
+ @Test
+ public void markHolderBenchmark() throws Exception {
+ List<String> jvmArgs = new ArrayList<>();
+ jvmArgs.add("-da");
+ jvmArgs.addAll(gc.jvmArgs());
+ Options options = new OptionsBuilder()
+ .include(VarHandleMarkHolderBenchmark.class.getCanonicalName())
+ .measurementIterations(10)
+ .warmupIterations(10)
+ .forks(1)
+ .warmupTime(TimeValue.seconds(1))
+ .measurementTime(TimeValue.seconds(1))
+ .param("GC", gc.name())
+ .shouldFailOnError(true)
+ // This is necessary to run in the IDE, otherwise it would inherit the VM args.
+ .jvmArgs(jvmArgs.toArray(new String[0]))
+ .build();
+
+ new Runner(options).run();
+ }
+
+ @State(Scope.Thread)
+ public static class VarHandleMarkHolderBenchmark extends MarkHolderBenchmark {
+ @Override
+ public MarkHolder getMarkHolder() {
+ return new VarHandleMarkHolder(16384);
+ }
+ }
+}
diff --git a/java9/src/main/java/io/perfmark/java9/Internal.java b/java9/src/main/java/io/perfmark/java9/Internal.java
new file mode 100644
index 0000000..f914736
--- /dev/null
+++ b/java9/src/main/java/io/perfmark/java9/Internal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+public final class Internal {
+ private Internal() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java9/src/main/java/io/perfmark/java9/SecretVarHandleGenerator.java b/java9/src/main/java/io/perfmark/java9/SecretVarHandleGenerator.java
new file mode 100644
index 0000000..e59ef44
--- /dev/null
+++ b/java9/src/main/java/io/perfmark/java9/SecretVarHandleGenerator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.impl.Generator;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+
+final class SecretVarHandleGenerator {
+
+ /**
+ * This class let's PerfMark have fairly low overhead detection if it is enabled, with reasonable
+ * time between enabled and other threads noticing. Since this uses Java 9 APIs, it may not be
+ * available.
+ */
+ public static final class VarHandleGenerator extends Generator {
+
+ private static final VarHandle GEN;
+
+ static {
+ try {
+ GEN = MethodHandles.lookup().findVarHandle(VarHandleGenerator.class, "gen", long.class);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public VarHandleGenerator() {}
+
+ @SuppressWarnings("unused")
+ private long gen;
+
+ @Override
+ public void setGeneration(long generation) {
+ GEN.setRelease(this, generation);
+ }
+
+ @Override
+ public long getGeneration() {
+ return (long) GEN.getAcquire(this);
+ }
+
+ @Override
+ public long costOfSetNanos() {
+ return 3;
+ }
+
+ @Override
+ public long costOfGetNanos() {
+ return 2;
+ }
+ }
+
+ private SecretVarHandleGenerator() {}
+}
diff --git a/java9/src/main/java/io/perfmark/java9/SecretVarHandleMarkHolderProvider.java b/java9/src/main/java/io/perfmark/java9/SecretVarHandleMarkHolderProvider.java
new file mode 100644
index 0000000..171e84c
--- /dev/null
+++ b/java9/src/main/java/io/perfmark/java9/SecretVarHandleMarkHolderProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.impl.MarkHolderProvider;
+
+final class SecretVarHandleMarkHolderProvider {
+
+ public static final class VarHandleMarkHolderProvider extends MarkHolderProvider {
+
+ public VarHandleMarkHolderProvider() {
+ // Do some basic operations to see if it works.
+ MarkHolder holder = create(12345);
+ holder.start(1 << Generator.GEN_OFFSET, "bogus", 0);
+ holder.stop(1 << Generator.GEN_OFFSET, "bogus", 0);
+ int size = holder.read(false).size();
+ if (size != 2) {
+ throw new AssertionError("Wrong size " + size);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public MarkHolder create() {
+ return new VarHandleMarkHolder();
+ }
+
+ @Override
+ public MarkHolder create(long markHolderId) {
+ return new VarHandleMarkHolder();
+ }
+ }
+
+ private SecretVarHandleMarkHolderProvider() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/java9/src/main/java/io/perfmark/java9/VarHandleMarkHolder.java b/java9/src/main/java/io/perfmark/java9/VarHandleMarkHolder.java
new file mode 100644
index 0000000..523663b
--- /dev/null
+++ b/java9/src/main/java/io/perfmark/java9/VarHandleMarkHolder.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.Deque;
+import java.util.List;
+
+/** VarHandleMarkHolder is a MarkHolder optimized for wait free writes and few reads. */
+final class VarHandleMarkHolder extends MarkHolder {
+ private static final long GEN_MASK = (1 << Generator.GEN_OFFSET) - 1;
+ private static final long START_OP = 1; // Mark.Operation.TASK_START.ordinal();
+ private static final long START_S_OP = 2;
+ private static final long START_T_OP = 3; // Mark.Operation.TASK_START_T.ordinal();
+ private static final long STOP_OP = 4; // Mark.Operation.TASK_END.ordinal();
+ private static final long STOP_V_OP = 5;
+ private static final long STOP_T_OP = 6; // Mark.Operation.TASK_END_T.ordinal();
+ private static final long STOP_S_OP = 7;
+ private static final long EVENT_OP = 8; // Mark.Operation.EVENT.ordinal();
+ private static final long EVENT_T_OP = 9; // Mark.Operation.EVENT_T.ordinal();
+ private static final long EVENT_S_OP = 10;
+ private static final long LINK_OP = 11; // Mark.Operation.LINK.ordinal();
+ private static final long ATTACH_T_OP = 12; // Mark.Operation.ATTACH_TAG.ordinal();
+ private static final long ATTACH_SS_OP = 13;
+ private static final long ATTACH_SN_OP = 14;
+ private static final long ATTACH_SNN_OP = 15;
+
+ private static final VarHandle IDX;
+ private static final VarHandle STRINGS;
+ private static final VarHandle LONGS;
+
+ static {
+ try {
+ IDX = MethodHandles.lookup().findVarHandle(VarHandleMarkHolder.class, "idx", long.class);
+ STRINGS = MethodHandles.arrayElementVarHandle(String[].class);
+ LONGS = MethodHandles.arrayElementVarHandle(long[].class);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final int maxEvents;
+ private final long maxEventsMax;
+
+ // where to write to next
+ @SuppressWarnings("unused") // Used Reflectively
+ private volatile long idx;
+
+ private final String[] taskNames;
+ private final String[] tagNames;
+ private final long[] tagIds;
+ private final long[] nanoTimes;
+ private final long[] genOps;
+
+ VarHandleMarkHolder() {
+ this(32768);
+ }
+
+ VarHandleMarkHolder(int maxEvents) {
+ if (((maxEvents - 1) & maxEvents) != 0) {
+ throw new IllegalArgumentException(maxEvents + " is not a power of two");
+ }
+ if (maxEvents <= 0) {
+ throw new IllegalArgumentException(maxEvents + " is not positive");
+ }
+ this.maxEvents = maxEvents;
+ this.maxEventsMax = maxEvents - 1L;
+ this.taskNames = new String[maxEvents];
+ this.tagNames = new String[maxEvents];
+ this.tagIds = new long[maxEvents];
+ this.nanoTimes = new long[maxEvents];
+ this.genOps = new long[maxEvents];
+ }
+
+ @Override
+ public void start(long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_T_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void start(long gen, String taskName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void start(long gen, String taskName, String subTaskName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, subTaskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + START_S_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void link(long gen, long linkId) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ LONGS.setOpaque(tagIds, i, linkId);
+ LONGS.setOpaque(genOps, i, gen + LINK_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_V_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_T_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void stop(long gen, String taskName, String subTaskName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, taskName);
+ STRINGS.setOpaque(tagNames, i, subTaskName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + STOP_S_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, String tagName, long tagId, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_T_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void event(long gen, String eventName, String subEventName, long nanoTime) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(taskNames, i, eventName);
+ STRINGS.setOpaque(tagNames, i, subEventName);
+ LONGS.setOpaque(nanoTimes, i, nanoTime);
+ LONGS.setOpaque(genOps, i, gen + EVENT_S_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachTag(long gen, String tagName, long tagId) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(tagNames, i, tagName);
+ LONGS.setOpaque(tagIds, i, tagId);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_T_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(tagNames, i, name);
+ LONGS.setOpaque(tagIds, i, value);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SN_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, long value0, long value1) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(tagNames, i, name);
+ LONGS.setOpaque(tagIds, i, value0);
+ LONGS.setOpaque(nanoTimes, i, value1);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SNN_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void attachKeyedTag(long gen, String name, String value) {
+ long localIdx = (long) IDX.get(this);
+ int i = (int) (localIdx & maxEventsMax);
+ STRINGS.setOpaque(tagNames, i, name);
+ STRINGS.setOpaque(taskNames, i, value);
+ LONGS.setOpaque(genOps, i, gen + ATTACH_SS_OP);
+ IDX.setRelease(this, localIdx + 1);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public void resetForTest() {
+ Arrays.fill(taskNames, null);
+ Arrays.fill(tagNames, null);
+ Arrays.fill(tagIds, 0);
+ Arrays.fill(nanoTimes, 0);
+ Arrays.fill(genOps, 0);
+ IDX.setRelease(this, 0L);
+ VarHandle.storeStoreFence();
+ }
+
+ @Override
+ public List<Mark> read(boolean concurrentWrites) {
+ final String[] localTaskNames = new String[maxEvents];
+ final String[] localTagNames = new String[maxEvents];
+ final long[] localTagIds = new long[maxEvents];
+ final long[] localNanoTimes = new long[maxEvents];
+ final long[] localGenOps = new long[maxEvents];
+ long startIdx = (long) IDX.getOpaque(this);
+ VarHandle.loadLoadFence();
+ int size = (int) Math.min(startIdx, maxEvents);
+ for (int i = 0; i < size; i++) {
+ localTaskNames[i] = (String) STRINGS.getOpaque(taskNames, i);
+ localTagNames[i] = (String) STRINGS.getOpaque(tagNames, i);
+ localTagIds[i] = (long) LONGS.getOpaque(tagIds, i);
+ localNanoTimes[i] = (long) LONGS.getOpaque(nanoTimes, i);
+ localGenOps[i] = (long) LONGS.getOpaque(genOps, i);
+ }
+ VarHandle.loadLoadFence();
+ long endIdx = (long) IDX.getOpaque(this);
+ if (endIdx < startIdx) {
+ throw new AssertionError();
+ }
+ // If we are reading from ourselves (such as in a test), we can assume there isn't an in
+ // progress write modifying the oldest entry. Additionally, if the writer has not yet
+ // wrapped around, the last entry cannot have been corrupted.
+ boolean tailValid = !concurrentWrites || endIdx < maxEvents - 1;
+ endIdx += !tailValid ? 1 : 0;
+ long eventsToDrop = endIdx - startIdx;
+ final Deque<Mark> marks = new ArrayDeque<>(size);
+ for (int i = 0; i < size - eventsToDrop; i++) {
+ int readIdx = (int) ((startIdx - i - 1) & maxEventsMax);
+ long gen = localGenOps[readIdx] & ~GEN_MASK;
+ int opVal = (int) (localGenOps[readIdx] & GEN_MASK);
+ switch (opVal) {
+ case (int) START_T_OP:
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ // fallthrough
+ case (int) START_OP:
+ marks.addFirst(Mark.taskStart(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) START_S_OP:
+ marks.addFirst(
+ Mark.taskStart(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) STOP_V_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx]));
+ break;
+ case (int) STOP_S_OP:
+ marks.addFirst(
+ Mark.taskEnd(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) STOP_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) STOP_T_OP:
+ marks.addFirst(Mark.taskEnd(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) EVENT_OP:
+ marks.addFirst(Mark.event(gen, localNanoTimes[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) EVENT_T_OP:
+ marks.addFirst(
+ Mark.event(
+ gen,
+ localNanoTimes[readIdx],
+ localTaskNames[readIdx],
+ localTagNames[readIdx],
+ localTagIds[readIdx]));
+ break;
+ case (int) EVENT_S_OP:
+ marks.addFirst(
+ Mark.event(
+ gen, localNanoTimes[readIdx], localTaskNames[readIdx], localTagNames[readIdx]));
+ break;
+ case (int) LINK_OP:
+ marks.addFirst(Mark.link(gen, localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_T_OP:
+ marks.addFirst(Mark.tag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_SS_OP:
+ marks.addFirst(Mark.keyedTag(gen, localTagNames[readIdx], localTaskNames[readIdx]));
+ break;
+ case (int) ATTACH_SN_OP:
+ marks.addFirst(Mark.keyedTag(gen, localTagNames[readIdx], localTagIds[readIdx]));
+ break;
+ case (int) ATTACH_SNN_OP:
+ marks.addFirst(
+ Mark.keyedTag(
+ gen, localTagNames[readIdx], localTagIds[readIdx], localNanoTimes[readIdx]));
+ break;
+ default:
+ throw new ConcurrentModificationException("Read of storage was not threadsafe " + opVal);
+ }
+ }
+
+ return Collections.unmodifiableList(new ArrayList<>(marks));
+ }
+
+ @Override
+ public int maxMarks() {
+ return maxEvents;
+ }
+}
diff --git a/java9/src/main/java/io/perfmark/java9/package-info.java b/java9/src/main/java/io/perfmark/java9/package-info.java
new file mode 100644
index 0000000..da6c957
--- /dev/null
+++ b/java9/src/main/java/io/perfmark/java9/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Java 9+ enabled generator and mark-holder. */
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.java9;
diff --git a/java9/src/test/java/io/perfmark/java9/AutoLoadTest.java b/java9/src/test/java/io/perfmark/java9/AutoLoadTest.java
new file mode 100644
index 0000000..3d2e020
--- /dev/null
+++ b/java9/src/test/java/io/perfmark/java9/AutoLoadTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.perfmark.PerfMark;
+import io.perfmark.impl.MarkList;
+import io.perfmark.impl.Storage;
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Checks that auto loading this provider works.
+ */
+@RunWith(JUnit4.class)
+public class AutoLoadTest {
+ @Test
+ public void autoLoad() throws Exception {
+ Storage.clearLocalStorage();
+ PerfMark.setEnabled(true);
+ PerfMark.startTask("hi");
+ PerfMark.stopTask("hi");
+ PerfMark.setEnabled(false);
+ MarkList markList = Storage.readForTest();
+ assertEquals(2, markList.size());
+
+ // Have to check after to ensure it loaded properly
+ Field field = Storage.class.getDeclaredField("markHolderProvider");
+ field.setAccessible(true);
+
+ assertTrue(field.get(null) instanceof SecretVarHandleMarkHolderProvider.VarHandleMarkHolderProvider);
+ }
+}
diff --git a/java9/src/test/java/io/perfmark/java9/PerfMarkStressTest.java b/java9/src/test/java/io/perfmark/java9/PerfMarkStressTest.java
new file mode 100644
index 0000000..28d416b
--- /dev/null
+++ b/java9/src/test/java/io/perfmark/java9/PerfMarkStressTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import io.perfmark.Link;
+import io.perfmark.PerfMark;
+import io.perfmark.Tag;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.RecursiveTask;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PerfMarkStressTest {
+
+ @Test
+ public void fibonacci() {
+ ForkJoinPool fjp = new ForkJoinPool(8);
+ final class Fibonacci extends RecursiveTask<Long> {
+
+ private final long input;
+ private final Link link;
+
+ Fibonacci(long input, Link link) {
+ this.input = input;
+ this.link = link;
+ }
+
+ @Override
+ protected Long compute() {
+ Tag tag = PerfMark.createTag(input);
+ PerfMark.startTask("compute", tag);
+ PerfMark.linkIn(link);
+ try {
+ if (input >= 20) {
+ Link link2 = PerfMark.linkOut();
+ ForkJoinTask<Long> task1 = new Fibonacci(input - 1, link2).fork();
+ Fibonacci task2 = new Fibonacci(input - 2, link2);
+ return task2.compute() + task1.join();
+ } else {
+ return computeUnboxed(input);
+ }
+ } finally {
+ PerfMark.stopTask("compute", tag);
+ }
+ }
+
+ private long computeUnboxed(long n) {
+ if (n <= 1) {
+ return n;
+ }
+ return computeUnboxed(n - 1) + computeUnboxed(n - 2);
+ }
+ }
+ PerfMark.setEnabled(true);
+ PerfMark.startTask("calc");
+ Link link = PerfMark.linkOut();
+ ForkJoinTask<Long> task = new Fibonacci(30, link);
+ fjp.execute(task);
+ PerfMark.stopTask("calc");
+ Long res;
+ try {
+ res = task.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ System.err.println(res);
+
+ fjp.shutdown();
+ }
+}
diff --git a/java9/src/test/java/io/perfmark/java9/VarHandleMarkHolderTest.java b/java9/src/test/java/io/perfmark/java9/VarHandleMarkHolderTest.java
new file mode 100644
index 0000000..06afb8d
--- /dev/null
+++ b/java9/src/test/java/io/perfmark/java9/VarHandleMarkHolderTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import static org.junit.Assert.assertEquals;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import io.perfmark.testing.MarkHolderTest;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VarHandleMarkHolderTest extends MarkHolderTest {
+
+ private final long gen = 1L << Generator.GEN_OFFSET;
+
+ @Override
+ protected MarkHolder getMarkHolder() {
+ return new VarHandleMarkHolder();
+ }
+
+ @Test
+ public void read_getsAllButLastIfNotWriter() {
+ MarkHolder mh = getMarkHolder();
+ int events = mh.maxMarks() - 1;
+ for (int i = 0; i < events; i++) {
+ mh.start(gen, "task", 3);
+ }
+
+ List<Mark> marks = mh.read(true);
+ assertEquals(events - 1, marks.size());
+ }
+
+ @Test
+ public void read_getsAllIfNotWriterButNoWrap() {
+ MarkHolder mh = getMarkHolder();
+
+ int events = mh.maxMarks() - 2;
+ for (int i = 0; i < events; i++) {
+ mh.start(gen, "task", 3);
+ }
+
+ List<Mark> marks = mh.read(true);
+ assertEquals(events, marks.size());
+ }
+}
diff --git a/java9/src/test/java/io/perfmark/java9/VersionTest.java b/java9/src/test/java/io/perfmark/java9/VersionTest.java
new file mode 100644
index 0000000..4dd650b
--- /dev/null
+++ b/java9/src/test/java/io/perfmark/java9/VersionTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.java9;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class VersionTest {
+
+ private static final short JAVA_VERSION_9 = 53;
+
+ @Test
+ public void blah() throws Exception {
+ Class<?> clz = SecretVarHandleGenerator.class;
+ try (InputStream stream =
+ clz.getClassLoader().getResourceAsStream(clz.getName().replace('.', '/') + ".class")) {
+ byte[] data = stream.readAllBytes();
+ ByteBuffer buf = ByteBuffer.wrap(data);
+ // Discard magic int
+ buf.getInt();
+ short major = buf.getShort();
+ short minor = buf.getShort();
+ assertEquals(0, major);
+ assertEquals(JAVA_VERSION_9, minor);
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..65d32e1
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,43 @@
+rootProject.name = 'perfmark'
+include ":perfmark-agent"
+include ":perfmark-api"
+include ":perfmark-api-testing"
+include ":perfmark-examples"
+include ":perfmark-impl"
+include ":perfmark-java6"
+include ":perfmark-java7"
+include ":perfmark-java9"
+include ":perfmark-java15"
+include ":perfmark-testing"
+include ":perfmark-tracewriter"
+include ":perfmark-traceviewer"
+
+project(':perfmark-agent').projectDir = "$rootDir/agent" as File
+project(':perfmark-api').projectDir = "$rootDir/api" as File
+project(':perfmark-api-testing').projectDir = "$rootDir/api/testing" as File
+project(':perfmark-examples').projectDir = "$rootDir/examples" as File
+project(':perfmark-impl').projectDir = "$rootDir/impl" as File
+project(':perfmark-java6').projectDir = "$rootDir/java6" as File
+project(':perfmark-java7').projectDir = "$rootDir/java7" as File
+project(':perfmark-java9').projectDir = "$rootDir/java9" as File
+project(':perfmark-java15').projectDir = "$rootDir/java15" as File
+project(':perfmark-testing').projectDir = "$rootDir/testing" as File
+project(':perfmark-tracewriter').projectDir = "$rootDir/tracewriter" as File
+project(':perfmark-traceviewer').projectDir = "$rootDir/traceviewer" as File
+
+dependencyResolutionManagement {
+ versionCatalogs {
+ libs {
+ version("jmh", "1.35")
+
+ library('junit', 'junit:junit:4.13.2')
+ library('jsr305', 'com.google.code.findbugs:jsr305:3.0.2')
+ library('errorprone', 'com.google.errorprone:error_prone_annotations:2.16')
+ library('truth', 'com.google.truth:truth:1.1.3')
+
+ library('jmhcore', 'org.openjdk.jmh', 'jmh-core').versionRef('jmh')
+ library('jmhanno', 'org.openjdk.jmh', 'jmh-generator-annprocess').versionRef('jmh')
+
+ }
+ }
+}
diff --git a/testing/build.gradle.kts b/testing/build.gradle.kts
new file mode 100644
index 0000000..d3c5a7c
--- /dev/null
+++ b/testing/build.gradle.kts
@@ -0,0 +1,25 @@
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.testing")
+ }
+}
+
+val jdkVersion = JavaVersion.VERSION_11
+
+description = "PerfMark Testing"
+
+dependencies {
+ implementation(project(":perfmark-api"))
+ implementation(project(":perfmark-impl"))
+ implementation(libs.jmhcore)
+ implementation(libs.junit)
+}
+
+tasks.named<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+}
+
+tasks.named<Javadoc>("javadoc") {
+ exclude("io/perfmark/testing/**")
+}
diff --git a/testing/src/main/java/io/perfmark/testing/GarbageCollector.java b/testing/src/main/java/io/perfmark/testing/GarbageCollector.java
new file mode 100644
index 0000000..8309b91
--- /dev/null
+++ b/testing/src/main/java/io/perfmark/testing/GarbageCollector.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.testing;
+
+import java.util.List;
+
+@SuppressWarnings("ImmutableEnumChecker")
+public enum GarbageCollector {
+ G1("-XX:+UseG1GC"),
+ ZGC("-XX:+UseZGC"),
+ SHENANDOAH("-XX:+UseShenandoahGC"),
+ SERIAL("-XX:+UseSerialGC"),
+ PARALLEL("-XX:+UseParallelGC"),
+ EPSILON("-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC"),
+ CMS("-XX:+UseConcMarkSweepGC"),
+ ;
+
+ private final List<String> jvmArgs;
+
+ GarbageCollector(String ... jvmArgs) {
+ this.jvmArgs = List.of(jvmArgs);
+ }
+
+ public List<String> jvmArgs() {
+ return jvmArgs;
+ }
+}
diff --git a/testing/src/main/java/io/perfmark/testing/GeneratorBenchmark.java b/testing/src/main/java/io/perfmark/testing/GeneratorBenchmark.java
new file mode 100644
index 0000000..fd3eca5
--- /dev/null
+++ b/testing/src/main/java/io/perfmark/testing/GeneratorBenchmark.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.testing;
+
+import io.perfmark.impl.Generator;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+@State(Scope.Benchmark)
+public class GeneratorBenchmark {
+
+ private Generator generator;
+
+ @Setup(Level.Trial)
+ public void setUp() {
+ generator = getGenerator();
+ generator.setGeneration(Generator.FAILURE);
+ }
+
+ protected Generator getGenerator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void ifEnabled() {
+ if (isEnabled(getGeneration())) {
+ Blackhole.consumeCPU(1000);
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public long getGeneration() {
+ return generator.getGeneration();
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public long getAndSetAndGetGeneration() {
+ long oldGeneration = generator.getGeneration();
+ generator.setGeneration(oldGeneration + 1);
+ return generator.getGeneration();
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @GroupThreads(3)
+ public long racyGetAndSetAndGetGeneration() {
+ long oldGeneration = generator.getGeneration();
+ generator.setGeneration(oldGeneration + 1);
+ return generator.getGeneration();
+ }
+
+ protected static boolean isEnabled(long gen) {
+ return ((gen >>> Generator.GEN_OFFSET) & 0x1L) != 0L;
+ }
+}
diff --git a/testing/src/main/java/io/perfmark/testing/MarkHolderBenchmark.java b/testing/src/main/java/io/perfmark/testing/MarkHolderBenchmark.java
new file mode 100644
index 0000000..7699f91
--- /dev/null
+++ b/testing/src/main/java/io/perfmark/testing/MarkHolderBenchmark.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.testing;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.MarkHolder;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+@State(Scope.Thread)
+public class MarkHolderBenchmark {
+
+ private static final long gen = 1 << Generator.GEN_OFFSET;
+ private static final String taskName = "hiya";
+
+ public static final List<String> ASM_FLAGS = List.of(
+ "-XX:+UnlockDiagnosticVMOptions",
+ "-XX:+LogCompilation",
+ "-XX:LogFile=/tmp/blah.txt",
+ "-XX:+PrintAssembly",
+ "-XX:+PrintInterpreter",
+ "-XX:+PrintNMethods",
+ "-XX:+PrintNativeNMethods",
+ "-XX:+PrintSignatureHandlers",
+ "-XX:+PrintAdapterHandlers",
+ "-XX:+PrintStubCode",
+ "-XX:+PrintCompilation",
+ "-XX:+PrintInlining",
+ "-XX:PrintAssemblyOptions=syntax",
+ "-XX:PrintAssemblyOptions=intel");
+
+ protected MarkHolder markHolder;
+
+ private String tagName = "tag";
+ private long tagId = 0xf0f0;
+ private long nanoTime = 0xf1f1;
+ private long linkId = 0xf2f2;
+
+ public MarkHolder getMarkHolder() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Setup
+ public final void setUp() {
+ markHolder = getMarkHolder();
+ }
+
+ @Param({"G1"})
+ GarbageCollector GC;
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void start_name_tag() {
+ markHolder.start(gen, taskName, tagName, tagId, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void start_name_noTag() {
+ markHolder.start(gen, taskName, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void start_name_subname() {
+ markHolder.start(gen, taskName, taskName, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void stop_name_tag() {
+ markHolder.stop(gen, taskName, tagName, tagId, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void stop_name_noTag() {
+ markHolder.stop(gen, taskName, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void stop_name_subname() {
+ markHolder.stop(gen, taskName, tagName, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void link() {
+ markHolder.link(gen, linkId);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void event_name_tag() {
+ markHolder.event(gen, taskName, tagName, tagId, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void event_name_noTag() {
+ markHolder.event(gen, taskName, nanoTime);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void event_name_subname() {
+ markHolder.event(gen, taskName, taskName, nanoTime);
+ }
+}
diff --git a/testing/src/main/java/io/perfmark/testing/MarkHolderTest.java b/testing/src/main/java/io/perfmark/testing/MarkHolderTest.java
new file mode 100644
index 0000000..cb471d8
--- /dev/null
+++ b/testing/src/main/java/io/perfmark/testing/MarkHolderTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2021 Carl Mastrangelo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.testing;
+
+import static org.junit.Assert.assertEquals;
+
+import io.perfmark.impl.Generator;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkHolder;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Base class for Mark holders.
+ *
+ * <p>This should really not be a super class, but JUnit4 makes doing this hard.
+ */
+@RunWith(JUnit4.class)
+public abstract class MarkHolderTest {
+
+ private final long gen = 1L << Generator.GEN_OFFSET;
+
+ protected MarkHolder getMarkHolder() {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Test
+ public void taskTagStartStop() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", 3);
+ mh.stop(gen, "task", 4);
+
+ List<Mark> marks = mh.read(false);
+ assertEquals(2, marks.size());
+ List<Mark> expected = List.of(Mark.taskStart(gen, 3, "task"), Mark.taskEnd(gen, 4, "task"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void taskTagStartStop_subTask() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", "subtask", 3);
+ mh.stop(gen, "task", "subtask", 4);
+
+ List<Mark> marks = mh.read(false);
+ assertEquals(2, marks.size());
+ List<Mark> expected = List.of(
+ Mark.taskStart(gen, 3, "task", "subtask"), Mark.taskEnd(gen, 4, "task", "subtask"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void taskTagStartStop_tag() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", "tag", 9, 3);
+ mh.stop(gen, "task", "tag", 9, 4);
+
+ List<Mark> marks = mh.read(false);
+ assertEquals(4, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.taskStart(gen, 3, "task"),
+ Mark.tag(gen, "tag", 9),
+ Mark.tag(gen, "tag", 9),
+ Mark.taskEnd(gen, 4, "task"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void taskStartStartStopStop() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task1", 3);
+ mh.start(gen, "task2", 4);
+ mh.start(gen, "task3", 5);
+ mh.stop(gen, 6);
+ mh.stop(gen, "task2", 7);
+ mh.stop(gen, "task1", 8);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(6, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.taskStart(gen, 3, "task1"),
+ Mark.taskStart(gen, 4, "task2"),
+ Mark.taskStart(gen, 5, "task3"),
+ Mark.taskEnd(gen, 6),
+ Mark.taskEnd(gen, 7, "task2"),
+ Mark.taskEnd(gen, 8, "task1"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void attachTag() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", 3);
+ mh.attachTag(gen, "tag", 8);
+ mh.stop(gen, "task", 4);
+
+ List<Mark> marks = mh.read(false);
+ assertEquals(3, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.taskStart(gen, 3, "task"), Mark.tag(gen, "tag", 8), Mark.taskEnd(gen, 4, "task"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void attachKeyedTag() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", 3);
+ mh.attachKeyedTag(gen, "key1", 8);
+ mh.attachKeyedTag(gen, "key2", 8, 9);
+ mh.attachKeyedTag(gen, "key3", "value");
+ mh.stop(gen, "task", 4);
+
+ List<Mark> marks = mh.read(false);
+ assertEquals(5, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.taskStart(gen, 3, "task"),
+ Mark.keyedTag(gen, "key1", 8),
+ Mark.keyedTag(gen, "key2", 8, 9),
+ Mark.keyedTag(gen, "key3", "value"),
+ Mark.taskEnd(gen, 4, "task"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void event() {
+ MarkHolder mh = getMarkHolder();
+ mh.event(gen, "task1", 8);
+ mh.event(gen, "task2", 5);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(2, marks.size());
+ List<Mark> expected = List.of(Mark.event(gen, 8, "task1"), Mark.event(gen, 5, "task2"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void event_tag() {
+ MarkHolder mh = getMarkHolder();
+ mh.event(gen, "task1", "tag1", 7, 8);
+ mh.event(gen, "task2", "tag2", 6, 5);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(2, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.event(gen, 8, "task1", "tag1", 7), Mark.event(gen, 5, "task2", "tag2", 6));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void event_subevent() {
+ MarkHolder mh = getMarkHolder();
+ mh.event(gen, "task1", "subtask3", 8);
+ mh.event(gen, "task2", "subtask4", 5);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(2, marks.size());
+ List<Mark> expected =
+ List.of(
+ Mark.event(gen, 8, "task1", "subtask3"), Mark.event(gen, 5, "task2", "subtask4"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void linkInLinkOut() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task1", 3);
+ mh.link(gen, 9);
+ mh.link(gen, -9);
+ mh.stop(gen, "task1", 4);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(marks.size(), 4);
+ List<Mark> expected =
+ List.of(
+ Mark.taskStart(gen, 3, "task1"),
+ Mark.link(gen, 9),
+ Mark.link(gen, -9),
+ Mark.taskEnd(gen, 4, "task1"));
+ assertEquals(expected, marks);
+ }
+
+ @Test
+ public void read_getsAllIfWriter() {
+ MarkHolder mh = getMarkHolder();
+ mh.start(gen, "task", 3);
+
+ List<Mark> marks = mh.read(false);
+
+ assertEquals(marks.size(), 1);
+ }
+}
diff --git a/traceviewer/BUILD.bazel b/traceviewer/BUILD.bazel
new file mode 100644
index 0000000..c05fd11
--- /dev/null
+++ b/traceviewer/BUILD.bazel
@@ -0,0 +1,12 @@
+java_library(
+ name = "tracewriter",
+ srcs = glob([
+ "src/main/java/io/perfmark/traceviewer/*.java",
+ ]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tracewriter",
+ "@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
diff --git a/traceviewer/build.gradle.kts b/traceviewer/build.gradle.kts
new file mode 100644
index 0000000..850d0e9
--- /dev/null
+++ b/traceviewer/build.gradle.kts
@@ -0,0 +1,22 @@
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.traceviewer")
+ }
+}
+
+description = "PerfMark Trace Viewer"
+
+val jdkVersion = JavaVersion.VERSION_1_8
+
+dependencies {
+ compileOnly(libs.jsr305)
+ compileOnly(libs.errorprone)
+ implementation(project(":perfmark-tracewriter"))
+ testImplementation(project(":perfmark-api"))
+}
+
+tasks.getByName<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+ options.compilerArgs.add("-Xlint:-options")
+}
diff --git a/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java b/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java
new file mode 100644
index 0000000..e37b9b4
--- /dev/null
+++ b/traceviewer/src/main/java/io/perfmark/traceviewer/TraceEventViewer.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.traceviewer;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.perfmark.tracewriter.TraceEventWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * This class converts from the Trace Event json data into a full HTML page. It includes the trace
+ * viewer from Catapult, the Chromium trace UI.
+ *
+ * <p>This class is separate from {@link TraceEventWriter}, because it includes a fairly large HTML
+ * chunk, and brings in a differently licenced piece of code.
+ *
+ * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed
+ * without notice.
+ *
+ * @since 0.17.0
+ */
+public final class TraceEventViewer {
+ private static final Logger logger = Logger.getLogger(TraceEventViewer.class.getName());
+
+ // Copied from trace2html.html in the Catapult tracing code.
+ private static final String INLINE_TRACE_DATA =
+ ""
+ + " const traces = [];\n"
+ + " const viewerDataScripts = Polymer.dom(document).querySelectorAll(\n"
+ + " '#viewer-data');\n"
+ + " for (let i = 0; i < viewerDataScripts.length; i++) {\n"
+ + " let text = Polymer.dom(viewerDataScripts[i]).textContent;\n"
+ + " // Trim leading newlines off the text. They happen during writing.\n"
+ + " while (text[0] === '\\n') {\n"
+ + " text = text.substring(1);\n"
+ + " }\n"
+ + " onResult(tr.b.Base64.atob(text));\n"
+ + " viewer.updateDocumentFavicon();\n"
+ + " viewer.globalMode = true;\n"
+ + " viewer.viewTitle = document.title;\n"
+ + " break;\n"
+ + " }\n";
+
+ /**
+ * A convenience function around {@link #writeTraceHtml(Writer)}. This writes the trace data to a
+ * temporary file and logs the output location.
+ *
+ * @return the Path of the written file.
+ * @throws IOException if it can't write to the destination.
+ */
+ @CanIgnoreReturnValue
+ public static Path writeTraceHtml() throws IOException {
+ Path path = Files.createTempFile("perfmark-trace-", ".html");
+ try (OutputStream os = Files.newOutputStream(path, TRUNCATE_EXISTING);
+ Writer w = new OutputStreamWriter(os, UTF_8)) {
+ writeTraceHtml(w);
+ }
+ logger.log(Level.INFO, "Wrote PerfMark Trace file://{0}", new Object[] {path.toAbsolutePath()});
+ return path;
+ }
+
+ /**
+ * Writes all available trace data as a single HTML file into the given writer.
+ *
+ * @param writer The destination to write all HTML to.
+ * @throws IOException if it can't write to the writer.
+ */
+ public static void writeTraceHtml(Writer writer) throws IOException {
+ InputStream indexStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/catapult/index.html");
+ if (indexStream == null) {
+ throw new IOException("unable to find index.html");
+ }
+ String index = readAll(indexStream);
+
+ InputStream webComponentsStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/polymer/webcomponents.min.js");
+ if (webComponentsStream == null) {
+ throw new IOException("unable to find webcomponents.min.js");
+ }
+ String webComponents = readAll(webComponentsStream);
+
+ InputStream traceViewerStream =
+ TraceEventViewer.class.getResourceAsStream("third_party/catapult/trace_viewer_full.html");
+ if (traceViewerStream == null) {
+ throw new IOException("unable to find trace_viewer_full.html");
+ }
+ String traceViewer = trimTraceViewer(readAll(traceViewerStream));
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (OutputStreamWriter w = new OutputStreamWriter(baos, UTF_8)) {
+ TraceEventWriter.writeTraceEvents(w);
+ }
+ byte[] traceData64 = Base64.getEncoder().encode(baos.toByteArray());
+
+ String indexWithWebComponents = replaceIndexWebComponents(index, webComponents);
+
+ String indexWithTraceViewer =
+ replaceIndexTraceImport(
+ indexWithWebComponents, traceViewer, new String(traceData64, UTF_8));
+
+ String fullIndex = replaceIndexTraceData(indexWithTraceViewer, INLINE_TRACE_DATA);
+ writer.write(fullIndex);
+ writer.flush();
+ }
+
+ /**
+ * Replaces the normal {@code <link>} tag in index.html with a custom replacement, and optionally
+ * the inlined Trace data as a base64 script. This is because the trace2html.html file imports the
+ * data as a top level text/plain script.
+ */
+ private static String replaceIndexTraceImport(
+ String index, String replacement, @Nullable String inlineTraceData64) {
+ int start = index.indexOf("IO_PERFMARK_TRACE_IMPORT");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_TRACE_IMPORT");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+ int line2pos = index.indexOf('\n', line1pos + 1);
+ assert line2pos != -1;
+ int line3pos = index.indexOf('\n', line2pos + 1);
+ assert line3pos != -1;
+ String inlineTraceData = "";
+ if (inlineTraceData64 != null) {
+ inlineTraceData =
+ "\n<script id=\"viewer-data\" type=\"text/plain\">" + inlineTraceData64 + "</script>";
+ }
+ return index.substring(0, line0pos + 1)
+ + replacement
+ + inlineTraceData
+ + index.substring(line3pos);
+ }
+
+ private static String replaceIndexWebComponents(String index, String replacement) {
+ int start = index.indexOf("IO_PERFMARK_WEBCOMPONENTS");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_WEBCOMPONENTS");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+
+ return index.substring(0, line0pos + 1) + replacement + index.substring(line1pos);
+ }
+
+ private static String replaceIndexTraceData(String index, String replacement) {
+ int start = index.indexOf("IO_PERFMARK_TRACE_URL");
+ if (start == -1) {
+ throw new IllegalArgumentException("index doesn't contain IO_PERFMARK_TRACE_URL");
+ }
+ int line0pos = index.lastIndexOf('\n', start);
+ assert line0pos != -1;
+ int line1pos = index.indexOf('\n', line0pos + 1);
+ assert line1pos != -1;
+ int line2pos = index.indexOf('\n', line1pos + 1);
+ assert line2pos != -1;
+ int line3pos = index.indexOf('\n', line2pos + 1);
+ assert line3pos != -1;
+ return index.substring(0, line0pos + 1) + replacement + index.substring(line3pos);
+ }
+
+ private static String trimTraceViewer(String traceViewer) {
+ int startpos = traceViewer.indexOf("<template");
+ int lastpos = traceViewer.lastIndexOf("</head>");
+ return traceViewer.substring(startpos, lastpos);
+ }
+
+ private static String readAll(InputStream stream) throws IOException {
+ int available = stream.available();
+ byte[] data;
+ if (available > 0) {
+ data = new byte[available + 1];
+ } else {
+ data = new byte[4096];
+ }
+ int pos = 0;
+ while (true) {
+ int read = stream.read(data, pos, data.length - pos);
+ if (read == -1) {
+ break;
+ } else {
+ pos += read;
+ }
+ if (pos == data.length) {
+ data = Arrays.copyOf(data, data.length + (data.length >> 2));
+ }
+ }
+ return new String(data, 0, pos, UTF_8);
+ }
+
+ private TraceEventViewer() {
+ throw new AssertionError("nope");
+ }
+}
diff --git a/traceviewer/src/main/java/io/perfmark/traceviewer/package-info.java b/traceviewer/src/main/java/io/perfmark/traceviewer/package-info.java
new file mode 100644
index 0000000..c9449e9
--- /dev/null
+++ b/traceviewer/src/main/java/io/perfmark/traceviewer/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The Trace View package reads the PerfMark recorded tasks, and converts them into the
+ * Chrome Trace Viewer format, bundled into a self-contained HTML page.
+ */
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.traceviewer;
diff --git a/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/LICENSE b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/LICENSE
new file mode 100644
index 0000000..c992fe4
--- /dev/null
+++ b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/LICENSE
@@ -0,0 +1,27 @@
+Copyright 2015 The Chromium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of catapult nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/index.html b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/index.html
new file mode 100644
index 0000000..e0abd7e
--- /dev/null
+++ b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/index.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<head>
+ <meta charset="utf-8">
+ <script>
+ // IO_PERFMARK_WEBCOMPONENTS
+ </script>
+ <script>
+'use strict';
+
+function onTraceViewerImportFail() {
+ document.addEventListener('DOMContentLoaded', function() {
+ document.body.textContent =
+ 'tracing/bin/trace_viewer_full.html is missing. ' +
+ 'Run vulcanize_trace_viewer from $TRACE_VIEWER and reload.';
+ });
+}
+</script>
+<!-- IO_PERFMARK_TRACE_IMPORT -->
+ <link rel="import" href="trace_viewer_full.html"
+ onerror="onTraceViewerImportFail(event)">
+
+ <style>
+ html, body {
+ box-sizing: border-box;
+ overflow: hidden;
+ margin: 0px;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ }
+ #trace-viewer {
+ width: 100%;
+ height: 100%;
+ }
+ #trace-viewer:focus {
+ outline: none;
+ }
+</style>
+ <script>
+'use strict';
+
+(function() {
+ let viewer;
+ let url;
+ let model;
+
+ // You can set this to true if you want to hide the WebComponentsV0 polyfill
+ // warning.
+ window.__hideTraceViewerPolyfillWarning = false;
+
+ function load() {
+ const req = new XMLHttpRequest();
+ const isBinary = /[.]gz$/.test(url) || /[.]zip$/.test(url);
+ req.overrideMimeType('text/plain; charset=x-user-defined');
+ req.open('GET', url, true);
+ if (isBinary) req.responseType = 'arraybuffer';
+
+ req.onreadystatechange = function(event) {
+ if (req.readyState !== 4) return;
+
+ window.setTimeout(function() {
+ if (req.status === 200) {
+ onResult(isBinary ? req.response : req.responseText);
+ } else {
+ onResultFail(req.status);
+ }
+ }, 0);
+ };
+ req.send(null);
+ }
+
+ function onResultFail(err) {
+ const overlay = new tr.ui.b.Overlay();
+ overlay.textContent = err + ': ' + url + ' could not be loaded';
+ overlay.title = 'Failed to fetch data';
+ overlay.visible = true;
+ }
+
+ function onResult(result) {
+ model = new tr.Model();
+ const i = new tr.importer.Import(model);
+ const p = i.importTracesWithProgressDialog([result]);
+ p.then(onModelLoaded, onImportFail);
+ }
+
+ function onModelLoaded() {
+ viewer.model = model;
+ viewer.viewTitle = url;
+ }
+
+ function onImportFail() {
+ const overlay = new tr.ui.b.Overlay();
+ overlay.textContent = tr.b.normalizeException(err).message;
+ overlay.title = 'Import error';
+ overlay.visible = true;
+ }
+
+ document.addEventListener('WebComponentsReady', function() {
+ const container = document.createElement('track-view-container');
+ container.id = 'track_view_container';
+
+ viewer = document.createElement('tr-ui-timeline-view');
+ viewer.track_view_container = container;
+ Polymer.dom(viewer).appendChild(container);
+
+ viewer.id = 'trace-viewer';
+ viewer.globalMode = true;
+ Polymer.dom(document.body).appendChild(viewer);
+
+ // IO_PERFMARK_TRACE_URL
+ url = './trace.json';
+ load();
+ });
+}());
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/trace_viewer_full.html b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/trace_viewer_full.html
new file mode 100644
index 0000000..30b70db
--- /dev/null
+++ b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/trace_viewer_full.html
@@ -0,0 +1 @@
+file traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/catapult/trace_viewer_full.html is too large for review but will still be cloned after the change is approved \ No newline at end of file
diff --git a/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/LICENSE b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/LICENSE
new file mode 100644
index 0000000..92d60b0
--- /dev/null
+++ b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/webcomponents.min.js b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/webcomponents.min.js
new file mode 100644
index 0000000..ad8196b
--- /dev/null
+++ b/traceviewer/src/main/resources/io/perfmark/traceviewer/third_party/polymer/webcomponents.min.js
@@ -0,0 +1,14 @@
+/**
+ * @license
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+ */
+// @version 0.7.24
+!function(){window.WebComponents=window.WebComponents||{flags:{}};var e="webcomponents.js",t=document.querySelector('script[src*="'+e+'"]'),n={};if(!n.noOpts){if(location.search.slice(1).split("&").forEach(function(e){var t,r=e.split("=");r[0]&&(t=r[0].match(/wc-(.+)/))&&(n[t[1]]=r[1]||!0)}),t)for(var r,o=0;r=t.attributes[o];o++)"src"!==r.name&&(n[r.name]=r.value||!0);if(n.log&&n.log.split){var i=n.log.split(",");n.log={},i.forEach(function(e){n.log[e]=!0})}else n.log={}}n.shadow=n.shadow||n.shadowdom||n.polyfill,"native"===n.shadow?n.shadow=!1:n.shadow=n.shadow||!HTMLElement.prototype.createShadowRoot,n.register&&(window.CustomElements=window.CustomElements||{flags:{}},window.CustomElements.flags.register=n.register),WebComponents.flags=n}(),WebComponents.flags.shadow&&("undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return!(!t||t[0]!==e)&&(t[0]=t[1]=void 0,!0)},has:function(e){var t=e[this.name];return!!t&&t[0]===e}},window.WeakMap=n}(),window.ShadowDOMPolyfill={},function(e){"use strict";function t(){if("undefined"!=typeof chrome&&chrome.app&&chrome.app.runtime)return!1;if(navigator.getDeviceStorage)return!1;try{var e=new Function("return true;");return e()}catch(t){return!1}}function n(e){if(!e)throw new Error("Assertion failed")}function r(e,t){for(var n=W(t),r=0;r<n.length;r++){var o=n[r];A(e,o,F(t,o))}return e}function o(e,t){for(var n=W(t),r=0;r<n.length;r++){var o=n[r];switch(o){case"arguments":case"caller":case"length":case"name":case"prototype":case"toString":continue}A(e,o,F(t,o))}return e}function i(e,t){for(var n=0;n<t.length;n++)if(t[n]in e)return t[n]}function a(e,t,n){U.value=n,A(e,t,U)}function s(e,t){var n=e.__proto__||Object.getPrototypeOf(e);if(q)try{W(n)}catch(r){n=n.__proto__}var o=R.get(n);if(o)return o;var i=s(n),a=E(i);return g(n,a,t),a}function c(e,t){w(e,t,!0)}function l(e,t){w(t,e,!1)}function u(e){return/^on[a-z]+$/.test(e)}function d(e){return/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(e)}function p(e){return k&&d(e)?new Function("return this.__impl4cf1e782hg__."+e):function(){return this.__impl4cf1e782hg__[e]}}function h(e){return k&&d(e)?new Function("v","this.__impl4cf1e782hg__."+e+" = v"):function(t){this.__impl4cf1e782hg__[e]=t}}function f(e){return k&&d(e)?new Function("return this.__impl4cf1e782hg__."+e+".apply(this.__impl4cf1e782hg__, arguments)"):function(){return this.__impl4cf1e782hg__[e].apply(this.__impl4cf1e782hg__,arguments)}}function m(e,t){try{return e===window&&"showModalDialog"===t?B:Object.getOwnPropertyDescriptor(e,t)}catch(n){return B}}function w(t,n,r,o){for(var i=W(t),a=0;a<i.length;a++){var s=i[a];if("polymerBlackList_"!==s&&!(s in n||t.polymerBlackList_&&t.polymerBlackList_[s])){q&&t.__lookupGetter__(s);var c,l,d=m(t,s);if("function"!=typeof d.value){var w=u(s);c=w?e.getEventHandlerGetter(s):p(s),(d.writable||d.set||V)&&(l=w?e.getEventHandlerSetter(s):h(s));var v=V||d.configurable;A(n,s,{get:c,set:l,configurable:v,enumerable:d.enumerable})}else r&&(n[s]=f(s))}}}function v(e,t,n){if(null!=e){var r=e.prototype;g(r,t,n),o(t,e)}}function g(e,t,r){var o=t.prototype;n(void 0===R.get(e)),R.set(e,t),I.set(o,e),c(e,o),r&&l(o,r),a(o,"constructor",t),t.prototype=o}function b(e,t){return R.get(t.prototype)===e}function y(e){var t=Object.getPrototypeOf(e),n=s(t),r=E(n);return g(t,r,e),r}function E(e){function t(t){e.call(this,t)}var n=Object.create(e.prototype);return n.constructor=t,t.prototype=n,t}function _(e){return e&&e.__impl4cf1e782hg__}function S(e){return!_(e)}function T(e){if(null===e)return null;n(S(e));var t=e.__wrapper8e3dd93a60__;return null!=t?t:e.__wrapper8e3dd93a60__=new(s(e,e))(e)}function M(e){return null===e?null:(n(_(e)),e.__impl4cf1e782hg__)}function O(e){return e.__impl4cf1e782hg__}function L(e,t){t.__impl4cf1e782hg__=e,e.__wrapper8e3dd93a60__=t}function N(e){return e&&_(e)?M(e):e}function C(e){return e&&!_(e)?T(e):e}function j(e,t){null!==t&&(n(S(e)),n(void 0===t||_(t)),e.__wrapper8e3dd93a60__=t)}function D(e,t,n){G.get=n,A(e.prototype,t,G)}function H(e,t){D(e,t,function(){return T(this.__impl4cf1e782hg__[t])})}function x(e,t){e.forEach(function(e){t.forEach(function(t){e.prototype[t]=function(){var e=C(this);return e[t].apply(e,arguments)}})})}var R=new WeakMap,I=new WeakMap,P=Object.create(null),k=t(),A=Object.defineProperty,W=Object.getOwnPropertyNames,F=Object.getOwnPropertyDescriptor,U={value:void 0,configurable:!0,enumerable:!1,writable:!0};W(window);var q=/Firefox/.test(navigator.userAgent),B={get:function(){},set:function(e){},configurable:!0,enumerable:!0},V=function(){var e=Object.getOwnPropertyDescriptor(Node.prototype,"nodeType");return e&&!e.get&&!e.set}(),G={get:void 0,configurable:!0,enumerable:!0};e.addForwardingProperties=c,e.assert=n,e.constructorTable=R,e.defineGetter=D,e.defineWrapGetter=H,e.forwardMethodsToWrapper=x,e.isIdentifierName=d,e.isWrapper=_,e.isWrapperFor=b,e.mixin=r,e.nativePrototypeTable=I,e.oneOf=i,e.registerObject=y,e.registerWrapper=v,e.rewrap=j,e.setWrapper=L,e.unsafeUnwrap=O,e.unwrap=M,e.unwrapIfNeeded=N,e.wrap=T,e.wrapIfNeeded=C,e.wrappers=P}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e,t,n){return{index:e,removed:t,addedCount:n}}function n(){}var r=0,o=1,i=2,a=3;n.prototype={calcEditDistances:function(e,t,n,r,o,i){for(var a=i-o+1,s=n-t+1,c=new Array(a),l=0;l<a;l++)c[l]=new Array(s),c[l][0]=l;for(var u=0;u<s;u++)c[0][u]=u;for(var l=1;l<a;l++)for(var u=1;u<s;u++)if(this.equals(e[t+u-1],r[o+l-1]))c[l][u]=c[l-1][u-1];else{var d=c[l-1][u]+1,p=c[l][u-1]+1;c[l][u]=d<p?d:p}return c},spliceOperationsFromEditDistances:function(e){for(var t=e.length-1,n=e[0].length-1,s=e[t][n],c=[];t>0||n>0;)if(0!=t)if(0!=n){var l,u=e[t-1][n-1],d=e[t-1][n],p=e[t][n-1];l=d<p?d<u?d:u:p<u?p:u,l==u?(u==s?c.push(r):(c.push(o),s=u),t--,n--):l==d?(c.push(a),t--,s=d):(c.push(i),n--,s=p)}else c.push(a),t--;else c.push(i),n--;return c.reverse(),c},calcSplices:function(e,n,s,c,l,u){var d=0,p=0,h=Math.min(s-n,u-l);if(0==n&&0==l&&(d=this.sharedPrefix(e,c,h)),s==e.length&&u==c.length&&(p=this.sharedSuffix(e,c,h-d)),n+=d,l+=d,s-=p,u-=p,s-n==0&&u-l==0)return[];if(n==s){for(var f=t(n,[],0);l<u;)f.removed.push(c[l++]);return[f]}if(l==u)return[t(n,[],s-n)];for(var m=this.spliceOperationsFromEditDistances(this.calcEditDistances(e,n,s,c,l,u)),f=void 0,w=[],v=n,g=l,b=0;b<m.length;b++)switch(m[b]){case r:f&&(w.push(f),f=void 0),v++,g++;break;case o:f||(f=t(v,[],0)),f.addedCount++,v++,f.removed.push(c[g]),g++;break;case i:f||(f=t(v,[],0)),f.addedCount++,v++;break;case a:f||(f=t(v,[],0)),f.removed.push(c[g]),g++}return f&&w.push(f),w},sharedPrefix:function(e,t,n){for(var r=0;r<n;r++)if(!this.equals(e[r],t[r]))return r;return n},sharedSuffix:function(e,t,n){for(var r=e.length,o=t.length,i=0;i<n&&this.equals(e[--r],t[--o]);)i++;return i},calculateSplices:function(e,t){return this.calcSplices(e,0,e.length,t,0,t.length)},equals:function(e,t){return e===t}},e.ArraySplice=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(){a=!1;var e=i.slice(0);i=[];for(var t=0;t<e.length;t++)(0,e[t])()}function n(e){i.push(e),a||(a=!0,r(t,0))}var r,o=window.MutationObserver,i=[],a=!1;if(o){var s=1,c=new o(t),l=document.createTextNode(s);c.observe(l,{characterData:!0}),r=function(){s=(s+1)%2,l.data=s}}else r=window.setTimeout;e.setEndOfMicrotask=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.scheduled_||(e.scheduled_=!0,f.push(e),m||(u(n),m=!0))}function n(){for(m=!1;f.length;){var e=f;f=[],e.sort(function(e,t){return e.uid_-t.uid_});for(var t=0;t<e.length;t++){var n=e[t];n.scheduled_=!1;var r=n.takeRecords();i(n),r.length&&n.callback_(r,n)}}}function r(e,t){this.type=e,this.target=t,this.addedNodes=new p.NodeList,this.removedNodes=new p.NodeList,this.previousSibling=null,this.nextSibling=null,this.attributeName=null,this.attributeNamespace=null,this.oldValue=null}function o(e,t){for(;e;e=e.parentNode){var n=h.get(e);if(n)for(var r=0;r<n.length;r++){var o=n[r];o.options.subtree&&o.addTransientObserver(t)}}}function i(e){for(var t=0;t<e.nodes_.length;t++){var n=e.nodes_[t],r=h.get(n);if(!r)return;for(var o=0;o<r.length;o++){var i=r[o];i.observer===e&&i.removeTransientObservers()}}}function a(e,n,o){for(var i=Object.create(null),a=Object.create(null),s=e;s;s=s.parentNode){var c=h.get(s);if(c)for(var l=0;l<c.length;l++){var u=c[l],d=u.options;if((s===e||d.subtree)&&("attributes"!==n||d.attributes)&&("attributes"!==n||!d.attributeFilter||null===o.namespace&&d.attributeFilter.indexOf(o.name)!==-1)&&("characterData"!==n||d.characterData)&&("childList"!==n||d.childList)){var p=u.observer;i[p.uid_]=p,("attributes"===n&&d.attributeOldValue||"characterData"===n&&d.characterDataOldValue)&&(a[p.uid_]=o.oldValue)}}}for(var f in i){var p=i[f],m=new r(n,e);"name"in o&&"namespace"in o&&(m.attributeName=o.name,m.attributeNamespace=o.namespace),o.addedNodes&&(m.addedNodes=o.addedNodes),o.removedNodes&&(m.removedNodes=o.removedNodes),o.previousSibling&&(m.previousSibling=o.previousSibling),o.nextSibling&&(m.nextSibling=o.nextSibling),void 0!==a[f]&&(m.oldValue=a[f]),t(p),p.records_.push(m)}}function s(e){if(this.childList=!!e.childList,this.subtree=!!e.subtree,"attributes"in e||!("attributeOldValue"in e||"attributeFilter"in e)?this.attributes=!!e.attributes:this.attributes=!0,"characterDataOldValue"in e&&!("characterData"in e)?this.characterData=!0:this.characterData=!!e.characterData,!this.attributes&&(e.attributeOldValue||"attributeFilter"in e)||!this.characterData&&e.characterDataOldValue)throw new TypeError;if(this.characterData=!!e.characterData,this.attributeOldValue=!!e.attributeOldValue,this.characterDataOldValue=!!e.characterDataOldValue,"attributeFilter"in e){if(null==e.attributeFilter||"object"!=typeof e.attributeFilter)throw new TypeError;this.attributeFilter=w.call(e.attributeFilter)}else this.attributeFilter=null}function c(e){this.callback_=e,this.nodes_=[],this.records_=[],this.uid_=++v,this.scheduled_=!1}function l(e,t,n){this.observer=e,this.target=t,this.options=n,this.transientObservedNodes=[]}var u=e.setEndOfMicrotask,d=e.wrapIfNeeded,p=e.wrappers,h=new WeakMap,f=[],m=!1,w=Array.prototype.slice,v=0;c.prototype={constructor:c,observe:function(e,t){e=d(e);var n,r=new s(t),o=h.get(e);o||h.set(e,o=[]);for(var i=0;i<o.length;i++)o[i].observer===this&&(n=o[i],n.removeTransientObservers(),n.options=r);n||(n=new l(this,e,r),o.push(n),this.nodes_.push(e))},disconnect:function(){this.nodes_.forEach(function(e){for(var t=h.get(e),n=0;n<t.length;n++){var r=t[n];if(r.observer===this){t.splice(n,1);break}}},this),this.records_=[]},takeRecords:function(){var e=this.records_;return this.records_=[],e}},l.prototype={addTransientObserver:function(e){if(e!==this.target){t(this.observer),this.transientObservedNodes.push(e);var n=h.get(e);n||h.set(e,n=[]),n.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[];for(var t=0;t<e.length;t++)for(var n=e[t],r=h.get(n),o=0;o<r.length;o++)if(r[o]===this){r.splice(o,1);break}}},e.enqueueMutation=a,e.registerTransientObservers=o,e.wrappers.MutationObserver=c,e.wrappers.MutationRecord=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e,t){this.root=e,this.parent=t}function n(e,t){if(e.treeScope_!==t){e.treeScope_=t;for(var r=e.shadowRoot;r;r=r.olderShadowRoot)r.treeScope_.parent=t;for(var o=e.firstChild;o;o=o.nextSibling)n(o,t)}}function r(n){if(n instanceof e.wrappers.Window,n.treeScope_)return n.treeScope_;var o,i=n.parentNode;return o=i?r(i):new t(n,null),n.treeScope_=o}t.prototype={get renderer(){return this.root instanceof e.wrappers.ShadowRoot?e.getRendererForHost(this.root.host):null},contains:function(e){for(;e;e=e.parent)if(e===this)return!0;return!1}},e.TreeScope=t,e.getTreeScope=r,e.setTreeScope=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e instanceof G.ShadowRoot}function n(e){return A(e).root}function r(e,r){var s=[],c=e;for(s.push(c);c;){var l=a(c);if(l&&l.length>0){for(var u=0;u<l.length;u++){var p=l[u];if(i(p)){var h=n(p),f=h.olderShadowRoot;f&&s.push(f)}s.push(p)}c=l[l.length-1]}else if(t(c)){if(d(e,c)&&o(r))break;c=c.host,s.push(c)}else c=c.parentNode,c&&s.push(c)}return s}function o(e){if(!e)return!1;switch(e.type){case"abort":case"error":case"select":case"change":case"load":case"reset":case"resize":case"scroll":case"selectstart":return!0}return!1}function i(e){return e instanceof HTMLShadowElement}function a(t){return e.getDestinationInsertionPoints(t)}function s(e,t){if(0===e.length)return t;t instanceof G.Window&&(t=t.document);for(var n=A(t),r=e[0],o=A(r),i=l(n,o),a=0;a<e.length;a++){var s=e[a];if(A(s)===i)return s}return e[e.length-1]}function c(e){for(var t=[];e;e=e.parent)t.push(e);return t}function l(e,t){for(var n=c(e),r=c(t),o=null;n.length>0&&r.length>0;){var i=n.pop(),a=r.pop();if(i!==a)break;o=i}return o}function u(e,t,n){t instanceof G.Window&&(t=t.document);var o,i=A(t),a=A(n),s=r(n,e),o=l(i,a);o||(o=a.root);for(var c=o;c;c=c.parent)for(var u=0;u<s.length;u++){var d=s[u];if(A(d)===c)return d}return null}function d(e,t){return A(e)===A(t)}function p(e){if(!K.get(e)&&(K.set(e,!0),f(V(e),V(e.target)),P)){var t=P;throw P=null,t}}function h(e){switch(e.type){case"load":case"beforeunload":case"unload":return!0}return!1}function f(t,n){if($.get(t))throw new Error("InvalidStateError");$.set(t,!0),e.renderAllPending();var o,i,a;if(h(t)&&!t.bubbles){var s=n;s instanceof G.Document&&(a=s.defaultView)&&(i=s,o=[])}if(!o)if(n instanceof G.Window)a=n,o=[];else if(o=r(n,t),!h(t)){var s=o[o.length-1];s instanceof G.Document&&(a=s.defaultView)}return ne.set(t,o),m(t,o,a,i)&&w(t,o,a,i)&&v(t,o,a,i),J.set(t,re),Y["delete"](t,null),$["delete"](t),t.defaultPrevented}function m(e,t,n,r){var o=oe;if(n&&!g(n,e,o,t,r))return!1;for(var i=t.length-1;i>0;i--)if(!g(t[i],e,o,t,r))return!1;return!0}function w(e,t,n,r){var o=ie,i=t[0]||n;return g(i,e,o,t,r)}function v(e,t,n,r){for(var o=ae,i=1;i<t.length;i++)if(!g(t[i],e,o,t,r))return;n&&t.length>0&&g(n,e,o,t,r)}function g(e,t,n,r,o){var i=z.get(e);if(!i)return!0;var a=o||s(r,e);if(a===e){if(n===oe)return!0;n===ae&&(n=ie)}else if(n===ae&&!t.bubbles)return!0;if("relatedTarget"in t){var c=B(t),l=c.relatedTarget;if(l){if(l instanceof Object&&l.addEventListener){var d=V(l),p=u(t,e,d);if(p===a)return!0}else p=null;Z.set(t,p)}}J.set(t,n);var h=t.type,f=!1;X.set(t,a),Y.set(t,e),i.depth++;for(var m=0,w=i.length;m<w;m++){var v=i[m];if(v.removed)f=!0;else if(!(v.type!==h||!v.capture&&n===oe||v.capture&&n===ae))try{if("function"==typeof v.handler?v.handler.call(e,t):v.handler.handleEvent(t),ee.get(t))return!1}catch(g){P||(P=g)}}if(i.depth--,f&&0===i.depth){var b=i.slice();i.length=0;for(var m=0;m<b.length;m++)b[m].removed||i.push(b[m])}return!Q.get(t)}function b(e,t,n){this.type=e,this.handler=t,this.capture=Boolean(n)}function y(e,t){if(!(e instanceof se))return V(T(se,"Event",e,t));var n=e;return be||"beforeunload"!==n.type||this instanceof M?void U(n,this):new M(n)}function E(e){return e&&e.relatedTarget?Object.create(e,{relatedTarget:{value:B(e.relatedTarget)}}):e}function _(e,t,n){var r=window[e],o=function(t,n){return t instanceof r?void U(t,this):V(T(r,e,t,n))};if(o.prototype=Object.create(t.prototype),n&&W(o.prototype,n),r)try{F(r,o,new r("temp"))}catch(i){F(r,o,document.createEvent(e))}return o}function S(e,t){return function(){arguments[t]=B(arguments[t]);var n=B(this);n[e].apply(n,arguments)}}function T(e,t,n,r){if(ve)return new e(n,E(r));var o=B(document.createEvent(t)),i=we[t],a=[n];return Object.keys(i).forEach(function(e){var t=null!=r&&e in r?r[e]:i[e];"relatedTarget"===e&&(t=B(t)),a.push(t)}),o["init"+t].apply(o,a),o}function M(e){y.call(this,e)}function O(e){return"function"==typeof e||e&&e.handleEvent}function L(e){switch(e){case"DOMAttrModified":case"DOMAttributeNameChanged":case"DOMCharacterDataModified":case"DOMElementNameChanged":case"DOMNodeInserted":case"DOMNodeInsertedIntoDocument":case"DOMNodeRemoved":case"DOMNodeRemovedFromDocument":case"DOMSubtreeModified":return!0}return!1}function N(e){U(e,this)}function C(e){return e instanceof G.ShadowRoot&&(e=e.host),B(e)}function j(e,t){var n=z.get(e);if(n)for(var r=0;r<n.length;r++)if(!n[r].removed&&n[r].type===t)return!0;return!1}function D(e,t){for(var n=B(e);n;n=n.parentNode)if(j(V(n),t))return!0;return!1}function H(e){k(e,Ee)}function x(t,n,o,i){e.renderAllPending();var a=V(_e.call(q(n),o,i));if(!a)return null;var c=r(a,null),l=c.lastIndexOf(t);return l==-1?null:(c=c.slice(0,l),s(c,t))}function R(e){return function(){var t=te.get(this);return t&&t[e]&&t[e].value||null}}function I(e){var t=e.slice(2);return function(n){var r=te.get(this);r||(r=Object.create(null),te.set(this,r));var o=r[e];if(o&&this.removeEventListener(t,o.wrapped,!1),"function"==typeof n){var i=function(t){var r=n.call(this,t);r===!1?t.preventDefault():"onbeforeunload"===e&&"string"==typeof r&&(t.returnValue=r)};this.addEventListener(t,i,!1),r[e]={value:n,wrapped:i}}}}var P,k=e.forwardMethodsToWrapper,A=e.getTreeScope,W=e.mixin,F=e.registerWrapper,U=e.setWrapper,q=e.unsafeUnwrap,B=e.unwrap,V=e.wrap,G=e.wrappers,z=(new WeakMap,new WeakMap),K=new WeakMap,$=new WeakMap,X=new WeakMap,Y=new WeakMap,Z=new WeakMap,J=new WeakMap,Q=new WeakMap,ee=new WeakMap,te=new WeakMap,ne=new WeakMap,re=0,oe=1,ie=2,ae=3;b.prototype={equals:function(e){return this.handler===e.handler&&this.type===e.type&&this.capture===e.capture},get removed(){return null===this.handler},remove:function(){this.handler=null}};var se=window.Event;se.prototype.polymerBlackList_={returnValue:!0,keyLocation:!0},y.prototype={get target(){return X.get(this)},get currentTarget(){return Y.get(this)},get eventPhase(){return J.get(this)},get path(){var e=ne.get(this);return e?e.slice():[]},stopPropagation:function(){Q.set(this,!0)},stopImmediatePropagation:function(){Q.set(this,!0),ee.set(this,!0)}};var ce=function(){var e=document.createEvent("Event");return e.initEvent("test",!0,!0),e.preventDefault(),e.defaultPrevented}();ce||(y.prototype.preventDefault=function(){this.cancelable&&(q(this).preventDefault(),Object.defineProperty(this,"defaultPrevented",{get:function(){return!0},configurable:!0}))}),F(se,y,document.createEvent("Event"));var le=_("UIEvent",y),ue=_("CustomEvent",y),de={get relatedTarget(){var e=Z.get(this);return void 0!==e?e:V(B(this).relatedTarget)}},pe=W({initMouseEvent:S("initMouseEvent",14)},de),he=W({initFocusEvent:S("initFocusEvent",5)},de),fe=_("MouseEvent",le,pe),me=_("FocusEvent",le,he),we=Object.create(null),ve=function(){try{new window.FocusEvent("focus")}catch(e){return!1}return!0}();if(!ve){var ge=function(e,t,n){if(n){var r=we[n];t=W(W({},r),t)}we[e]=t};ge("Event",{bubbles:!1,cancelable:!1}),ge("CustomEvent",{detail:null},"Event"),ge("UIEvent",{view:null,detail:0},"Event"),ge("MouseEvent",{screenX:0,screenY:0,clientX:0,clientY:0,ctrlKey:!1,altKey:!1,shiftKey:!1,metaKey:!1,button:0,relatedTarget:null},"UIEvent"),ge("FocusEvent",{relatedTarget:null},"UIEvent")}var be=window.BeforeUnloadEvent;M.prototype=Object.create(y.prototype),W(M.prototype,{get returnValue(){return q(this).returnValue},set returnValue(e){q(this).returnValue=e}}),be&&F(be,M);var ye=window.EventTarget,Ee=["addEventListener","removeEventListener","dispatchEvent"];[Node,Window].forEach(function(e){var t=e.prototype;Ee.forEach(function(e){Object.defineProperty(t,e+"_",{value:t[e]})})}),N.prototype={addEventListener:function(e,t,n){if(O(t)&&!L(e)){var r=new b(e,t,n),o=z.get(this);if(o){for(var i=0;i<o.length;i++)if(r.equals(o[i]))return}else o=[],o.depth=0,z.set(this,o);o.push(r);var a=C(this);a.addEventListener_(e,p,!0)}},removeEventListener:function(e,t,n){n=Boolean(n);var r=z.get(this);if(r){for(var o=0,i=!1,a=0;a<r.length;a++)r[a].type===e&&r[a].capture===n&&(o++,r[a].handler===t&&(i=!0,r[a].remove()));if(i&&1===o){var s=C(this);s.removeEventListener_(e,p,!0)}}},dispatchEvent:function(t){var n=B(t),r=n.type;K.set(n,!1),e.renderAllPending();var o;D(this,r)||(o=function(){},this.addEventListener(r,o,!0));try{return B(this).dispatchEvent_(n)}finally{o&&this.removeEventListener(r,o,!0)}}},ye&&F(ye,N);var _e=document.elementFromPoint;e.elementFromPoint=x,e.getEventHandlerGetter=R,e.getEventHandlerSetter=I,e.wrapEventTargetMethods=H,e.wrappers.BeforeUnloadEvent=M,e.wrappers.CustomEvent=ue,e.wrappers.Event=y,e.wrappers.EventTarget=N,e.wrappers.FocusEvent=me,e.wrappers.MouseEvent=fe,e.wrappers.UIEvent=le}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e,t){Object.defineProperty(e,t,m)}function n(e){l(e,this)}function r(){this.length=0,t(this,"length")}function o(e){for(var t=new r,o=0;o<e.length;o++)t[o]=new n(e[o]);return t.length=o,t}function i(e){a.call(this,e)}var a=e.wrappers.UIEvent,s=e.mixin,c=e.registerWrapper,l=e.setWrapper,u=e.unsafeUnwrap,d=e.wrap,p=window.TouchEvent;if(p){var h;try{h=document.createEvent("TouchEvent")}catch(f){return}var m={enumerable:!1};n.prototype={get target(){return d(u(this).target)}};var w={configurable:!0,enumerable:!0,get:null};["clientX","clientY","screenX","screenY","pageX","pageY","identifier","webkitRadiusX","webkitRadiusY","webkitRotationAngle","webkitForce"].forEach(function(e){w.get=function(){return u(this)[e]},Object.defineProperty(n.prototype,e,w)}),r.prototype={item:function(e){return this[e]}},i.prototype=Object.create(a.prototype),s(i.prototype,{get touches(){return o(u(this).touches)},get targetTouches(){return o(u(this).targetTouches)},get changedTouches(){return o(u(this).changedTouches)},initTouchEvent:function(){throw new Error("Not implemented")}}),c(p,i,h),e.wrappers.Touch=n,e.wrappers.TouchEvent=i,e.wrappers.TouchList=r}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e,t){Object.defineProperty(e,t,s)}function n(){this.length=0,t(this,"length")}function r(e){if(null==e)return e;for(var t=new n,r=0,o=e.length;r<o;r++)t[r]=a(e[r]);return t.length=o,t}function o(e,t){e.prototype[t]=function(){return r(i(this)[t].apply(i(this),arguments))}}var i=e.unsafeUnwrap,a=e.wrap,s={enumerable:!1};n.prototype={item:function(e){return this[e]}},t(n.prototype,"item"),e.wrappers.NodeList=n,e.addWrapNodeListMethod=o,e.wrapNodeList=r}(window.ShadowDOMPolyfill),function(e){"use strict";e.wrapHTMLCollection=e.wrapNodeList,e.wrappers.HTMLCollection=e.wrappers.NodeList}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){O(e instanceof _)}function n(e){var t=new T;return t[0]=e,t.length=1,t}function r(e,t,n){N(t,"childList",{removedNodes:n,previousSibling:e.previousSibling,nextSibling:e.nextSibling})}function o(e,t){N(e,"childList",{removedNodes:t})}function i(e,t,r,o){if(e instanceof DocumentFragment){var i=s(e);U=!0;for(var a=i.length-1;a>=0;a--)e.removeChild(i[a]),i[a].parentNode_=t;U=!1;for(var a=0;a<i.length;a++)i[a].previousSibling_=i[a-1]||r,i[a].nextSibling_=i[a+1]||o;return r&&(r.nextSibling_=i[0]),o&&(o.previousSibling_=i[i.length-1]),i}var i=n(e),c=e.parentNode;return c&&c.removeChild(e),e.parentNode_=t,e.previousSibling_=r,e.nextSibling_=o,r&&(r.nextSibling_=e),o&&(o.previousSibling_=e),i}function a(e){if(e instanceof DocumentFragment)return s(e);var t=n(e),o=e.parentNode;return o&&r(e,o,t),t}function s(e){for(var t=new T,n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t.length=n,o(e,t),t}function c(e){return e}function l(e,t){R(e,t),e.nodeIsInserted_()}function u(e,t){for(var n=C(t),r=0;r<e.length;r++)l(e[r],n)}function d(e){R(e,new M(e,null))}function p(e){for(var t=0;t<e.length;t++)d(e[t])}function h(e,t){var n=e.nodeType===_.DOCUMENT_NODE?e:e.ownerDocument;n!==t.ownerDocument&&n.adoptNode(t)}function f(t,n){if(n.length){var r=t.ownerDocument;if(r!==n[0].ownerDocument)for(var o=0;o<n.length;o++)e.adoptNodeNoRemove(n[o],r)}}function m(e,t){f(e,t);var n=t.length;if(1===n)return P(t[0]);for(var r=P(e.ownerDocument.createDocumentFragment()),o=0;o<n;o++)r.appendChild(P(t[o]));return r}function w(e){if(void 0!==e.firstChild_)for(var t=e.firstChild_;t;){var n=t;t=t.nextSibling_,n.parentNode_=n.previousSibling_=n.nextSibling_=void 0}e.firstChild_=e.lastChild_=void 0}function v(e){if(e.invalidateShadowRenderer()){for(var t=e.firstChild;t;){O(t.parentNode===e);var n=t.nextSibling,r=P(t),o=r.parentNode;o&&X.call(o,r),t.previousSibling_=t.nextSibling_=t.parentNode_=null,t=n}e.firstChild_=e.lastChild_=null}else for(var n,i=P(e),a=i.firstChild;a;)n=a.nextSibling,X.call(i,a),a=n}function g(e){var t=e.parentNode;return t&&t.invalidateShadowRenderer()}function b(e){for(var t,n=0;n<e.length;n++)t=e[n],t.parentNode.removeChild(t)}function y(e,t,n){var r;if(r=A(n?q.call(n,I(e),!1):B.call(I(e),!1)),t){for(var o=e.firstChild;o;o=o.nextSibling)r.appendChild(y(o,!0,n));if(e instanceof F.HTMLTemplateElement)for(var i=r.content,o=e.content.firstChild;o;o=o.nextSibling)i.appendChild(y(o,!0,n))}return r}function E(e,t){if(!t||C(e)!==C(t))return!1;for(var n=t;n;n=n.parentNode)if(n===e)return!0;return!1}function _(e){O(e instanceof V),S.call(this,e),this.parentNode_=void 0,this.firstChild_=void 0,this.lastChild_=void 0,this.nextSibling_=void 0,this.previousSibling_=void 0,this.treeScope_=void 0}var S=e.wrappers.EventTarget,T=e.wrappers.NodeList,M=e.TreeScope,O=e.assert,L=e.defineWrapGetter,N=e.enqueueMutation,C=e.getTreeScope,j=e.isWrapper,D=e.mixin,H=e.registerTransientObservers,x=e.registerWrapper,R=e.setTreeScope,I=e.unsafeUnwrap,P=e.unwrap,k=e.unwrapIfNeeded,A=e.wrap,W=e.wrapIfNeeded,F=e.wrappers,U=!1,q=document.importNode,B=window.Node.prototype.cloneNode,V=window.Node,G=window.DocumentFragment,z=(V.prototype.appendChild,V.prototype.compareDocumentPosition),K=V.prototype.isEqualNode,$=V.prototype.insertBefore,X=V.prototype.removeChild,Y=V.prototype.replaceChild,Z=/Trident|Edge/.test(navigator.userAgent),J=Z?function(e,t){try{X.call(e,t)}catch(n){if(!(e instanceof G))throw n}}:function(e,t){X.call(e,t)};_.prototype=Object.create(S.prototype),D(_.prototype,{appendChild:function(e){return this.insertBefore(e,null)},insertBefore:function(e,n){t(e);var r;n?j(n)?r=P(n):(r=n,n=A(r)):(n=null,r=null),n&&O(n.parentNode===this);var o,s=n?n.previousSibling:this.lastChild,c=!this.invalidateShadowRenderer()&&!g(e);if(o=c?a(e):i(e,this,s,n),c)h(this,e),w(this),$.call(I(this),P(e),r);else{s||(this.firstChild_=o[0]),n||(this.lastChild_=o[o.length-1],void 0===this.firstChild_&&(this.firstChild_=this.firstChild));var l=r?r.parentNode:I(this);l?$.call(l,m(this,o),r):f(this,o)}return N(this,"childList",{addedNodes:o,nextSibling:n,previousSibling:s}),u(o,this),e},removeChild:function(e){if(t(e),e.parentNode!==this){for(var r=!1,o=(this.childNodes,this.firstChild);o;o=o.nextSibling)if(o===e){r=!0;break}if(!r)throw new Error("NotFoundError")}var i=P(e),a=e.nextSibling,s=e.previousSibling;if(this.invalidateShadowRenderer()){var c=this.firstChild,l=this.lastChild,u=i.parentNode;u&&J(u,i),c===e&&(this.firstChild_=a),l===e&&(this.lastChild_=s),s&&(s.nextSibling_=a),a&&(a.previousSibling_=s),e.previousSibling_=e.nextSibling_=e.parentNode_=void 0}else w(this),J(I(this),i);return U||N(this,"childList",{removedNodes:n(e),nextSibling:a,previousSibling:s}),H(this,e),e},replaceChild:function(e,r){t(e);var o;if(j(r)?o=P(r):(o=r,r=A(o)),r.parentNode!==this)throw new Error("NotFoundError");var s,c=r.nextSibling,l=r.previousSibling,p=!this.invalidateShadowRenderer()&&!g(e);return p?s=a(e):(c===e&&(c=e.nextSibling),s=i(e,this,l,c)),p?(h(this,e),w(this),Y.call(I(this),P(e),o)):(this.firstChild===r&&(this.firstChild_=s[0]),this.lastChild===r&&(this.lastChild_=s[s.length-1]),r.previousSibling_=r.nextSibling_=r.parentNode_=void 0,o.parentNode&&Y.call(o.parentNode,m(this,s),o)),N(this,"childList",{addedNodes:s,removedNodes:n(r),nextSibling:c,previousSibling:l}),d(r),u(s,this),r},nodeIsInserted_:function(){for(var e=this.firstChild;e;e=e.nextSibling)e.nodeIsInserted_()},hasChildNodes:function(){return null!==this.firstChild},get parentNode(){return void 0!==this.parentNode_?this.parentNode_:A(I(this).parentNode)},get firstChild(){return void 0!==this.firstChild_?this.firstChild_:A(I(this).firstChild)},get lastChild(){return void 0!==this.lastChild_?this.lastChild_:A(I(this).lastChild)},get nextSibling(){return void 0!==this.nextSibling_?this.nextSibling_:A(I(this).nextSibling)},get previousSibling(){return void 0!==this.previousSibling_?this.previousSibling_:A(I(this).previousSibling)},get parentElement(){for(var e=this.parentNode;e&&e.nodeType!==_.ELEMENT_NODE;)e=e.parentNode;return e},get textContent(){for(var e="",t=this.firstChild;t;t=t.nextSibling)t.nodeType!=_.COMMENT_NODE&&(e+=t.textContent);return e},set textContent(e){null==e&&(e="");var t=c(this.childNodes);if(this.invalidateShadowRenderer()){if(v(this),""!==e){var n=I(this).ownerDocument.createTextNode(e);this.appendChild(n)}}else w(this),I(this).textContent=e;var r=c(this.childNodes);N(this,"childList",{addedNodes:r,removedNodes:t}),p(t),u(r,this)},get childNodes(){for(var e=new T,t=0,n=this.firstChild;n;n=n.nextSibling)e[t++]=n;return e.length=t,e},cloneNode:function(e){return y(this,e)},contains:function(e){return E(this,W(e))},compareDocumentPosition:function(e){return z.call(I(this),k(e))},isEqualNode:function(e){return K.call(I(this),k(e))},normalize:function(){for(var e,t,n=c(this.childNodes),r=[],o="",i=0;i<n.length;i++)t=n[i],t.nodeType===_.TEXT_NODE?e||t.data.length?e?(o+=t.data,r.push(t)):e=t:this.removeChild(t):(e&&r.length&&(e.data+=o,b(r)),r=[],o="",e=null,t.childNodes.length&&t.normalize());e&&r.length&&(e.data+=o,b(r))}}),L(_,"ownerDocument"),x(V,_,document.createDocumentFragment()),delete _.prototype.querySelector,delete _.prototype.querySelectorAll,_.prototype=D(Object.create(S.prototype),_.prototype),e.cloneNode=y,e.nodeWasAdded=l,e.nodeWasRemoved=d,e.nodesWereAdded=u,e.nodesWereRemoved=p,e.originalInsertBefore=$,e.originalRemoveChild=X,e.snapshotNodeList=c,e.wrappers.Node=_}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t,n,r,o){for(var i=null,a=null,s=0,c=t.length;s<c;s++)i=b(t[s]),!o&&(a=v(i).root)&&a instanceof e.wrappers.ShadowRoot||(r[n++]=i);return n}function n(e){return String(e).replace(/\/deep\/|::shadow|>>>/g," ")}function r(e){return String(e).replace(/:host\(([^\s]+)\)/g,"$1").replace(/([^\s]):host/g,"$1").replace(":host","*").replace(/\^|\/shadow\/|\/shadow-deep\/|::shadow|\/deep\/|::content|>>>/g," ")}function o(e,t){for(var n,r=e.firstElementChild;r;){if(r.matches(t))return r;if(n=o(r,t))return n;r=r.nextElementSibling}return null}function i(e,t){return e.matches(t)}function a(e,t,n){var r=e.localName;return r===t||r===n&&e.namespaceURI===j}function s(){return!0}function c(e,t,n){return e.localName===n}function l(e,t){return e.namespaceURI===t}function u(e,t,n){return e.namespaceURI===t&&e.localName===n}function d(e,t,n,r,o,i){for(var a=e.firstElementChild;a;)r(a,o,i)&&(n[t++]=a),t=d(a,t,n,r,o,i),a=a.nextElementSibling;return t}function p(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,null);if(c instanceof N)s=S.call(c,i);else{if(!(c instanceof C))return d(this,r,o,n,i,null);s=_.call(c,i)}return t(s,r,o,a)}function h(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=M.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=T.call(c,i,a)}return t(s,r,o,!1)}function f(n,r,o,i,a){var s,c=g(this),l=v(this).root;if(l instanceof e.wrappers.ShadowRoot)return d(this,r,o,n,i,a);if(c instanceof N)s=L.call(c,i,a);else{if(!(c instanceof C))return d(this,r,o,n,i,a);s=O.call(c,i,a)}return t(s,r,o,!1)}var m=e.wrappers.HTMLCollection,w=e.wrappers.NodeList,v=e.getTreeScope,g=e.unsafeUnwrap,b=e.wrap,y=document.querySelector,E=document.documentElement.querySelector,_=document.querySelectorAll,S=document.documentElement.querySelectorAll,T=document.getElementsByTagName,M=document.documentElement.getElementsByTagName,O=document.getElementsByTagNameNS,L=document.documentElement.getElementsByTagNameNS,N=window.Element,C=window.HTMLDocument||window.Document,j="http://www.w3.org/1999/xhtml",D={
+querySelector:function(t){var r=n(t),i=r!==t;t=r;var a,s=g(this),c=v(this).root;if(c instanceof e.wrappers.ShadowRoot)return o(this,t);if(s instanceof N)a=b(E.call(s,t));else{if(!(s instanceof C))return o(this,t);a=b(y.call(s,t))}return a&&!i&&(c=v(a).root)&&c instanceof e.wrappers.ShadowRoot?o(this,t):a},querySelectorAll:function(e){var t=n(e),r=t!==e;e=t;var o=new w;return o.length=p.call(this,i,0,o,e,r),o}},H={matches:function(t){return t=r(t),e.originalMatches.call(g(this),t)}},x={getElementsByTagName:function(e){var t=new m,n="*"===e?s:a;return t.length=h.call(this,n,0,t,e,e.toLowerCase()),t},getElementsByClassName:function(e){return this.querySelectorAll("."+e)},getElementsByTagNameNS:function(e,t){var n=new m,r=null;return r="*"===e?"*"===t?s:c:"*"===t?l:u,n.length=f.call(this,r,0,n,e||null,t),n}};e.GetElementsByInterface=x,e.SelectorsInterface=D,e.MatchesInterface=H}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;return e}function n(e){for(;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.previousSibling;return e}var r=e.wrappers.NodeList,o={get firstElementChild(){return t(this.firstChild)},get lastElementChild(){return n(this.lastChild)},get childElementCount(){for(var e=0,t=this.firstElementChild;t;t=t.nextElementSibling)e++;return e},get children(){for(var e=new r,t=0,n=this.firstElementChild;n;n=n.nextElementSibling)e[t++]=n;return e.length=t,e},remove:function(){var e=this.parentNode;e&&e.removeChild(this)}},i={get nextElementSibling(){return t(this.nextSibling)},get previousElementSibling(){return n(this.previousSibling)}},a={getElementById:function(e){return/[ \t\n\r\f]/.test(e)?null:this.querySelector('[id="'+e+'"]')}};e.ChildNodeInterface=i,e.NonElementParentNodeInterface=a,e.ParentNodeInterface=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}var n=e.ChildNodeInterface,r=e.wrappers.Node,o=e.enqueueMutation,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=window.CharacterData;t.prototype=Object.create(r.prototype),i(t.prototype,{get nodeValue(){return this.data},set nodeValue(e){this.data=e},get textContent(){return this.data},set textContent(e){this.data=e},get data(){return s(this).data},set data(e){var t=s(this).data;o(this,"characterData",{oldValue:t}),s(this).data=e}}),i(t.prototype,n),a(c,t,document.createTextNode("")),e.wrappers.CharacterData=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e>>>0}function n(e){r.call(this,e)}var r=e.wrappers.CharacterData,o=(e.enqueueMutation,e.mixin),i=e.registerWrapper,a=window.Text;n.prototype=Object.create(r.prototype),o(n.prototype,{splitText:function(e){e=t(e);var n=this.data;if(e>n.length)throw new Error("IndexSizeError");var r=n.slice(0,e),o=n.slice(e);this.data=r;var i=this.ownerDocument.createTextNode(o);return this.parentNode&&this.parentNode.insertBefore(i,this.nextSibling),i}}),i(a,n,document.createTextNode("")),e.wrappers.Text=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return i(e).getAttribute("class")}function n(e,t){a(e,"attributes",{name:"class",namespace:null,oldValue:t})}function r(t){e.invalidateRendererBasedOnAttribute(t,"class")}function o(e,o,i){var a=e.ownerElement_;if(null==a)return o.apply(e,i);var s=t(a),c=o.apply(e,i);return t(a)!==s&&(n(a,s),r(a)),c}if(!window.DOMTokenList)return void console.warn("Missing DOMTokenList prototype, please include a compatible classList polyfill such as http://goo.gl/uTcepH.");var i=e.unsafeUnwrap,a=e.enqueueMutation,s=DOMTokenList.prototype.add;DOMTokenList.prototype.add=function(){o(this,s,arguments)};var c=DOMTokenList.prototype.remove;DOMTokenList.prototype.remove=function(){o(this,c,arguments)};var l=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(){return o(this,l,arguments)}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t,n){var r=t.parentNode;if(r&&r.shadowRoot){var o=e.getRendererForHost(r);o.dependsOnAttribute(n)&&o.invalidate()}}function n(e,t,n){u(e,"attributes",{name:t,namespace:null,oldValue:n})}function r(e){a.call(this,e)}var o=e.ChildNodeInterface,i=e.GetElementsByInterface,a=e.wrappers.Node,s=e.ParentNodeInterface,c=e.SelectorsInterface,l=e.MatchesInterface,u=(e.addWrapNodeListMethod,e.enqueueMutation),d=e.mixin,p=(e.oneOf,e.registerWrapper),h=e.unsafeUnwrap,f=e.wrappers,m=window.Element,w=["matches","mozMatchesSelector","msMatchesSelector","webkitMatchesSelector"].filter(function(e){return m.prototype[e]}),v=w[0],g=m.prototype[v],b=new WeakMap;r.prototype=Object.create(a.prototype),d(r.prototype,{createShadowRoot:function(){var t=new f.ShadowRoot(this);h(this).polymerShadowRoot_=t;var n=e.getRendererForHost(this);return n.invalidate(),t},get shadowRoot(){return h(this).polymerShadowRoot_||null},setAttribute:function(e,r){var o=h(this).getAttribute(e);h(this).setAttribute(e,r),n(this,e,o),t(this,e)},removeAttribute:function(e){var r=h(this).getAttribute(e);h(this).removeAttribute(e),n(this,e,r),t(this,e)},get classList(){var e=b.get(this);if(!e){if(e=h(this).classList,!e)return;e.ownerElement_=this,b.set(this,e)}return e},get className(){return h(this).className},set className(e){this.setAttribute("class",e)},get id(){return h(this).id},set id(e){this.setAttribute("id",e)}}),w.forEach(function(e){"matches"!==e&&(r.prototype[e]=function(e){return this.matches(e)})}),m.prototype.webkitCreateShadowRoot&&(r.prototype.webkitCreateShadowRoot=r.prototype.createShadowRoot),d(r.prototype,o),d(r.prototype,i),d(r.prototype,s),d(r.prototype,c),d(r.prototype,l),p(m,r,document.createElementNS(null,"x")),e.invalidateRendererBasedOnAttribute=t,e.matchesNames=w,e.originalMatches=g,e.wrappers.Element=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e){case"&":return"&amp;";case"<":return"&lt;";case">":return"&gt;";case'"':return"&quot;";case" ":return"&nbsp;"}}function n(e){return e.replace(L,t)}function r(e){return e.replace(N,t)}function o(e){for(var t={},n=0;n<e.length;n++)t[e[n]]=!0;return t}function i(e){if(e.namespaceURI!==D)return!0;var t=e.ownerDocument.doctype;return t&&t.publicId&&t.systemId}function a(e,t){switch(e.nodeType){case Node.ELEMENT_NODE:for(var o,a=e.tagName.toLowerCase(),c="<"+a,l=e.attributes,u=0;o=l[u];u++)c+=" "+o.name+'="'+n(o.value)+'"';return C[a]?(i(e)&&(c+="/"),c+">"):c+">"+s(e)+"</"+a+">";case Node.TEXT_NODE:var d=e.data;return t&&j[t.localName]?d:r(d);case Node.COMMENT_NODE:return"<!--"+e.data+"-->";default:throw console.error(e),new Error("not implemented")}}function s(e){e instanceof O.HTMLTemplateElement&&(e=e.content);for(var t="",n=e.firstChild;n;n=n.nextSibling)t+=a(n,e);return t}function c(e,t,n){var r=n||"div";e.textContent="";var o=T(e.ownerDocument.createElement(r));o.innerHTML=t;for(var i;i=o.firstChild;)e.appendChild(M(i))}function l(e){m.call(this,e)}function u(e,t){var n=T(e.cloneNode(!1));n.innerHTML=t;for(var r,o=T(document.createDocumentFragment());r=n.firstChild;)o.appendChild(r);return M(o)}function d(t){return function(){return e.renderAllPending(),S(this)[t]}}function p(e){w(l,e,d(e))}function h(t){Object.defineProperty(l.prototype,t,{get:d(t),set:function(n){e.renderAllPending(),S(this)[t]=n},configurable:!0,enumerable:!0})}function f(t){Object.defineProperty(l.prototype,t,{value:function(){return e.renderAllPending(),S(this)[t].apply(S(this),arguments)},configurable:!0,enumerable:!0})}var m=e.wrappers.Element,w=e.defineGetter,v=e.enqueueMutation,g=e.mixin,b=e.nodesWereAdded,y=e.nodesWereRemoved,E=e.registerWrapper,_=e.snapshotNodeList,S=e.unsafeUnwrap,T=e.unwrap,M=e.wrap,O=e.wrappers,L=/[&\u00A0"]/g,N=/[&\u00A0<>]/g,C=o(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),j=o(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]),D="http://www.w3.org/1999/xhtml",H=/MSIE/.test(navigator.userAgent),x=window.HTMLElement,R=window.HTMLTemplateElement;l.prototype=Object.create(m.prototype),g(l.prototype,{get innerHTML(){return s(this)},set innerHTML(e){if(H&&j[this.localName])return void(this.textContent=e);var t=_(this.childNodes);this.invalidateShadowRenderer()?this instanceof O.HTMLTemplateElement?c(this.content,e):c(this,e,this.tagName):!R&&this instanceof O.HTMLTemplateElement?c(this.content,e):S(this).innerHTML=e;var n=_(this.childNodes);v(this,"childList",{addedNodes:n,removedNodes:t}),y(t),b(n,this)},get outerHTML(){return a(this,this.parentNode)},set outerHTML(e){var t=this.parentNode;if(t){t.invalidateShadowRenderer();var n=u(t,e);t.replaceChild(n,this)}},insertAdjacentHTML:function(e,t){var n,r;switch(String(e).toLowerCase()){case"beforebegin":n=this.parentNode,r=this;break;case"afterend":n=this.parentNode,r=this.nextSibling;break;case"afterbegin":n=this,r=this.firstChild;break;case"beforeend":n=this,r=null;break;default:return}var o=u(n,t);n.insertBefore(o,r)},get hidden(){return this.hasAttribute("hidden")},set hidden(e){e?this.setAttribute("hidden",""):this.removeAttribute("hidden")}}),["clientHeight","clientLeft","clientTop","clientWidth","offsetHeight","offsetLeft","offsetTop","offsetWidth","scrollHeight","scrollWidth"].forEach(p),["scrollLeft","scrollTop"].forEach(h),["focus","getBoundingClientRect","getClientRects","scrollIntoView"].forEach(f),E(x,l,document.createElement("b")),e.wrappers.HTMLElement=l,e.getInnerHTML=s,e.setInnerHTML=c}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.HTMLCanvasElement;t.prototype=Object.create(n.prototype),r(t.prototype,{getContext:function(){var e=i(this).getContext.apply(i(this),arguments);return e&&a(e)}}),o(s,t,document.createElement("canvas")),e.wrappers.HTMLCanvasElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=window.HTMLContentElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get select(){return this.getAttribute("select")},set select(e){this.setAttribute("select",e)},setAttribute:function(e,t){n.prototype.setAttribute.call(this,e,t),"select"===String(e).toLowerCase()&&this.invalidateShadowRenderer(!0)}}),i&&o(i,t),e.wrappers.HTMLContentElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=window.HTMLFormElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get elements(){return i(a(this).elements)}}),o(s,t,document.createElement("form")),e.wrappers.HTMLFormElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e,t){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var o=i(document.createElement("img"));r.call(this,o),a(o,this),void 0!==e&&(o.width=e),void 0!==t&&(o.height=t)}var r=e.wrappers.HTMLElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLImageElement;t.prototype=Object.create(r.prototype),o(s,t,document.createElement("img")),n.prototype=t.prototype,e.wrappers.HTMLImageElement=t,e.wrappers.Image=n}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=(e.mixin,e.wrappers.NodeList,e.registerWrapper),o=window.HTMLShadowElement;t.prototype=Object.create(n.prototype),t.prototype.constructor=t,o&&r(o,t),e.wrappers.HTMLShadowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){if(!e.defaultView)return e;var t=d.get(e);if(!t){for(t=e.implementation.createHTMLDocument("");t.lastChild;)t.removeChild(t.lastChild);d.set(e,t)}return t}function n(e){for(var n,r=t(e.ownerDocument),o=c(r.createDocumentFragment());n=e.firstChild;)o.appendChild(n);return o}function r(e){if(o.call(this,e),!p){var t=n(e);u.set(this,l(t))}}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.wrap,u=new WeakMap,d=new WeakMap,p=window.HTMLTemplateElement;r.prototype=Object.create(o.prototype),i(r.prototype,{constructor:r,get content(){return p?l(s(this).content):u.get(this)}}),p&&a(p,r),e.wrappers.HTMLTemplateElement=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.registerWrapper,o=window.HTMLMediaElement;o&&(t.prototype=Object.create(n.prototype),r(o,t,document.createElement("audio")),e.wrappers.HTMLMediaElement=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r.call(this,e)}function n(e){if(!(this instanceof n))throw new TypeError("DOM object constructor cannot be called as a function.");var t=i(document.createElement("audio"));r.call(this,t),a(t,this),t.setAttribute("preload","auto"),void 0!==e&&t.setAttribute("src",e)}var r=e.wrappers.HTMLMediaElement,o=e.registerWrapper,i=e.unwrap,a=e.rewrap,s=window.HTMLAudioElement;s&&(t.prototype=Object.create(r.prototype),o(s,t,document.createElement("audio")),n.prototype=t.prototype,e.wrappers.HTMLAudioElement=t,e.wrappers.Audio=n)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){return e.replace(/\s+/g," ").trim()}function n(e){o.call(this,e)}function r(e,t,n,i){if(!(this instanceof r))throw new TypeError("DOM object constructor cannot be called as a function.");var a=c(document.createElement("option"));o.call(this,a),s(a,this),void 0!==e&&(a.text=e),void 0!==t&&a.setAttribute("value",t),n===!0&&a.setAttribute("selected",""),a.selected=i===!0}var o=e.wrappers.HTMLElement,i=e.mixin,a=e.registerWrapper,s=e.rewrap,c=e.unwrap,l=e.wrap,u=window.HTMLOptionElement;n.prototype=Object.create(o.prototype),i(n.prototype,{get text(){return t(this.textContent)},set text(e){this.textContent=t(String(e))},get form(){return l(c(this).form)}}),a(u,n,document.createElement("option")),r.prototype=n.prototype,e.wrappers.HTMLOptionElement=n,e.wrappers.Option=r}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=window.HTMLSelectElement;t.prototype=Object.create(n.prototype),r(t.prototype,{add:function(e,t){"object"==typeof t&&(t=i(t)),i(this).add(i(e),t)},remove:function(e){return void 0===e?void n.prototype.remove.call(this):("object"==typeof e&&(e=i(e)),void i(this).remove(e))},get form(){return a(i(this).form)}}),o(s,t,document.createElement("select")),e.wrappers.HTMLSelectElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.unwrap,a=e.wrap,s=e.wrapHTMLCollection,c=window.HTMLTableElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get caption(){return a(i(this).caption)},createCaption:function(){return a(i(this).createCaption())},get tHead(){return a(i(this).tHead)},createTHead:function(){return a(i(this).createTHead())},createTFoot:function(){return a(i(this).createTFoot())},get tFoot(){return a(i(this).tFoot)},get tBodies(){return s(i(this).tBodies)},createTBody:function(){return a(i(this).createTBody())},get rows(){return s(i(this).rows)},insertRow:function(e){return a(i(this).insertRow(e))}}),o(c,t,document.createElement("table")),e.wrappers.HTMLTableElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableSectionElement;t.prototype=Object.create(n.prototype),r(t.prototype,{constructor:t,get rows(){return i(a(this).rows)},insertRow:function(e){return s(a(this).insertRow(e))}}),o(c,t,document.createElement("thead")),e.wrappers.HTMLTableSectionElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.HTMLElement,r=e.mixin,o=e.registerWrapper,i=e.wrapHTMLCollection,a=e.unwrap,s=e.wrap,c=window.HTMLTableRowElement;t.prototype=Object.create(n.prototype),r(t.prototype,{get cells(){return i(a(this).cells)},insertCell:function(e){return s(a(this).insertCell(e))}}),o(c,t,document.createElement("tr")),e.wrappers.HTMLTableRowElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){switch(e.localName){case"content":return new n(e);case"shadow":return new o(e);case"template":return new i(e)}r.call(this,e)}var n=e.wrappers.HTMLContentElement,r=e.wrappers.HTMLElement,o=e.wrappers.HTMLShadowElement,i=e.wrappers.HTMLTemplateElement,a=(e.mixin,e.registerWrapper),s=window.HTMLUnknownElement;t.prototype=Object.create(r.prototype),a(s,t),e.wrappers.HTMLUnknownElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.Element,r=e.wrappers.HTMLElement,o=e.registerWrapper,i=(e.defineWrapGetter,e.unsafeUnwrap),a=e.wrap,s=e.mixin,c="http://www.w3.org/2000/svg",l=window.SVGElement,u=document.createElementNS(c,"title");if(!("classList"in u)){var d=Object.getOwnPropertyDescriptor(n.prototype,"classList");Object.defineProperty(r.prototype,"classList",d),delete n.prototype.classList}t.prototype=Object.create(n.prototype),s(t.prototype,{get ownerSVGElement(){return a(i(this).ownerSVGElement)}}),o(l,t,document.createElementNS(c,"title")),e.wrappers.SVGElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){p.call(this,e)}var n=e.mixin,r=e.registerWrapper,o=e.unwrap,i=e.wrap,a=window.SVGUseElement,s="http://www.w3.org/2000/svg",c=i(document.createElementNS(s,"g")),l=document.createElementNS(s,"use"),u=c.constructor,d=Object.getPrototypeOf(u.prototype),p=d.constructor;t.prototype=Object.create(d),"instanceRoot"in l&&n(t.prototype,{get instanceRoot(){return i(o(this).instanceRoot)},get animatedInstanceRoot(){return i(o(this).animatedInstanceRoot)}}),r(a,t,l),e.wrappers.SVGUseElement=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.mixin,o=e.registerWrapper,i=e.unsafeUnwrap,a=e.wrap,s=window.SVGElementInstance;s&&(t.prototype=Object.create(n.prototype),r(t.prototype,{get correspondingElement(){return a(i(this).correspondingElement)},get correspondingUseElement(){return a(i(this).correspondingUseElement)},get parentNode(){return a(i(this).parentNode)},get childNodes(){throw new Error("Not implemented")},get firstChild(){return a(i(this).firstChild)},get lastChild(){return a(i(this).lastChild)},get previousSibling(){return a(i(this).previousSibling)},get nextSibling(){return a(i(this).nextSibling)}}),o(s,t),e.wrappers.SVGElementInstance=t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){o(e,this)}var n=e.mixin,r=e.registerWrapper,o=e.setWrapper,i=e.unsafeUnwrap,a=e.unwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.CanvasRenderingContext2D;n(t.prototype,{get canvas(){return c(i(this).canvas)},drawImage:function(){arguments[0]=s(arguments[0]),i(this).drawImage.apply(i(this),arguments)},createPattern:function(){return arguments[0]=a(arguments[0]),i(this).createPattern.apply(i(this),arguments)}}),r(l,t,document.createElement("canvas").getContext("2d")),e.wrappers.CanvasRenderingContext2D=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){i(e,this)}var n=e.addForwardingProperties,r=e.mixin,o=e.registerWrapper,i=e.setWrapper,a=e.unsafeUnwrap,s=e.unwrapIfNeeded,c=e.wrap,l=window.WebGLRenderingContext;if(l){r(t.prototype,{get canvas(){return c(a(this).canvas)},texImage2D:function(){arguments[5]=s(arguments[5]),a(this).texImage2D.apply(a(this),arguments)},texSubImage2D:function(){arguments[6]=s(arguments[6]),a(this).texSubImage2D.apply(a(this),arguments)}});var u=Object.getPrototypeOf(l.prototype);u!==Object.prototype&&n(u,t.prototype);var d=/WebKit/.test(navigator.userAgent)?{drawingBufferHeight:null,drawingBufferWidth:null}:{};o(l,t,d),e.wrappers.WebGLRenderingContext=t}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.Node,r=e.GetElementsByInterface,o=e.NonElementParentNodeInterface,i=e.ParentNodeInterface,a=e.SelectorsInterface,s=e.mixin,c=e.registerObject,l=e.registerWrapper,u=window.DocumentFragment;t.prototype=Object.create(n.prototype),s(t.prototype,i),s(t.prototype,a),s(t.prototype,r),s(t.prototype,o),l(u,t,document.createDocumentFragment()),e.wrappers.DocumentFragment=t;var d=c(document.createComment(""));e.wrappers.Comment=d}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(u(e).ownerDocument.createDocumentFragment());n.call(this,t),c(t,this);var o=e.shadowRoot;f.set(this,o),this.treeScope_=new r(this,a(o||e)),h.set(this,e)}var n=e.wrappers.DocumentFragment,r=e.TreeScope,o=e.elementFromPoint,i=e.getInnerHTML,a=e.getTreeScope,s=e.mixin,c=e.rewrap,l=e.setInnerHTML,u=e.unsafeUnwrap,d=e.unwrap,p=e.wrap,h=new WeakMap,f=new WeakMap;t.prototype=Object.create(n.prototype),s(t.prototype,{constructor:t,get innerHTML(){return i(this)},set innerHTML(e){l(this,e),this.invalidateShadowRenderer()},get olderShadowRoot(){return f.get(this)||null},get host(){return h.get(this)||null},invalidateShadowRenderer:function(){return h.get(this).invalidateShadowRenderer()},elementFromPoint:function(e,t){return o(this,this.ownerDocument,e,t)},getSelection:function(){return document.getSelection()},get activeElement(){var e=d(this).ownerDocument.activeElement;if(!e||!e.nodeType)return null;for(var t=p(e);!this.contains(t);){for(;t.parentNode;)t=t.parentNode;if(!t.host)return null;t=t.host}return t}}),e.wrappers.ShadowRoot=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=d(e).root;return t instanceof h?t.host:null}function n(t,n){if(t.shadowRoot){n=Math.min(t.childNodes.length-1,n);var r=t.childNodes[n];if(r){var o=e.getDestinationInsertionPoints(r);if(o.length>0){var i=o[0].parentNode;i.nodeType==Node.ELEMENT_NODE&&(t=i)}}}return t}function r(e){return e=u(e),t(e)||e}function o(e){a(e,this)}var i=e.registerWrapper,a=e.setWrapper,s=e.unsafeUnwrap,c=e.unwrap,l=e.unwrapIfNeeded,u=e.wrap,d=e.getTreeScope,p=window.Range,h=e.wrappers.ShadowRoot;o.prototype={get startContainer(){return r(s(this).startContainer)},get endContainer(){return r(s(this).endContainer)},get commonAncestorContainer(){return r(s(this).commonAncestorContainer)},setStart:function(e,t){e=n(e,t),s(this).setStart(l(e),t)},setEnd:function(e,t){e=n(e,t),s(this).setEnd(l(e),t)},setStartBefore:function(e){s(this).setStartBefore(l(e))},setStartAfter:function(e){s(this).setStartAfter(l(e))},setEndBefore:function(e){s(this).setEndBefore(l(e))},setEndAfter:function(e){s(this).setEndAfter(l(e))},selectNode:function(e){s(this).selectNode(l(e))},selectNodeContents:function(e){s(this).selectNodeContents(l(e))},compareBoundaryPoints:function(e,t){return s(this).compareBoundaryPoints(e,c(t))},extractContents:function(){return u(s(this).extractContents())},cloneContents:function(){return u(s(this).cloneContents())},insertNode:function(e){s(this).insertNode(l(e))},surroundContents:function(e){s(this).surroundContents(l(e))},cloneRange:function(){return u(s(this).cloneRange())},isPointInRange:function(e,t){return s(this).isPointInRange(l(e),t)},comparePoint:function(e,t){return s(this).comparePoint(l(e),t)},intersectsNode:function(e){return s(this).intersectsNode(l(e))},toString:function(){return s(this).toString()}},p.prototype.createContextualFragment&&(o.prototype.createContextualFragment=function(e){return u(s(this).createContextualFragment(e))}),i(window.Range,o,document.createRange()),e.wrappers.Range=o}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){e.previousSibling_=e.previousSibling,e.nextSibling_=e.nextSibling,e.parentNode_=e.parentNode}function n(n,o,i){var a=x(n),s=x(o),c=i?x(i):null;if(r(o),t(o),i)n.firstChild===i&&(n.firstChild_=i),i.previousSibling_=i.previousSibling;else{n.lastChild_=n.lastChild,n.lastChild===n.firstChild&&(n.firstChild_=n.firstChild);var l=R(a.lastChild);l&&(l.nextSibling_=l.nextSibling)}e.originalInsertBefore.call(a,s,c)}function r(n){var r=x(n),o=r.parentNode;if(o){var i=R(o);t(n),n.previousSibling&&(n.previousSibling.nextSibling_=n),n.nextSibling&&(n.nextSibling.previousSibling_=n),i.lastChild===n&&(i.lastChild_=n),i.firstChild===n&&(i.firstChild_=n),e.originalRemoveChild.call(o,r)}}function o(e){P.set(e,[])}function i(e){var t=P.get(e);return t||P.set(e,t=[]),t}function a(e){for(var t=[],n=0,r=e.firstChild;r;r=r.nextSibling)t[n++]=r;return t}function s(){for(var e=0;e<F.length;e++){var t=F[e],n=t.parentRenderer;n&&n.dirty||t.render()}F=[]}function c(){T=null,s()}function l(e){var t=A.get(e);return t||(t=new h(e),A.set(e,t)),t}function u(e){var t=j(e).root;return t instanceof C?t:null}function d(e){return l(e.host)}function p(e){this.skip=!1,this.node=e,this.childNodes=[]}function h(e){this.host=e,this.dirty=!1,this.invalidateAttributes(),this.associateNode(e)}function f(e){for(var t=[],n=e.firstChild;n;n=n.nextSibling)E(n)?t.push.apply(t,i(n)):t.push(n);return t}function m(e){if(e instanceof L)return e;if(e instanceof O)return null;for(var t=e.firstChild;t;t=t.nextSibling){var n=m(t);if(n)return n}return null}function w(e,t){i(t).push(e);var n=k.get(e);n?n.push(t):k.set(e,[t])}function v(e){return k.get(e)}function g(e){k.set(e,void 0)}function b(e,t){var n=t.getAttribute("select");if(!n)return!0;if(n=n.trim(),!n)return!0;if(!(e instanceof M))return!1;if(!q.test(n))return!1;try{return e.matches(n)}catch(r){return!1}}function y(e,t){var n=v(t);return n&&n[n.length-1]===e}function E(e){return e instanceof O||e instanceof L}function _(e){return e.shadowRoot}function S(e){for(var t=[],n=e.shadowRoot;n;n=n.olderShadowRoot)t.push(n);return t}var T,M=e.wrappers.Element,O=e.wrappers.HTMLContentElement,L=e.wrappers.HTMLShadowElement,N=e.wrappers.Node,C=e.wrappers.ShadowRoot,j=(e.assert,e.getTreeScope),D=(e.mixin,e.oneOf),H=e.unsafeUnwrap,x=e.unwrap,R=e.wrap,I=e.ArraySplice,P=new WeakMap,k=new WeakMap,A=new WeakMap,W=D(window,["requestAnimationFrame","mozRequestAnimationFrame","webkitRequestAnimationFrame","setTimeout"]),F=[],U=new I;U.equals=function(e,t){return x(e.node)===t},p.prototype={append:function(e){var t=new p(e);return this.childNodes.push(t),t},sync:function(e){if(!this.skip){for(var t=this.node,o=this.childNodes,i=a(x(t)),s=e||new WeakMap,c=U.calculateSplices(o,i),l=0,u=0,d=0,p=0;p<c.length;p++){for(var h=c[p];d<h.index;d++)u++,o[l++].sync(s);for(var f=h.removed.length,m=0;m<f;m++){var w=R(i[u++]);s.get(w)||r(w)}for(var v=h.addedCount,g=i[u]&&R(i[u]),m=0;m<v;m++){var b=o[l++],y=b.node;n(t,y,g),s.set(y,!0),b.sync(s)}d+=v}for(var p=d;p<o.length;p++)o[p].sync(s)}}},h.prototype={render:function(e){if(this.dirty){this.invalidateAttributes();var t=this.host;this.distribution(t);var n=e||new p(t);this.buildRenderTree(n,t);var r=!e;r&&n.sync(),this.dirty=!1}},get parentRenderer(){return j(this.host).renderer},invalidate:function(){if(!this.dirty){this.dirty=!0;var e=this.parentRenderer;if(e&&e.invalidate(),F.push(this),T)return;T=window[W](c,0)}},distribution:function(e){this.resetAllSubtrees(e),this.distributionResolution(e)},resetAll:function(e){E(e)?o(e):g(e),this.resetAllSubtrees(e)},resetAllSubtrees:function(e){for(var t=e.firstChild;t;t=t.nextSibling)this.resetAll(t);e.shadowRoot&&this.resetAll(e.shadowRoot),e.olderShadowRoot&&this.resetAll(e.olderShadowRoot)},distributionResolution:function(e){if(_(e)){for(var t=e,n=f(t),r=S(t),o=0;o<r.length;o++)this.poolDistribution(r[o],n);for(var o=r.length-1;o>=0;o--){var i=r[o],a=m(i);if(a){var s=i.olderShadowRoot;s&&(n=f(s));for(var c=0;c<n.length;c++)w(n[c],a)}this.distributionResolution(i)}}for(var l=e.firstChild;l;l=l.nextSibling)this.distributionResolution(l)},poolDistribution:function(e,t){if(!(e instanceof L))if(e instanceof O){var n=e;this.updateDependentAttributes(n.getAttribute("select"));for(var r=!1,o=0;o<t.length;o++){var e=t[o];e&&b(e,n)&&(w(e,n),t[o]=void 0,r=!0)}if(!r)for(var i=n.firstChild;i;i=i.nextSibling)w(i,n)}else for(var i=e.firstChild;i;i=i.nextSibling)this.poolDistribution(i,t)},buildRenderTree:function(e,t){for(var n=this.compose(t),r=0;r<n.length;r++){var o=n[r],i=e.append(o);this.buildRenderTree(i,o)}if(_(t)){var a=l(t);a.dirty=!1}},compose:function(e){for(var t=[],n=e.shadowRoot||e,r=n.firstChild;r;r=r.nextSibling)if(E(r)){this.associateNode(n);for(var o=i(r),a=0;a<o.length;a++){var s=o[a];y(r,s)&&t.push(s)}}else t.push(r);return t},invalidateAttributes:function(){this.attributes=Object.create(null)},updateDependentAttributes:function(e){if(e){var t=this.attributes;/\.\w+/.test(e)&&(t["class"]=!0),/#\w+/.test(e)&&(t.id=!0),e.replace(/\[\s*([^\s=\|~\]]+)/g,function(e,n){t[n]=!0})}},dependsOnAttribute:function(e){return this.attributes[e]},associateNode:function(e){H(e).polymerShadowRenderer_=this}};var q=/^(:not\()?[*.#[a-zA-Z_|]/;N.prototype.invalidateShadowRenderer=function(e){var t=H(this).polymerShadowRenderer_;return!!t&&(t.invalidate(),!0)},O.prototype.getDistributedNodes=L.prototype.getDistributedNodes=function(){return s(),i(this)},M.prototype.getDestinationInsertionPoints=function(){return s(),v(this)||[]},O.prototype.nodeIsInserted_=L.prototype.nodeIsInserted_=function(){this.invalidateShadowRenderer();var e,t=u(this);t&&(e=d(t)),H(this).polymerShadowRenderer_=e,e&&e.invalidate()},e.getRendererForHost=l,e.getShadowTrees=S,e.renderAllPending=s,e.getDestinationInsertionPoints=v,e.visual={insertBefore:n,remove:r}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(t){if(window[t]){r(!e.wrappers[t]);var c=function(e){n.call(this,e)};c.prototype=Object.create(n.prototype),o(c.prototype,{get form(){return s(a(this).form)}}),i(window[t],c,document.createElement(t.slice(4,-7))),e.wrappers[t]=c}}var n=e.wrappers.HTMLElement,r=e.assert,o=e.mixin,i=e.registerWrapper,a=e.unwrap,s=e.wrap,c=["HTMLButtonElement","HTMLFieldSetElement","HTMLInputElement","HTMLKeygenElement","HTMLLabelElement","HTMLLegendElement","HTMLObjectElement","HTMLOutputElement","HTMLTextAreaElement"];c.forEach(t)}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r(e,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unsafeUnwrap,i=e.unwrap,a=e.unwrapIfNeeded,s=e.wrap,c=window.Selection;t.prototype={get anchorNode(){return s(o(this).anchorNode)},get focusNode(){return s(o(this).focusNode)},addRange:function(e){o(this).addRange(a(e))},collapse:function(e,t){o(this).collapse(a(e),t)},containsNode:function(e,t){return o(this).containsNode(a(e),t)},getRangeAt:function(e){return s(o(this).getRangeAt(e))},removeRange:function(e){o(this).removeRange(i(e))},selectAllChildren:function(e){o(this).selectAllChildren(e instanceof ShadowRoot?o(e.host):a(e))},toString:function(){return o(this).toString()}},c.prototype.extend&&(t.prototype.extend=function(e,t){o(this).extend(a(e),t)}),n(window.Selection,t,window.getSelection()),e.wrappers.Selection=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){r(e,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unsafeUnwrap,i=e.unwrapIfNeeded,a=e.wrap,s=window.TreeWalker;t.prototype={get root(){return a(o(this).root)},get currentNode(){return a(o(this).currentNode)},set currentNode(e){o(this).currentNode=i(e)},get filter(){return o(this).filter},parentNode:function(){return a(o(this).parentNode())},firstChild:function(){return a(o(this).firstChild())},lastChild:function(){return a(o(this).lastChild())},previousSibling:function(){return a(o(this).previousSibling())},previousNode:function(){return a(o(this).previousNode())},nextNode:function(){return a(o(this).nextNode())}},n(s,t),e.wrappers.TreeWalker=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){u.call(this,e),this.treeScope_=new w(this,null)}function n(e){var n=document[e];t.prototype[e]=function(){return j(n.apply(N(this),arguments))}}function r(e,t){x.call(N(t),C(e)),o(e,t)}function o(e,t){e.shadowRoot&&t.adoptNode(e.shadowRoot),e instanceof m&&i(e,t);for(var n=e.firstChild;n;n=n.nextSibling)o(n,t)}function i(e,t){var n=e.olderShadowRoot;n&&t.adoptNode(n)}function a(e){L(e,this)}function s(e,t){var n=document.implementation[t];e.prototype[t]=function(){
+return j(n.apply(N(this),arguments))}}function c(e,t){var n=document.implementation[t];e.prototype[t]=function(){return n.apply(N(this),arguments)}}var l=e.GetElementsByInterface,u=e.wrappers.Node,d=e.ParentNodeInterface,p=e.NonElementParentNodeInterface,h=e.wrappers.Selection,f=e.SelectorsInterface,m=e.wrappers.ShadowRoot,w=e.TreeScope,v=e.cloneNode,g=e.defineGetter,b=e.defineWrapGetter,y=e.elementFromPoint,E=e.forwardMethodsToWrapper,_=e.matchesNames,S=e.mixin,T=e.registerWrapper,M=e.renderAllPending,O=e.rewrap,L=e.setWrapper,N=e.unsafeUnwrap,C=e.unwrap,j=e.wrap,D=e.wrapEventTargetMethods,H=(e.wrapNodeList,new WeakMap);t.prototype=Object.create(u.prototype),b(t,"documentElement"),b(t,"body"),b(t,"head"),g(t,"activeElement",function(){var e=C(this).activeElement;if(!e||!e.nodeType)return null;for(var t=j(e);!this.contains(t);){for(;t.parentNode;)t=t.parentNode;if(!t.host)return null;t=t.host}return t}),["createComment","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createRange","createTextNode"].forEach(n);var x=document.adoptNode,R=document.getSelection;S(t.prototype,{adoptNode:function(e){return e.parentNode&&e.parentNode.removeChild(e),r(e,this),e},elementFromPoint:function(e,t){return y(this,this,e,t)},importNode:function(e,t){return v(e,t,N(this))},getSelection:function(){return M(),new h(R.call(C(this)))},getElementsByName:function(e){return f.querySelectorAll.call(this,"[name="+JSON.stringify(String(e))+"]")}});var I=document.createTreeWalker,P=e.wrappers.TreeWalker;if(t.prototype.createTreeWalker=function(e,t,n,r){var o=null;return n&&(n.acceptNode&&"function"==typeof n.acceptNode?o={acceptNode:function(e){return n.acceptNode(j(e))}}:"function"==typeof n&&(o=function(e){return n(j(e))})),new P(I.call(C(this),C(e),t,o,r))},document.registerElement){var k=document.registerElement;t.prototype.registerElement=function(t,n){function r(e){return e?void L(e,this):i?document.createElement(i,t):document.createElement(t)}var o,i;if(void 0!==n&&(o=n.prototype,i=n["extends"]),o||(o=Object.create(HTMLElement.prototype)),e.nativePrototypeTable.get(o))throw new Error("NotSupportedError");for(var a,s=Object.getPrototypeOf(o),c=[];s&&!(a=e.nativePrototypeTable.get(s));)c.push(s),s=Object.getPrototypeOf(s);if(!a)throw new Error("NotSupportedError");for(var l=Object.create(a),u=c.length-1;u>=0;u--)l=Object.create(l);["createdCallback","attachedCallback","detachedCallback","attributeChangedCallback"].forEach(function(e){var t=o[e];t&&(l[e]=function(){j(this)instanceof r||O(this),t.apply(j(this),arguments)})});var d={prototype:l};i&&(d["extends"]=i),r.prototype=o,r.prototype.constructor=r,e.constructorTable.set(l,r),e.nativePrototypeTable.set(o,l);k.call(C(this),t,d);return r},E([window.HTMLDocument||window.Document],["registerElement"])}E([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement,window.HTMLHtmlElement],["appendChild","compareDocumentPosition","contains","getElementsByClassName","getElementsByTagName","getElementsByTagNameNS","insertBefore","querySelector","querySelectorAll","removeChild","replaceChild"]),E([window.HTMLBodyElement,window.HTMLHeadElement,window.HTMLHtmlElement],_),E([window.HTMLDocument||window.Document],["adoptNode","importNode","contains","createComment","createDocumentFragment","createElement","createElementNS","createEvent","createEventNS","createRange","createTextNode","createTreeWalker","elementFromPoint","getElementById","getElementsByName","getSelection"]),S(t.prototype,l),S(t.prototype,d),S(t.prototype,f),S(t.prototype,p),S(t.prototype,{get implementation(){var e=H.get(this);return e?e:(e=new a(C(this).implementation),H.set(this,e),e)},get defaultView(){return j(C(this).defaultView)}}),T(window.Document,t,document.implementation.createHTMLDocument("")),window.HTMLDocument&&T(window.HTMLDocument,t),D([window.HTMLBodyElement,window.HTMLDocument||window.Document,window.HTMLHeadElement]);var A=document.implementation.createDocument;a.prototype.createDocument=function(){return arguments[2]=C(arguments[2]),j(A.apply(N(this),arguments))},s(a,"createDocumentType"),s(a,"createHTMLDocument"),c(a,"hasFeature"),T(window.DOMImplementation,a),E([window.DOMImplementation],["createDocument","createDocumentType","createHTMLDocument","hasFeature"]),e.adoptNodeNoRemove=r,e.wrappers.DOMImplementation=a,e.wrappers.Document=t}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){n.call(this,e)}var n=e.wrappers.EventTarget,r=e.wrappers.Selection,o=e.mixin,i=e.registerWrapper,a=e.renderAllPending,s=e.unwrap,c=e.unwrapIfNeeded,l=e.wrap,u=window.Window,d=window.getComputedStyle,p=window.getDefaultComputedStyle,h=window.getSelection;t.prototype=Object.create(n.prototype),u.prototype.getComputedStyle=function(e,t){return l(this||window).getComputedStyle(c(e),t)},p&&(u.prototype.getDefaultComputedStyle=function(e,t){return l(this||window).getDefaultComputedStyle(c(e),t)}),u.prototype.getSelection=function(){return l(this||window).getSelection()},delete window.getComputedStyle,delete window.getDefaultComputedStyle,delete window.getSelection,["addEventListener","removeEventListener","dispatchEvent"].forEach(function(e){u.prototype[e]=function(){var t=l(this||window);return t[e].apply(t,arguments)},delete window[e]}),o(t.prototype,{getComputedStyle:function(e,t){return a(),d.call(s(this),c(e),t)},getSelection:function(){return a(),new r(h.call(s(this)))},get document(){return l(s(this).document)}}),p&&(t.prototype.getDefaultComputedStyle=function(e,t){return a(),p.call(s(this),c(e),t)}),i(u,t,window),e.wrappers.Window=t}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrap,n=window.DataTransfer||window.Clipboard,r=n.prototype.setDragImage;r&&(n.prototype.setDragImage=function(e,n,o){r.call(this,t(e),n,o)})}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t;t=e instanceof i?e:new i(e&&o(e)),r(t,this)}var n=e.registerWrapper,r=e.setWrapper,o=e.unwrap,i=window.FormData;i&&(n(i,t,new i),e.wrappers.FormData=t)}(window.ShadowDOMPolyfill),function(e){"use strict";var t=e.unwrapIfNeeded,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.send=function(e){return n.call(this,t(e))}}(window.ShadowDOMPolyfill),function(e){"use strict";function t(e){var t=n[e],r=window[t];if(r){var o=document.createElement(e),i=o.constructor;window[t]=i}}var n=(e.isWrapperFor,{a:"HTMLAnchorElement",area:"HTMLAreaElement",audio:"HTMLAudioElement",base:"HTMLBaseElement",body:"HTMLBodyElement",br:"HTMLBRElement",button:"HTMLButtonElement",canvas:"HTMLCanvasElement",caption:"HTMLTableCaptionElement",col:"HTMLTableColElement",content:"HTMLContentElement",data:"HTMLDataElement",datalist:"HTMLDataListElement",del:"HTMLModElement",dir:"HTMLDirectoryElement",div:"HTMLDivElement",dl:"HTMLDListElement",embed:"HTMLEmbedElement",fieldset:"HTMLFieldSetElement",font:"HTMLFontElement",form:"HTMLFormElement",frame:"HTMLFrameElement",frameset:"HTMLFrameSetElement",h1:"HTMLHeadingElement",head:"HTMLHeadElement",hr:"HTMLHRElement",html:"HTMLHtmlElement",iframe:"HTMLIFrameElement",img:"HTMLImageElement",input:"HTMLInputElement",keygen:"HTMLKeygenElement",label:"HTMLLabelElement",legend:"HTMLLegendElement",li:"HTMLLIElement",link:"HTMLLinkElement",map:"HTMLMapElement",marquee:"HTMLMarqueeElement",menu:"HTMLMenuElement",menuitem:"HTMLMenuItemElement",meta:"HTMLMetaElement",meter:"HTMLMeterElement",object:"HTMLObjectElement",ol:"HTMLOListElement",optgroup:"HTMLOptGroupElement",option:"HTMLOptionElement",output:"HTMLOutputElement",p:"HTMLParagraphElement",param:"HTMLParamElement",pre:"HTMLPreElement",progress:"HTMLProgressElement",q:"HTMLQuoteElement",script:"HTMLScriptElement",select:"HTMLSelectElement",shadow:"HTMLShadowElement",source:"HTMLSourceElement",span:"HTMLSpanElement",style:"HTMLStyleElement",table:"HTMLTableElement",tbody:"HTMLTableSectionElement",template:"HTMLTemplateElement",textarea:"HTMLTextAreaElement",thead:"HTMLTableSectionElement",time:"HTMLTimeElement",title:"HTMLTitleElement",tr:"HTMLTableRowElement",track:"HTMLTrackElement",ul:"HTMLUListElement",video:"HTMLVideoElement"});Object.keys(n).forEach(t),Object.getOwnPropertyNames(e.wrappers).forEach(function(t){window[t]=e.wrappers[t]})}(window.ShadowDOMPolyfill),function(e){function t(e,t){var n="";return Array.prototype.forEach.call(e,function(e){n+=e.textContent+"\n\n"}),t||(n=n.replace(d,"")),n}function n(e){var t=document.createElement("style");return t.textContent=e,t}function r(e){var t=n(e);document.head.appendChild(t);var r=[];if(t.sheet)try{r=t.sheet.cssRules}catch(o){}else console.warn("sheet not found",t);return t.parentNode.removeChild(t),r}function o(){C.initialized=!0,document.body.appendChild(C);var e=C.contentDocument,t=e.createElement("base");t.href=document.baseURI,e.head.appendChild(t)}function i(e){C.initialized||o(),document.body.appendChild(C),e(C.contentDocument),document.body.removeChild(C)}function a(e,t){if(t){var o;if(e.match("@import")&&D){var a=n(e);i(function(e){e.head.appendChild(a.impl),o=Array.prototype.slice.call(a.sheet.cssRules,0),t(o)})}else o=r(e),t(o)}}function s(e){e&&l().appendChild(document.createTextNode(e))}function c(e,t){var r=n(e);r.setAttribute(t,""),r.setAttribute(x,""),document.head.appendChild(r)}function l(){return j||(j=document.createElement("style"),j.setAttribute(x,""),j[x]=!0),j}var u={strictStyling:!1,registry:{},shimStyling:function(e,n,r){var o=this.prepareRoot(e,n,r),i=this.isTypeExtension(r),a=this.makeScopeSelector(n,i),s=t(o,!0);s=this.scopeCssText(s,a),e&&(e.shimmedStyle=s),this.addCssToDocument(s,n)},shimStyle:function(e,t){return this.shimCssText(e.textContent,t)},shimCssText:function(e,t){return e=this.insertDirectives(e),this.scopeCssText(e,t)},makeScopeSelector:function(e,t){return e?t?"[is="+e+"]":e:""},isTypeExtension:function(e){return e&&e.indexOf("-")<0},prepareRoot:function(e,t,n){var r=this.registerRoot(e,t,n);return this.replaceTextInStyles(r.rootStyles,this.insertDirectives),this.removeStyles(e,r.rootStyles),this.strictStyling&&this.applyScopeToContent(e,t),r.scopeStyles},removeStyles:function(e,t){for(var n,r=0,o=t.length;r<o&&(n=t[r]);r++)n.parentNode.removeChild(n)},registerRoot:function(e,t,n){var r=this.registry[t]={root:e,name:t,extendsName:n},o=this.findStyles(e);r.rootStyles=o,r.scopeStyles=r.rootStyles;var i=this.registry[r.extendsName];return i&&(r.scopeStyles=i.scopeStyles.concat(r.scopeStyles)),r},findStyles:function(e){if(!e)return[];var t=e.querySelectorAll("style");return Array.prototype.filter.call(t,function(e){return!e.hasAttribute(R)})},applyScopeToContent:function(e,t){e&&(Array.prototype.forEach.call(e.querySelectorAll("*"),function(e){e.setAttribute(t,"")}),Array.prototype.forEach.call(e.querySelectorAll("template"),function(e){this.applyScopeToContent(e.content,t)},this))},insertDirectives:function(e){return e=this.insertPolyfillDirectivesInCssText(e),this.insertPolyfillRulesInCssText(e)},insertPolyfillDirectivesInCssText:function(e){return e=e.replace(p,function(e,t){return t.slice(0,-2)+"{"}),e.replace(h,function(e,t){return t+" {"})},insertPolyfillRulesInCssText:function(e){return e=e.replace(f,function(e,t){return t.slice(0,-1)}),e.replace(m,function(e,t,n,r){var o=e.replace(t,"").replace(n,"");return r+o})},scopeCssText:function(e,t){var n=this.extractUnscopedRulesFromCssText(e);if(e=this.insertPolyfillHostInCssText(e),e=this.convertColonHost(e),e=this.convertColonHostContext(e),e=this.convertShadowDOMSelectors(e),t){var e,r=this;a(e,function(n){e=r.scopeRules(n,t)})}return e=e+"\n"+n,e.trim()},extractUnscopedRulesFromCssText:function(e){for(var t,n="";t=w.exec(e);)n+=t[1].slice(0,-1)+"\n\n";for(;t=v.exec(e);)n+=t[0].replace(t[2],"").replace(t[1],t[3])+"\n\n";return n},convertColonHost:function(e){return this.convertColonRule(e,E,this.colonHostPartReplacer)},convertColonHostContext:function(e){return this.convertColonRule(e,_,this.colonHostContextPartReplacer)},convertColonRule:function(e,t,n){return e.replace(t,function(e,t,r,o){if(t=O,r){for(var i,a=r.split(","),s=[],c=0,l=a.length;c<l&&(i=a[c]);c++)i=i.trim(),s.push(n(t,i,o));return s.join(",")}return t+o})},colonHostContextPartReplacer:function(e,t,n){return t.match(g)?this.colonHostPartReplacer(e,t,n):e+t+n+", "+t+" "+e+n},colonHostPartReplacer:function(e,t,n){return e+t.replace(g,"")+n},convertShadowDOMSelectors:function(e){for(var t=0;t<N.length;t++)e=e.replace(N[t]," ");return e},scopeRules:function(e,t){var n="";return e&&Array.prototype.forEach.call(e,function(e){if(e.selectorText&&e.style&&void 0!==e.style.cssText)n+=this.scopeSelector(e.selectorText,t,this.strictStyling)+" {\n\t",n+=this.propertiesFromRule(e)+"\n}\n\n";else if(e.type===CSSRule.MEDIA_RULE)n+="@media "+e.media.mediaText+" {\n",n+=this.scopeRules(e.cssRules,t),n+="\n}\n\n";else try{e.cssText&&(n+=e.cssText+"\n\n")}catch(r){e.type===CSSRule.KEYFRAMES_RULE&&e.cssRules&&(n+=this.ieSafeCssTextFromKeyFrameRule(e))}},this),n},ieSafeCssTextFromKeyFrameRule:function(e){var t="@keyframes "+e.name+" {";return Array.prototype.forEach.call(e.cssRules,function(e){t+=" "+e.keyText+" {"+e.style.cssText+"}"}),t+=" }"},scopeSelector:function(e,t,n){var r=[],o=e.split(",");return o.forEach(function(e){e=e.trim(),this.selectorNeedsScoping(e,t)&&(e=n&&!e.match(O)?this.applyStrictSelectorScope(e,t):this.applySelectorScope(e,t)),r.push(e)},this),r.join(", ")},selectorNeedsScoping:function(e,t){if(Array.isArray(t))return!0;var n=this.makeScopeMatcher(t);return!e.match(n)},makeScopeMatcher:function(e){return e=e.replace(/\[/g,"\\[").replace(/\]/g,"\\]"),new RegExp("^("+e+")"+S,"m")},applySelectorScope:function(e,t){return Array.isArray(t)?this.applySelectorScopeList(e,t):this.applySimpleSelectorScope(e,t)},applySelectorScopeList:function(e,t){for(var n,r=[],o=0;n=t[o];o++)r.push(this.applySimpleSelectorScope(e,n));return r.join(", ")},applySimpleSelectorScope:function(e,t){return e.match(L)?(e=e.replace(O,t),e.replace(L,t+" ")):t+" "+e},applyStrictSelectorScope:function(e,t){t=t.replace(/\[is=([^\]]*)\]/g,"$1");var n=[" ",">","+","~"],r=e,o="["+t+"]";return n.forEach(function(e){var t=r.split(e);r=t.map(function(e){var t=e.trim().replace(L,"");return t&&n.indexOf(t)<0&&t.indexOf(o)<0&&(e=t.replace(/([^:]*)(:*)(.*)/,"$1"+o+"$2$3")),e}).join(e)}),r},insertPolyfillHostInCssText:function(e){return e.replace(M,b).replace(T,g)},propertiesFromRule:function(e){var t=e.style.cssText;e.style.content&&!e.style.content.match(/['"]+|attr/)&&(t=t.replace(/content:[^;]*;/g,"content: '"+e.style.content+"';"));var n=e.style;for(var r in n)"initial"===n[r]&&(t+=r+": initial; ");return t},replaceTextInStyles:function(e,t){e&&t&&(e instanceof Array||(e=[e]),Array.prototype.forEach.call(e,function(e){e.textContent=t.call(this,e.textContent)},this))},addCssToDocument:function(e,t){e.match("@import")?c(e,t):s(e)}},d=/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim,p=/\/\*\s*@polyfill ([^*]*\*+([^\/*][^*]*\*+)*\/)([^{]*?){/gim,h=/polyfill-next-selector[^}]*content\:[\s]*?['"](.*?)['"][;\s]*}([^{]*?){/gim,f=/\/\*\s@polyfill-rule([^*]*\*+([^\/*][^*]*\*+)*)\//gim,m=/(polyfill-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,w=/\/\*\s@polyfill-unscoped-rule([^*]*\*+([^\/*][^*]*\*+)*)\//gim,v=/(polyfill-unscoped-rule)[^}]*(content\:[\s]*['"](.*?)['"])[;\s]*[^}]*}/gim,g="-shadowcsshost",b="-shadowcsscontext",y=")(?:\\(((?:\\([^)(]*\\)|[^)(]*)+?)\\))?([^,{]*)",E=new RegExp("("+g+y,"gim"),_=new RegExp("("+b+y,"gim"),S="([>\\s~+[.,{:][\\s\\S]*)?$",T=/\:host/gim,M=/\:host-context/gim,O=g+"-no-combinator",L=new RegExp(g,"gim"),N=(new RegExp(b,"gim"),[/>>>/g,/::shadow/g,/::content/g,/\/deep\//g,/\/shadow\//g,/\/shadow-deep\//g,/\^\^/g,/\^(?!=)/g]),C=document.createElement("iframe");C.style.display="none";var j,D=navigator.userAgent.match("Chrome"),H="shim-shadowdom",x="shim-shadowdom-css",R="no-shim";if(window.ShadowDOMPolyfill){s("style { display: none !important; }\n");var I=ShadowDOMPolyfill.wrap(document),P=I.querySelector("head");P.insertBefore(l(),P.childNodes[0]),document.addEventListener("DOMContentLoaded",function(){e.urlResolver;if(window.HTMLImports&&!HTMLImports.useNative){var t="link[rel=stylesheet]["+H+"]",n="style["+H+"]";HTMLImports.importer.documentPreloadSelectors+=","+t,HTMLImports.importer.importsPreloadSelectors+=","+t,HTMLImports.parser.documentSelectors=[HTMLImports.parser.documentSelectors,t,n].join(",");var r=HTMLImports.parser.parseGeneric;HTMLImports.parser.parseGeneric=function(e){if(!e[x]){var t=e.__importElement||e;if(!t.hasAttribute(H))return void r.call(this,e);e.__resource&&(t=e.ownerDocument.createElement("style"),t.textContent=e.__resource),HTMLImports.path.resolveUrlsInStyle(t,e.href),t.textContent=u.shimStyle(t),t.removeAttribute(H,""),t.setAttribute(x,""),t[x]=!0,t.parentNode!==P&&(e.parentNode===P?P.replaceChild(t,e):this.addElementToDocument(t)),t.__importParsed=!0,this.markParsingComplete(e),this.parseNext()}};var o=HTMLImports.parser.hasResource;HTMLImports.parser.hasResource=function(e){return"link"===e.localName&&"stylesheet"===e.rel&&e.hasAttribute(H)?e.__resource:o.call(this,e)}}})}e.ShadowCSS=u}(window.WebComponents)),function(e){window.ShadowDOMPolyfill?(window.wrap=ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}}(window.WebComponents),function(e){"use strict";function t(e){return void 0!==p[e]}function n(){s.call(this),this._isInvalid=!0}function r(e){return""==e&&n.call(this),e.toLowerCase()}function o(e){var t=e.charCodeAt(0);return t>32&&t<127&&[34,35,60,62,63,96].indexOf(t)==-1?e:encodeURIComponent(e)}function i(e){var t=e.charCodeAt(0);return t>32&&t<127&&[34,35,60,62,96].indexOf(t)==-1?e:encodeURIComponent(e)}function a(e,a,s){function c(e){b.push(e)}var l=a||"scheme start",u=0,d="",v=!1,g=!1,b=[];e:for(;(e[u-1]!=f||0==u)&&!this._isInvalid;){var y=e[u];switch(l){case"scheme start":if(!y||!m.test(y)){if(a){c("Invalid scheme.");break e}d="",l="no scheme";continue}d+=y.toLowerCase(),l="scheme";break;case"scheme":if(y&&w.test(y))d+=y.toLowerCase();else{if(":"!=y){if(a){if(f==y)break e;c("Code point not allowed in scheme: "+y);break e}d="",u=0,l="no scheme";continue}if(this._scheme=d,d="",a)break e;t(this._scheme)&&(this._isRelative=!0),l="file"==this._scheme?"relative":this._isRelative&&s&&s._scheme==this._scheme?"relative or authority":this._isRelative?"authority first slash":"scheme data"}break;case"scheme data":"?"==y?(this._query="?",l="query"):"#"==y?(this._fragment="#",l="fragment"):f!=y&&"\t"!=y&&"\n"!=y&&"\r"!=y&&(this._schemeData+=o(y));break;case"no scheme":if(s&&t(s._scheme)){l="relative";continue}c("Missing scheme."),n.call(this);break;case"relative or authority":if("/"!=y||"/"!=e[u+1]){c("Expected /, got: "+y),l="relative";continue}l="authority ignore slashes";break;case"relative":if(this._isRelative=!0,"file"!=this._scheme&&(this._scheme=s._scheme),f==y){this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._username=s._username,this._password=s._password;break e}if("/"==y||"\\"==y)"\\"==y&&c("\\ is an invalid code point."),l="relative slash";else if("?"==y)this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query="?",this._username=s._username,this._password=s._password,l="query";else{if("#"!=y){var E=e[u+1],_=e[u+2];("file"!=this._scheme||!m.test(y)||":"!=E&&"|"!=E||f!=_&&"/"!=_&&"\\"!=_&&"?"!=_&&"#"!=_)&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password,this._path=s._path.slice(),this._path.pop()),l="relative path";continue}this._host=s._host,this._port=s._port,this._path=s._path.slice(),this._query=s._query,this._fragment="#",this._username=s._username,this._password=s._password,l="fragment"}break;case"relative slash":if("/"!=y&&"\\"!=y){"file"!=this._scheme&&(this._host=s._host,this._port=s._port,this._username=s._username,this._password=s._password),l="relative path";continue}"\\"==y&&c("\\ is an invalid code point."),l="file"==this._scheme?"file host":"authority ignore slashes";break;case"authority first slash":if("/"!=y){c("Expected '/', got: "+y),l="authority ignore slashes";continue}l="authority second slash";break;case"authority second slash":if(l="authority ignore slashes","/"!=y){c("Expected '/', got: "+y);continue}break;case"authority ignore slashes":if("/"!=y&&"\\"!=y){l="authority";continue}c("Expected authority, got: "+y);break;case"authority":if("@"==y){v&&(c("@ already seen."),d+="%40"),v=!0;for(var S=0;S<d.length;S++){var T=d[S];if("\t"!=T&&"\n"!=T&&"\r"!=T)if(":"!=T||null!==this._password){var M=o(T);null!==this._password?this._password+=M:this._username+=M}else this._password="";else c("Invalid whitespace in authority.")}d=""}else{if(f==y||"/"==y||"\\"==y||"?"==y||"#"==y){u-=d.length,d="",l="host";continue}d+=y}break;case"file host":if(f==y||"/"==y||"\\"==y||"?"==y||"#"==y){2!=d.length||!m.test(d[0])||":"!=d[1]&&"|"!=d[1]?0==d.length?l="relative path start":(this._host=r.call(this,d),d="",l="relative path start"):l="relative path";continue}"\t"==y||"\n"==y||"\r"==y?c("Invalid whitespace in file host."):d+=y;break;case"host":case"hostname":if(":"!=y||g){if(f==y||"/"==y||"\\"==y||"?"==y||"#"==y){if(this._host=r.call(this,d),d="",l="relative path start",a)break e;continue}"\t"!=y&&"\n"!=y&&"\r"!=y?("["==y?g=!0:"]"==y&&(g=!1),d+=y):c("Invalid code point in host/hostname: "+y)}else if(this._host=r.call(this,d),d="",l="port","hostname"==a)break e;break;case"port":if(/[0-9]/.test(y))d+=y;else{if(f==y||"/"==y||"\\"==y||"?"==y||"#"==y||a){if(""!=d){var O=parseInt(d,10);O!=p[this._scheme]&&(this._port=O+""),d=""}if(a)break e;l="relative path start";continue}"\t"==y||"\n"==y||"\r"==y?c("Invalid code point in port: "+y):n.call(this)}break;case"relative path start":if("\\"==y&&c("'\\' not allowed in path."),l="relative path","/"!=y&&"\\"!=y)continue;break;case"relative path":if(f!=y&&"/"!=y&&"\\"!=y&&(a||"?"!=y&&"#"!=y))"\t"!=y&&"\n"!=y&&"\r"!=y&&(d+=o(y));else{"\\"==y&&c("\\ not allowed in relative path.");var L;(L=h[d.toLowerCase()])&&(d=L),".."==d?(this._path.pop(),"/"!=y&&"\\"!=y&&this._path.push("")):"."==d&&"/"!=y&&"\\"!=y?this._path.push(""):"."!=d&&("file"==this._scheme&&0==this._path.length&&2==d.length&&m.test(d[0])&&"|"==d[1]&&(d=d[0]+":"),this._path.push(d)),d="","?"==y?(this._query="?",l="query"):"#"==y&&(this._fragment="#",l="fragment")}break;case"query":a||"#"!=y?f!=y&&"\t"!=y&&"\n"!=y&&"\r"!=y&&(this._query+=i(y)):(this._fragment="#",l="fragment");break;case"fragment":f!=y&&"\t"!=y&&"\n"!=y&&"\r"!=y&&(this._fragment+=y)}u++}}function s(){this._scheme="",this._schemeData="",this._username="",this._password=null,this._host="",this._port="",this._path=[],this._query="",this._fragment="",this._isInvalid=!1,this._isRelative=!1}function c(e,t){void 0===t||t instanceof c||(t=new c(String(t))),this._url=e,s.call(this);var n=e.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g,"");a.call(this,n,null,t)}var l=!1;if(!e.forceJURL)try{var u=new URL("b","http://a");u.pathname="c%20d",l="http://a/c%20d"===u.href}catch(d){}if(!l){var p=Object.create(null);p.ftp=21,p.file=0,p.gopher=70,p.http=80,p.https=443,p.ws=80,p.wss=443;var h=Object.create(null);h["%2e"]=".",h[".%2e"]="..",h["%2e."]="..",h["%2e%2e"]="..";var f=void 0,m=/[a-zA-Z]/,w=/[a-zA-Z0-9\+\-\.]/;c.prototype={toString:function(){return this.href},get href(){if(this._isInvalid)return this._url;var e="";return""==this._username&&null==this._password||(e=this._username+(null!=this._password?":"+this._password:"")+"@"),this.protocol+(this._isRelative?"//"+e+this.host:"")+this.pathname+this._query+this._fragment},set href(e){s.call(this),a.call(this,e)},get protocol(){return this._scheme+":"},set protocol(e){this._isInvalid||a.call(this,e+":","scheme start")},get host(){return this._isInvalid?"":this._port?this._host+":"+this._port:this._host},set host(e){!this._isInvalid&&this._isRelative&&a.call(this,e,"host")},get hostname(){return this._host},set hostname(e){!this._isInvalid&&this._isRelative&&a.call(this,e,"hostname")},get port(){return this._port},set port(e){!this._isInvalid&&this._isRelative&&a.call(this,e,"port")},get pathname(){return this._isInvalid?"":this._isRelative?"/"+this._path.join("/"):this._schemeData},set pathname(e){!this._isInvalid&&this._isRelative&&(this._path=[],a.call(this,e,"relative path start"))},get search(){return this._isInvalid||!this._query||"?"==this._query?"":this._query},set search(e){!this._isInvalid&&this._isRelative&&(this._query="?","?"==e[0]&&(e=e.slice(1)),a.call(this,e,"query"))},get hash(){return this._isInvalid||!this._fragment||"#"==this._fragment?"":this._fragment},set hash(e){this._isInvalid||(this._fragment="#","#"==e[0]&&(e=e.slice(1)),a.call(this,e,"fragment"))},get origin(){var e;if(this._isInvalid||!this._scheme)return"";switch(this._scheme){case"data":case"file":case"javascript":case"mailto":return"null"}return e=this.host,e?this._scheme+"://"+e:""}};var v=e.URL;v&&(c.createObjectURL=function(e){return v.createObjectURL.apply(v,arguments)},c.revokeObjectURL=function(e){v.revokeObjectURL(e)}),e.URL=c}}(self),function(e){function t(e){y.push(e),b||(b=!0,m(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){b=!1;var e=y;y=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=w.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=w.get(n);if(r)for(var o=0;o<r.length;o++){var i=r[o],a=i.options;if(n===e||a.subtree){var s=t(a);s&&i.enqueue(s)}}}}function a(e){this.callback_=e,this.nodes_=[],this.records_=[],this.uid_=++E}function s(e,t){this.type=e,this.target=t,this.addedNodes=[],this.removedNodes=[],this.previousSibling=null,this.nextSibling=null,this.attributeName=null,this.attributeNamespace=null,this.oldValue=null}function c(e){var t=new s(e.type,e.target);return t.addedNodes=e.addedNodes.slice(),t.removedNodes=e.removedNodes.slice(),t.previousSibling=e.previousSibling,t.nextSibling=e.nextSibling,t.attributeName=e.attributeName,t.attributeNamespace=e.attributeNamespace,t.oldValue=e.oldValue,t}function l(e,t){return _=new s(e,t)}function u(e){return S?S:(S=c(_),S.oldValue=e,S)}function d(){_=S=void 0}function p(e){return e===S||e===_}function h(e,t){return e===t?e:S&&p(e)?S:null}function f(e,t,n){this.observer=e,this.target=t,this.options=n,this.transientObservedNodes=[]}if(!e.JsMutationObserver){var m,w=new WeakMap;if(/Trident|Edge/.test(navigator.userAgent))m=setTimeout;else if(window.setImmediate)m=window.setImmediate;else{var v=[],g=String(Math.random());window.addEventListener("message",function(e){if(e.data===g){var t=v;v=[],t.forEach(function(e){e()})}}),m=function(e){v.push(e),window.postMessage(g,"*")}}var b=!1,y=[],E=0;a.prototype={observe:function(e,t){if(e=n(e),!t.childList&&!t.attributes&&!t.characterData||t.attributeOldValue&&!t.attributes||t.attributeFilter&&t.attributeFilter.length&&!t.attributes||t.characterDataOldValue&&!t.characterData)throw new SyntaxError;var r=w.get(e);r||w.set(e,r=[]);for(var o,i=0;i<r.length;i++)if(r[i].observer===this){o=r[i],o.removeListeners(),o.options=t;break}o||(o=new f(this,e,t),r.push(o),this.nodes_.push(e)),o.addListeners()},disconnect:function(){this.nodes_.forEach(function(e){for(var t=w.get(e),n=0;n<t.length;n++){var r=t[n];if(r.observer===this){r.removeListeners(),t.splice(n,1);break}}},this),this.records_=[]},takeRecords:function(){var e=this.records_;return this.records_=[],e}};var _,S;f.prototype={enqueue:function(e){var n=this.observer.records_,r=n.length;if(n.length>0){var o=n[r-1],i=h(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=w.get(e);t||w.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=w.get(e),n=0;n<t.length;n++)if(t[n]===this){t.splice(n,1);break}},this)},handleEvent:function(e){switch(e.stopImmediatePropagation(),e.type){case"DOMAttrModified":var t=e.attrName,n=e.relatedNode.namespaceURI,r=e.target,o=new l("attributes",r);o.attributeName=t,o.attributeNamespace=n;var a=e.attrChange===MutationEvent.ADDITION?null:e.prevValue;i(r,function(e){if(e.attributes&&(!e.attributeFilter||!e.attributeFilter.length||e.attributeFilter.indexOf(t)!==-1||e.attributeFilter.indexOf(n)!==-1))return e.attributeOldValue?u(a):o});break;case"DOMCharacterDataModified":var r=e.target,o=l("characterData",r),a=e.prevValue;i(r,function(e){if(e.characterData)return e.characterDataOldValue?u(a):o});break;case"DOMNodeRemoved":this.addTransientObserver(e.target);case"DOMNodeInserted":var s,c,p=e.target;"DOMNodeInserted"===e.type?(s=[p],c=[]):(s=[],c=[p]);var h=p.previousSibling,f=p.nextSibling,o=l("childList",e.target.parentNode);o.addedNodes=s,o.removedNodes=c,o.previousSibling=h,o.nextSibling=f,i(e.relatedNode,function(e){if(e.childList)return o})}d()}},e.JsMutationObserver=a,e.MutationObserver||(e.MutationObserver=a,a._isPolyfilled=!0)}}(self),function(e){"use strict";if(!window.performance||!window.performance.now){var t=Date.now();window.performance={now:function(){return Date.now()-t}}}window.requestAnimationFrame||(window.requestAnimationFrame=function(){var e=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame;return e?function(t){return e(function(){t(performance.now())})}:function(e){return window.setTimeout(e,1e3/60)}}()),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(){return window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||function(e){clearTimeout(e)}}());var n=function(){var e=document.createEvent("Event");return e.initEvent("foo",!0,!0),e.preventDefault(),e.defaultPrevented}();if(!n){var r=Event.prototype.preventDefault;Event.prototype.preventDefault=function(){this.cancelable&&(r.call(this),Object.defineProperty(this,"defaultPrevented",{get:function(){return!0},configurable:!0}))}}var o=/Trident/.test(navigator.userAgent);if((!window.CustomEvent||o&&"function"!=typeof window.CustomEvent)&&(window.CustomEvent=function(e,t){t=t||{};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,Boolean(t.bubbles),Boolean(t.cancelable),t.detail),n},window.CustomEvent.prototype=window.Event.prototype),!window.Event||o&&"function"!=typeof window.Event){var i=window.Event;window.Event=function(e,t){t=t||{};var n=document.createEvent("Event");return n.initEvent(e,Boolean(t.bubbles),Boolean(t.cancelable)),n},window.Event.prototype=i.prototype}}(window.WebComponents),window.HTMLImports=window.HTMLImports||{flags:{}},function(e){function t(e,t){t=t||f,r(function(){i(e,t)},t)}function n(e){return"complete"===e.readyState||e.readyState===v}function r(e,t){if(n(t))e&&e();else{var o=function(){"complete"!==t.readyState&&t.readyState!==v||(t.removeEventListener(g,o),r(e,t))};t.addEventListener(g,o)}}function o(e){e.target.__loaded=!0}function i(e,t){function n(){c==l&&e&&e({allImports:s,loadedImports:u,errorImports:d})}function r(e){o(e),u.push(this),c++,n()}function i(e){
+d.push(this),c++,n()}var s=t.querySelectorAll("link[rel=import]"),c=0,l=s.length,u=[],d=[];if(l)for(var p,h=0;h<l&&(p=s[h]);h++)a(p)?(u.push(this),c++,n()):(p.addEventListener("load",r),p.addEventListener("error",i));else n()}function a(e){return d?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;n<r&&(t=e[n]);n++)c(t)&&l(t)}function c(e){return"link"===e.localName&&"import"===e.rel}function l(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",d=Boolean(u in document.createElement("link")),p=Boolean(window.ShadowDOMPolyfill),h=function(e){return p?window.ShadowDOMPolyfill.wrapIfNeeded(e):e},f=h(document),m={get:function(){var e=window.HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return h(e)},configurable:!0};Object.defineProperty(document,"_currentScript",m),Object.defineProperty(f,"_currentScript",m);var w=/Trident/.test(navigator.userAgent),v=w?"complete":"interactive",g="readystatechange";d&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;n<r&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;n<r&&(e=t[n]);n++)l(e)}()),t(function(e){window.HTMLImports.ready=!0,window.HTMLImports.readyTime=(new Date).getTime();var t=f.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),f.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=u,e.useNative=d,e.rootDocument=f,e.whenReady=t,e.isIE=w}(window.HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(window.HTMLImports),window.HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),window.HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=null;try{var a=i.getResponseHeader("Location");a&&(n="/"===a.substr(0,1)?location.origin+a:a)}catch(e){console.error(e.message)}r.call(o,!t.ok(i)&&i,i.response||i.responseText,n)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;n<r&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,c=a.length;s<c&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),window.HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;n<r&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;n<r&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),window.HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,c=e.flags,l=e.isIE,u=e.IMPORT_LINK_TYPE,d="link[rel="+u+"]",p={documentSelectors:d,importsSelectors:[d,"link[rel=stylesheet]:not([type])","style:not([type])","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(c.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){c.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,c.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(e["import"]=e.__doc,window.HTMLImports.__importsParsingHook&&window.HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.__resource&&!e.__error?e.dispatchEvent(new CustomEvent("load",{bubbles:!1})):e.dispatchEvent(new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(o){e.removeEventListener("load",r),e.removeEventListener("error",r),t&&t(o),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),l&&"style"===e.localName){var o=!1;if(e.textContent.indexOf("@import")==-1)o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,c=0;c<s&&(i=a[c]);c++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode&&r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;i<a&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r.__doc,r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return!t(e)||void 0!==e.__doc}};e.parser=p,e.IMPORT_SELECTOR=d}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,c=e.rootDocument,l=e.Loader,u=e.Observer,d=e.parser,p={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){h.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);h.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===c?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var c=this.documents[e];void 0===c&&(c=a?null:o(r,s||e),c&&(c.__importLink=n,this.bootDocument(c)),this.documents[e]=c),n.__doc=c}d.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),d.parseNext()},loadedAll:function(){d.parseNext()}},h=new l(p.loaded.bind(p),p.loadedAll.bind(p));if(p.observer=new u,!document.baseURI){var f={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",f),Object.defineProperty(c,"baseURI",f)}e.importer=p,e.importLoader=h}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,c=e.length;s<c&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){window.HTMLImports.importer.bootDocument(r)}var n=e.initializeModules;e.isIE;if(!e.useNative){n();var r=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(window.HTMLImports),window.CustomElements=window.CustomElements||{flags:{}},function(e){var t=e.flags,n=[],r=function(e){n.push(e)},o=function(){n.forEach(function(t){t(e)})};e.addModule=r,e.initializeModules=o,e.hasNative=Boolean(document.registerElement),e.isIE=/Trident/.test(navigator.userAgent),e.useNative=!t.register&&e.hasNative&&!window.ShadowDOMPolyfill&&(!window.HTMLImports||window.HTMLImports.useNative)}(window.CustomElements),window.CustomElements.addModule(function(e){function t(e,t){n(e,function(e){return!!t(e)||void r(e,t)}),r(e,t)}function n(e,t,r){var o=e.firstElementChild;if(!o)for(o=e.firstChild;o&&o.nodeType!==Node.ELEMENT_NODE;)o=o.nextSibling;for(;o;)t(o,r)!==!0&&n(o,t,r),o=o.nextElementSibling;return null}function r(e,n){for(var r=e.shadowRoot;r;)t(r,n),r=r.olderShadowRoot}function o(e,t){i(e,t,[])}function i(e,t,n){if(e=window.wrap(e),!(n.indexOf(e)>=0)){n.push(e);for(var r,o=e.querySelectorAll("link[rel="+a+"]"),s=0,c=o.length;s<c&&(r=o[s]);s++)r["import"]&&i(r["import"],t,n);t(e)}}var a=window.HTMLImports?window.HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=o,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e,t){return n(e,t)||r(e,t)}function n(t,n){return!!e.upgrade(t,n)||void(n&&a(t))}function r(e,t){b(e,function(e){if(n(e,t))return!0})}function o(e){S.push(e),_||(_=!0,setTimeout(i))}function i(){_=!1;for(var e,t=S,n=0,r=t.length;n<r&&(e=t[n]);n++)e();S=[]}function a(e){E?o(function(){s(e)}):s(e)}function s(e){e.__upgraded__&&!e.__attached&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function c(e){l(e),b(e,function(e){l(e)})}function l(e){E?o(function(){u(e)}):u(e)}function u(e){e.__upgraded__&&e.__attached&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function d(e){for(var t=e,n=window.wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function p(e){if(e.shadowRoot&&!e.shadowRoot.__watched){g.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)m(t),t=t.olderShadowRoot}}function h(e,n){if(g.dom){var r=n[0];if(r&&"childList"===r.type&&r.addedNodes&&r.addedNodes){for(var o=r.addedNodes[0];o&&o!==document&&!o.host;)o=o.parentNode;var i=o&&(o.URL||o._URL||o.host&&o.host.localName)||"";i=i.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",n.length,i||"")}var a=d(e);n.forEach(function(e){"childList"===e.type&&(T(e.addedNodes,function(e){e.localName&&t(e,a)}),T(e.removedNodes,function(e){e.localName&&c(e)}))}),g.dom&&console.groupEnd()}function f(e){for(e=window.wrap(e),e||(e=window.wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(h(e,t.takeRecords()),i())}function m(e){if(!e.__observer){var t=new MutationObserver(h.bind(this,e));t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function w(e){e=window.wrap(e),g.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop());var n=e===window.wrap(document);t(e,n),m(e),g.dom&&console.groupEnd()}function v(e){y(e,w)}var g=e.flags,b=e.forSubtree,y=e.forDocumentTree,E=window.MutationObserver._isPolyfilled&&g["throttle-attached"];e.hasPolyfillMutations=E,e.hasThrottledAttached=E;var _=!1,S=[],T=Array.prototype.forEach.call.bind(Array.prototype.forEach),M=Element.prototype.createShadowRoot;M&&(Element.prototype.createShadowRoot=function(){var e=M.call(this);return window.CustomElements.watchShadow(this),e}),e.watchShadow=p,e.upgradeDocumentTree=v,e.upgradeDocument=w,e.upgradeSubtree=r,e.upgradeAll=t,e.attached=a,e.takeRecords=f}),window.CustomElements.addModule(function(e){function t(t,r){if("template"===t.localName&&window.HTMLTemplateElement&&HTMLTemplateElement.decorate&&HTMLTemplateElement.decorate(t),!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var o=t.getAttribute("is"),i=e.getRegisteredDefinition(t.localName)||e.getRegisteredDefinition(o);if(i&&(o&&i.tag==t.localName||!o&&!i["extends"]))return n(t,i,r)}}function n(t,n,o){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),r(t,n),t.__upgraded__=!0,i(t),o&&e.attached(t),e.upgradeSubtree(t,o),a.upgrade&&console.groupEnd(),t}function r(e,t){Object.__proto__?e.__proto__=t.prototype:(o(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function o(e,t,n){for(var r={},o=t;o!==n&&o!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(o),s=0;i=a[s];s++)r[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(o,i)),r[i]=1);o=Object.getPrototypeOf(o)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=r}),window.CustomElements.addModule(function(e){function t(t,r){var c=r||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(o(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(l(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return c.prototype||(c.prototype=Object.create(HTMLElement.prototype)),c.__name=t.toLowerCase(),c["extends"]&&(c["extends"]=c["extends"].toLowerCase()),c.lifecycle=c.lifecycle||{},c.ancestry=i(c["extends"]),a(c),s(c),n(c.prototype),u(c.__name,c),c.ctor=d(c),c.ctor.prototype=c.prototype,c.prototype.constructor=c.ctor,e.ready&&w(document),c.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){r.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){r.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function r(e,t,n){e=e.toLowerCase();var r=this.getAttribute(e);n.apply(this,arguments);var o=this.getAttribute(e);this.attributeChangedCallback&&o!==r&&this.attributeChangedCallback(e,r,o)}function o(e){for(var t=0;t<E.length;t++)if(e===E[t])return!0}function i(e){var t=l(e);return t?i(t["extends"]).concat([t]):[]}function a(e){for(var t,n=e["extends"],r=0;t=e.ancestry[r];r++)n=t.is&&t.tag;e.tag=n||e.__name,n&&(e.is=e.__name)}function s(e){if(!Object.__proto__){var t=HTMLElement.prototype;if(e.is){var n=document.createElement(e.tag);t=Object.getPrototypeOf(n)}for(var r,o=e.prototype,i=!1;o;)o==t&&(i=!0),r=Object.getPrototypeOf(o),r&&(o.__proto__=r),o=r;i||console.warn(e.tag+" prototype not found in prototype chain for "+e.is),e["native"]=t}}function c(e){return g(T(e.tag),e)}function l(e){if(e)return _[e.toLowerCase()]}function u(e,t){_[e]=t}function d(e){return function(){return c(e)}}function p(e,t,n){return e===S?h(t,n):M(e,t)}function h(e,t){e&&(e=e.toLowerCase()),t&&(t=t.toLowerCase());var n=l(t||e);if(n){if(e==n.tag&&t==n.is)return new n.ctor;if(!t&&!n.is)return new n.ctor}var r;return t?(r=h(e),r.setAttribute("is",t),r):(r=T(e),e.indexOf("-")>=0&&b(r,HTMLElement),r)}function f(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return v(e),e}}var m,w=(e.isIE,e.upgradeDocumentTree),v=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,y=e.useNative,E=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],_={},S="http://www.w3.org/1999/xhtml",T=document.createElement.bind(document),M=document.createElementNS.bind(document);m=Object.__proto__||y?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},f(Node.prototype,"cloneNode"),f(document,"importNode"),document.registerElement=t,document.createElement=h,document.createElementNS=p,e.registry=_,e["instanceof"]=m,e.reservedTagList=E,e.getRegisteredDefinition=l,document.register=document.registerElement}),function(e){function t(){i(window.wrap(document)),window.CustomElements.ready=!0;var e=window.requestAnimationFrame||function(e){setTimeout(e,16)};e(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,r=e.initializeModules;e.isIE;if(n){var o=function(){};e.watchShadow=o,e.upgrade=o,e.upgradeAll=o,e.upgradeDocumentTree=o,e.upgradeSubtree=o,e.takeRecords=o,e["instanceof"]=function(e,t){return e instanceof t}}else r();var i=e.upgradeDocumentTree,a=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&a(wrap(e["import"]))}),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var s=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(s,t)}else t()}(window.CustomElements),function(e){Function.prototype.bind||(Function.prototype.bind=function(e){var t=this,n=Array.prototype.slice.call(arguments,1);return function(){var r=n.slice();return r.push.apply(r,arguments),t.apply(e,r)}})}(window.WebComponents),function(e){var t=document.createElement("style");t.textContent="body {transition: opacity ease-in 0.2s; } \nbody[unresolved] {opacity: 0; display: block; overflow: hidden; position: relative; } \n";var n=document.querySelector("head");n.insertBefore(t,n.firstChild)}(window.WebComponents),function(e){window.Platform=e}(window.WebComponents); \ No newline at end of file
diff --git a/traceviewer/src/test/java/io/perfmark/traceviewer/TraceEventViewerTest.java b/traceviewer/src/test/java/io/perfmark/traceviewer/TraceEventViewerTest.java
new file mode 100644
index 0000000..fd0094e
--- /dev/null
+++ b/traceviewer/src/test/java/io/perfmark/traceviewer/TraceEventViewerTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.traceviewer;
+
+import io.perfmark.PerfMark;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class TraceEventViewerTest {
+
+ /** This is an example function to show how to use the recorder. */
+ @Test
+ @Ignore
+ public void exampleRecorder() throws Exception {
+ PerfMark.setEnabled(true);
+ PerfMark.startTask(this, s -> s.getClass().getSimpleName() + ".this");
+ PerfMark.stopTask();
+ PerfMark.startTask(this, s -> s.getClass().getSimpleName() + ".that");
+ PerfMark.stopTask();
+ PerfMark.startTask(this, s -> s.getClass().getSimpleName() + "::hi");
+ PerfMark.stopTask();
+ PerfMark.startTask(this, s -> s.getClass().getSimpleName() + "::hey");
+ PerfMark.stopTask();
+ PerfMark.startTask(this, s -> s.getClass().getSimpleName() + "::hey");
+ PerfMark.stopTask();
+ PerfMark.setEnabled(false);
+ TraceEventViewer.writeTraceHtml();
+ }
+}
diff --git a/tracewriter/BUILD.bazel b/tracewriter/BUILD.bazel
new file mode 100644
index 0000000..39e07be
--- /dev/null
+++ b/tracewriter/BUILD.bazel
@@ -0,0 +1,15 @@
+java_library(
+ name = "tracewriter",
+ srcs = glob([
+ "src/main/java/io/perfmark/tracewriter/*.java",
+ ]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//impl:mark",
+ "//impl:mark-list",
+ "//impl:storage",
+ "@maven//:com_google_code_findbugs_jsr305",
+ "@maven//:com_google_code_gson_gson",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ ],
+)
diff --git a/tracewriter/build.gradle.kts b/tracewriter/build.gradle.kts
new file mode 100644
index 0000000..930e3aa
--- /dev/null
+++ b/tracewriter/build.gradle.kts
@@ -0,0 +1,27 @@
+buildscript {
+ extra.apply{
+ set("moduleName", "io.perfmark.tracewriter")
+ }
+}
+
+description = "PerfMark Tracer Output"
+
+val jdkVersion = JavaVersion.VERSION_1_7
+
+dependencies {
+ api(project(":perfmark-impl"))
+ // Included because it's easy to forget
+ runtimeOnly(project(":perfmark-java6"))
+
+ implementation(project(":perfmark-api"))
+ implementation("com.google.code.gson:gson:2.9.0")
+
+ compileOnly(libs.jsr305)
+ compileOnly(libs.errorprone)
+}
+
+tasks.getByName<JavaCompile>("compileJava") {
+ sourceCompatibility = jdkVersion.toString()
+ targetCompatibility = jdkVersion.toString()
+ options.compilerArgs.add("-Xlint:-options")
+}
diff --git a/tracewriter/src/main/java/io/perfmark/tracewriter/MarkListWalker.java b/tracewriter/src/main/java/io/perfmark/tracewriter/MarkListWalker.java
new file mode 100644
index 0000000..8456c86
--- /dev/null
+++ b/tracewriter/src/main/java/io/perfmark/tracewriter/MarkListWalker.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.tracewriter;
+
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkList;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+class MarkListWalker {
+ MarkListWalker() {}
+
+ static final String UNKNOWN_TASK_NAME = "(unknown)";
+
+ // TODO: make sure the generations dont have any timestamp overlap
+ final void walk(List<? extends MarkList> markLists, long nowNanoTime) {
+ Map<Long, List<MarkList>> generationToMarkLists = groupMarkListsByGeneration(markLists);
+ for (Map.Entry<Long, List<MarkList>> entry : generationToMarkLists.entrySet()) {
+ enterGeneration(entry.getKey());
+ for (MarkList markList : entry.getValue()) {
+ enterMarkList(markList.getThreadName(), markList.getThreadId(), markList.getMarkListId());
+ Deque<Mark> fakeStarts = new ArrayDeque<>();
+ Deque<Mark> fakeEnds = new ArrayDeque<>();
+ Set<Mark> unmatchedPairMarks =
+ Collections.newSetFromMap(new IdentityHashMap<Mark, Boolean>());
+ createFakes(fakeStarts, fakeEnds, unmatchedPairMarks, markList, nowNanoTime);
+ for (Mark mark : fakeStarts) {
+ onTaskStart(mark, true, false);
+ }
+ for (Mark mark : markList) {
+ onRealMark(mark, unmatchedPairMarks);
+ }
+ for (Mark mark : fakeEnds) {
+ onTaskEnd(mark, false, true);
+ }
+ exitMarkList();
+ }
+ exitGeneration();
+ }
+ }
+
+ protected void enterGeneration(long generation) {}
+
+ protected void exitGeneration() {}
+
+ protected void enterMarkList(String threadName, long threadId, long markListId) {}
+
+ protected void exitMarkList() {}
+
+ private void onRealMark(Mark mark, Collection<Mark> unmatchedPairMarks) {
+ switch (mark.getOperation().getOpType()) {
+ case TASK_START:
+ onTaskStart(mark, false, unmatchedPairMarks.contains(mark));
+ return;
+ case TASK_END:
+ onTaskEnd(mark, unmatchedPairMarks.contains(mark), false);
+ return;
+ case TAG:
+ onAttachTag(mark);
+ return;
+ case EVENT:
+ onEvent(mark);
+ return;
+ case LINK:
+ onLink(mark);
+ return;
+ case NONE:
+ break;
+ }
+ throw new AssertionError();
+ }
+
+ protected void onTaskStart(Mark mark, boolean unmatchedStart, boolean unmatchedEnd) {}
+
+ protected void onTaskEnd(Mark mark, boolean unmatchedStart, boolean unmatchedEnd) {}
+
+ protected void onLink(Mark mark) {}
+
+ protected void onEvent(Mark mark) {}
+
+ protected void onAttachTag(Mark mark) {}
+
+ private static Map<Long, List<MarkList>> groupMarkListsByGeneration(
+ List<? extends MarkList> markLists) {
+ Map<Long, List<MarkList>> generationToMarkLists = new TreeMap<>();
+ for (MarkList markList : markLists) {
+ if (markList.isEmpty()) {
+ continue;
+ }
+ Map<Long, List<Mark>> generationToMarks = new TreeMap<>();
+ for (Mark mark : markList) {
+ List<Mark> groupedMarks = generationToMarks.get(mark.getGeneration());
+ if (groupedMarks == null) {
+ generationToMarks.put(mark.getGeneration(), groupedMarks = new ArrayList<>());
+ }
+ groupedMarks.add(mark);
+ }
+ // note: marklists without any marks are lost here, since they have no generation.
+ for (Map.Entry<Long, List<Mark>> entry : generationToMarks.entrySet()) {
+ List<MarkList> groupedMarkLists = generationToMarkLists.get(entry.getKey());
+ if (groupedMarkLists == null) {
+ generationToMarkLists.put(entry.getKey(), groupedMarkLists = new ArrayList<>());
+ }
+ groupedMarkLists.add(markList.toBuilder().setMarks(entry.getValue()).build());
+ }
+ }
+ // TODO: make a defensive copy of this and the sublists
+ return generationToMarkLists;
+ }
+
+ private static void createFakes(
+ Deque<? super Mark> fakeStarts,
+ Deque<? super Mark> fakeEnds,
+ Set<? super Mark> unmatchedPairMarks,
+ List<Mark> marks,
+ long nowNanoTime) {
+ final Deque<Mark> unmatchedMarks = new ArrayDeque<>();
+ long[] nanoTimeBounds = new long[2]; // first, last
+ nanoTimeBounds[0] = nowNanoTime; // forces each subsequent overwrite to succeed.
+ nanoTimeBounds[1] = nowNanoTime;
+
+ loop:
+ for (Mark mark : marks) {
+ setNanoTimeBounds(nanoTimeBounds, mark);
+ switch (mark.getOperation().getOpType()) {
+ case TASK_START:
+ unmatchedMarks.addLast(mark);
+ continue loop;
+ case TASK_END:
+ if (!unmatchedMarks.isEmpty()) {
+ // TODO: maybe double check the tags and task names match
+ unmatchedMarks.removeLast();
+ } else {
+ fakeStarts.addFirst(createFakeStart(mark, nanoTimeBounds[0]));
+ unmatchedPairMarks.add(mark);
+ }
+ continue loop;
+ case EVENT:
+ case LINK:
+ case TAG:
+ continue loop;
+ case NONE:
+ break;
+ }
+ throw new AssertionError();
+ }
+ for (Mark unmatchedMark : unmatchedMarks) {
+ fakeEnds.addFirst(createFakeEnd(unmatchedMark, nanoTimeBounds[1]));
+ unmatchedPairMarks.add(unmatchedMark);
+ }
+ unmatchedMarks.clear();
+ }
+
+ private static void setNanoTimeBounds(long[] nanoTimeBounds, Mark mark) {
+ switch (mark.getOperation().getOpType()) {
+ case TASK_START:
+ case TASK_END:
+ case EVENT:
+ if (mark.getNanoTime() - nanoTimeBounds[0] < 0) {
+ nanoTimeBounds[0] = mark.getNanoTime();
+ }
+ if (mark.getNanoTime() - nanoTimeBounds[1] > 0) {
+ nanoTimeBounds[1] = mark.getNanoTime();
+ }
+ return;
+ case LINK:
+ case TAG:
+ return;
+ case NONE:
+ break;
+ }
+ throw new AssertionError();
+ }
+
+ private static Mark createFakeEnd(Mark start, long lastNanoTime) {
+ switch (start.getOperation()) {
+ case TASK_START_N1S1:
+ return Mark.taskEnd(start.getGeneration(), lastNanoTime, start.getTaskName());
+ case TASK_START_N1S2:
+ return Mark.taskEnd(
+ start.getGeneration(), lastNanoTime, start.getTaskName(), start.getSubTaskName());
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case LINK:
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N2S1:
+ case TAG_KEYED_N1S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case NONE:
+ break;
+ }
+ throw new AssertionError(start.getOperation());
+ }
+
+ private static Mark createFakeStart(Mark end, long firstNanoTime) {
+ switch (end.getOperation()) {
+ case TASK_END_N1S0:
+ return Mark.taskStart(end.getGeneration(), firstNanoTime, UNKNOWN_TASK_NAME);
+ case TASK_END_N1S1:
+ return Mark.taskStart(end.getGeneration(), firstNanoTime, end.getTaskName());
+ case TASK_END_N1S2:
+ return Mark.taskStart(
+ end.getGeneration(), firstNanoTime, end.getTaskName(), end.getSubTaskName());
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case LINK:
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N2S1:
+ case TAG_KEYED_N1S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ break;
+ }
+ throw new AssertionError(end.getOperation());
+ }
+}
diff --git a/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEvent.java b/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEvent.java
new file mode 100644
index 0000000..08e8ca5
--- /dev/null
+++ b/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEvent.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.tracewriter;
+
+import com.google.gson.annotations.SerializedName;
+import io.perfmark.impl.Mark;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+
+@CheckReturnValue
+final class TraceEvent implements Cloneable {
+
+ private TraceEvent() {}
+
+ static final TraceEvent EVENT = new TraceEvent();
+
+ @SerializedName("ph")
+ @SuppressWarnings("unused")
+ private String phase;
+
+ @SerializedName("name")
+ @SuppressWarnings("unused")
+ private String name;
+
+ @Nullable
+ @SerializedName("cat")
+ @SuppressWarnings("unused")
+ private String categories;
+
+ @Nullable
+ @SerializedName("ts")
+ @SuppressWarnings("unused")
+ private Double traceClockMicros;
+
+ @Nullable
+ @SerializedName("pid")
+ @SuppressWarnings("unused")
+ private Long pid;
+
+ @SerializedName("tid")
+ @Nullable
+ @SuppressWarnings("unused")
+ private Long tid;
+
+ @Nullable
+ @SerializedName("id")
+ @SuppressWarnings("unused")
+ private Long id;
+
+ @Nullable
+ @SerializedName("args")
+ @SuppressWarnings("unused")
+ private TagMap args = null;
+
+ @Nullable
+ @SerializedName("cname")
+ @SuppressWarnings("unused")
+ private String colorName = null;
+
+ TraceEvent name(String name) {
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+ TraceEvent other = clone();
+ other.name = name;
+ return other;
+ }
+
+ TraceEvent categories(String... categories) {
+ if (categories == null) {
+ throw new NullPointerException("categories");
+ }
+ return categories(Arrays.asList(categories));
+ }
+
+ TraceEvent categories(List<String> categories) {
+ if (categories == null) {
+ throw new NullPointerException("categories");
+ }
+ TraceEvent other = clone();
+ if (!categories.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ ListIterator<String> it = categories.listIterator();
+ sb.append(it.next());
+ while (it.hasNext()) {
+ String next = it.next();
+ if (next == null) {
+ throw new NullPointerException("next null at " + (it.nextIndex() - 1));
+ }
+ sb.append(',').append(next);
+ }
+ other.categories = sb.toString();
+ } else {
+ other.categories = null;
+ }
+ return other;
+ }
+
+ strictfp TraceEvent traceClockNanos(long traceClockNanos) {
+ TraceEvent other = clone();
+ other.traceClockMicros = traceClockNanos / 1000.0;
+ return other;
+ }
+
+ TraceEvent phase(String phase) {
+ if (phase == null) {
+ throw new NullPointerException("phase");
+ }
+ TraceEvent other = clone();
+ other.phase = phase;
+ return other;
+ }
+
+ TraceEvent tid(long tid) {
+ TraceEvent other = clone();
+ other.tid = tid;
+ return other;
+ }
+
+ TraceEvent pid(long pid) {
+ TraceEvent other = clone();
+ other.pid = pid;
+ return other;
+ }
+
+ TraceEvent id(long id) {
+ TraceEvent other = clone();
+ other.id = id;
+ return other;
+ }
+
+ /**
+ * Note This should only be used for tags, as the map size is used to determine the arg names in
+ * TraceEventWriter. This will overwrite any existing args.
+ *
+ * @param tagMap the args to use.
+ * @return this
+ */
+ TraceEvent args(TagMap tagMap) {
+ if (tagMap == null) {
+ throw new NullPointerException("tagMap");
+ }
+ TraceEvent other = clone();
+ other.args = tagMap;
+ return other;
+ }
+
+ TagMap args() {
+ if (args == null) {
+ return TagMap.EMPTY;
+ } else {
+ return args;
+ }
+ }
+
+ @Override
+ protected TraceEvent clone() {
+ try {
+ return (TraceEvent) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static final class TagMap extends AbstractMap<String, Object> {
+
+ static final TagMap EMPTY =
+ new TagMap(Collections.<Entry<String, ?>>emptyList(), Collections.emptyList());
+
+ private final List<Entry<String, ?>> keyedValues;
+ private final List<?> unkeyedValues;
+
+ private TagMap(List<Entry<String, ?>> keyedValues, List<?> unkeyedValues) {
+ this.keyedValues = keyedValues;
+ this.unkeyedValues = unkeyedValues;
+ }
+
+ TagMap withUnkeyed(@Nullable String tagName, long tagId) {
+ List<Object> unkeyedValues = null;
+ if (tagName != null && !Mark.NO_TAG_NAME.equals(tagName)) {
+ unkeyedValues = new ArrayList<>(this.unkeyedValues);
+ unkeyedValues.add(tagName);
+ }
+ if (tagId != Mark.NO_TAG_ID) {
+ unkeyedValues = unkeyedValues != null ? unkeyedValues : new ArrayList<>(this.unkeyedValues);
+ unkeyedValues.add(tagId);
+ }
+ if (unkeyedValues != null) {
+ return new TagMap(keyedValues, Collections.unmodifiableList(unkeyedValues));
+ } else {
+ return new TagMap(keyedValues, this.unkeyedValues);
+ }
+ }
+
+ TagMap withKeyed(@Nullable String tagName, Object tagValue) {
+ List<Entry<String, ?>> keyedValues = new ArrayList<>(this.keyedValues);
+ keyedValues.add(new SimpleImmutableEntry<>(String.valueOf(tagName), tagValue));
+ return new TagMap(Collections.unmodifiableList(keyedValues), unkeyedValues);
+ }
+
+ TagMap withKeyed(@Nullable String tagName, long tagValue0, long tagValue1) {
+ List<Entry<String, ?>> keyedValues = new ArrayList<>(this.keyedValues);
+ keyedValues.add(
+ new SimpleImmutableEntry<>(String.valueOf(tagName), tagValue0 + ":" + tagValue1));
+ return new TagMap(Collections.unmodifiableList(keyedValues), unkeyedValues);
+ }
+
+ @Override
+ public Set<Entry<String, Object>> entrySet() {
+ List<Entry<String, ?>> pairs = new ArrayList<>(keyedValues.size() + unkeyedValues.size());
+ pairs.addAll(keyedValues);
+ for (Object value : unkeyedValues) {
+ if (value instanceof Long) {
+ pairs.add(new SimpleImmutableEntry<>("id", value));
+ } else if (value instanceof String) {
+ pairs.add(new SimpleImmutableEntry<>("tag", value));
+ } else {
+ pairs.add(new SimpleImmutableEntry<>("tag", String.valueOf(value)));
+ }
+ }
+
+ Map<String, Object> ret = new LinkedHashMap<>();
+ addEntry:
+ for (Entry<String, ?> kv : pairs) {
+ String name = kv.getKey();
+ Object value = kv.getValue();
+ String derivedName = name;
+ int usages = 0;
+ while (true) {
+ if (!ret.containsKey(derivedName)) {
+ ret.put(derivedName, value);
+ continue addEntry;
+ }
+ if (ret.get(derivedName).equals(value)) {
+ continue addEntry;
+ }
+ usages++;
+ derivedName = name + " (" + usages + ')';
+ }
+ }
+ return Collections.unmodifiableSet(ret.entrySet());
+ }
+ }
+}
diff --git a/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEventWriter.java b/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEventWriter.java
new file mode 100644
index 0000000..d9a1fc3
--- /dev/null
+++ b/tracewriter/src/main/java/io/perfmark/tracewriter/TraceEventWriter.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.perfmark.tracewriter;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonIOException;
+import com.google.gson.annotations.SerializedName;
+import io.perfmark.impl.Mark;
+import io.perfmark.impl.MarkList;
+import io.perfmark.impl.Storage;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Writes the PerfMark results to a "Trace Event" JSON file usable by the Chromium Profiler
+ * "Catapult". The format is defined at
+ * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
+ *
+ * <p>This code is <strong>NOT</strong> API stable, and may be removed in the future, or changed
+ * without notice.
+ *
+ * @since 0.16.0
+ */
+public final class TraceEventWriter {
+
+ private static final Logger logger = Logger.getLogger(TraceEventWriter.class.getName());
+
+ /**
+ * Writes trace events the home directory. By default, it prefers the location in {@code
+ * $XDG_DATA_HOME/perfmark} environment variable. If unset, it attempts to use {@code
+ * $HOME/.local/share/perfmark}.
+ *
+ * <p>Authors note: if you are on Windows, or the above defaults aren't right, I'm not really sure
+ * where else is a good place to put this data. Please file an issue at https://www.perfmark.io/
+ * if you have a preference.
+ *
+ * <p>Updated in 0.17.0 to return the created path.
+ *
+ * @throws IOException if there is an error writing to the file.
+ * @return the path used to create the trace file.
+ */
+ @CanIgnoreReturnValue
+ public static Path writeTraceEvents() throws IOException {
+ Path p = pickNextDest(guessDirectory());
+ try (OutputStream os = Files.newOutputStream(p);
+ OutputStream gzos = new GZIPOutputStream(os);
+ Writer osw = new OutputStreamWriter(gzos, UTF_8)) {
+ writeTraceEvents(osw);
+ }
+ logger.info("Wrote trace to " + p);
+ return p;
+ }
+
+ /**
+ * Writes all trace events in in JSON format to the given destination.
+ *
+ * @param destination the destination for the JSON data.
+ * @throws IOException if there are errors build the JSON, or can't write to the destination.
+ */
+ public static void writeTraceEvents(Writer destination) throws IOException {
+ writeTraceEvents(
+ destination, Storage.read(), Storage.getInitNanoTime(), System.nanoTime(), getPid());
+ }
+
+ /**
+ * Writes the trace events gathered from {@link Storage#read()}. This method is not API stable. It
+ * will be eventually.
+ *
+ * @param destination the destination for the JSON data.
+ * @param markLists the data to use to build the trace event JSON
+ * @param initNanoTime the time PerfMark classes were first loaded as specified by {@link
+ * System#nanoTime()}
+ * @param nowNanoTime the current time as specified by {@link System#nanoTime()}.
+ * @param pid the PID of the current process.
+ * @throws IOException if there are errors build the JSON, or can't write to the destination.
+ */
+ public static void writeTraceEvents(
+ Writer destination,
+ List<? extends MarkList> markLists,
+ long initNanoTime,
+ long nowNanoTime,
+ long pid)
+ throws IOException {
+ List<TraceEvent> traceEvents = new ArrayList<>();
+ new TraceEventWalker(traceEvents, pid, initNanoTime).walk(markLists, nowNanoTime);
+ Gson gson = new GsonBuilder().create();
+ try {
+ gson.toJson(new TraceEventObject(traceEvents), destination);
+ } catch (JsonIOException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private static Path pickNextDest(Path dir) throws IOException {
+ String fmt = "perfmark-trace-%03d.json.gz";
+ int lo;
+ int hi = 0;
+ while (true) {
+ Path candidate = dir.resolve(String.format(fmt, hi));
+ if (!Files.exists(candidate)) {
+ lo = hi >>> 1;
+ break;
+ }
+ if (hi == 0) {
+ hi++;
+ } else if (hi >>> 1 >= Integer.MAX_VALUE) {
+ throw new IOException("too many files in dir");
+ } else {
+ hi <<= 1;
+ }
+ }
+ // After this point, hi must always point to a non-existent file.
+ while (hi > lo) {
+ int mid = (hi + lo) >>> 1; // take THAT, overflow!
+ Path candidate = dir.resolve(String.format(fmt, mid));
+ if (Files.exists(candidate)) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ return dir.resolve(String.format(fmt, hi));
+ }
+
+ private static Path guessDirectory() throws IOException {
+ final String PERFMARK_TRACE_DIR = "perfmark";
+ final String sep = File.separator;
+
+ List<Path> dataHomeChoices = new ArrayList<>();
+ String dataHome = System.getenv("XDG_DATA_HOME");
+ if (dataHome != null) {
+ dataHomeChoices.add(new File(dataHome + sep + PERFMARK_TRACE_DIR).toPath());
+ }
+ String home = System.getProperty("user.home");
+ if (home != null) {
+ dataHomeChoices.add(
+ new File(home + sep + ".local" + sep + "share" + sep + PERFMARK_TRACE_DIR).toPath());
+ }
+ for (Path path : dataHomeChoices) {
+ if (!Files.exists(path)) {
+ Files.createDirectories(path);
+ } else {
+ if (!Files.isDirectory(path)) {
+ continue;
+ }
+ }
+ return path;
+ }
+ return new File(".").toPath();
+ }
+
+ static final class TraceEventObject {
+ @SerializedName("traceEvents")
+ @SuppressWarnings("unused")
+ final List<TraceEvent> traceEvents;
+
+ @SerializedName("displayTimeUnit")
+ @SuppressWarnings("unused")
+ final String displayTimeUnit = "ns";
+
+ @SerializedName("systemTraceEvents")
+ @SuppressWarnings("unused")
+ final String systemTraceData = "";
+
+ @SerializedName("samples")
+ @SuppressWarnings("unused")
+ final List<Object> samples = new ArrayList<>();
+
+ @SerializedName("stackFrames")
+ @SuppressWarnings("unused")
+ final Map<String, ?> stackFrames = new HashMap<>();
+
+ TraceEventObject(List<TraceEvent> traceEvents) {
+ this.traceEvents = Collections.unmodifiableList(new ArrayList<>(traceEvents));
+ }
+ }
+
+ private static long getPid() {
+ List<Throwable> errors = new ArrayList<>(0);
+ Level level = Level.FINE;
+ try {
+ try {
+ Class<?> clz = Class.forName("java.lang.ProcessHandle");
+ Method currentMethod = clz.getMethod("current");
+ Object processHandle = currentMethod.invoke(null);
+ Method pidMethod = clz.getMethod("pid");
+ return (long) pidMethod.invoke(processHandle);
+ } catch (Exception | Error e) {
+ errors.add(e);
+ }
+ try {
+ String name = ManagementFactory.getRuntimeMXBean().getName();
+ int index = name.indexOf('@');
+ if (index != -1) {
+ return Long.parseLong(name.substring(0, index));
+ }
+ } catch (Exception | Error e) {
+ errors.add(e);
+ }
+ level = Level.WARNING;
+ } finally {
+ for (Throwable error : errors) {
+ logger.log(level, "Error getting pid", error);
+ }
+ }
+ return -1;
+ }
+
+ private static final class TraceEventWalker extends MarkListWalker {
+
+ private static final class TaskStart {
+ final Mark mark;
+ final int traceEventIdx;
+
+ TaskStart(Mark mark, int traceEventIdx) {
+ this.mark = mark;
+ this.traceEventIdx = traceEventIdx;
+ }
+ }
+
+ private long uniqueLinkPairId = 1;
+ private long currentThreadId = -1;
+ private long currentMarkListId = -1;
+ private final Deque<TaskStart> taskStack = new ArrayDeque<>();
+ private final Map<Long, LinkTuple> linkIdToLinkOut = new LinkedHashMap<>();
+ private final List<LinkTuple> linkIdToLinkIn = new ArrayList<>();
+
+ private final long pid;
+ private final long initNanoTime;
+ private final List<TraceEvent> traceEvents;
+
+ TraceEventWalker(List<TraceEvent> traceEvents, long pid, long initNanoTime) {
+ this.pid = pid;
+ this.initNanoTime = initNanoTime;
+ this.traceEvents = traceEvents;
+ }
+
+ @Override
+ protected void enterGeneration(long generation) {
+ linkIdToLinkOut.clear();
+ linkIdToLinkIn.clear();
+ }
+
+ @Override
+ protected void exitGeneration() {
+ for (LinkTuple linkIn : linkIdToLinkIn) {
+ long inLinkId = linkIn.link.getLinkId();
+ long outLinkId = -inLinkId;
+ LinkTuple linkOut = linkIdToLinkOut.get(outLinkId);
+ if (linkOut == null) {
+ // TODO: log?
+ continue;
+ }
+ if (linkOut.markListId == linkIn.markListId) {
+ // continue;
+ }
+ // The name must be the same to match links together.
+ String name =
+ "link("
+ + taskName(linkOut.lastTaskStart)
+ + " -> "
+ + taskName(linkIn.lastTaskStart)
+ + ")";
+ long localUniqueLinkPairId = uniqueLinkPairId++;
+ traceEvents.add(
+ TraceEvent.EVENT
+ .name(name)
+ .tid(linkOut.threadId)
+ .pid(pid)
+ .phase("s")
+ .id(localUniqueLinkPairId)
+ .args(TraceEvent.TagMap.EMPTY.withKeyed("linkid", linkOut.link.getLinkId()))
+ .traceClockNanos(linkOut.lastTaskStart.getNanoTime() - initNanoTime));
+
+ traceEvents.add(
+ TraceEvent.EVENT
+ .name(name)
+ .tid(linkIn.threadId)
+ .pid(pid)
+ .phase("t")
+ .id(localUniqueLinkPairId)
+ .args(TraceEvent.TagMap.EMPTY.withKeyed("linkid", linkOut.link.getLinkId()))
+ .traceClockNanos(linkIn.lastTaskStart.getNanoTime() - initNanoTime));
+ }
+ super.exitGeneration();
+ }
+
+ @Override
+ protected void enterMarkList(String threadName, long threadId, long markListId) {
+ currentThreadId = threadId;
+ currentMarkListId = markListId;
+ traceEvents.add(
+ TraceEvent.EVENT
+ .name("thread_name")
+ .phase("M")
+ .pid(pid)
+ .args(
+ TraceEvent.TagMap.EMPTY
+ .withKeyed("name", threadName)
+ .withKeyed("markListId", markListId))
+ .tid(currentThreadId));
+ }
+
+ @Override
+ protected void onTaskStart(Mark mark, boolean unmatchedStart, boolean unmatchedEnd) {
+ assert !(unmatchedStart && unmatchedEnd);
+ List<String> categories = Collections.emptyList();
+ if (unmatchedStart) {
+ categories = Collections.singletonList("unknownStart");
+ } else if (unmatchedEnd) {
+ categories = Collections.singletonList("unfinished");
+ }
+ TraceEvent traceEvent =
+ TraceEvent.EVENT
+ .name(taskName(mark))
+ .phase("B")
+ .pid(pid)
+ .categories(categories)
+ .tid(currentThreadId)
+ .traceClockNanos(mark.getNanoTime() - initNanoTime);
+ traceEvents.add(traceEvent);
+ taskStack.add(new TaskStart(mark, traceEvents.size() - 1));
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality") // For checking if it's an empty end mark
+ protected void onTaskEnd(Mark mark, boolean unmatchedStart, boolean unmatchedEnd) {
+ assert !(unmatchedStart && unmatchedEnd);
+ List<String> categories = Collections.emptyList();
+ if (unmatchedStart) {
+ categories = Collections.singletonList("unknownStart");
+ } else if (unmatchedEnd) {
+ categories = Collections.singletonList("unfinished");
+ }
+ // TODO: maybe copy the args from the start task
+ taskStack.pollLast();
+ TraceEvent traceEvent =
+ TraceEvent.EVENT
+ .phase("E")
+ .pid(pid)
+ .categories(categories)
+ .tid(currentThreadId)
+ .traceClockNanos(mark.getNanoTime() - initNanoTime);
+ String name = taskName(mark);
+ if (name != MarkListWalker.UNKNOWN_TASK_NAME) {
+ traceEvent = traceEvent.name(name);
+ }
+ traceEvents.add(traceEvent);
+ }
+
+ @Override
+ protected void onAttachTag(Mark mark) {
+ if (taskStack.isEmpty()) {
+ // In a mark list of only links (i.e. no starts or ends) it's possible there are no tasks
+ // to bind to. This is probably due to not calling link() correctly.
+ logger.fine("Tag not associated with any task");
+ return;
+ }
+ TaskStart taskStart = taskStack.peekLast();
+ TraceEvent taskEvent = traceEvents.get(taskStart.traceEventIdx);
+ TraceEvent.TagMap args = taskEvent.args();
+ out:
+ {
+ switch (mark.getOperation()) {
+ case TAG_N0S1:
+ args = args.withUnkeyed(mark.getTagStringValue(), Mark.NO_TAG_ID);
+ break out;
+ case TAG_N1S0:
+ args = args.withUnkeyed(Mark.NO_TAG_NAME, mark.getTagFirstNumeric());
+ break out;
+ case TAG_N1S1:
+ args = args.withUnkeyed(mark.getTagStringValue(), mark.getTagFirstNumeric());
+ break out;
+ case TAG_KEYED_N0S2:
+ args = args.withKeyed(mark.getTagKey(), mark.getTagStringValue());
+ break out;
+ case TAG_KEYED_N1S1:
+ args = args.withKeyed(mark.getTagKey(), mark.getTagFirstNumeric());
+ break out;
+ case TAG_KEYED_N2S1:
+ args =
+ args.withKeyed(
+ mark.getTagKey(), mark.getTagFirstNumeric(), mark.getTagSecondNumeric());
+ break out;
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ case LINK:
+ break;
+ }
+ throw new AssertionError(mark.getOperation());
+ }
+ traceEvents.set(taskStart.traceEventIdx, taskEvent.args(args));
+ }
+
+ @Override
+ protected void onEvent(Mark mark) {
+ TraceEvent.TagMap tagMap = TraceEvent.TagMap.EMPTY;
+ out:
+ {
+ switch (mark.getOperation()) {
+ case EVENT_N1S1:
+ case EVENT_N1S2:
+ break out;
+ case EVENT_N2S2:
+ case EVENT_N2S3:
+ tagMap = tagMap.withUnkeyed(mark.getTagStringValue(), mark.getTagFirstNumeric());
+ break out;
+ case NONE:
+ case TASK_START_N1S1:
+ case TASK_START_N1S2:
+ case TASK_END_N1S0:
+ case TASK_END_N1S1:
+ case TASK_END_N1S2:
+ case LINK:
+ case TAG_N0S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ break;
+ }
+ throw new AssertionError(mark.getOperation());
+ }
+ TraceEvent traceEvent =
+ TraceEvent.EVENT
+ .name(taskName(mark))
+ .phase("i")
+ .pid(pid)
+ .args(tagMap)
+ .tid(currentThreadId)
+ .traceClockNanos(mark.getNanoTime() - initNanoTime);
+ traceEvents.add(traceEvent);
+ }
+
+ static final class LinkTuple {
+ final Mark lastTaskStart;
+ final Mark link;
+ final long threadId;
+ final long markListId;
+
+ LinkTuple(Mark lastTaskStart, Mark link, long threadId, long markListId) {
+ this.lastTaskStart = lastTaskStart;
+ this.link = link;
+ this.threadId = threadId;
+ this.markListId = markListId;
+ }
+ }
+
+ @Override
+ protected void onLink(Mark mark) {
+ if (taskStack.isEmpty()) {
+ // In a mark list of only links (i.e. no starts or ends) it's possible there are no tasks
+ // to bind to. This is probably due to not calling link() correctly.
+ logger.fine("Link not associated with any task");
+ return;
+ }
+ LinkTuple linkTuple =
+ new LinkTuple(taskStack.peekLast().mark, mark, currentThreadId, currentMarkListId);
+ if (mark.getLinkId() > 0) {
+ LinkTuple old = linkIdToLinkOut.put(mark.getLinkId(), linkTuple);
+ assert old == null;
+ } else if (mark.getLinkId() < 0) {
+ linkIdToLinkIn.add(linkTuple);
+ }
+ }
+ }
+
+ private static String taskName(Mark mark) {
+ switch (mark.getOperation()) {
+ case TASK_END_N1S0:
+ return MarkListWalker.UNKNOWN_TASK_NAME;
+ case TASK_START_N1S1:
+ case TASK_END_N1S1:
+ case EVENT_N1S1:
+ case EVENT_N2S2:
+ return mark.getTaskName();
+ case TASK_START_N1S2:
+ case TASK_END_N1S2:
+ case EVENT_N1S2:
+ case EVENT_N2S3:
+ return mark.getTaskName() + '.' + mark.getSubTaskName();
+ case LINK:
+ case TAG_N0S1:
+ case TAG_KEYED_N0S2:
+ case TAG_KEYED_N1S1:
+ case TAG_KEYED_N2S1:
+ case TAG_N1S0:
+ case TAG_N1S1:
+ case NONE:
+ throw new UnsupportedOperationException(mark.toString());
+ }
+ throw new AssertionError(mark.getOperation());
+ }
+}
diff --git a/tracewriter/src/main/java/io/perfmark/tracewriter/package-info.java b/tracewriter/src/main/java/io/perfmark/tracewriter/package-info.java
new file mode 100644
index 0000000..38443b8
--- /dev/null
+++ b/tracewriter/src/main/java/io/perfmark/tracewriter/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The Trace Writer package reads the PerfMark recorded tasks, and converts them into the
+ * Chrome Trace Viewer format.
+ */
+@javax.annotation.CheckReturnValue
+@javax.annotation.ParametersAreNonnullByDefault
+package io.perfmark.tracewriter;