From 4eceb95409e844fdc33c9c706e1dc307bfd40303 Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Wed, 19 Mar 2014 16:25:37 +0100 Subject: Initial Jack import. Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43 --- java-allocation-instrumenter/AUTHORS | 1 + java-allocation-instrumenter/Android.mk | 48 ++ java-allocation-instrumenter/COPYING | 202 +++++++ java-allocation-instrumenter/NOTICE | 202 +++++++ java-allocation-instrumenter/README.android | 4 + java-allocation-instrumenter/build.xml | 65 +++ java-allocation-instrumenter/etc/manifest.txt | 5 + java-allocation-instrumenter/jarjar-rules.txt | 2 + java-allocation-instrumenter/lib/asm-4.0.jar | Bin 0 -> 46018 bytes .../lib/asm-analysis-4.0.jar | Bin 0 -> 19611 bytes .../lib/asm-commons-4.0.jar | Bin 0 -> 37774 bytes java-allocation-instrumenter/lib/asm-tree-4.0.jar | Bin 0 -> 21980 bytes java-allocation-instrumenter/lib/asm-util-4.0.jar | Bin 0 -> 36993 bytes java-allocation-instrumenter/lib/asm-xml-4.0.jar | Bin 0 -> 51707 bytes java-allocation-instrumenter/lib/guava-r06.jar | Bin 0 -> 934385 bytes java-allocation-instrumenter/lib/jarjar-1.0.jar | Bin 0 -> 112749 bytes .../instrumentation/AllocationClassAdapter.java | 56 ++ .../instrumentation/AllocationInstrumenter.java | 208 ++++++++ .../instrumentation/AllocationMethodAdapter.java | 589 +++++++++++++++++++++ .../instrumentation/AllocationRecorder.java | 260 +++++++++ .../instrumentation/ConstructorCallback.java | 47 ++ .../instrumentation/ConstructorInstrumenter.java | 231 ++++++++ .../runtime/instrumentation/Sampler.java | 46 ++ .../runtime/instrumentation/StaticClassWriter.java | 273 ++++++++++ .../instrumentation/VerifyingClassAdapter.java | 129 +++++ 25 files changed, 2368 insertions(+) create mode 100644 java-allocation-instrumenter/AUTHORS create mode 100644 java-allocation-instrumenter/Android.mk create mode 100644 java-allocation-instrumenter/COPYING create mode 100644 java-allocation-instrumenter/NOTICE create mode 100644 java-allocation-instrumenter/README.android create mode 100644 java-allocation-instrumenter/build.xml create mode 100644 java-allocation-instrumenter/etc/manifest.txt create mode 100644 java-allocation-instrumenter/jarjar-rules.txt create mode 100644 java-allocation-instrumenter/lib/asm-4.0.jar create mode 100644 java-allocation-instrumenter/lib/asm-analysis-4.0.jar create mode 100644 java-allocation-instrumenter/lib/asm-commons-4.0.jar create mode 100644 java-allocation-instrumenter/lib/asm-tree-4.0.jar create mode 100644 java-allocation-instrumenter/lib/asm-util-4.0.jar create mode 100644 java-allocation-instrumenter/lib/asm-xml-4.0.jar create mode 100644 java-allocation-instrumenter/lib/guava-r06.jar create mode 100644 java-allocation-instrumenter/lib/jarjar-1.0.jar create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationClassAdapter.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationInstrumenter.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationMethodAdapter.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationRecorder.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorCallback.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/Sampler.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/StaticClassWriter.java create mode 100644 java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/VerifyingClassAdapter.java (limited to 'java-allocation-instrumenter') diff --git a/java-allocation-instrumenter/AUTHORS b/java-allocation-instrumenter/AUTHORS new file mode 100644 index 0000000..e491a9e --- /dev/null +++ b/java-allocation-instrumenter/AUTHORS @@ -0,0 +1 @@ +Google Inc. diff --git a/java-allocation-instrumenter/Android.mk b/java-allocation-instrumenter/Android.mk new file mode 100644 index 0000000..51654a9 --- /dev/null +++ b/java-allocation-instrumenter/Android.mk @@ -0,0 +1,48 @@ +# Copyright (C) 2013 The Android Open Source Project +# +# 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. + +LOCAL_PATH:= $(call my-dir) + +# +# java-allocation-instrumenter +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java) + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_MODULE := allocation-jack + +LOCAL_MODULE_TAGS := optional + +LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt + +LOCAL_JAVA_LIBRARIES := \ + guava-jack + +LOCAL_STATIC_JAVA_LIBRARIES := \ + asm-all-4.1-jack \ + guava-collect-jack + +include $(BUILD_HOST_JAVA_LIBRARY) + +$(LOCAL_BUILT_MODULE): PRIVATE_LOCAL_PATH := $(LOCAL_PATH) +$(LOCAL_BUILT_MODULE): $(full_classes_compiled_jar) | $(JARJAR) + @echo JarJar: $@ + $(hide) java -jar $(JARJAR) process $(PRIVATE_JARJAR_RULES) $< $@ + $(hide)jar umf $(PRIVATE_LOCAL_PATH)/etc/manifest.txt $@ + +$(call dist-for-goals, dist_files, $(LOCAL_BUILT_MODULE):allocation-jack.jar) + diff --git a/java-allocation-instrumenter/COPYING b/java-allocation-instrumenter/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/java-allocation-instrumenter/COPYING @@ -0,0 +1,202 @@ + + 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/java-allocation-instrumenter/NOTICE b/java-allocation-instrumenter/NOTICE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/java-allocation-instrumenter/NOTICE @@ -0,0 +1,202 @@ + + 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/java-allocation-instrumenter/README.android b/java-allocation-instrumenter/README.android new file mode 100644 index 0000000..4609678 --- /dev/null +++ b/java-allocation-instrumenter/README.android @@ -0,0 +1,4 @@ +URL: https://code.google.com/p/java-allocation-instrumenter/ +Version: 2.1r30 +License: Apache 2.O +Description: "The Allocation Instrumenter is a Java agent written using the java.lang.instrument API and ASM. Each allocation in your Java program is instrumented; a user-defined callback is invoked on each allocation." diff --git a/java-allocation-instrumenter/build.xml b/java-allocation-instrumenter/build.xml new file mode 100644 index 0000000..1a2a001 --- /dev/null +++ b/java-allocation-instrumenter/build.xml @@ -0,0 +1,65 @@ + + + Builds the allocation instrumenter Java agent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java-allocation-instrumenter/etc/manifest.txt b/java-allocation-instrumenter/etc/manifest.txt new file mode 100644 index 0000000..1f038fb --- /dev/null +++ b/java-allocation-instrumenter/etc/manifest.txt @@ -0,0 +1,5 @@ +Premain-Class: com.google.monitoring.runtime.instrumentation.AllocationInstrumenter +Boot-Class-Path: ./allocation.jar +Can-Redefine-Classes: true +Can-Retransform-Classes: true +Main-Class: NotSuitableAsMain diff --git a/java-allocation-instrumenter/jarjar-rules.txt b/java-allocation-instrumenter/jarjar-rules.txt new file mode 100644 index 0000000..05d00f5 --- /dev/null +++ b/java-allocation-instrumenter/jarjar-rules.txt @@ -0,0 +1,2 @@ +rule org.objectweb.asm.** com.google.monitoring.runtime.instrumentation.asm.@1 +rule com.google.common.** com.google.monitoring.runtime.instrumentation.common.@0 diff --git a/java-allocation-instrumenter/lib/asm-4.0.jar b/java-allocation-instrumenter/lib/asm-4.0.jar new file mode 100644 index 0000000..6d63075 Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/asm-analysis-4.0.jar b/java-allocation-instrumenter/lib/asm-analysis-4.0.jar new file mode 100644 index 0000000..48bb9b8 Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-analysis-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/asm-commons-4.0.jar b/java-allocation-instrumenter/lib/asm-commons-4.0.jar new file mode 100644 index 0000000..8d564b1 Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-commons-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/asm-tree-4.0.jar b/java-allocation-instrumenter/lib/asm-tree-4.0.jar new file mode 100644 index 0000000..aa99d3a Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-tree-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/asm-util-4.0.jar b/java-allocation-instrumenter/lib/asm-util-4.0.jar new file mode 100644 index 0000000..0e10595 Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-util-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/asm-xml-4.0.jar b/java-allocation-instrumenter/lib/asm-xml-4.0.jar new file mode 100644 index 0000000..fe5d718 Binary files /dev/null and b/java-allocation-instrumenter/lib/asm-xml-4.0.jar differ diff --git a/java-allocation-instrumenter/lib/guava-r06.jar b/java-allocation-instrumenter/lib/guava-r06.jar new file mode 100644 index 0000000..8ff3a81 Binary files /dev/null and b/java-allocation-instrumenter/lib/guava-r06.jar differ diff --git a/java-allocation-instrumenter/lib/jarjar-1.0.jar b/java-allocation-instrumenter/lib/jarjar-1.0.jar new file mode 100644 index 0000000..89390bf Binary files /dev/null and b/java-allocation-instrumenter/lib/jarjar-1.0.jar differ diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationClassAdapter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationClassAdapter.java new file mode 100644 index 0000000..d66bd15 --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationClassAdapter.java @@ -0,0 +1,56 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.LocalVariablesSorter; +import org.objectweb.asm.commons.JSRInlinerAdapter; + +/** + * Instruments bytecodes that allocate heap memory to call a recording hook. + * A ClassVisitor that processes methods with a + * AllocationMethodAdapter to instrument heap allocations. + * + * @author jeremymanson@google.com (Jeremy Manson) + * @author fischman@google.com (Ami Fischman) (Original Author) + */ +class AllocationClassAdapter extends ClassVisitor { + private final String recorderClass; + private final String recorderMethod; + + public AllocationClassAdapter(ClassVisitor cv, String recorderClass, + String recorderMethod) { + super(Opcodes.ASM4, cv); + this.recorderClass = recorderClass; + this.recorderMethod = recorderMethod; + } + + /** + * For each method in the class being instrumented, visitMethod + * is called and the returned MethodVisitor is used to visit the method. + * Note that a new MethodVisitor is constructed for each method. + */ + @Override + public MethodVisitor visitMethod(int access, String base, String desc, + String signature, String[] exceptions) { + MethodVisitor mv = + cv.visitMethod(access, base, desc, signature, exceptions); + + if (mv != null) { + // We need to compute stackmaps (see + // AllocationInstrumenter#instrument). This can't really be + // done for old bytecode that contains JSR and RET instructions. + // So, we remove JSRs and RETs. + JSRInlinerAdapter jsria = new JSRInlinerAdapter( + mv, access, base, desc, signature, exceptions); + AllocationMethodAdapter aimv = + new AllocationMethodAdapter(jsria, recorderClass, recorderMethod); + LocalVariablesSorter lvs = new LocalVariablesSorter(access, desc, aimv); + aimv.lvs = lvs; + mv = lvs; + } + return mv; + } +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationInstrumenter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationInstrumenter.java new file mode 100644 index 0000000..c4d6ab6 --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationInstrumenter.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2007 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Instruments bytecodes that allocate heap memory to call a recording hook. + * This will add a static invocation to a recorder function to any bytecode that + * looks like it will be allocating heap memory allowing users to implement heap + * profiling schemes. + * + * @author Ami Fischman + * @author Jeremy Manson + */ +public class AllocationInstrumenter implements ClassFileTransformer { + static final Logger logger = + Logger.getLogger(AllocationInstrumenter.class.getName()); + + // We can rewrite classes loaded by the bootstrap class loader + // iff the agent is loaded by the bootstrap class loader. It is + // always *supposed* to be loaded by the bootstrap class loader, but + // this relies on the Boot-Class-Path attribute in the JAR file always being + // set to the name of the JAR file that contains this agent, which we cannot + // guarantee programmatically. + private static volatile boolean canRewriteBootstrap; + + static boolean canRewriteClass(String className, ClassLoader loader) { + // There are two conditions under which we don't rewrite: + // 1. If className was loaded by the bootstrap class loader and + // the agent wasn't (in which case the class being rewritten + // won't be able to call agent methods). + // 2. If it is java.lang.ThreadLocal, which can't be rewritten because the + // JVM depends on its structure. + if (((loader == null) && !canRewriteBootstrap) || + className.startsWith("java/lang/ThreadLocal")) { + return false; + } + // third_party/java/webwork/*/ognl.jar contains bad class files. Ugh. + if (className.startsWith("ognl/")) { + return false; + } + + return true; + } + + // No instantiating me except in premain() or in {@link JarClassTransformer}. + AllocationInstrumenter() { } + + public static void premain(String agentArgs, Instrumentation inst) { + AllocationRecorder.setInstrumentation(inst); + + // Force eager class loading here; we need this class to do + // instrumentation, so if we don't do the eager class loading, we + // get a ClassCircularityError when trying to load and instrument + // this class. + try { + Class.forName("sun.security.provider.PolicyFile"); + } catch (Throwable t) { + // NOP + } + + if (!inst.isRetransformClassesSupported()) { + System.err.println("Some JDK classes are already loaded and " + + "will not be instrumented."); + } + + // Don't try to rewrite classes loaded by the bootstrap class + // loader if this class wasn't loaded by the bootstrap class + // loader. + if (AllocationRecorder.class.getClassLoader() != null) { + canRewriteBootstrap = false; + // The loggers aren't installed yet, so we use println. + System.err.println("Class loading breakage: " + + "Will not be able to instrument JDK classes"); + return; + } + + canRewriteBootstrap = true; + + inst.addTransformer(new ConstructorInstrumenter(), + inst.isRetransformClassesSupported()); + + List args = Arrays.asList( + agentArgs == null ? new String[0] : agentArgs.split(",")); + if (!args.contains("manualOnly")) { + bootstrap(inst); + } + } + + private static void bootstrap(Instrumentation inst) { + inst.addTransformer(new AllocationInstrumenter(), + inst.isRetransformClassesSupported()); + + if (!canRewriteBootstrap) { + return; + } + + // Get the set of already loaded classes that can be rewritten. + Class[] classes = inst.getAllLoadedClasses(); + ArrayList> classList = new ArrayList>(); + for (int i = 0; i < classes.length; i++) { + if (inst.isModifiableClass(classes[i])) { + classList.add(classes[i]); + } + } + + // Reload classes, if possible. + Class[] workaround = new Class[classList.size()]; + try { + inst.retransformClasses(classList.toArray(workaround)); + } catch (UnmodifiableClassException e) { + System.err.println("AllocationInstrumenter was unable to " + + "retransform early loaded classes."); + } + + + } + + @Override public byte[] transform( + ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] origBytes) { + if (!canRewriteClass(className, loader)) { + return null; + } + + return instrument(origBytes, loader); + } + + /** + * Given the bytes representing a class, go through all the bytecode in it and + * instrument any occurences of new/newarray/anewarray/multianewarray with + * pre- and post-allocation hooks. Even more fun, intercept calls to the + * reflection API's Array.newInstance() and instrument those too. + * + * @param originalBytes the original byte[] code. + * @param recorderClass the String internal name of the class + * containing the recorder method to run. + * @param recorderMethod the String name of the recorder method + * to run. + * @return the instrumented byte[] code. + */ + public static byte[] instrument(byte[] originalBytes, String recorderClass, + String recorderMethod, ClassLoader loader) { + try { + ClassReader cr = new ClassReader(originalBytes); + // The verifier in JDK7 requires accurate stackmaps, so we use + // COMPUTE_FRAMES. + ClassWriter cw = + new StaticClassWriter(cr, ClassWriter.COMPUTE_FRAMES, loader); + + VerifyingClassAdapter vcw = + new VerifyingClassAdapter(cw, originalBytes, cr.getClassName()); + ClassVisitor adapter = + new AllocationClassAdapter(vcw, recorderClass, recorderMethod); + + cr.accept(adapter, ClassReader.SKIP_FRAMES); + + return vcw.toByteArray(); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Failed to instrument class.", e); + throw e; + } catch (Error e) { + logger.log(Level.WARNING, "Failed to instrument class.", e); + throw e; + } + } + + + /** + * @see #instrument(byte[], String, String, ClassLoader) + * documentation for the 4-arg version. This is a convenience + * version that uses the recorder in this class. + */ + public static byte[] instrument(byte[] originalBytes, ClassLoader loader) { + return instrument( + originalBytes, + "com/google/monitoring/runtime/instrumentation/AllocationRecorder", + "recordAllocation", + loader); + } +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationMethodAdapter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationMethodAdapter.java new file mode 100644 index 0000000..eaa0bad --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationMethodAdapter.java @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.LinkedList; +import java.util.List; + +/** + * A MethodAdapter that instruments all heap allocation bytecodes + * to record the allocation being done for profiling. + * Instruments bytecodes that allocate heap memory to call a recording hook. + * + * @author Ami Fischman + */ +class AllocationMethodAdapter extends MethodVisitor { + /** + * The signature string the recorder method must have. The method must be + * static, return void, and take as arguments: + *
    + *
  1. an int count of how many instances are being allocated. -1 means a + * simple new to distinguish from a 1-element array. 0 shows up as a value + * here sometimes; one reason is toArray()-type methods that require an array + * type argument (see ArrayList.toArray() for example).
  2. + *
  3. a String descriptor of the class/primitive type being allocated.
  4. + *
  5. an Object reference to the just-allocated Object.
  6. + *
+ */ + public static final String RECORDER_SIGNATURE = + "(ILjava/lang/String;Ljava/lang/Object;)V"; + + /** + * Like RECORDER_SIGNATURE, but for a method that extracts all of + * the information dynamically from a class. + */ + public static final String CLASS_RECORDER_SIG = + "(Ljava/lang/Class;Ljava/lang/Object;)V"; + + // A helper struct for describing the scope of temporary local variables we + // create as part of the instrumentation. + private static class VariableScope { + public final int index; + public final Label start; + public final Label end; + public final String desc; + public VariableScope(int index, Label start, Label end, String desc) { + this.index = index; this.start = start; this.end = end; this.desc = desc; + } + } + + // Dictionary of primitive type opcode to English name. + private static final String[] primitiveTypeNames = new String[] { + "INVALID0", "INVALID1", "INVALID2", "INVALID3", + "boolean", "char", "float", "double", + "byte", "short", "int", "long" + }; + + // To track the difference between 's called as the result of a NEW + // and 's called because of superclass initialization, we track the + // number of NEWs that still need to have their 's called. + private int outstandingAllocs = 0; + + // We need to set the scope of any local variables we materialize; + // accumulate the scopes here and set them all at the end of the visit to + // ensure all labels have been resolved. Allocated on-demand. + private List localScopes = null; + + private List getLocalScopes() { + if (localScopes == null) { + localScopes = new LinkedList(); + } + return localScopes; + } + + private final String recorderClass; + private final String recorderMethod; + + /** + * The LocalVariablesSorter used in this adapter. Lame that it's public but + * the ASM architecture requires setting it from the outside after this + * AllocationMethodAdapter is fully constructed and the LocalVariablesSorter + * constructor requires a reference to this adapter. The only setter of + * this should be AllocationClassAdapter.visitMethod(). + */ + public LocalVariablesSorter lvs = null; + + /** + * A new AllocationMethodAdapter is created for each method that gets visited. + */ + public AllocationMethodAdapter(MethodVisitor mv, String recorderClass, + String recorderMethod) { + super(Opcodes.ASM4, mv); + this.recorderClass = recorderClass; + this.recorderMethod = recorderMethod; + } + + /** + * newarray shows up as an instruction taking an int operand (the primitive + * element type of the array) so we hook it here. + */ + @Override + public void visitIntInsn(int opcode, int operand) { + if (opcode == Opcodes.NEWARRAY) { + // instack: ... count + // outstack: ... aref + if (operand >= 4 && operand <= 11) { + super.visitInsn(Opcodes.DUP); // -> stack: ... count count + super.visitIntInsn(opcode, operand); // -> stack: ... count aref + invokeRecordAllocation(primitiveTypeNames[operand]); + // -> stack: ... aref + } else { + AllocationInstrumenter.logger.severe("NEWARRAY called with an invalid operand " + + operand + ". Not instrumenting this allocation!"); + super.visitIntInsn(opcode, operand); + } + } else { + super.visitIntInsn(opcode, operand); + } + } + + // Helper method to compute class name as a String and push it on the stack. + // pre: stack: ... class + // post: stack: ... class className + private void pushClassNameOnStack() { + super.visitInsn(Opcodes.DUP); + // -> stack: ... class class + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", + "getName", "()Ljava/lang/String;"); + // -> stack: ... class classNameDotted + super.visitLdcInsn('.'); + // -> stack: ... class classNameDotted '.' + super.visitLdcInsn('/'); + // -> stack: ... class classNameDotted '.' '/' + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", + "replace", "(CC)Ljava/lang/String;"); + // -> stack: ... class className + } + + // Helper method to compute the product of an integer array and push it on + // the stack. + // pre: stack: ... intArray + // post: stack: ... intArray product + private void pushProductOfIntArrayOnStack() { + Label beginScopeLabel = new Label(); + Label endScopeLabel = new Label(); + + int dimsArrayIndex = newLocal("[I", beginScopeLabel, endScopeLabel); + int counterIndex = newLocal("I", beginScopeLabel, endScopeLabel); + int productIndex = newLocal("I", beginScopeLabel, endScopeLabel); + Label loopLabel = new Label(); + Label endLabel = new Label(); + + super.visitLabel(beginScopeLabel); + + // stack: ... intArray + super.visitVarInsn(Opcodes.ASTORE, dimsArrayIndex); + // -> stack: ... + + // counter = 0 + super.visitInsn(Opcodes.ICONST_0); + super.visitVarInsn(Opcodes.ISTORE, counterIndex); + // product = 1 + super.visitInsn(Opcodes.ICONST_1); + super.visitVarInsn(Opcodes.ISTORE, productIndex); + // loop: + super.visitLabel(loopLabel); + // if index >= arraylength goto end: + super.visitVarInsn(Opcodes.ILOAD, counterIndex); + super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); + super.visitInsn(Opcodes.ARRAYLENGTH); + super.visitJumpInsn(Opcodes.IF_ICMPGE, endLabel); + // product = product * max(array[counter],1) + super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); + super.visitVarInsn(Opcodes.ILOAD, counterIndex); + super.visitInsn(Opcodes.IALOAD); + super.visitInsn(Opcodes.DUP); + Label nonZeroDimension = new Label(); + super.visitJumpInsn(Opcodes.IFNE, nonZeroDimension); + super.visitInsn(Opcodes.POP); + super.visitInsn(Opcodes.ICONST_1); + super.visitLabel(nonZeroDimension); + super.visitVarInsn(Opcodes.ILOAD, productIndex); + super.visitInsn(Opcodes.IMUL); // if overflow happens it happens. + super.visitVarInsn(Opcodes.ISTORE, productIndex); + // iinc counter 1 + super.visitIincInsn(counterIndex, 1); + // goto loop + super.visitJumpInsn(Opcodes.GOTO, loopLabel); + // end: + super.visitLabel(endLabel); + // re-push dimensions array + super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); + // push product + super.visitVarInsn(Opcodes.ILOAD, productIndex); + + super.visitLabel(endScopeLabel); + } + + /** + * Reflection-based allocation (@see java.lang.reflect.Array#newInstance) is + * triggered with a static method call (INVOKESTATIC), so we hook it here. + * Class initialization is triggered with a constructor call (INVOKESPECIAL) + * so we hook that here too as a proxy for the new bytecode which leaves an + * uninitialized object on the stack that we're not allowed to touch. + * {@link java.lang.Object#clone} is also a call to INVOKESPECIAL, + * and is hooked here. {@link java.lang.Class#newInstance} and + * {@link java.lang.reflect.Constructor#newInstance} are both + * INVOKEVIRTUAL calls, so they are hooked here, as well. + */ + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String signature) { + if (opcode == Opcodes.INVOKESTATIC && + // Array does its own native allocation. Grr. + owner.equals("java/lang/reflect/Array") && + name.equals("newInstance")) { + if (signature.equals("(Ljava/lang/Class;I)Ljava/lang/Object;")) { + + Label beginScopeLabel = new Label(); + Label endScopeLabel = new Label(); + super.visitLabel(beginScopeLabel); + + // stack: ... class count + int countIndex = newLocal("I", beginScopeLabel, endScopeLabel); + super.visitVarInsn(Opcodes.ISTORE, countIndex); + // -> stack: ... class + pushClassNameOnStack(); + // -> stack: ... class className + int typeNameIndex = + newLocal("Ljava/lang/String;", beginScopeLabel, endScopeLabel); + super.visitVarInsn(Opcodes.ASTORE, typeNameIndex); + // -> stack: ... class + super.visitVarInsn(Opcodes.ILOAD, countIndex); + // -> stack: ... class count + super.visitMethodInsn(opcode, owner, name, signature); + // -> stack: ... newobj + super.visitInsn(Opcodes.DUP); + // -> stack: ... newobj newobj + super.visitVarInsn(Opcodes.ILOAD, countIndex); + // -> stack: ... newobj newobj count + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj count newobj + super.visitVarInsn(Opcodes.ALOAD, typeNameIndex); + super.visitLabel(endScopeLabel); + // -> stack: ... newobj count newobj className + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj count className newobj + super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, + recorderMethod, RECORDER_SIGNATURE); + // -> stack: ... newobj + return; + } else if (signature.equals("(Ljava/lang/Class;[I)Ljava/lang/Object;")){ + Label beginScopeLabel = new Label(); + Label endScopeLabel = new Label(); + super.visitLabel(beginScopeLabel); + + int dimsArrayIndex = newLocal("[I", beginScopeLabel, endScopeLabel); + // stack: ... class dimsArray + pushProductOfIntArrayOnStack(); + // -> stack: ... class dimsArray product + int productIndex = newLocal("I", beginScopeLabel, endScopeLabel); + super.visitVarInsn(Opcodes.ISTORE, productIndex); + // -> stack: ... class dimsArray + + super.visitVarInsn(Opcodes.ASTORE, dimsArrayIndex); + // -> stack: ... class + pushClassNameOnStack(); + // -> stack: ... class className + int typeNameIndex = + newLocal("Ljava/lang/String;", beginScopeLabel, endScopeLabel); + super.visitVarInsn(Opcodes.ASTORE, typeNameIndex); + // -> stack: ... class + super.visitVarInsn(Opcodes.ALOAD, dimsArrayIndex); + // -> stack: ... class dimsArray + super.visitMethodInsn(opcode, owner, name, signature); + // -> stack: ... newobj + + super.visitInsn(Opcodes.DUP); + // -> stack: ... newobj newobj + super.visitVarInsn(Opcodes.ILOAD, productIndex); + // -> stack: ... newobj newobj product + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj product newobj + super.visitVarInsn(Opcodes.ALOAD, typeNameIndex); + super.visitLabel(endScopeLabel); + // -> stack: ... newobj product newobj className + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj product className newobj + super.visitMethodInsn(Opcodes.INVOKESTATIC, recorderClass, + recorderMethod, RECORDER_SIGNATURE); + // -> stack: ... newobj + return; + } + } + + if (opcode == Opcodes.INVOKEVIRTUAL) { + if ("clone".equals(name) && owner.startsWith("[")) { + super.visitMethodInsn(opcode, owner, name, signature); + + int i = 0; + while (i < owner.length()) { + if (owner.charAt(i) != '[') { + break; + } + i++; + } + if (i > 1) { + // -> stack: ... newobj + super.visitTypeInsn(Opcodes.CHECKCAST, owner); + // -> stack: ... arrayref + calculateArrayLengthAndDispatch(owner.substring(i), i); + } else { + // -> stack: ... newobj + super.visitInsn(Opcodes.DUP); + // -> stack: ... newobj newobj + super.visitTypeInsn(Opcodes.CHECKCAST, owner); + // -> stack: ... newobj arrayref + super.visitInsn(Opcodes.ARRAYLENGTH); + // -> stack: ... newobj length + super.visitInsn(Opcodes.SWAP); + // -> stack: ... length newobj + invokeRecordAllocation(owner.substring(i)); + } + return; + } else if ("newInstance".equals(name)) { + if ("java/lang/Class".equals(owner) && + "()Ljava/lang/Object;".equals(signature)) { + super.visitInsn(Opcodes.DUP); + // -> stack: ... Class Class + super.visitMethodInsn(opcode, owner, name, signature); + // -> stack: ... Class newobj + super.visitInsn(Opcodes.DUP_X1); + // -> stack: ... newobj Class newobj + super.visitMethodInsn(Opcodes.INVOKESTATIC, + recorderClass, recorderMethod, + CLASS_RECORDER_SIG); + // -> stack: ... newobj + return; + } else if ("java/lang/reflect/Constructor".equals(owner) && + "([Ljava/lang/Object;)Ljava/lang/Object;".equals(signature)) { + buildRecorderFromObject(opcode, owner, name, signature); + return; + } + } + } + + if (opcode == Opcodes.INVOKESPECIAL) { + if ("clone".equals(name) && "java/lang/Object".equals(owner)) { + buildRecorderFromObject(opcode, owner, name, signature); + return; + } else if ("".equals(name) && outstandingAllocs > 0) { + // Tricky because superclass initializers mean there can be more calls + // to than calls to NEW; hence outstandingAllocs. + --outstandingAllocs; + + // Most of the time (i.e. in bytecode generated by javac) it is the case + // that following an call the top of the stack has a reference ot + // the newly-initialized object. But nothing in the JVM Spec requires + // this, so we need to play games with the stack to make an explicit + // extra copy (and then discard it). + + dupStackElementBeforeSignatureArgs(signature); + super.visitMethodInsn(opcode, owner, name, signature); + super.visitLdcInsn(-1); + super.visitInsn(Opcodes.SWAP); + invokeRecordAllocation(owner); + super.visitInsn(Opcodes.POP); + return; + } + } + + super.visitMethodInsn(opcode, owner, name, signature); + } + + // This is the instrumentation that occurs when there is no static + // information about the class we are instantiating. First we build the + // object, then we get the class and invoke the recorder. + private void buildRecorderFromObject( + int opcode, String owner, String name, String signature) { + super.visitMethodInsn(opcode, owner, name, signature); + // -> stack: ... newobj + super.visitInsn(Opcodes.DUP); + // -> stack: ... newobj newobj + super.visitInsn(Opcodes.DUP); + // -> stack: ... newobj newobj newobj + // We could be instantiating this class or a subclass, so we + // have to get the class the hard way. + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/Object", + "getClass", + "()Ljava/lang/Class;"); + // -> stack: ... newobj newobj Class + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj Class newobj + super.visitMethodInsn(Opcodes.INVOKESTATIC, + recorderClass, recorderMethod, + CLASS_RECORDER_SIG); + // -> stack: ... newobj + } + + // Given a method signature interpret the top of the stack as the arguments + // to the method, dup the top-most element preceding these arguments, and + // leave the arguments alone. This is done by inspecting each parameter + // type, popping off the stack elements using the type information, + // duplicating the target element, and pushing the arguments back on the + // stack. + private void dupStackElementBeforeSignatureArgs(final String sig) { + final Label beginScopeLabel = new Label(); + final Label endScopeLabel = new Label(); + super.visitLabel(beginScopeLabel); + + Type[] argTypes = Type.getArgumentTypes(sig); + int[] args = new int[argTypes.length]; + + for (int i = argTypes.length - 1; i >= 0; --i) { + args[i] = newLocal(argTypes[i], beginScopeLabel, endScopeLabel); + super.visitVarInsn(argTypes[i].getOpcode(Opcodes.ISTORE), args[i]); + } + super.visitInsn(Opcodes.DUP); + for (int i = 0; i < argTypes.length; ++i) { + super.visitVarInsn(argTypes[i].getOpcode(Opcodes.ILOAD), args[i]); + } + super.visitLabel(endScopeLabel); + } + + /** + * new and anewarray bytecodes take a String operand for the type of + * the object or array element so we hook them here. Note that new doesn't + * actually result in any instrumentation here; we just do a bit of + * book-keeping and do the instrumentation following the constructor call + * (because we're not allowed to touch the object until it is initialized). + */ + @Override + public void visitTypeInsn(int opcode, String typeName) { + if (opcode == Opcodes.NEW) { + // We can't actually tag this object right after allocation because it + // must be initialized with a ctor before we can touch it (Verifier + // enforces this). Instead, we just note it and tag following + // initialization. + super.visitTypeInsn(opcode, typeName); + ++outstandingAllocs; + } else if (opcode == Opcodes.ANEWARRAY) { + super.visitInsn(Opcodes.DUP); + super.visitTypeInsn(opcode, typeName); + invokeRecordAllocation(typeName); + } else { + super.visitTypeInsn(opcode, typeName); + } + } + + /** + * Called by the ASM framework once the class is done being visited to + * compute stack & local variable count maximums. + */ + @Override + public void visitMaxs(int maxStack, int maxLocals) { + if (localScopes != null) { + for (VariableScope scope : localScopes) { + super.visitLocalVariable("xxxxx$" + scope.index, scope.desc, null, + scope.start, scope.end, scope.index); + } + } + super.visitMaxs(maxStack, maxLocals); + } + + // Helper method to allocate a new local variable and account for its scope. + private int newLocal(Type type, String typeDesc, + Label begin, Label end) { + int newVar = lvs.newLocal(type); + getLocalScopes().add(new VariableScope(newVar, begin, end, typeDesc)); + return newVar; + } + + // Sometimes I happen to have a string descriptor and sometimes a type; + // these alternate versions let me avoid recomputing whatever I already + // know. + private int newLocal(String typeDescriptor, Label begin, Label end) { + return newLocal(Type.getType(typeDescriptor), typeDescriptor, begin, end); + } + private int newLocal(Type type, Label begin, Label end) { + return newLocal(type, type.getDescriptor(), begin, end); + } + + // Helper method to actually invoke the recorder function for an allocation + // event. + // pre: stack: ... count newobj + // post: stack: ... newobj + private void invokeRecordAllocation(String typeName) { + typeName = typeName.replaceAll("^\\[*L", "").replaceAll(";$", ""); + // stack: ... count newobj + super.visitInsn(Opcodes.DUP_X1); + // -> stack: ... newobj count newobj + super.visitLdcInsn(typeName); + // -> stack: ... newobj count newobj typename + super.visitInsn(Opcodes.SWAP); + // -> stack: ... newobj count typename newobj + super.visitMethodInsn(Opcodes.INVOKESTATIC, + recorderClass, recorderMethod, RECORDER_SIGNATURE); + // -> stack: ... newobj + } + + /** + * multianewarray gets its very own visit method in the ASM framework, so we + * hook it here. This bytecode is different from most in that it consumes a + * variable number of stack elements during execution. The number of stack + * elements consumed is specified by the dimCount operand. + */ + @Override + public void visitMultiANewArrayInsn(String typeName, int dimCount) { + // stack: ... dim1 dim2 dim3 ... dimN + super.visitMultiANewArrayInsn(typeName, dimCount); + // -> stack: ... aref + calculateArrayLengthAndDispatch(typeName, dimCount); + } + + void calculateArrayLengthAndDispatch(String typeName, int dimCount) { + // Since the dimensions of the array are not known at instrumentation + // time, we take the created multi-dimensional array and peel off nesting + // levels from the left. For each nesting layer we probe the array length + // and accumulate a partial product which we can then feed the recording + // function. + + // below we note the partial product of dimensions 1 to X-1 as productToX + // (so productTo1 == 1 == no dimensions yet). We denote by aref0 the + // array reference at the current nesting level (the containing aref's [0] + // element). If we hit a level whose arraylength is 0 there's no point + // continuing so we shortcut out. + Label zeroDimension = new Label(); + super.visitInsn(Opcodes.DUP); // -> stack: ... origaref aref0 + super.visitLdcInsn(1); // -> stack: ... origaref aref0 productTo1 + for (int i = 0; i < dimCount; ++i) { + // pre: stack: ... origaref aref0 productToI + super.visitInsn(Opcodes.SWAP); // -> stack: ... origaref productToI aref + super.visitInsn(Opcodes.DUP_X1); + // -> stack: ... origaref aref0 productToI aref + super.visitInsn(Opcodes.ARRAYLENGTH); + // -> stack: ... origaref aref0 productToI dimI + + Label nonZeroDimension = new Label(); + super.visitInsn(Opcodes.DUP); + // -> stack: ... origaref aref0 productToI dimI dimI + super.visitJumpInsn(Opcodes.IFNE, nonZeroDimension); + // -> stack: ... origaref aref0 productToI dimI + super.visitInsn(Opcodes.POP); + // -> stack: ... origaref aref0 productToI + super.visitJumpInsn(Opcodes.GOTO, zeroDimension); + super.visitLabel(nonZeroDimension); + // -> stack: ... origaref aref0 productToI max(dimI,1) + + super.visitInsn(Opcodes.IMUL); + // -> stack: ... origaref aref0 productTo{I+1} + if (i < dimCount - 1) { + super.visitInsn(Opcodes.SWAP); + // -> stack: ... origaref productTo{I+1} aref0 + super.visitInsn(Opcodes.ICONST_0); + // -> stack: ... origaref productTo{I+1} aref0 0 + super.visitInsn(Opcodes.AALOAD); + // -> stack: ... origaref productTo{I+1} aref0' + super.visitInsn(Opcodes.SWAP); + } + // post: stack: ... origaref aref0 productTo{I+1} + } + super.visitLabel(zeroDimension); + + super.visitInsn(Opcodes.SWAP); // -> stack: ... origaref product aref0 + super.visitInsn(Opcodes.POP); // -> stack: ... origaref product + super.visitInsn(Opcodes.SWAP); // -> stack: ... product origaref + invokeRecordAllocation(typeName); + } +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationRecorder.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationRecorder.java new file mode 100644 index 0000000..1ead9ca --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/AllocationRecorder.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.MapMaker; + +/** + * The logic for recording allocations, called from bytecode rewritten by + * {@link AllocationInstrumenter}. + * + * @author jeremymanson@google.com (Jeremy Manson) + * @author fischman@google.com (Ami Fischman) + */ +public class AllocationRecorder { + static { + // Sun's JVMs in 1.5.0_06 and 1.6.0{,_01} have a bug where calling + // Instrumentation.getObjectSize() during JVM shutdown triggers a + // JVM-crashing assert in JPLISAgent.c, so we make sure to not call it after + // shutdown. There can still be a race here, depending on the extent of the + // JVM bug, but this seems to be good enough. + // instrumentation is volatile to make sure the threads reading it (in + // recordAllocation()) see the updated value; we could do more + // synchronization but it's not clear that it'd be worth it, given the + // ambiguity of the bug we're working around in the first place. + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + setInstrumentation(null); + } + }); + } + + // See the comment above the addShutdownHook in the static block above + // for why this is volatile. + private static volatile Instrumentation instrumentation = null; + + static Instrumentation getInstrumentation() { + return instrumentation; + } + + static void setInstrumentation(Instrumentation inst) { + instrumentation = inst; + } + + // Mostly because, yes, arrays are faster than collections. + private static volatile Sampler [] additionalSamplers; + + // Protects mutations of additionalSamplers. Reads are okay because + // the field is volatile, so anyone who reads additionalSamplers + // will get a consistent view of it. + private static final Object samplerLock = new Object(); + + // List of packages that can add samplers. + private static final List classNames = new ArrayList(); + + static { + classNames.add("com.google.monitoring.runtime."); + } + + // Used for reentrancy checks + private static final ThreadLocal recordingAllocation = new ThreadLocal(); + + // Stores the object sizes for the last ~100000 encountered classes + private static final ForwardingMap, Long> classSizesMap = + new ForwardingMap, Long>() { + private final ConcurrentMap, Long> map = new MapMaker() + .weakKeys() + .makeMap(); + + @Override public Map, Long> delegate() { + return map; + } + + // The approximate maximum size of the map + private static final int MAX_SIZE = 100000; + + // The approximate current size of the map; since this is not an AtomicInteger + // and since we do not synchronize the updates to this field, it will only be + // an approximate size of the map; it's good enough for our purposes though, + // and not synchronizing the updates saves us some time + private int approximateSize = 0; + + @Override + public Long put(Class key, Long value) { + // if we have too many elements, delete about 10% of them + // this is expensive, but needs to be done to keep the map bounded + // we also need to randomize the elements we delete: if we remove the same + // elements all the time, we might end up adding them back to the map + // immediately after, and then remove them again, then add them back, etc. + // which will cause this expensive code to be executed too often + if (approximateSize >= MAX_SIZE) { + for (Iterator> it = keySet().iterator(); it.hasNext(); ) { + it.next(); + if (Math.random() < 0.1) { + it.remove(); + } + } + + // get the exact size; another expensive call, but we need to correct + // approximateSize every once in a while, or the difference between + // approximateSize and the actual size might become significant over time; + // the other solution is synchronizing every time we update approximateSize, + // which seems even more expensive + approximateSize = size(); + } + + approximateSize++; + return super.put(key, value); + } + }; + + /** + * Adds a {@link Sampler} that will get run every time an allocation is + * performed from Java code. Use this with extreme judiciousness! + * + * @param sampler The sampler to add. + */ + public static void addSampler(Sampler sampler) { + synchronized (samplerLock) { + Sampler[] samplers = additionalSamplers; + /* create a new list of samplers from the old, adding this sampler */ + if (samplers != null) { + Sampler [] newSamplers = new Sampler[samplers.length + 1]; + System.arraycopy(samplers, 0, newSamplers, 0, samplers.length); + newSamplers[samplers.length] = sampler; + additionalSamplers = newSamplers; + } else { + Sampler[] newSamplers = new Sampler[1]; + newSamplers[0] = sampler; + additionalSamplers = newSamplers; + } + } + } + + /** + * Removes the given {@link Sampler}. + * + * @param sampler The sampler to remove. + */ + public static void removeSampler(Sampler sampler) { + synchronized (samplerLock) { + Sampler[] samplers = additionalSamplers; + List l = Arrays.asList(samplers); + while (l.remove(sampler)); + additionalSamplers = l.toArray(new Sampler[0]); + } + } + + /** + * Returns the size of the given object. If the object is not an array, we + * check the cache first, and update it as necessary. + * + * @param obj the object. + * @param isArray indicates if the given object is an array. + * @return the size of the given object. + */ + private static long getObjectSize(Object obj, boolean isArray) { + if (isArray) { + return instrumentation.getObjectSize(obj); + } + + Class clazz = obj.getClass(); + Long classSize = classSizesMap.get(clazz); + if (classSize == null) { + classSize = instrumentation.getObjectSize(obj); + classSizesMap.put(clazz, classSize); + } + + return classSize; + } + + public static void recordAllocation(Class cls, Object newObj) { + // The use of replace makes calls to this method relatively ridiculously + // expensive. + String typename = cls.getName().replace(".", "/"); + recordAllocation(-1, typename, newObj); + } + + /** + * Records the allocation. This method is invoked on every allocation + * performed by the system. + * + * @param count the count of how many instances are being + * allocated, if an array is being allocated. If an array is not being + * allocated, then this value will be -1. + * @param desc the descriptor of the class/primitive type + * being allocated. + * @param newObj the new Object whose allocation is being + * recorded. + */ + public static void recordAllocation(int count, String desc, Object newObj) { + if (recordingAllocation.get() == Boolean.TRUE) { + return; + } else { + recordingAllocation.set(Boolean.TRUE); + } + + // NB: This could be smaller if the defaultSampler were merged with the + // optional samplers. However, you don't need the optional samplers in + // the common case, so I thought I'd save some space. + if (instrumentation != null) { + // calling getObjectSize() could be expensive, + // so make sure we do it only once per object + long objectSize = -1; + + Sampler[] samplers = additionalSamplers; + if (samplers != null) { + if (objectSize < 0) { + objectSize = getObjectSize(newObj, (count >= 0)); + } + for (Sampler sampler : samplers) { + sampler.sampleAllocation(count, desc, newObj, objectSize); + } + } + } + + recordingAllocation.set(Boolean.FALSE); + } + + /** + * Helper method to force recording; for unit tests only. + */ + public static void recordAllocationForceForTest(int count, String desc, + Object newObj) { + // Make sure we get the right number of elided frames + recordAllocationForceForTestReal(count, desc, newObj, 2); + } + + public static void recordAllocationForceForTestReal( + int count, String desc, Object newObj, int recurse) { + if (recurse != 0) { + recordAllocationForceForTestReal(count, desc, newObj, recurse - 1); + return; + } + } +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorCallback.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorCallback.java new file mode 100644 index 0000000..e9fabd3 --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +/** + * This interface describes a function that is used to sample a + * constructor. It is intended to be invoked every time a constructor + * for class T is invoked. This will not be invoked when subclasses of + * T are instantiated. + * + * This mechanism works independently of whether the class is part of the + * JDK core library. + * + * @param The class that will be sampled with this ConstructorCallback + * + * @author Jeremy Manson + */ +public interface ConstructorCallback { + /** + * When an object implementing interface + * ConstructorCallback is passed to {@link + * com.google.monitoring.runtime.allocation.AllocationInspector# + * addConstructorCallback(Class, ConstructorCallback)}, it will get executed + * whenever a constructor for type T is invoked. + * + * @param newObj the new Object whose construction + * we're recording. The object is not fully constructed; any + * references to this object that are stored in this callback are + * subject to the memory model constraints related to such + * objects. + */ + public void sample(T newObj); +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter.java new file mode 100644 index 0000000..f84e151 --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.LocalVariablesSorter; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Instruments bytecode by inserting a specified call in the + * constructor of a given class. This class is intended to be loaded + * by a javaagent; end-users will want to add {@link ConstructorCallback}s by + * invoking {@link #instrumentClass(Class, ConstructorCallback)}. + * + * @author Jeremy Manson + */ +public class ConstructorInstrumenter implements ClassFileTransformer { + // Implementation details: uses the java.lang.instrument API to + // insert an INVOKESTATIC call to a specified method directly prior to + // constructor return for the given class. + + private static final Logger logger = + Logger.getLogger(ConstructorInstrumenter.class.getName()); + private static ConcurrentHashMap, List>> + samplerMap = + new ConcurrentHashMap, List>>(); + + /** + * We have a read-modify-write operation when doing a put in samplerMap + * (above) and retransforming the class. This lock protects multiple threads + * from performing that operation concurrently. + */ + private static final Object samplerPutAtomicityLock = new Object(); + + // Only for package access (specifically, AllocationInstrumenter) + ConstructorInstrumenter() { } + + /** + * Ensures that the given sampler will be invoked every time a constructor + * for class c is invoked. + * + * @param c The class to be tracked + * @param sampler the code to be invoked when an instance of c is constructed + */ + public static void instrumentClass(Class c, ConstructorCallback sampler) + throws UnmodifiableClassException { + // IMPORTANT: Don't forget that other threads may be accessing this + // class while this code is running. Specifically, the class may be + // executed directly after the retransformClasses is called. Thus, we need + // to be careful about what happens after the retransformClasses call. + synchronized (samplerPutAtomicityLock) { + List> list = samplerMap.get(c); + if (list == null) { + CopyOnWriteArrayList> samplerList = + new CopyOnWriteArrayList>(); + samplerList.add(sampler); + samplerMap.put(c, samplerList); + Instrumentation inst = AllocationRecorder.getInstrumentation(); + Class[] cs = new Class[1]; + cs[0] = c; + inst.retransformClasses(c); + } else { + list.add(sampler); + } + } + } + + /** + * {@inheritDoc} + */ + @Override public byte[] transform( + ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if ((classBeingRedefined == null) || + (!samplerMap.containsKey(classBeingRedefined))) { + return null; + } + if (!AllocationInstrumenter.canRewriteClass(className, loader)) { + throw new RuntimeException( + new UnmodifiableClassException("cannot instrument " + className)); + } + return instrument(classfileBuffer, classBeingRedefined); + } + + /** + * Given the bytes representing a class, add invocations of the + * ConstructorCallback method to the constructor. + * + * @param originalBytes the original byte[] code. + * @return the instrumented byte[] code. + */ + public static byte[] instrument( + byte[] originalBytes, Class classBeingRedefined) { + try { + ClassReader cr = new ClassReader(originalBytes); + ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); + VerifyingClassAdapter vcw = + new VerifyingClassAdapter(cw, originalBytes, cr.getClassName()); + ClassVisitor adapter = + new ConstructorClassAdapter(vcw, classBeingRedefined); + + cr.accept(adapter, ClassReader.SKIP_FRAMES); + + return vcw.toByteArray(); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Failed to instrument class.", e); + throw e; + } catch (Error e) { + logger.log(Level.WARNING, "Failed to instrument class.", e); + throw e; + } + } + + /** + * The per-method transformations to make. Really only affects the + * methods. + */ + static class ConstructorMethodAdapter extends MethodVisitor { + /** + * The LocalVariablesSorter used in this adapter. Lame that it's public but + * the ASM architecture requires setting it from the outside after this + * AllocationMethodAdapter is fully constructed and the LocalVariablesSorter + * constructor requires a reference to this adapter. The only setter of + * this should be AllocationClassAdapter.visitMethod(). + */ + public LocalVariablesSorter lvs = null; + Class cl; + ConstructorMethodAdapter(MethodVisitor mv, Class cl) { + super(Opcodes.ASM4, mv); + this.cl = cl; + } + + /** + * Inserts the appropriate INVOKESTATIC call + */ + @Override public void visitInsn(int opcode) { + if ((opcode == Opcodes.ARETURN) || + (opcode == Opcodes.IRETURN) || + (opcode == Opcodes.LRETURN) || + (opcode == Opcodes.FRETURN) || + (opcode == Opcodes.DRETURN)) { + throw new RuntimeException(new UnmodifiableClassException( + "Constructors are supposed to return void")); + } + if (opcode == Opcodes.RETURN) { + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + "com/google/monitoring/runtime/instrumentation/ConstructorInstrumenter", + "invokeSamplers", + "(Ljava/lang/Object;)V"); + } + super.visitInsn(opcode); + } + } + + /** + * Bytecode is rewritten to invoke this method; it calls the sampler for + * the given class. Note that it won't do anything if o is a subclass + * of the class that was supposed to be tracked. + */ + @SuppressWarnings("unchecked") + public static void invokeSamplers(Object o) { + List> samplers = samplerMap.get(o.getClass()); + if (samplers != null) { + for (@SuppressWarnings("rawtypes") ConstructorCallback sampler : samplers) { + sampler.sample(o); + } + } + } + + /** + * The class that deals with per-class transformations. Basically, invokes + * the per-method transformer above if the method is an {@code } method. + */ + static class ConstructorClassAdapter extends ClassVisitor { + Class cl; + public ConstructorClassAdapter(ClassVisitor cv, Class cl) { + super(Opcodes.ASM4, cv); + this.cl = cl; + } + + /** + * For each method in the class being instrumented, + * visitMethod is called and the returned + * MethodVisitor is used to visit the method. Note that a new + * MethodVisitor is constructed for each method. + */ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + MethodVisitor mv = + cv.visitMethod(access, name, desc, signature, exceptions); + + if ((mv != null) && "".equals(name)){ + ConstructorMethodAdapter aimv = new ConstructorMethodAdapter(mv, cl); + LocalVariablesSorter lvs = new LocalVariablesSorter(access, desc, aimv); + aimv.lvs = lvs; + mv = lvs; + } + return mv; + } + } +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/Sampler.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/Sampler.java new file mode 100644 index 0000000..35b0a50 --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/Sampler.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +/** + * This interface describes a function that is used to sample an allocation. + * + * @author jeremymanson@google.com (Jeremy Manson) + */ +public interface Sampler { + /** + * Determines whether the object currently being allocated, with the given + * size, should be traced. + * + * CAUTION: DO NOT DO ALLOCATION IN THIS METHOD WITHOUT ENSURING THAT + * THE SAMPLER WILL NOT BE INVOKED ON THE RESULTING ALLOCATION. + * Otherwise, you will get an infinite regress of calls to the sampler. + * + * @param count the int count of how many instances are being + * allocated. -1 means a simple new to distinguish from a 1-element array. 0 + * shows up as a value here sometimes; one reason is T[] toArray()-type + * methods that require an array type argument (see ArrayList.toArray() for + * example). + * @param desc the String descriptor of the class/primitive type + * being allocated. + * @param newObj the new Object whose allocation we're + * recording. + * @param size the size of the object being allocated. + */ + public void sampleAllocation(int count, String desc, + Object newObj, long size); +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/StaticClassWriter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/StaticClassWriter.java new file mode 100644 index 0000000..1e26d4c --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/StaticClassWriter.java @@ -0,0 +1,273 @@ +/*** + * ASM tests + * Copyright (c) 2002-2005 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. + */ + +// Portions Copyright 2011 Google, Inc. +// +// This is an extracted version of the ClassInfo and ClassWriter +// portions of ClassWriterComputeFramesTest in the set of ASM tests. +// We have done a fair bit of rewriting for readability, and changed +// the comments. The original author is Eric Bruneton. + + +package com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.io.InputStream; +import java.io.IOException; + +/** + * A {@link ClassWriter} that looks for static class data in the + * classpath when the classes are not available at runtime. + * + *

ClassWriter uses class hierarchy information, which it gets by + * looking at loaded classes, to make some decisions about the best + * way to write classes. The problem with this is that it fails if + * the superclass hasn't been loaded yet. StaticClassWriter fails + * over to looking for the class hierarchy information in the + * ClassLoader's resources (usually the classpath) if the class it + * needs hasn't been loaded yet. + * + *

This class was heavily influenced by ASM's + * org.objectweb.asm.util.ClassWriterComputeFramesTest, which contains + * the same logic in a subclass. The code here has been slightly + * cleaned up for readability. + * + * @author jeremymanson@google.com (Jeremy Manson) + */ +class StaticClassWriter extends ClassWriter { + + /* The classloader that we use to look for the unloaded class */ + private final ClassLoader classLoader; + + /** + * {@inheritDoc} + * @param classLoader the class loader that loaded this class + */ + public StaticClassWriter( + ClassReader classReader, int flags, ClassLoader classLoader) { + super(classReader, flags); + this.classLoader = classLoader; + } + + /** + * {@inheritDoc} + */ + @Override protected String getCommonSuperClass( + final String type1, final String type2) { + try { + return super.getCommonSuperClass(type1, type2); + } catch (Throwable e) { + // Try something else... + } + // Exactly the same as in ClassWriter, but gets the superclass + // directly from the class file. + ClassInfo ci1, ci2; + try { + ci1 = new ClassInfo(type1, classLoader); + ci2 = new ClassInfo(type2, classLoader); + } catch (Throwable e) { + throw new RuntimeException(e); + } + if (ci1.isAssignableFrom(ci2)) { + return type1; + } + if (ci2.isAssignableFrom(ci1)) { + return type2; + } + if (ci1.isInterface() || ci2.isInterface()) { + return "java/lang/Object"; + } + + do { + // Should never be null, because if ci1 were the Object class + // or an interface, it would have been caught above. + ci1 = ci1.getSuperclass(); + } while (!ci1.isAssignableFrom(ci2)); + return ci1.getType().getInternalName(); + } + + /** + * For a given class, this stores the information needed by the + * getCommonSuperClass test. This determines if the class is + * available at runtime, and then, if it isn't, it tries to get the + * class file, and extract the appropriate information from that. + */ + static class ClassInfo { + + private final Type type; + private final ClassLoader loader; + private final boolean isInterface; + private final String superClass; + private final String[] interfaces; + + public ClassInfo(String type, ClassLoader loader) { + Class cls = null; + // First, see if we can extract the information from the class... + try { + cls = Class.forName(type); + } catch (Exception e) { + // failover... + } + + if (cls != null) { + this.type = Type.getType(cls); + this.loader = loader; + this.isInterface = cls.isInterface(); + this.superClass = cls.getSuperclass().getName(); + Class[] ifs = cls.getInterfaces(); + this.interfaces = new String[ifs.length]; + for (int i = 0; i < ifs.length; i++) { + this.interfaces[i] = ifs[i].getName(); + } + return; + } + + // The class isn't loaded. Try to get the class file, and + // extract the information from that. + this.loader = loader; + this.type = Type.getObjectType(type); + String fileName = type.replace('.', '/') + ".class"; + InputStream is = null; + ClassReader cr; + try { + is = (loader == null) ? + ClassLoader.getSystemResourceAsStream(fileName) : + loader.getResourceAsStream(fileName); + cr = new ClassReader(is); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (Exception e) { + } + } + } + + int offset = cr.header; + isInterface = (cr.readUnsignedShort(offset) & Opcodes.ACC_INTERFACE) != 0; + char[] buf = new char[2048]; + + // Read the superclass + offset += 4; + superClass = readConstantPoolString(cr, offset, buf); + + // Read the interfaces + offset += 2; + int numInterfaces = cr.readUnsignedShort(offset); + interfaces = new String[numInterfaces]; + offset += 2; + for (int i = 0; i < numInterfaces; i++) { + interfaces[i] = readConstantPoolString(cr, offset, buf); + offset += 2; + } + } + + String readConstantPoolString(ClassReader cr, int offset, char[] buf) { + int cpIndex = cr.getItem(cr.readUnsignedShort(offset)); + if (cpIndex == 0) { + return null; + // throw new RuntimeException("Bad constant pool index"); + } + return cr.readUTF8(cpIndex, buf); + } + + Type getType() { + return type; + } + + ClassInfo getSuperclass() { + if (superClass == null) { + return null; + } + return new ClassInfo(superClass, loader); + } + + /** + * Same as {@link Class#getInterfaces()} + */ + ClassInfo[] getInterfaces() { + if (interfaces == null) { + return new ClassInfo[0]; + } + ClassInfo[] result = new ClassInfo[interfaces.length]; + for (int i = 0; i < result.length; ++i) { + result[i] = new ClassInfo(interfaces[i], loader); + } + return result; + } + + /** + * Same as {@link Class#isInterface} + */ + boolean isInterface() { + return isInterface; + } + + private boolean implementsInterface(ClassInfo that) { + for (ClassInfo c = this; c != null; c = c.getSuperclass()) { + for (ClassInfo iface : c.getInterfaces()) { + if (iface.type.equals(that.type) || + iface.implementsInterface(that)) { + return true; + } + } + } + return false; + } + + private boolean isSubclassOf(ClassInfo that) { + for (ClassInfo ci = this; ci != null; ci = ci.getSuperclass()) { + if (ci.getSuperclass() != null && + ci.getSuperclass().type.equals(that.type)) { + return true; + } + } + return false; + } + + /** + * Same as {@link Class#isAssignableFrom(Class)} + */ + boolean isAssignableFrom(ClassInfo that) { + return (this == that || + that.isSubclassOf(this) || + that.implementsInterface(this) || + (that.isInterface() + && getType().getDescriptor().equals("Ljava/lang/Object;"))); + } + } + +} diff --git a/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/VerifyingClassAdapter.java b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/VerifyingClassAdapter.java new file mode 100644 index 0000000..8e5ca6c --- /dev/null +++ b/java-allocation-instrumenter/src/main/java/com/google/monitoring/runtime/instrumentation/VerifyingClassAdapter.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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 com.google.monitoring.runtime.instrumentation; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.CodeSizeEvaluator; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is a class writer that gets used in place of the existing + * {@link ClassWriter}, and verifies properties of the class getting written. + * + * Currently, it only checks to see if the methods are of the correct length + * for Java methods (<64K). + * + * @author jeremymanson@google.com (Jeremy Manson) + */ +public class VerifyingClassAdapter extends ClassVisitor { + private static final Logger logger = + Logger.getLogger(VerifyingClassAdapter.class.getName()); + + /** + * An enum which indicates whether the class in question is verified. + */ + public enum State { + PASS, UNKNOWN, FAIL_TOO_LONG; + } + + final ClassWriter cw; + final byte [] original; + final String className; + String message; + State state; + + /** + * @param cw A class writer that is wrapped by this class adapter + * @param original the original bytecode + * @param className the name of the class being examined. + */ + public VerifyingClassAdapter(ClassWriter cw, byte [] original, + String className) { + super(Opcodes.ASM4, cw); + state = State.UNKNOWN; + message = "The class has not finished being examined"; + this.cw = cw; + this.original = original; + this.className = className.replace('/', '.'); + } + + /** + * {@inheritDoc} + * + * In addition, the returned {@link MethodVisitor} will throw an exception + * if the method is greater than 64K in length. + */ + @Override + public MethodVisitor visitMethod( + final int access, + final String name, + final String desc, + final String signature, + final String[] exceptions) { + MethodVisitor mv = + super.visitMethod(access, name, desc, signature, exceptions); + return new CodeSizeEvaluator(mv) { + @Override + public void visitEnd() { + super.visitEnd(); + if (getMaxSize() > 64 * 1024) { + state = State.FAIL_TOO_LONG; + message = "the method " + name + " was too long."; + } + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + public void visitEnd() { + super.visitEnd(); + if (state == State.UNKNOWN) { + state = State.PASS; + } + } + + /** + * Gets the verification state of this class. + * + * @return true iff the class passed inspection. + */ + public boolean isVerified() { + return state == State.PASS; + } + + /** + * Returns the byte array that contains the byte code for this class. + * + * @return a byte array. + */ + public byte[] toByteArray() { + if (state != State.PASS) { + logger.log(Level.WARNING, + "Failed to instrument class " + className + " because " + message); + return original; + } + return cw.toByteArray(); + } +} -- cgit v1.1