├── .gitignore
├── LICENSE.txt
├── README.md
├── acceptance-test
└── TraceTarget.java
├── project
├── Build.scala
├── build.properties
└── plugins.sbt
└── src
├── main
├── java
│ └── com
│ │ └── github
│ │ └── zhongl
│ │ └── housemd
│ │ ├── command
│ │ ├── Env.java
│ │ └── Inspect.java
│ │ └── duck
│ │ └── Duck.java
└── scala
│ └── com
│ └── github
│ └── zhongl
│ └── housemd
│ ├── command
│ ├── ClassMemberCompleter.scala
│ ├── ClassSimpleName.scala
│ ├── Last.scala
│ ├── Loaded.scala
│ ├── MethodFilter.scala
│ ├── Prop.scala
│ ├── Resources.scala
│ ├── Trace.scala
│ └── TransformCommand.scala
│ ├── duck
│ └── Telephone.scala
│ ├── house
│ ├── House.scala
│ ├── Mobilephone.scala
│ └── Signal.scala
│ ├── instrument
│ ├── Advice.java
│ ├── ClassDecorator.scala
│ ├── Context.scala
│ ├── Filter.scala
│ ├── Hook.scala
│ ├── Seconds.scala
│ └── Transform.scala
│ └── misc
│ ├── ObjUtils.scala
│ ├── ReflectionUtils.scala
│ └── Utils.scala
└── test
├── resources
└── res.xml
└── scala
└── com
└── github
└── zhongl
├── housemd
├── command
│ ├── AdviceReflection.scala
│ ├── ClassSimpleNameCompleterSpec.scala
│ ├── EnvSpec.scala
│ ├── InspectSpec.scala
│ ├── LastSpec.scala
│ ├── LoadedSpec.scala
│ ├── MethodFilterCompleterSpec.scala
│ ├── MethodFilterSpec.scala
│ ├── PropSpec.scala
│ ├── ResourcesSpec.scala
│ ├── TraceSpec.scala
│ └── TransformCommandSpec.scala
└── misc
│ ├── ObjUtilsSpec.scala
│ ├── ReflectionUtilsSpec.scala
│ └── UtilsSpec.scala
└── test
├── A.scala
├── F.scala
├── G.scala
└── I.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .idea/
3 | *.iml
4 | .classpath
5 | .project
6 | .settings/
7 | .cache
8 | .#*
9 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sorry guys, HouseMD may not be updated any more except merging PR.
2 |
3 | # I highly recommend [Grey anatomy](https://github.com/oldmanpushcart/greys-anatomy) If you are looking for a tool like HouseMD.
4 |
5 | [README moved here.](https://github.com/CSUG/HouseMD/wiki/readme)
6 |
--------------------------------------------------------------------------------
/acceptance-test/TraceTarget.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import java.io.ByteArrayOutputStream;
18 | import java.io.InputStream;
19 |
20 | public class TraceTarget {
21 |
22 | public static void main(String[] args) throws Exception {
23 | Object o = loadClass();
24 |
25 | while (true) {
26 | addOne(0);
27 | o.getClass().getMethod("m", String.class).invoke(o, (String) null);
28 | new E(1);
29 | try {
30 | Thread.sleep(500L);
31 | } catch (Exception e) {
32 | break;
33 | }
34 | }
35 | }
36 |
37 | private static Object loadClass() throws Exception {
38 | return Class.forName("TraceTarget$A", false, new CL()).getConstructor(String.class).newInstance("123");
39 | }
40 |
41 | public static int addOne(int i) {
42 | return i + 1;
43 | }
44 |
45 | public static class CL extends ClassLoader {
46 | @Override
47 | public Class> loadClass(String name) throws ClassNotFoundException {
48 | Class> aClass = findLoadedClass(name);
49 | if (aClass != null) return aClass;
50 | if (name.startsWith("java") || name.startsWith("com.sun") || name.startsWith("sun")) {
51 | return getParent().loadClass(name);
52 | } else {
53 | try {
54 | InputStream inputStream = getResourceAsStream(name + ".class");
55 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
56 | int read = 0;
57 | while ((read = inputStream.read()) > -1) outputStream.write(read);
58 | byte[] bytes = outputStream.toByteArray();
59 | return defineClass(name, bytes, 0, bytes.length);
60 | } catch (Exception e) {
61 | throw new ClassNotFoundException(name, e);
62 | }
63 | }
64 | }
65 |
66 | @Override
67 | public String toString() {
68 | return "CL";
69 | }
70 | }
71 |
72 | public static class A {
73 | static {
74 | System.out.println(A.class.getClassLoader());
75 | }
76 |
77 | public final String s;
78 | public final B b = new B();
79 |
80 | public A(String s) {
81 | this.s = s;
82 | }
83 |
84 | public void m(int i, String s) {
85 | }
86 |
87 | public final void m(String s) {
88 | b.mD1(1);
89 | b.mC(s);
90 | b.mD2(1, 2);
91 | }
92 | }
93 |
94 | public final static class B extends D implements C {
95 | private String s;
96 | private int i;
97 |
98 | public void mC(String s) {
99 | this.s = s;
100 | }
101 |
102 | public void mD2(int i, int j) {
103 | this.i = i;
104 | }
105 | }
106 |
107 | public interface C {
108 | void mC(String s);
109 | }
110 |
111 | public static abstract class D {
112 | public void mD1(int i) {
113 | }
114 |
115 | public abstract void mD2(int i, int j);
116 | }
117 |
118 | public static class E {
119 | public E(int i) {}
120 | }
121 | }
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import sbt._
18 | import sbt.Keys._
19 | import ProguardPlugin._
20 |
21 | object Build extends sbt.Build {
22 |
23 | import Dependencies._
24 | import Unmanaged._
25 |
26 | val VERSION = "0.2.7"
27 | val javaHome = sys.props("java.home").replace("/jre", "")
28 |
29 | lazy val proguard = proguardSettings ++ Seq(
30 | proguardOptions := Seq(
31 | "-keepclasseswithmembers public class * { public static void main(java.lang.String[]); }",
32 | "-keep class * implements org.xml.sax.EntityResolver",
33 | "-keep class com.github.zhongl.housemd.** { *;} ",
34 | "-keep class com.github.zhongl.yascli.** { *;} ",
35 | "-keepclassmembers class * { ** MODULE$;}",
36 | """-keepclassmembernames class scala.concurrent.forkjoin.ForkJoinPool {
37 | long eventCount;
38 | int workerCounts;
39 | int runControl;
40 | scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode syncStack;
41 | scala.concurrent.forkjoin.ForkJoinPool$WaitQueueNode spareStack;
42 | }""",
43 | """-keepclassmembernames class scala.concurrent.forkjoin.ForkJoinWorkerThread {
44 | int base;
45 | int sp;
46 | int runState;
47 | }""",
48 | "-keepclassmembernames class scala.concurrent.forkjoin.ForkJoinTask { int status; }",
49 | """-keepclassmembernames class scala.concurrent.forkjoin.LinkedTransferQueue {
50 | scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference head;
51 | scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference tail;
52 | scala.concurrent.forkjoin.LinkedTransferQueue$PaddedAtomicReference cleanMe;
53 | }"""),
54 | proguardLibraryJars := {
55 | (jdkJarPath: PathFinder).get
56 | }
57 | )
58 |
59 | private def jdkJarPath = {
60 | val home = new java.io.File(sys.props("java.home"))
61 | val rtJar = home / "lib" / "rt.jar"
62 | val toolsJar = home.getParentFile / "lib" / "tools.jar"
63 | val classesJar = home.getParentFile / "Classes" / "classes.jar"
64 | if (classesJar.asFile.exists()) // it means current os is Mac OSX
65 | Seq(classesJar.asFile)
66 | else if (rtJar.asFile.exists())
67 | Seq(rtJar.asFile, toolsJar.asFile)
68 | else
69 | throw new IllegalStateException("Unknown location for rt.jar")
70 | }
71 |
72 | lazy val root = Project(
73 | id = "housemd",
74 | base = file("."),
75 | settings = Defaults.defaultSettings ++ classpathSettings ++ proguard ++ Seq(
76 | name := "housemd",
77 | organization := "com.github.zhongl",
78 | version := VERSION,
79 | scalaVersion := "2.9.2",
80 | scalacOptions ++= Seq("-unchecked", "-deprecation"),
81 | resolvers += "Local Maven Repository" at "file://" + Path.userHome.absolutePath + "/.m2/repository",
82 | libraryDependencies := compileLibs ++ testLibs,
83 | packageOptions += Package.ManifestAttributes(
84 | ("Main-Class", "com.github.zhongl.housemd.house.House"),
85 | ("Agent-Class", "com.github.zhongl.housemd.duck.Duck"),
86 | ("Can-Retransform-Classes", "true"),
87 | ("Can-Redefine-Classes", "true"),
88 | ("Signature-Version", VERSION)
89 | ),
90 | parallelExecution in Test := false
91 | )
92 | )
93 |
94 | object Dependencies {
95 | lazy val testLibs = Seq(
96 | "org.mockito" % "mockito-all" % "1.9.0" % "test",
97 | "org.scalatest" %% "scalatest" % "1.7.2" % "test"
98 | )
99 |
100 | lazy val compileLibs = Seq(
101 | "asm" % "asm" % "3.3.1",
102 | "asm" % "asm-commons" % "3.3.1",
103 | "com.github.zhongl" %% "yascli" % "0.1.0",
104 | "org.scala-lang" % "scala-library" % "2.9.2",
105 | "com.cedarsoftware" % "json-io" % "2.7.3"
106 | )
107 | }
108 |
109 | object Unmanaged {
110 | lazy val toolsFile = file(javaHome + "/lib/tools.jar")
111 | lazy val classpathSettings =
112 | if (toolsFile.exists()) Seq(
113 | unmanagedClasspath in Compile += Attributed.blank(toolsFile),
114 | unmanagedClasspath in Test <<= unmanagedClasspath in Compile
115 | )
116 | else Seq()
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.12.4
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | //addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.0.0")
2 |
3 | //addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0-RC1")
4 |
5 | resolvers += Resolver.url("sbt-plugin-releases-scalasbt", url("http://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/"))(Resolver.ivyStylePatterns)
6 |
7 | addSbtPlugin("org.scala-sbt" % "xsbt-proguard-plugin" % "0.1.3")
8 |
9 | resolvers += Resolver.url("sbt-plugin-releases",
10 | new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases/"))(Resolver.ivyStylePatterns)
11 |
12 | resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
13 |
--------------------------------------------------------------------------------
/src/main/java/com/github/zhongl/housemd/command/Env.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command;
18 |
19 | import com.github.zhongl.yascli.Command;
20 | import com.github.zhongl.yascli.PrintOut;
21 | import jline.console.completer.Completer;
22 | import scala.Function0;
23 |
24 | import java.util.*;
25 |
26 | import static com.github.zhongl.yascli.JavaConvertions.*;
27 |
28 |
29 | /**
30 | * @author zhongl
31 | */
32 | public class Env extends Command implements Completer {
33 |
34 | private final Function0 regexable = flag(list("-e", "--regex"), "enable name as regex pattern.");
35 |
36 | private final Function0 keyName = parameter("name", "system env key name.", none(String.class), manifest(String.class), defaultConverter());
37 |
38 | public Env(PrintOut out) {
39 | super("env", "display system env.", out);
40 | }
41 |
42 | @Override
43 | public void run() {
44 | if (is(regexable))
45 | listEnvMatchs(get(keyName));
46 | else
47 | printEnvEquals(get(keyName));
48 | }
49 |
50 | private void printEnvEquals(String key) {
51 | String value = System.getenv(key);
52 | if (value == null) println("Invalid key " + key);
53 | else println(key + " = " + value);
54 | }
55 |
56 | private void listEnvMatchs(String regex) {
57 | SortedMap sortedMap = new TreeMap();
58 | int maxKeyLength = 0;
59 | for (String key : System.getenv().keySet()) {
60 | if (!key.matches(regex)) continue;
61 | maxKeyLength = Math.max(maxKeyLength, key.length());
62 | sortedMap.put(key, System.getenv(key));
63 | }
64 |
65 | if (sortedMap.isEmpty()) return;
66 |
67 | String format = "%1$-" + maxKeyLength + "s = %2$s";
68 | for (String key : sortedMap.keySet())
69 | println(String.format(format, key, sortedMap.get(key)));
70 | }
71 |
72 | @Override
73 | public int complete(String buffer, int cursor, List candidates) {
74 | Map env = System.getenv();
75 | Set keys = env.keySet();
76 |
77 | TreeSet sortedKeySet = new TreeSet(keys);
78 | SortedSet tail = sortedKeySet.tailSet(buffer);
79 | for (String k : tail) {
80 | if (k.startsWith(buffer)) candidates.add(k);
81 | }
82 | if (candidates.isEmpty()) return -1;
83 | return cursor - buffer.length();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/github/zhongl/housemd/command/Inspect.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command;
18 |
19 | import com.github.zhongl.housemd.instrument.Context;
20 | import com.github.zhongl.housemd.instrument.Hook;
21 | import com.github.zhongl.yascli.PrintOut;
22 | import scala.Function0;
23 | import scala.runtime.AbstractFunction1;
24 |
25 | import java.lang.instrument.Instrumentation;
26 | import java.lang.reflect.Field;
27 | import java.util.HashSet;
28 | import java.util.List;
29 | import java.util.Set;
30 |
31 | import static com.github.zhongl.housemd.misc.ReflectionUtils.simpleNameOf;
32 | import static com.github.zhongl.yascli.JavaConvertions.manifest;
33 | import static com.github.zhongl.yascli.JavaConvertions.none;
34 |
35 | /**
36 | * @author zhongl
37 | */
38 | public class Inspect extends TransformCommand implements FieldFilterCompleter {
39 |
40 | private final Function0 fieldFilter = parameter(
41 | "field-filter",
42 | "field filter pattern like \"ClassSimpleName.fieldName\".",
43 | none(FieldFilter.class),
44 | manifest(FieldFilter.class),
45 | new AbstractFunction1() {
46 | @Override
47 | public FieldFilter apply(String value) {
48 | return new FieldFilter(value);
49 | }
50 | });
51 |
52 | private final Instrumentation inst;
53 |
54 | public Inspect(Instrumentation inst, PrintOut out) {
55 | super("inspect", "display fields of a class.", inst, out);
56 | this.inst = inst;
57 | }
58 |
59 | public Instrumentation inst() {
60 | return inst;
61 | }
62 |
63 | @Override
64 | public boolean isCandidate(Class> klass) {
65 | return fieldFilter.apply().filter(klass);
66 | }
67 |
68 | @Override
69 | public boolean isDecorating(Class> klass, String methodName) {
70 | return true;
71 | }
72 |
73 | @Override
74 | public Hook hook() {
75 | return new Hook() {
76 | private final Set targets = new HashSet();
77 | private final FieldFilter accessor = fieldFilter.apply();
78 |
79 | public void enterWith(Context context) {
80 | }
81 |
82 | public void exitWith(Context context) {
83 | targets.add(context.thisObject());
84 | }
85 |
86 | public void heartbeat(long now) {
87 | if (targets.isEmpty())
88 | println("Can't inspect " + accessor + " because there's no invocation on " + accessor.classSimpleName);
89 | else
90 | for (Object target : targets) printStat(target);
91 | println();
92 | }
93 |
94 | private void printStat(Object target) {
95 | try {
96 | println(accessor + " " + accessor.getField(target) + " " + target + " " + target.getClass().getClassLoader());
97 | } catch (Exception e) {
98 | error(e);
99 | }
100 | }
101 |
102 | public void finalize(scala.Option throwable) {
103 | heartbeat(0L); // last print
104 | targets.clear();
105 | }
106 | };
107 | }
108 |
109 | @Override
110 | public int complete(String buffer, int cursor, List candidates) {
111 | return ClassSimpleNameCompleter$class.complete(this, buffer, cursor, candidates);
112 | }
113 |
114 | @Override
115 | public int completeClassSimpleName(String buffer, int cursor, List candidates) {
116 | return ClassMemberCompleter$class.completeClassSimpleName(this, buffer, cursor, candidates);
117 | }
118 |
119 | @Override
120 | public String[] collectLoadedClassNames(String prefix) {
121 | return ClassMemberCompleter$class.collectLoadedClassNames(this, prefix);
122 | }
123 |
124 | @Override
125 | public int completeAll(String classSimpleName, int cursor, List candidates) {
126 | return FieldFilterCompleter$class.completeAll(this, classSimpleName, cursor, candidates);
127 | }
128 |
129 | @Override
130 | public int complete(String simpleName, String prefix, int cursor, List candidates) {
131 | return FieldFilterCompleter$class.complete(this, simpleName, prefix, cursor, candidates);
132 | }
133 |
134 | public int com$github$zhongl$housemd$command$ClassMemberCompleter$$super$completeClassSimpleName(
135 | String buffer, int cursor, List candidates) {
136 | return ClassSimpleNameCompleter$class.completeClassSimpleName(this, buffer, cursor, candidates);
137 | }
138 |
139 | static class FieldFilter {
140 |
141 | private final String classSimpleName;
142 | private final String fieldName;
143 |
144 | public FieldFilter(String value) {
145 | String[] split = value.split("\\.");
146 | if (split.length != 2)
147 | throw new IllegalArgumentException(", it should be \"ClassSimpleName.fieldName\"");
148 | classSimpleName = split[0];
149 | fieldName = split[1];
150 | }
151 |
152 | private boolean filterOnly(String className, String superClassName, String[] interfaceNames) {
153 | if (classSimpleName.endsWith("+")) {
154 | String realname = classSimpleName.substring(0, classSimpleName.length() - 1);
155 | return (simpleNameOf(className).equals(realname) ||
156 | superClassName != null && simpleNameOf(superClassName).equals(realname)) ||
157 | contain(realname, interfaceNames);
158 | } else {
159 | return simpleNameOf(className).equals(classSimpleName);
160 | }
161 | }
162 |
163 | private boolean contain(String realname, String[] interfaceNames) {
164 | for (String interfaceName : interfaceNames) {
165 | if (simpleNameOf(interfaceName).equals(realname)) return true;
166 | }
167 | return false;
168 | }
169 |
170 | public boolean filter(Class> c) {
171 | String superClassName = (c.getSuperclass() == null) ? null : c.getSuperclass().getName();
172 | Class>[] interfaces = c.getInterfaces();
173 | String[] interfaceNames = new String[interfaces.length];
174 | for (int i = 0; i < interfaceNames.length; i++) {
175 | interfaceNames[i] = interfaces[i].getName();
176 | }
177 | return filterOnly(c.getName(), superClassName, interfaceNames);
178 | }
179 |
180 | public Object getField(Object obj) throws Exception {
181 | Field field = obj.getClass().getDeclaredField(fieldName);
182 | field.setAccessible(true);
183 | return field.get(obj);
184 | }
185 |
186 | @Override
187 | public String toString() {
188 | return classSimpleName + "." + fieldName;
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/main/java/com/github/zhongl/housemd/duck/Duck.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.duck;
18 |
19 | import java.io.File;
20 | import java.lang.instrument.Instrumentation;
21 | import java.lang.reflect.Array;
22 | import java.net.URL;
23 | import java.net.URLClassLoader;
24 |
25 | /**
26 | * @author zhongl
27 | */
28 | public class Duck {
29 | public static void agentmain(String arguments, Instrumentation instrumentation) throws Exception {
30 | String[] parts = arguments.split("\\s+", 4);
31 | URL agentJar = new File(parts[0]).toURI().toURL();
32 | String telephoneClassName = parts[1];
33 | int port = Integer.parseInt(parts[2]);
34 |
35 | ClassLoader classLoader = new URLClassLoader(new URL[]{agentJar}) {
36 | @Override
37 | protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
38 | Class> loadedClass = findLoadedClass(name);
39 | if (loadedClass != null) return loadedClass;
40 |
41 | try {
42 | Class> aClass = findClass(name);
43 | if (resolve) resolveClass(aClass);
44 | return aClass;
45 | } catch (Exception e) {
46 | return super.loadClass(name, resolve);
47 | }
48 | }
49 | };
50 |
51 | Class>[] commandClasses = loadClasses(parts[3].split("\\s+"), classLoader);
52 |
53 | Runnable executor = (Runnable) classLoader.loadClass(telephoneClassName)
54 | .getConstructor(Instrumentation.class, int.class, Class[].class)
55 | .newInstance(instrumentation, port, commandClasses);
56 |
57 | Thread thread = new Thread(executor, "HouseMD-Duck");
58 | thread.setDaemon(true);
59 | thread.start();
60 | }
61 |
62 | private static Class>[] loadClasses(String[] classNames, ClassLoader classLoader) throws ClassNotFoundException {
63 | Class>[] classes = (Class>[]) Array.newInstance(Class.class, classNames.length);
64 | for (int i = 0; i < classes.length; i++) {
65 | classes[i] = Class.forName(classNames[i], false, classLoader);
66 | }
67 | return classes;
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/ClassMemberCompleter.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import java.lang.reflect.Field
20 | import java.util.List
21 | import com.github.zhongl.housemd.misc.ReflectionUtils._
22 |
23 | /**
24 | * @author zhongl
25 | */
26 |
27 | trait ClassMemberCompleter extends ClassSimpleNameCompleter {
28 |
29 | override protected def completeClassSimpleName(buffer: String, cursor: Int, candidates: List[CharSequence]) =
30 | buffer.split("\\.") match {
31 | case Array(classSimpleName) if buffer.endsWith(".") => completeAll(classSimpleName, cursor, candidates)
32 | case Array(prefix) => super.completeClassSimpleName(buffer, cursor, candidates)
33 | case Array(classSimpleName, prefix) => complete(classSimpleName, prefix, cursor, candidates)
34 | case _ => -1
35 | }
36 |
37 | override protected def collectLoadedClassNames(prefix: String) = inst.getAllLoadedClasses collect {
38 | case c@ClassSimpleName(n) if n.startsWith(prefix) && isNotConcrete(c) => n + "+"
39 | case ClassSimpleName(n) if n.startsWith(prefix) => n
40 | }
41 |
42 | protected def completeAll(buffer: String, cursor: Int, candidates: List[CharSequence]): Int
43 |
44 | protected def complete(simpleName: String, member: String, cursor: Int, candidates: List[CharSequence]): Int
45 |
46 | }
47 |
48 | trait MethodFilterCompleter extends ClassMemberCompleter {
49 |
50 | private def allDeclaredMethodsOf(classSimpleName: String)(collect: Array[String] => Array[String]) =
51 | inst.getAllLoadedClasses collect {
52 | case c@ClassSimpleName(n) if (n == classSimpleName || n + "+" == classSimpleName) => collect(constructorAndMethodNamesOf(c))
53 | }
54 |
55 | override protected def completeAll(classSimpleName: String, cursor: Int, candidates: List[CharSequence]) =
56 | allDeclaredMethodsOf(classSimpleName) { a => a } match {
57 | case Array() => -1
58 | case all => all.flatten.sorted foreach { candidates.add }; cursor
59 | }
60 |
61 | override protected def complete(simpleName: String, prefix: String, cursor: Int, candidates: List[CharSequence]) =
62 | allDeclaredMethodsOf(simpleName) { _ collect { case m if m.startsWith(prefix) => m } } match {
63 | case Array() => -1
64 | case all => all.flatten.sorted foreach { candidates.add }; cursor - prefix.length
65 | }
66 | }
67 |
68 | // FIXME: reduce duplication in MemberFilterCompleter
69 | trait FieldFilterCompleter extends ClassMemberCompleter {
70 |
71 | private def allDeclaredFieldsOf(classSimpleName: String)(collect: Array[Field] => Array[String]) =
72 | inst.getAllLoadedClasses collect {
73 | case c@ClassSimpleName(n) if (n == classSimpleName || n + "+" == classSimpleName) => collect(c.getDeclaredFields)
74 | }
75 |
76 | override protected def completeAll(classSimpleName: String, cursor: Int, candidates: List[CharSequence]) =
77 | allDeclaredFieldsOf(classSimpleName) { _ map { _.getName } } match {
78 | case Array() => -1
79 | case all => all.flatten.sorted foreach { candidates.add }; cursor
80 | }
81 |
82 | override protected def complete(simpleName: String, prefix: String, cursor: Int, candidates: List[CharSequence]) =
83 | allDeclaredFieldsOf(simpleName) { _ collect { case f if f.getName.startsWith(prefix) => f.getName } } match {
84 | case Array() => -1
85 | case all => all.flatten.sorted foreach { candidates.add }; cursor - prefix.length
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/ClassSimpleName.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import com.github.zhongl.housemd.misc.ReflectionUtils._
4 | import jline.console.completer.Completer
5 | import instrument.Instrumentation
6 |
7 | /**
8 | * @author zhongl
9 | */
10 | object ClassSimpleName {
11 | def unapply(c: Class[_]) = Some(simpleNameOf(c))
12 | }
13 |
14 | trait ClassSimpleNameCompleter extends Completer {
15 | val inst: Instrumentation
16 |
17 | def complete(buffer: String, cursor: Int, candidates: java.util.List[CharSequence]) = buffer.split("\\s+") match {
18 | case Array() => -1
19 | case all => completeClassSimpleName(all.last, cursor, candidates)
20 | }
21 |
22 | protected def completeClassSimpleName(prefix: String, cursor: Int, candidates: java.util.List[CharSequence]): Int =
23 | collectLoadedClassNames(prefix) match {
24 | case Array() => -1
25 | case all => all.distinct.sorted foreach {candidates.add}; cursor - prefix.length
26 | }
27 |
28 | protected def collectLoadedClassNames(prefix: String) = inst.getAllLoadedClasses collect {
29 | case ClassSimpleName(n) if n.startsWith(prefix) => n
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/Last.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import com.github.zhongl.yascli.{PrintOut, Command}
4 |
5 | /**
6 | * @author zhongl
7 | */
8 | class Last(out: PrintOut) extends Command("last","show exception stack trace of last error.", out) {
9 |
10 | private var stackTraces:Array[StackTraceElement] = _
11 |
12 | def keep(throwable: Throwable) {
13 | stackTraces = throwable.getStackTrace
14 | }
15 |
16 |
17 | def run() {
18 | if (stackTraces != null) stackTraces foreach { s => println("\t" + s) }
19 | else println("There is no error.")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/Loaded.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import instrument.Instrumentation
20 | import scala.annotation.tailrec
21 | import com.github.zhongl.yascli.{Command, PrintOut}
22 | import com.github.zhongl.housemd.misc.ReflectionUtils._
23 | import com.github.zhongl.housemd.misc.Utils._
24 |
25 |
26 | class Loaded(val inst: Instrumentation, out: PrintOut)
27 | extends Command("loaded", "display loaded classes information.", out)
28 | with ClassSimpleNameCompleter {
29 |
30 | private val tab = "\t"
31 |
32 | private val hierarchyable = flag("-h" :: "--classloader-hierarchies" :: Nil, "display classloader hierarchies of loaded class.")
33 | private val classSimpleName = parameter[String]("name", "class name without package name.")
34 |
35 | override def run() {
36 | val k = classSimpleName()
37 | val matched = inst.getAllLoadedClasses filter { simpleNameOf(_) == k }
38 | if (matched.isEmpty) println("No matched class")
39 | else matched foreach {
40 | c =>
41 | println(c.getName + " -> " + sourceOf(Manifest.classType(c)))
42 | if (hierarchyable()) layout(Option(c.getClassLoader))
43 | }
44 | }
45 |
46 | @tailrec
47 | private def layout(cl: Option[ClassLoader], lastIndents: String = "- ") {
48 | cl match {
49 | case Some(loader) =>
50 | val indents = tab + lastIndents
51 | println(indents + getOrForceToNativeString(loader))
52 | layout(Option(loader.getParent), indents)
53 | case None =>
54 | }
55 | }
56 |
57 | }
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/MethodFilter.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import com.github.zhongl.housemd.misc.ReflectionUtils._
20 | import java.lang.reflect.Type
21 |
22 |
23 | /**
24 | * @author zhongl
25 | */
26 | class MethodFilter(classSimpleName: String, methodName: String = "*") {
27 | implicit val type2Class = (_: Type).asInstanceOf[Class[_]]
28 |
29 | def filter(c: Class[_], methodName: String): Boolean = lazyFilter(c)(methodName)
30 |
31 | def filter(c: Class[_]): Boolean = {
32 | if (!filterOnly(c)) false
33 | else {
34 | val filter = lazyFilter(true)(_)
35 | constructorAndMethodNamesOf(c).find { m => filter(m) }.isDefined
36 | }
37 | }
38 |
39 | private def lazyFilter(cond: => Boolean)(m: String) = if (cond) {methodName == "*" || methodName == m } else false
40 |
41 | private def lazyFilter(c: Class[_]): (String => Boolean) = lazyFilter(filterOnly(c))
42 |
43 | private def filterOnly(className: String, superClassName: String, interfaceNames: Array[String]): Boolean = {
44 | if (classSimpleName.endsWith("+")) {
45 | val realname = classSimpleName.dropRight(1)
46 | (simpleNameOf(className) == realname ||
47 | superClassName != null && simpleNameOf(superClassName) == realname) ||
48 | interfaceNames.find(simpleNameOf(_) == realname).isDefined
49 | } else {
50 | simpleNameOf(className) == classSimpleName
51 | }
52 | }
53 |
54 | private def filterOnly(c: Class[_]): Boolean = {
55 | val superClassName = if (c.getSuperclass == null) null else c.getSuperclass.getName
56 | val interfaceNames = c.getInterfaces.map(_.getName)
57 | filterOnly(c.getName, superClassName, interfaceNames)
58 | }
59 |
60 | }
61 |
62 | object MethodFilter {
63 |
64 | implicit def apply(s: String) = {
65 | s.split("\\.") match {
66 | case Array(classSimpleName, methodName) => new MethodFilter(classSimpleName, methodName)
67 | case Array(classSimpleName) => new MethodFilter(classSimpleName)
68 | case _ =>
69 | throw new IllegalArgumentException(", it should be \"ClassSimpleName.methodName\" or \"ClassSimpleName\"")
70 | }
71 | }
72 |
73 | implicit val string2MethodFilters = (_: String).split("\\s+") map { apply }
74 | }
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/Prop.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import com.github.zhongl.yascli.{Command, PrintOut}
4 | import jline.console.completer.Completer
5 | import collection.JavaConversions._
6 |
7 |
8 | /**
9 | * @author zhongl
10 | */
11 | class Prop(out: PrintOut) extends Command("prop", "display system properties.", out) with Completer {
12 |
13 | private val regexable = flag("-e" :: "--regex" :: Nil, "enable name as regex pattern.")
14 |
15 | private val keyName = parameter[String]("name", "system env key name.")
16 |
17 | def run() {
18 | if (regexable()) listEnvMatchs(keyName()) else printPropEquals(keyName())
19 | }
20 |
21 | private def printPropEquals(key: String) {
22 | val value = System.getProperty(key)
23 | if (value == null) println("Invalid key " + key)
24 | else println(key + " = " + value)
25 | }
26 |
27 | private def listEnvMatchs(regex: String) {
28 | val matched = System.getProperties.stringPropertyNames.toList filter {_.matches(regex)}
29 | val pattern = "%1$-" + matched.maxBy(_.length).length + "s = %2$s"
30 | matched.sorted.foreach { k => println(pattern.format(k, System.getProperty(k))) }
31 | }
32 |
33 | def complete(buffer: String, cursor: Int, candidates: java.util.List[CharSequence]) = {
34 | val names = System.getProperties.stringPropertyNames.toList filter (_.startsWith(buffer))
35 | names.sorted foreach candidates.add
36 | if (candidates.isEmpty) -1 else cursor - buffer.length
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/Resources.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import instrument.Instrumentation
4 | import com.github.zhongl.yascli.{Command, PrintOut}
5 | import collection.JavaConversions._
6 |
7 | /**
8 | * @author zhongl
9 | */
10 | class Resources(inst: Instrumentation, out: PrintOut)
11 | extends Command("resources", "list all source paths can loaded from every classloader by resource full name.", out) {
12 |
13 | private val fullname = parameter[String]("full-name", "resource full name, eg: com/example/xxx.xxx .")
14 |
15 | private object Loader {
16 | def unapply(c: Class[_]) = if (c.getClassLoader == null) None else Some(c.getClassLoader)
17 | }
18 |
19 | def run() {
20 | val name = fullname()
21 | val loaders = inst.getAllLoadedClasses.collect { case Loader(cl) => cl }.distinct
22 | val urls = loaders.map {_.getResources(name).toList}.flatten
23 | urls.distinct.foreach {println}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/Trace.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import instrument.Instrumentation
20 | import management.ManagementFactory
21 | import com.cedarsoftware.util.io.JsonWriter
22 | import com.github.zhongl.housemd.misc.ObjUtils
23 | import com.github.zhongl.yascli.PrintOut
24 | import com.github.zhongl.housemd.misc.ReflectionUtils._
25 | import java.util.Date
26 | import collection.immutable.SortedSet
27 | import com.github.zhongl.housemd.instrument.{Hook, Context}
28 | import org.objectweb.asm.Type
29 | import java.io.{BufferedWriter, FileWriter, File}
30 |
31 | /**
32 | * @author zhongl
33 | */
34 | class Trace(val inst: Instrumentation, out: PrintOut)
35 | extends TransformCommand("trace", "display or output infomation of method invocaton.", inst, out)
36 | with MethodFilterCompleter {
37 |
38 | val outputRoot = {
39 | val dir = new File("/tmp/" + name + "/" + ManagementFactory.getRuntimeMXBean.getName)
40 | dir.mkdirs()
41 | dir
42 | }
43 | val detailFile = new File(outputRoot, "detail")
44 | val stackFile = new File(outputRoot, "stack")
45 |
46 | private val detailable = flag("-d" :: "--detail" :: Nil, "enable append invocation detail to " + detailFile + ".")
47 | private val detailInJson = flag("-j" :: "--json" :: Nil, "convert detail info into json format.")
48 | private val stackable = flag("-s" :: "--stack" :: Nil, "enable append invocation calling stack to " + stackFile + ".")
49 |
50 | private val methodFilters = parameter[Array[MethodFilter]]("method-filter", "method filter pattern like \"ClassSimpleName.methodName\" or \"ClassSimpleName\".")
51 |
52 | protected def isCandidate(klass: Class[_]) = methodFilters().find(_.filter(klass)).isDefined
53 |
54 | protected def isDecorating(klass: Class[_], methodName: String) =
55 | methodFilters().find(_.filter(klass, methodName)).isDefined
56 |
57 | override protected def hook = new Hook() {
58 |
59 | val enableDetail = detailable()
60 | val enableStack = stackable()
61 | val showDetailInJson = detailInJson()
62 |
63 | implicit val statisticOrdering = Ordering.by((_: Statistic).methodSign)
64 |
65 | var statistics = SortedSet.empty[Statistic]
66 | var maxMethodSignLength = 0
67 | var maxClassLoaderLength = 0
68 |
69 | if (showDetailInJson) ObjUtils.useJsonFormat() else ObjUtils.useToStringFormat()
70 |
71 | lazy val detailWriter = new DetailWriter(new BufferedWriter(new FileWriter(detailFile, true)))
72 | lazy val stackWriter = new StackWriter(new BufferedWriter(new FileWriter(stackFile, true)))
73 |
74 | override def enterWith(context: Context) {}
75 |
76 | override def exitWith(context: Context) {
77 | if (enableDetail) detailWriter.write(context)
78 | if (enableStack) stackWriter.write(context)
79 |
80 | val statistic = new Statistic(context, 1L, context.stopped.get - context.started)
81 |
82 | maxClassLoaderLength = math.max(maxClassLoaderLength, statistic.loader.length)
83 | maxMethodSignLength = math.max(maxMethodSignLength, statistic.methodSign.length)
84 |
85 | statistics = statistics find { _.filter(context) } match {
86 | case Some(s) => (statistics - s) + (s + statistic)
87 | case None => statistics + statistic
88 | }
89 | }
90 |
91 | override def heartbeat(now: Long) {
92 | if (statistics.isEmpty) println("No traced method invoked")
93 | else statistics foreach { s => println(s.reps(maxMethodSignLength, maxClassLoaderLength)) }
94 | println()
95 | }
96 |
97 | override def finalize(throwable: Option[Throwable]) {
98 | heartbeat(0L) // last print
99 | if (enableDetail) {
100 | detailWriter.close()
101 | info("You can get invocation detail from " + detailFile)
102 | }
103 | if (enableStack) {
104 | stackWriter.close()
105 | info("You can get invocation stack from " + stackFile)
106 | }
107 | }
108 | }
109 |
110 | class Statistic(val context: Context, val totalTimes: Long, val totalElapseMills: Long) {
111 |
112 | lazy val methodSign = "%1$s.%2$s(%3$s)".format(
113 | simpleNameOf(context.className),
114 | context.methodName,
115 | Type.getArgumentTypes(context.descriptor).map(t => simpleNameOf(t.getClassName)).mkString(", ")
116 | )
117 |
118 | lazy val loader = if (context.loader == null) "BootClassLoader" else getOrForceToNativeString(context.loader)
119 |
120 | private val NaN = "-"
121 |
122 | private lazy val thisObjectString = context match {
123 | case c if c.thisObject == null => "[Static Method]"
124 | case c if isInit(c.methodName) => "[Initialize Method]"
125 | case _ => getOrForceToNativeString(context.thisObject)
126 | }
127 |
128 | private lazy val avgElapseMillis =
129 | if (totalTimes == 0) NaN else if (totalElapseMills < totalTimes) "<1" else totalElapseMills / totalTimes
130 |
131 | def +(s: Statistic) = new Statistic(context, totalTimes + s.totalTimes, totalElapseMills + s.totalElapseMills)
132 |
133 | def filter(context: Context) = this.context.loader == context.loader &&
134 | this.context.className == context.className &&
135 | this.context.methodName == context.methodName &&
136 | this.context.arguments.size == context.arguments.size &&
137 | this.context.descriptor == context.descriptor &&
138 | (isInit(context.methodName) || this.context.thisObject == context.thisObject)
139 |
140 | def reps(maxMethodSignLength: Int, maxClassLoaderLength: Int) =
141 | "%1$-" + maxMethodSignLength + "s %2$-" + maxClassLoaderLength + "s %3$9s %4$9sms %5$s" format(
142 | methodSign,
143 | loader,
144 | totalTimes,
145 | avgElapseMillis,
146 | thisObjectString)
147 |
148 | private def isInit(method: String) = method == ""
149 |
150 | }
151 |
152 | }
153 |
154 | class DetailWriter(writer: BufferedWriter) {
155 | def write(context: Context) {
156 | val started = "%1$tF %1$tT" format (new Date(context.started))
157 | val elapse = "%,dms" format (context.stopped.get - context.started)
158 | val thread = "[" + context.thread.getName + "]"
159 | val thisObject = if (context.thisObject == null) "null" else context.thisObject.toString
160 | val method = context.className + "." + context.methodName
161 | val arguments = context.arguments.mkString("[" + ObjUtils.parameterSeparator, ObjUtils.parameterSeparator, "]")
162 | val resultOrExcption = context.resultOrException match {
163 | case Some(x) => ObjUtils.toString(x)
164 | case None if context.isVoidReturn => "void"
165 | case None => "null"
166 | }
167 |
168 | val argumentsAndResult = "Arguments: " + arguments + ObjUtils.parameterSeparator + "Result: " + resultOrExcption
169 |
170 | val line = (started :: elapse :: thread :: thisObject :: method :: argumentsAndResult :: Nil)
171 | .mkString(" ")
172 | writer.write(line)
173 | writer.newLine()
174 |
175 | context.resultOrException match {
176 | case Some(x) if x.isInstanceOf[Throwable] => x.asInstanceOf[Throwable].getStackTrace.foreach {
177 | s =>
178 | writer.write("\tat " + s)
179 | writer.newLine()
180 | }
181 | case _ =>
182 | }
183 | }
184 |
185 | def close() {
186 | try {writer.close()} catch {case _ => }
187 | }
188 | }
189 |
190 | class StackWriter(writer: BufferedWriter) {
191 | def write(context: Context) {
192 | // TODO Avoid duplicated stack
193 |
194 | val head = "%1$s.%2$s%3$s call by thread [%4$s]"
195 | .format(context.className, context.methodName, context.descriptor, context.thread.getName)
196 |
197 | writer.write(head)
198 | writer.newLine()
199 | context.stack foreach { s => writer.write("\t" + s); writer.newLine() }
200 | writer.newLine()
201 | }
202 |
203 | def close() {
204 | try {writer.close()} catch {case _ => }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/command/TransformCommand.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import instrument._
20 | import com.github.zhongl.yascli.{PrintOut, Command}
21 | import com.github.zhongl.housemd.instrument._
22 | import java.util.regex.Pattern
23 |
24 | /**
25 | * @author zhongl
26 | */
27 | abstract class TransformCommand(name: String, description: String, inst: Instrumentation, out: PrintOut)
28 | extends Command(name, description, out) {
29 |
30 | import com.github.zhongl.yascli.Converters._
31 |
32 | private val packagePattern = option[Pattern]("-p" :: "--package" :: Nil, "package regex pattern for filtering.", ".*")
33 | private val interval = option[Seconds]("-i" :: "--interval" :: Nil, "display trace statistics interval.", 1)
34 | private val timeout = option[Seconds]("-t" :: "--timeout" :: Nil, "limited trace seconds.", 10)
35 | private val overLimit = option[Int]("-l" :: "--limit" :: Nil, "limited limited times.", 1000)
36 |
37 | private val transform = new Transform
38 |
39 | override def run() {
40 | val delegate = hook
41 | val h = new Hook {
42 | val intervalMillis = interval().toMillis
43 | var last = 0L
44 |
45 | def heartbeat(now: Long) {
46 | if (now - last > intervalMillis) {
47 | delegate.heartbeat(now)
48 | last = now
49 | }
50 | }
51 |
52 | def finalize(throwable: Option[Throwable]) {
53 | delegate.finalize(throwable)
54 | }
55 |
56 | def enterWith(context: Context) {
57 | delegate.enterWith(context)
58 | }
59 |
60 | def exitWith(context: Context) {
61 | delegate.exitWith(context)
62 | }
63 | }
64 |
65 | transform(inst, filter, timeout(), overLimit(), this, h)
66 | }
67 |
68 | protected def isCandidate(klass: Class[_]): Boolean
69 |
70 | protected def isDecorating(klass: Class[_], methodName: String): Boolean
71 |
72 | protected def hook: Hook
73 |
74 | private def filter = new Filter {
75 | // Used for getting candidates of probing
76 | def apply(klass: Class[_]) = {
77 | @inline
78 | def matchesPackagePattern = {
79 | val p = packagePattern()
80 | p.pattern() == ".*" || (klass.getPackage != null && p.matcher(klass.getPackage.getName).matches())
81 | }
82 |
83 | matchesPackagePattern && isCandidate(klass)
84 | }
85 |
86 | // Used for class decorating
87 | def apply(klass: Class[_], methodName: String) = isDecorating(klass, methodName)
88 | }
89 |
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/duck/Telephone.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.duck
18 |
19 | import instrument.Instrumentation
20 | import java.net.Socket
21 | import jline.console.ConsoleReader
22 | import com.github.zhongl.yascli.{Shell, PrintOut, Command}
23 | import jline.TerminalFactory
24 | import jline.console.history.FileHistory
25 | import java.io.File
26 | import com.github.zhongl.housemd.command.Last
27 |
28 | /**
29 | * Telephone is used by Duck to communicate with HouseMD.
30 | *
31 | * @author zhongl
32 | */
33 | class Telephone(inst: Instrumentation, port: Int, classes: Array[Class[Command]]) extends Runnable {
34 |
35 |
36 | def run() {
37 | val socket = new Socket("localhost", port)
38 | val reader = new ConsoleReader(socket.getInputStream, socket.getOutputStream)
39 | val history = new FileHistory(new File("/tmp/housemd/.history"))
40 | reader.setHistory(history)
41 |
42 | try {
43 | new Shell(name = "housemd", description = "a runtime diagnosis tool of jvm.", reader = reader) {
44 | private val lastCommand = new Last(out)
45 |
46 | override protected def commands = Quit :: helpCommand :: lastCommand :: classes
47 | .map { toCommand(_, PrintOut(reader)) }
48 | .toList
49 |
50 | override def error(a: Any) {
51 | super.error(a)
52 | if (a.isInstanceOf[Throwable]) lastCommand.keep(a.asInstanceOf[Throwable])
53 | }
54 |
55 | } main (Array.empty[String])
56 | } finally {
57 | TerminalFactory.reset()
58 | history.flush()
59 | socket.shutdownOutput()
60 | socket.shutdownInput()
61 | socket.close()
62 | }
63 | }
64 |
65 | private def toCommand(c: Class[Command], out: PrintOut) = {
66 | val I = classOf[Instrumentation]
67 | val O = classOf[PrintOut]
68 | val constructors = c.getConstructors
69 | val args = constructors(0).getParameterTypes map {
70 | case I => inst
71 | case O => out
72 | case x => throw new IllegalStateException("Unsupport parameter type: " + x)
73 | }
74 | constructors(0).newInstance(args: _*).asInstanceOf[Command]
75 | }
76 |
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/house/House.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.house
18 |
19 | import com.sun.tools.attach.VirtualMachine
20 | import com.github.zhongl.yascli.{PrintOut, Command, Application}
21 | import jline.NoInterruptUnixTerminal
22 | import com.github.zhongl.housemd._
23 | import command._
24 | import misc.Utils._
25 | import duck.Telephone
26 | import management.ManagementFactory
27 | import java.io.{FileInputStream, FileWriter, BufferedWriter, File}
28 | import java.util.jar.{Attributes, JarInputStream}
29 |
30 |
31 | /**
32 | * @author zhongl
33 | */
34 | object House extends Command("housemd", "a runtime diagnosis tool of JVM.", PrintOut(System.out)) with Application {
35 |
36 | implicit private val string2Port = {
37 | value: String =>
38 | val p = value.toInt
39 | if (p > 1024 && p < 65536) p else throw new IllegalArgumentException(", it should be between 1025 and 65535")
40 | }
41 |
42 | implicit private val string2File = {
43 | value: String =>
44 | val file = new File(value)
45 | if (file.exists() && file.isFile) file else throw new IllegalArgumentException(", it should be an existed file")
46 | }
47 |
48 | private val port = option[Int]("-p" :: "--port" :: Nil, "set console local socket server port number.", 54321)
49 | private val pid = parameter[String]("pid", "id of process to be diagnosing.")
50 |
51 | private val printVersion = flag("-v" :: "--version" :: Nil, "show version.")
52 |
53 |
54 | private lazy val agentJarFile = sourceOf(Manifest.classType(getClass))
55 | private lazy val agentOptions = agentJarFile ::
56 | classNameOf[Telephone] ::
57 | port() ::
58 | classNameOf[Trace] ::
59 | classNameOf[Loaded] ::
60 | classNameOf[Env] ::
61 | classNameOf[Inspect] ::
62 | classNameOf[Prop] ::
63 | classNameOf[Resources] ::
64 | Nil
65 |
66 | private lazy val errorDetailFile = "/tmp/housemd.err." + pid()
67 | private lazy val errorDetailWriter = new BufferedWriter(new FileWriter(errorDetailFile))
68 |
69 | def run() {
70 | if (printVersion()) {
71 | println("v" + version)
72 | return
73 | }
74 |
75 | if (ManagementFactory.getOperatingSystemMXBean.getName.toLowerCase.contains("window")) {
76 | throw new IllegalStateException("Sorry, Windows is not supported now.")
77 | }
78 | try {
79 | val terminal = new NoInterruptUnixTerminal()
80 | terminal.init()
81 | val sout = terminal.wrapOutIfNeeded(System.out)
82 | val sin = terminal.wrapInIfNeeded(System.in)
83 | val vm = VirtualMachine.attach(pid())
84 |
85 | val mobilephone = new Mobilephone(port(), {
86 | case PickUp => info("connection established on " + port())
87 | case ListenTo(earphone) => earphone(sout)
88 | case SpeakTo(microphone) => microphone(sin)
89 | case BreakOff(reason) => error("connection breaked causeby"); error(reason)
90 | case HangUp => terminal.restore(); silentClose(errorDetailWriter); info("bye")
91 |
92 | })
93 |
94 | info("Welcome to HouseMD " + version)
95 |
96 | vm.loadAgent(agentJarFile, agentOptions mkString " ")
97 | vm.detach()
98 |
99 | mobilephone.start()
100 | } catch {
101 | case e: Throwable => error(e); silentClose(errorDetailWriter)
102 | }
103 | }
104 |
105 | override def help = "version: " + version + '\n' + super.help
106 |
107 | private lazy val version = {
108 | val stream = new JarInputStream(new FileInputStream(agentJarFile))
109 | try {
110 | val attributes = stream.getManifest.getMainAttributes
111 | attributes.getValue(Attributes.Name.SIGNATURE_VERSION)
112 | } finally {
113 | silentClose(stream)
114 | }
115 | }
116 |
117 | override def error(a: Any) {
118 | super.error(a)
119 | a match {
120 | case throwable: Throwable =>
121 | super.error("You can get more details in " + errorDetailFile)
122 | throwable.getStackTrace foreach { s => errorDetailWriter.write(s + "\n") }
123 | case _ =>
124 | }
125 | }
126 |
127 | }
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/house/Mobilephone.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.house
18 |
19 | import java.net.InetSocketAddress
20 | import java.io.{IOException, OutputStream, InputStream}
21 | import java.nio.channels._
22 | import annotation.tailrec
23 | import collection.JavaConversions._
24 | import actors.Actor._
25 | import com.github.zhongl.housemd.misc.Utils._
26 | import java.nio.ByteBuffer
27 | import actors.{Actor, OutputChannel, TIMEOUT}
28 |
29 |
30 | /**
31 | * Mobilephone is used by House$ to communicate with Duck
32 | *
33 | * @author zhongl
34 | */
35 | class Mobilephone(port: Int, handle: PartialFunction[Signal, Any]) extends Actor {
36 |
37 | private val server = ServerSocketChannel.open()
38 | private val selector = Selector.open()
39 |
40 | server.configureBlocking(false)
41 | server.socket().bind(new InetSocketAddress(port))
42 | server.register(selector, SelectionKey.OP_ACCEPT)
43 |
44 | def act() {
45 | val a = self
46 | val hook = sys.addShutdownHook { a !? PowerOff }
47 | var killer = Option.empty[OutputChannel[Any]]
48 |
49 | def select() {
50 | val selected = selector.select(500L)
51 | if (selected > 0) {
52 | selector.selectedKeys() foreach {
53 | case k if k.isAcceptable => accept(k, selector)
54 | case k if k.isReadable => read(k)
55 | case k if k.isWritable => if (killer.isEmpty) write(k) else sendExit(k)
56 | case ignore =>
57 | }
58 | selector.selectedKeys().clear()
59 | }
60 | }
61 |
62 | def endCall() {
63 | if (killer.isEmpty) hook.remove()
64 | self ! EndCall
65 | }
66 |
67 | loop {
68 | reactWithin(1L) {
69 | case PowerOff => killer = Some(sender)
70 | case EndCall =>
71 | handle(HangUp)
72 | killer foreach { o => o !() } // reply to killer for termination
73 | exit()
74 | case TIMEOUT =>
75 | try {select()} catch {
76 | case Closed => endCall()
77 | case t => handle(BreakOff(t)); hook.remove(); endCall()
78 | }
79 | }
80 | }
81 | }
82 |
83 | private def sendExit(key: SelectionKey) {
84 | write(key.channel(), "quit\n".getBytes)
85 | }
86 |
87 | private def accept(key: SelectionKey, selector: Selector) {
88 | val channel = key.channel().asInstanceOf[ServerSocketChannel].accept()
89 | if (channel == null) return
90 | try {
91 | channel.socket().setSoLinger(true, 1)
92 | channel.socket().setTcpNoDelay(true)
93 | channel.socket().setSendBufferSize(1024)
94 | channel.configureBlocking(false)
95 | channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)
96 | handle(PickUp)
97 | } catch {
98 | case e: IOException => silentClose(channel); throw e
99 | }
100 | uninterestOps(SelectionKey.OP_ACCEPT, key)
101 | }
102 |
103 | private def read(key: SelectionKey) {
104 | val channel = key.channel().asInstanceOf[ReadableByteChannel]
105 |
106 | @tailrec
107 | def output(stream: OutputStream) /*: Unit =*/ {
108 | val bytes = new Array[Byte](4096)
109 | val read = channel.read(ByteBuffer.wrap(bytes))
110 | if (read == -1) {
111 | silentClose(channel)
112 | throw Closed
113 | }
114 | stream.write(bytes, 0, read)
115 | if (read == bytes.length) output(stream)
116 | }
117 |
118 | handle(ListenTo(output))
119 | interestOps(SelectionKey.OP_READ, key)
120 | }
121 |
122 | private def write(key: SelectionKey) {
123 |
124 | def input(stream: InputStream) {
125 | val available = stream.available()
126 | if (available > 0) {
127 | val channel = key.channel()
128 | val bytes = new Array[Byte](available)
129 |
130 | stream.read(bytes)
131 | write(channel, bytes)
132 | }
133 | }
134 |
135 | handle(SpeakTo(input))
136 | interestOps(SelectionKey.OP_WRITE, key)
137 | }
138 |
139 | private def write(channel: Channel, bytes: Array[Byte]) {
140 | val write = channel.asInstanceOf[WritableByteChannel].write(ByteBuffer.wrap(bytes))
141 | if (write < bytes.length)
142 | throw new IllegalStateException("Can't send all input, you should enlarge socket send buffer.")
143 | }
144 |
145 | private def interestOps(op: Int, key: SelectionKey) = key.interestOps(key.interestOps() | op)
146 |
147 | private def uninterestOps(op: Int, key: SelectionKey) = key.interestOps(key.interestOps() & ~op)
148 |
149 | private case object EndCall
150 |
151 | private case object Closed extends Exception
152 |
153 | }
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/house/Signal.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.house
2 |
3 | import java.io.{InputStream, OutputStream}
4 |
5 | sealed trait Signal
6 |
7 | case object PickUp extends Signal
8 |
9 | case class ListenTo(earphone: OutputStream => Unit) extends Signal
10 |
11 | case class SpeakTo(microphone: InputStream => Unit) extends Signal
12 |
13 | case class BreakOff(reason: Throwable) extends Signal
14 |
15 | case object HangUp extends Signal
16 |
17 | case object PowerOff
18 |
19 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Advice.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument;
18 |
19 | import java.lang.reflect.Method;
20 | import java.util.Arrays;
21 | import java.util.HashMap;
22 | import java.util.Map;
23 | import java.util.Stack;
24 | import java.util.concurrent.ConcurrentHashMap;
25 | import java.util.concurrent.atomic.AtomicReference;
26 |
27 | /**
28 | * See #17 for more information.
29 | *
30 | * @author zhongl
31 | */
32 | public abstract class Advice {
33 |
34 | public static final String CLASS = "class";
35 | public static final String METHOD = "method";
36 | public static final String VOID_RETURN = "voidReturn";
37 | public static final String THIS = "this";
38 | public static final String ARGUMENTS = "arguments";
39 | public static final String DESCRIPTOR = "descriptor";
40 | public static final String STACK = "stack";
41 | public static final String STARTED = "started";
42 | public static final String STOPPED = "stopped";
43 | public static final String RESULT = "result";
44 | public static final String THREAD = "thread";
45 | public static final String SET_DELEGATE = "setDelegate";
46 | public static final String SET_DEFAULT_DELEGATE = "setDefaultDelegate";
47 | public static final String CLASS_LOADER = "classLoader";
48 |
49 | public static final Method ON_METHOD_BEGIN;
50 | public static final Method ON_METHOD_END;
51 |
52 | private static final AtomicReference delegate;
53 | private static final Map>> threadBoundContexts;
54 | private static final Advice nullAdvice;
55 | private static final ClassLoader loader;
56 |
57 | static {
58 | try {
59 | ON_METHOD_BEGIN = Advice.class.getMethod("onMethodBegin", String.class, String.class, String.class, Object.class, Object[].class);
60 | ON_METHOD_END = Advice.class.getMethod("onMethodEnd", Object.class);
61 | loader = Advice.class.getClassLoader();
62 | } catch (NoSuchMethodException e) {
63 | throw new RuntimeException(e);
64 | }
65 |
66 | nullAdvice = null;
67 |
68 | delegate = new AtomicReference(nullAdvice);
69 | threadBoundContexts = new ConcurrentHashMap>>();
70 | }
71 |
72 | public static void setDelegate(Object obj) {
73 | delegate.set(obj);
74 | }
75 |
76 | public static void setDefaultDelegate() {
77 | delegate.set(nullAdvice);
78 | }
79 |
80 | public static void onMethodBegin(String className, String methodName, String descriptor, Object thisObject, Object[] arguments) {
81 | Map context = new HashMap();
82 | context.put(CLASS, className);
83 | context.put(METHOD, methodName);
84 | context.put(CLASS_LOADER, loader);
85 | context.put(VOID_RETURN, isVoidReturn(descriptor));
86 | context.put(THIS, thisObject);
87 | context.put(ARGUMENTS, arguments);
88 | context.put(DESCRIPTOR, descriptor);
89 | context.put(STACK, currentStackTrace());
90 | context.put(STARTED, System.currentTimeMillis());
91 | context.put(THREAD, Thread.currentThread());
92 | invoke("enterWith", context);
93 | stackPush(context);
94 | }
95 |
96 | private static void invoke(String name, Map context) {
97 | try {
98 | Object o = delegate.get();
99 | if (o == null) return;
100 | o.getClass().getMethod(name, Map.class).invoke(o, context);
101 | } catch (Throwable ignore) {
102 | }
103 | }
104 |
105 | public static void onMethodEnd(Object resultOrException) {
106 | Map context = stackPop();
107 | context.put(STOPPED, System.currentTimeMillis());
108 | context.put(RESULT, resultOrException);
109 | invoke("exitWith", context);
110 | }
111 |
112 | private static void stackPush(Map context) {
113 | Thread t = Thread.currentThread();
114 | Stack> s = threadBoundContexts.get(t);
115 | if (s == null) {
116 | s = new Stack>();
117 | threadBoundContexts.put(t, s);
118 | }
119 | s.push(context);
120 | }
121 |
122 | private static Map stackPop() {
123 | return threadBoundContexts.get(Thread.currentThread()).pop();
124 | }
125 |
126 | private static StackTraceElement[] currentStackTrace() {
127 | StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
128 | return Arrays.copyOfRange(stackTrace, 4, stackTrace.length); // trim useless stack trace elements.
129 | }
130 |
131 |
132 | private static Boolean isVoidReturn(String descriptor) {
133 | return descriptor.charAt(descriptor.indexOf(')') + 1) == 'V';
134 | }
135 |
136 | public abstract void enterWith(Map context);
137 |
138 | public abstract void exitWith(Map context);
139 | }
140 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/ClassDecorator.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument
18 |
19 | import org.objectweb.asm._
20 | import commons.{Method, AdviceAdapter}
21 | import org.objectweb.asm.Opcodes._
22 | import org.objectweb.asm.Type._
23 |
24 | object ClassDecorator {
25 |
26 | def decorate(classfileBuffer: Array[Byte], className: String, methodFilter: (String => Boolean)) = {
27 | val cr: ClassReader = new ClassReader(classfileBuffer)
28 | val cw: ClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS)
29 | cr.accept(classAdapter(cw, className, methodFilter), ClassReader.EXPAND_FRAMES)
30 | cw.toByteArray
31 | }
32 |
33 | def classAdapter(cw: ClassWriter, className: String, filter: (String => Boolean)) =
34 | new ClassAdapter(cw) {
35 |
36 | override def visitMethod(acc: Int, name: String, desc: String, sign: String, exces: Array[String]) = {
37 | val mv = super.visitMethod(acc, name, desc, sign, exces)
38 | if ((mv != null && isNotAbstract(acc) && filter(name))) methodAdapter(mv, acc, name, desc) else mv
39 | }
40 |
41 | private def isNotAbstract(acc: Int) = (Opcodes.ACC_ABSTRACT & acc) == 0
42 |
43 | private[this] def methodAdapter(mv: MethodVisitor, access: Int, methodName: String, desc: String) =
44 | new AdviceAdapter(mv, access, methodName, desc) {
45 | val advice = Type.getType(classOf[Advice])
46 | val enter = Method.getMethod(Advice.ON_METHOD_BEGIN)
47 | val exit = Method.getMethod(Advice.ON_METHOD_END)
48 |
49 | override def visitMaxs(maxStack: Int, maxLocals: Int) {
50 | mark(end)
51 | catchException(start, end, Type.getType(classOf[Throwable]))
52 | dup()
53 | invokeStatic(advice, exit)
54 | throwException()
55 | super.visitMaxs(maxStack, maxLocals)
56 | }
57 |
58 | protected override def onMethodEnter() {
59 | push(className)
60 | push(methodName)
61 | push(methodDesc)
62 | loadThisOrPushNullIfIsStatic()
63 | loadArgArray()
64 | invokeStatic(advice, enter)
65 | mark(start)
66 | }
67 |
68 | protected override def onMethodExit(opcode: Int) {
69 | if (opcode != ATHROW) {
70 | prepareResultBy(opcode)
71 | invokeStatic(advice, exit)
72 | }
73 | }
74 |
75 | private[this] def isStaticMethod = (methodAccess & ACC_STATIC) != 0
76 |
77 | private[this] def loadThisOrPushNullIfIsStatic() { if (isStaticMethod) pushNull() else loadThis() }
78 |
79 | private[this] def prepareResultBy(opcode: Int) {
80 | opcode match {
81 | case RETURN => pushNull() // void
82 | case ARETURN => dup() // object
83 | case LRETURN | DRETURN => dup2(); box(getReturnType(methodDesc)) // long or double
84 | case _ => dup(); box(getReturnType(methodDesc)) // object or boolean or byte or char or short or int
85 | }
86 | }
87 |
88 | private[this] def pushNull() { push(null.asInstanceOf[Type]) }
89 |
90 | private[this] val start = new Label
91 | private[this] val end = new Label
92 | }
93 |
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Context.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument
18 |
19 | import com.github.zhongl.housemd.misc.ObjUtils
20 |
21 |
22 | /**
23 | * @author zhongl
24 | */
25 | case class Context(
26 | className: String,
27 | methodName: String,
28 | loader: ClassLoader,
29 | arguments: Array[String],
30 | descriptor: String,
31 | isVoidReturn: Boolean,
32 | thisObject: AnyRef,
33 | started: Long,
34 | stack: Array[StackTraceElement],
35 | thread: Thread,
36 | stopped: Option[Long],
37 | resultOrException: Option[AnyRef])
38 |
39 | object Context {
40 |
41 | implicit val map2Context: (java.util.Map[String, AnyRef] => Context) = apply(_: java.util.Map[String, AnyRef])
42 |
43 | def apply(map: java.util.Map[String, AnyRef]) = new Context(
44 | map.get(Advice.CLASS).asInstanceOf[String],
45 | map.get(Advice.METHOD).asInstanceOf[String],
46 | map.get(Advice.CLASS_LOADER).asInstanceOf[ClassLoader],
47 | map.get(Advice.ARGUMENTS).asInstanceOf[Array[AnyRef]] map ObjUtils.toString,
48 | map.get(Advice.DESCRIPTOR).asInstanceOf[String],
49 | map.get(Advice.VOID_RETURN).asInstanceOf[Boolean],
50 | map.get(Advice.THIS),
51 | map.get(Advice.STARTED).asInstanceOf[Long],
52 | map.get(Advice.STACK).asInstanceOf[Array[StackTraceElement]],
53 | map.get(Advice.THREAD).asInstanceOf[Thread],
54 | map.get(Advice.STOPPED) match {
55 | case v if v != null => Some(v.asInstanceOf[Long])
56 | case _ => None
57 | },
58 | map.get(Advice.RESULT) match {
59 | case v if v != null => Some(v)
60 | case _ => None
61 | }
62 | )
63 | }
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Filter.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.instrument
2 |
3 |
4 | /**
5 | * @author zhongl
6 | */
7 | trait Filter extends (Class[_] => Boolean) with ((Class[_], String) => Boolean)
8 |
9 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Hook.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument
18 |
19 | trait Hook {
20 | def enterWith(context: Context)
21 |
22 | def exitWith(context: Context)
23 |
24 | def heartbeat(now: Long)
25 |
26 | def finalize(throwable: Option[Throwable])
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Seconds.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument
18 |
19 | import java.util.concurrent.TimeUnit
20 |
21 | /**
22 | * @author zhongl
23 | */
24 |
25 | class Seconds(val value: Int) {
26 | def toMillis = TimeUnit.SECONDS.toMillis(value)
27 |
28 | override def toString = value.toString
29 | }
30 |
31 | object Seconds {
32 | implicit def apply(value: Int) = new Seconds(value)
33 |
34 | implicit def apply(value: String) = new Seconds(value.toInt)
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/instrument/Transform.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.instrument
18 |
19 | import java.lang.instrument.{ClassFileTransformer, Instrumentation}
20 | import java.security.ProtectionDomain
21 | import java.util.concurrent.atomic.AtomicInteger
22 |
23 | import com.github.zhongl.housemd.misc.ReflectionUtils._
24 | import com.github.zhongl.yascli.Loggable
25 | import java.lang.System.{currentTimeMillis => now}
26 | import java.util
27 | import scala.actors.Actor._
28 | import scala.actors._
29 |
30 | /**
31 | * @author zhongl
32 | */
33 | class Transform extends ((Instrumentation, Filter, Seconds, Int, Loggable, Hook) => Unit) {
34 |
35 | def apply(inst: Instrumentation, filter: Filter, timeout: Seconds, overLimit: Int, log: Loggable, hook: Hook) {
36 | implicit val i = inst
37 | implicit val t = timeout
38 | implicit val l = log
39 | implicit val h = hook
40 |
41 | val candidates = inst.getAllLoadedClasses filter {
42 | c =>
43 |
44 | @inline
45 | def skipClass(description: String)(cause: Class[_] => Boolean) =
46 | if (cause(c)) {log.warn("Skip %1$s %2$s" format(c, description)); false } else true
47 |
48 | @inline
49 | def isNotBelongsHouseMD = skipClass("belongs to HouseMD") { _.getName.startsWith("com.github.zhongl.housemd") }
50 |
51 | @inline
52 | def isNotInterface = skipClass("") { _.isInterface }
53 |
54 | @inline
55 | def isNotFromBootClassLoader = skipClass("loaded from bootclassloader") { isFromBootClassLoader }
56 |
57 | filter(c) && isNotBelongsHouseMD && isNotInterface && isNotFromBootClassLoader
58 | }
59 |
60 | if (candidates.isEmpty) {
61 | log.println("No matched class")
62 | } else {
63 | val probeDecorator = classFileTransformer(filter, candidates)
64 |
65 | inst.addTransformer(probeDecorator, true)
66 | probe(candidates, advice(overLimit))
67 |
68 | try {handleAdviceEvent} finally {
69 | inst.removeTransformer(probeDecorator)
70 | reset(candidates)
71 | }
72 |
73 | }
74 |
75 | }
76 |
77 | private def handleAdviceEvent(implicit timeout: Seconds, log: Loggable, h: Hook) {
78 | val start = now
79 | val timoutMillis = timeout.toMillis
80 |
81 | try {
82 | while (true) {
83 |
84 | receiveWithin(500) {
85 | case TIMEOUT => // ignore
86 | case OverLimit => throw OverLimitBreak
87 | case EnterWith(context) => h.enterWith(context)
88 | case ExitWith(context) => h.exitWith(context)
89 | case x => // ignore last unread messages, error("Unknown case: " + x)
90 | }
91 |
92 | val t = now
93 | h.heartbeat(t)
94 | if (t - start >= timoutMillis) throw TimeoutBreak
95 | }
96 | } catch {
97 | case OverLimitBreak => h.finalize(None); log.info("Ended by overlimit")
98 | case TimeoutBreak => h.finalize(None); log.info("Ended by timeout")
99 | case t => h.finalize(Some(t)); throw t
100 | }
101 | }
102 |
103 | private def classFileTransformer(filter: Filter, classes: Array[Class[_]])(implicit log: Loggable) =
104 | new ClassFileTransformer {
105 | def transform(loader: ClassLoader, name: String, klass: Class[_], pd: ProtectionDomain, bytecode: Array[Byte]) = {
106 | try {
107 | if (classes.contains(klass)) ClassDecorator.decorate(bytecode, name, filter.curried(klass)) else null
108 | } catch {
109 | case e => log.error(e); null
110 | }
111 | }
112 | }
113 |
114 | private def probe(classes: Array[Class[_]], advice: Advice)(implicit inst: Instrumentation, log: Loggable) {
115 | classes foreach {
116 | c =>
117 | try {
118 | loadOrDefineAdviceClassFrom(c.getClassLoader)
119 | .getMethod(Advice.SET_DELEGATE, classOf[Object])
120 | .invoke(null, advice)
121 | inst.retransformClasses(c)
122 | log.info("Probe " + c)
123 | } catch {
124 | case e => log.warn("Failed to probe " + c + " because of " + e)
125 | }
126 | }
127 | }
128 |
129 | // FIXME : reduce duplication between probe and reset .
130 | private def reset(classes: Array[Class[_]])(implicit inst: Instrumentation, log: Loggable) {
131 | classes foreach {
132 | c =>
133 | try {
134 | loadOrDefineAdviceClassFrom(c.getClassLoader).getMethod(Advice.SET_DEFAULT_DELEGATE).invoke(null)
135 | inst.retransformClasses(c)
136 | log.info("Reset " + c)
137 | } catch {
138 | case e => log.warn("Failed to reset " + c + " because of " + e)
139 | }
140 | }
141 | }
142 |
143 |
144 | private def loadOrDefineAdviceClassFrom(loader: ClassLoader): Class[_] = loadOrDefine(classOf[Advice], loader)
145 |
146 | private def advice(limit: Int) = new Advice() {
147 |
148 | val count = new AtomicInteger()
149 | val host = self
150 |
151 | def enterWith(context: util.Map[String, AnyRef]) {
152 | host ! EnterWith(context)
153 | }
154 |
155 | def exitWith(context: util.Map[String, AnyRef]) {
156 | host ! ExitWith(context)
157 | if (count.incrementAndGet() >= limit) host ! OverLimit
158 | }
159 | }
160 |
161 | private sealed trait Event
162 |
163 | private case object OverLimit extends Event
164 |
165 | private case class EnterWith(context: Context) extends Event
166 |
167 | private case class ExitWith(context: Context) extends Event
168 |
169 | private object OverLimitBreak extends Exception
170 |
171 | private object TimeoutBreak extends Exception
172 |
173 | }
174 |
175 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/misc/ObjUtils.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.misc
2 |
3 | import com.cedarsoftware.util.io.JsonWriter
4 |
5 | /**
6 | * @author eagleinfly
7 | */
8 | object ObjUtils {
9 | val jsonFormater = (ref: AnyRef) => JsonWriter.objectToJson(ref)
10 |
11 | val toStringFormater = (ref: AnyRef) => Option(ref).getOrElse("null").toString
12 |
13 | var formatter = toStringFormater
14 | var parameterSeparator: String = " "
15 |
16 | def useJsonFormat() = {
17 | formatter = jsonFormater
18 | parameterSeparator = "\n"
19 | }
20 |
21 | def useToStringFormat() = {
22 | formatter = toStringFormater
23 | parameterSeparator = " "
24 | }
25 |
26 | def toString(obj: AnyRef) = {
27 | formatter(obj)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/misc/ReflectionUtils.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.misc
18 |
19 | import System.identityHashCode
20 | import java.lang._
21 | import java.lang.reflect.{Method, Modifier}
22 |
23 | /**
24 | * @author zhongl
25 | */
26 |
27 | object ReflectionUtils {
28 |
29 | private val S = classOf[String]
30 |
31 | private val I = classOf[Integer]
32 | private val i = Integer.TYPE
33 |
34 | private val L = classOf[Long]
35 | private val l = Long.TYPE
36 |
37 | private val T = classOf[Boolean]
38 | private val t = Boolean.TYPE
39 |
40 | private val D = classOf[Double]
41 | private val d = Double.TYPE
42 |
43 | private val F = classOf[Float]
44 | private val f = Float.TYPE
45 |
46 | private val C = classOf[Char]
47 | private val c = Character.TYPE
48 |
49 | private val B = classOf[Byte]
50 | private val b = Byte.TYPE
51 |
52 | private lazy val defineClassMethod = {
53 | val int = Integer.TYPE
54 | val objects = classOf[Array[scala.Byte]]
55 | val string = classOf[String]
56 | val m = classOf[ClassLoader].getDeclaredMethod("defineClass", string, objects, int, int)
57 | m.setAccessible(true)
58 | m
59 | }
60 |
61 | def toBoxClass(k: Class[_]) = k match {
62 | case `i` => I
63 | case `l` => L
64 | case `d` => D
65 | case `f` => F
66 | case `t` => T
67 | case `b` => B
68 | case `c` => C
69 | case _ => k
70 | }
71 |
72 | def toNativeString(instance: AnyRef) =
73 | instance.getClass.getName + "@" + Integer.toHexString(identityHashCode(instance))
74 |
75 | def getOrForceToNativeString(instance: AnyRef) =
76 | if (instance.toString.startsWith(instance.getClass + "@")) instance.toString else toNativeString(instance)
77 |
78 | /** see https://github.com/zhongl/HouseMD/issues/17 */
79 | def loadOrDefine(clazz: Class[_], inClassLoader: ClassLoader) = {
80 | val name = clazz.getName
81 | try {
82 | inClassLoader.loadClass(name)
83 | } catch {
84 | case e: ClassNotFoundException =>
85 | import Utils._
86 |
87 | val bytes = toBytes(clazz.getResourceAsStream("/" + name.replace('.', '/') + ".class"))
88 | val zero: java.lang.Integer = 0
89 | val length: java.lang.Integer = bytes.length
90 |
91 | defineClassMethod.invoke(inClassLoader, name, bytes, zero, length).asInstanceOf[Class[_]]
92 | }
93 | }
94 |
95 | def simpleNameOf(c: Class[_]): String = simpleNameOf(c.getName)
96 |
97 | def simpleNameOf(className: String) = className.split("\\.").last
98 |
99 | def isNotConcrete(c: Class[_]) = c.isInterface || Modifier.isAbstract(c.getModifiers)
100 |
101 | def isAbstract(m: Method) = Modifier.isAbstract(m.getModifiers)
102 |
103 | def isFromBootClassLoader(c: Class[_]) = c.getClassLoader == null
104 |
105 | def constructorAndMethodNamesOf(c: Class[_]): Array[String] = {
106 | val names = c.getDeclaredMethods.map(_.getName)
107 | if (!c.isInterface) names :+ "" else names
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/zhongl/housemd/misc/Utils.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.misc
18 |
19 | import java.io.{ByteArrayOutputStream, InputStream}
20 | import scala.Array
21 | import java.net.URL
22 |
23 |
24 | /**
25 | * @author zhongl
26 | */
27 |
28 | object Utils {
29 | lazy val FileRE = """(file:)?([^!]+)!?.*""".r
30 |
31 | private val noPath = "null"
32 |
33 | private[misc] object File {
34 | def unapply(url: URL) = url.getFile match {
35 | case FileRE(_, source) => Some(source)
36 | case _ => None
37 | }
38 | }
39 |
40 | def toBytes(stream: InputStream): Array[Byte] = {
41 | val bytes = new ByteArrayOutputStream
42 | var read = stream.read
43 | while (read > -1) {
44 | bytes.write(read)
45 | read = stream.read
46 | }
47 | bytes.toByteArray
48 | }
49 |
50 | def locationOf[T: Manifest] = Option(manifest[T].erasure.getProtectionDomain.getCodeSource) match {
51 | case Some(codeSource) => Option(codeSource.getLocation)
52 | case None => Option(manifest[T].erasure.getResource(resourceNameOf[T]))
53 | }
54 |
55 | def resourceNameOf[T: Manifest] = "/" + manifest[T].erasure.getName.replace('.', '/') + ".class"
56 |
57 | def sourceOf[T: Manifest] = locationOf[T] match {
58 | case Some(File(path)) => path
59 | case None => noPath
60 | }
61 |
62 | def classNameOf[T: Manifest] = manifest[T].erasure.getName
63 |
64 | def silentClose(closable: {def close()}) {
65 | if (closable != null) try {closable.close()} catch {case _ => /*ignore*/ }
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/resources/res.xml:
--------------------------------------------------------------------------------
1 | value
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/AdviceReflection.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import com.github.zhongl.housemd.instrument.Advice
20 |
21 | /**
22 | * @author zhongl
23 | */
24 | trait AdviceReflection {
25 |
26 | def invoke(c: String, m: String, d: String, i: AnyRef, a: Array[AnyRef], r: AnyRef) {
27 | Advice.onMethodBegin(c, m, d, i, a)
28 | Advice.onMethodEnd(r)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/ClassSimpleNameCompleterSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import org.mockito.Mockito._
21 | import instrument.Instrumentation
22 | import org.scalatest.matchers.ShouldMatchers
23 |
24 | /**
25 | * @author zhongl
26 | */
27 |
28 | class ClassSimpleNameCompleterSpec extends FunSpec with ShouldMatchers {
29 |
30 | val c = new ClassSimpleNameCompleter {
31 | lazy val inst = {
32 | val m = mock(classOf[Instrumentation])
33 | doReturn(Array(classOf[BB])).when(m).getAllLoadedClasses
34 | m
35 | }
36 | }
37 |
38 | describe("ClassSimpleNameCompleter") {
39 | it("should complete nothing without input") {
40 | val candidates = new java.util.ArrayList[CharSequence]()
41 | c.complete(" ", 3, candidates) should be(-1)
42 | }
43 |
44 | it("should complete nothing with unknown prefix") {
45 | val candidates = new java.util.ArrayList[CharSequence]()
46 | c.complete("XX", 2, candidates) should be(-1)
47 | }
48 |
49 | it("should complete BB") {
50 | val candidates = new java.util.ArrayList[CharSequence]()
51 | c.complete("B", 1, candidates) should be(0)
52 | candidates should contain("BB".asInstanceOf[CharSequence])
53 | }
54 | }
55 |
56 | }
57 |
58 | class BB {}
59 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/EnvSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import org.scalatest.FunSpec
4 | import com.github.zhongl.yascli.PrintOut
5 | import java.io.ByteArrayOutputStream
6 | import org.scalatest.matchers.ShouldMatchers
7 |
8 | /**
9 | * @author zhongl
10 | */
11 | class EnvSpec extends FunSpec with ShouldMatchers {
12 | describe("Env") {
13 | it("should print env with key name") {
14 | val out = new ByteArrayOutputStream()
15 | val env = new Env(PrintOut(out))
16 | val name = "USER"
17 | env parse (name.split("\\s+"))
18 | env.run()
19 | out.toString should be(name + " = " + System.getenv(name) + "\n")
20 | }
21 |
22 | it("should list env with regex") {
23 | val out = new ByteArrayOutputStream()
24 | val env = new Env(PrintOut(out))
25 | val name = "USER"
26 | env parse ("-e US.*".split("\\s+"))
27 | env.run()
28 | out.toString should be(name + " = " + System.getenv(name) + "\n")
29 | }
30 |
31 | it("should complete USER") {
32 | val candidates = new java.util.ArrayList[CharSequence]()
33 | new Env(null).complete("US", 2, candidates)
34 | candidates should {
35 | have size (1)
36 | contain("USER".asInstanceOf[CharSequence])
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/InspectSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import org.scalatest.matchers.ShouldMatchers
21 | import org.mockito.Mockito._
22 | import instrument.Instrumentation
23 | import com.github.zhongl.yascli.PrintOut
24 | import java.io.ByteArrayOutputStream
25 | import actors.Actor._
26 | import actors.TIMEOUT
27 | import com.github.zhongl.test.{G, A}
28 | import java.util
29 | import com.github.zhongl.housemd.duck.Duck
30 |
31 | /**
32 | * @author zhongl
33 | */
34 |
35 | class InspectSpec extends FunSpec with ShouldMatchers with AdviceReflection {
36 |
37 | describe("Inspect") {
38 | it("should display G.i") {
39 | val inst = mock(classOf[Instrumentation])
40 | val out = new ByteArrayOutputStream()
41 | val inspect = new Inspect(inst, PrintOut(out))
42 |
43 | inspect.parse("-l 1 G.i".split("\\s+"))
44 |
45 | doReturn(Array(classOf[G])).when(inst).getAllLoadedClasses
46 |
47 | val host = self
48 | actor {
49 | inspect.run()
50 | host ! "exit"
51 | }
52 |
53 | var cond = true
54 | val g = new G
55 | while (cond) {
56 | host.receiveWithin(10) {
57 | case TIMEOUT =>
58 | invoke(classOf[A].getName, "m", "()V", g, Array.empty[AnyRef], null)
59 | case "exit" => cond = false
60 | }
61 | }
62 |
63 | out.toString.split("\n").filter(l => !l.isEmpty && !l.startsWith("INFO")) should contain("G.i 5 " + g + " " + g
64 | .getClass
65 | .getClassLoader)
66 | }
67 | }
68 |
69 | it("should complete G.i") {
70 | val inst = mock(classOf[Instrumentation])
71 | val inspect = new Inspect(inst, null)
72 |
73 | doReturn(Array(classOf[G])).when(inst).getAllLoadedClasses
74 |
75 | val candidates = new util.ArrayList[CharSequence]()
76 | inspect.complete("G.", 2, candidates) should be(2)
77 |
78 | candidates should contain("i".asInstanceOf[CharSequence])
79 | }
80 |
81 | it("should complete Duck") {
82 | val inst = mock(classOf[Instrumentation])
83 | val inspect = new Inspect(inst, null)
84 |
85 | doReturn(Array(classOf[Duck])).when(inst).getAllLoadedClasses
86 |
87 | val candidates = new util.ArrayList[CharSequence]()
88 | inspect.complete("Duc", 3, candidates) should be(0)
89 |
90 | candidates should contain("Duck".asInstanceOf[CharSequence])
91 | }
92 |
93 | }
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/LastSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import org.scalatest.FunSpec
4 | import org.scalatest.matchers.ShouldMatchers
5 | import java.io.ByteArrayOutputStream
6 | import com.github.zhongl.yascli.PrintOut
7 | import org.mockito.Mockito._
8 | import instrument.Instrumentation
9 |
10 | /**
11 | * @author zhongl
12 | */
13 | class LastSpec extends FunSpec with ShouldMatchers {
14 | describe("Last") {
15 | it("should show exception stack traces of last error") {
16 | val bout = new ByteArrayOutputStream()
17 | val last = new Last(PrintOut(bout))
18 | val throwable = new Exception()
19 | last.keep(throwable)
20 | last.parse(Array.empty[String])
21 | last.run()
22 | bout.toString should be(throwable.getStackTrace.map("\t" + _) mkString("", "\n", "\n"))
23 | }
24 |
25 | it("should show no exception stacks trace with out last error"){
26 | val bout = new ByteArrayOutputStream()
27 | val last = new Last(PrintOut(bout))
28 | last.parse(Array.empty[String])
29 | last.run()
30 | bout.toString should be ("There is no error.\n")
31 | }
32 |
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/LoadedSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import com.github.zhongl.yascli.PrintOut
21 | import org.scalatest.matchers.ShouldMatchers
22 | import org.mockito.Mockito._
23 | import instrument.Instrumentation
24 | import java.io.ByteArrayOutputStream
25 | import annotation.tailrec
26 |
27 | /**
28 | * @author zhongl
29 | */
30 | class LoadedSpec extends FunSpec with ShouldMatchers {
31 | describe("Loaded") {
32 | it("should display the source jar of String") {
33 | parseAndRun("String") { _ should startWith("java.lang.String -> ") }
34 | }
35 |
36 | it("should display the classloader hierarchies") {
37 | parseAndRun("-h Loaded") {
38 | out =>
39 | val lines = out.split("\n")
40 | lines.head should startWith("com.github.zhongl.housemd.command.Loaded -> ")
41 |
42 | @tailrec
43 | def eq(list: List[String], classLoader: ClassLoader) {
44 | list match {
45 | case head :: tail => head should endWith(classLoader.toString); eq(tail, classLoader.getParent)
46 | case Nil => // end
47 | }
48 | }
49 |
50 | eq(lines.tail.toList, classOf[Loaded].getClassLoader)
51 | }
52 | }
53 |
54 | it("should complete class simple name") {
55 | complete("Lo") {
56 | (cursor, candidates) =>
57 | cursor should be(0)
58 | candidates should contain("Loaded".asInstanceOf[CharSequence])
59 | }
60 | complete("-h Lo") {
61 | (cursor, candidates) =>
62 | cursor should be(3)
63 | candidates should contain("Loaded".asInstanceOf[CharSequence])
64 | }
65 | }
66 |
67 | it("should complete all class simple name") {
68 | complete("") {
69 | (cursor, candidates) =>
70 | cursor should be(0)
71 | candidates should not be ('empty)
72 | }
73 | }
74 |
75 | }
76 |
77 | def complete(buffer: String)(verify: (Int, java.util.List[CharSequence]) => Unit) {
78 |
79 | val inst = mock(classOf[Instrumentation])
80 | val out = new ByteArrayOutputStream()
81 |
82 | doReturn(Array(classOf[String], classOf[Loaded])).when(inst).getAllLoadedClasses
83 |
84 | val loaded = new Loaded(inst, PrintOut(out))
85 |
86 | val candidates = new java.util.ArrayList[CharSequence]()
87 | val cursor = loaded.complete(buffer, buffer.length, candidates)
88 | verify(cursor, candidates)
89 | }
90 |
91 |
92 | def parseAndRun(arguments: String)(verify: String => Unit) {
93 | val inst = mock(classOf[Instrumentation])
94 | val out = new ByteArrayOutputStream()
95 |
96 | doReturn(Array(classOf[String], classOf[Loaded])).when(inst).getAllLoadedClasses
97 |
98 | val loaded = new Loaded(inst, PrintOut(out))
99 |
100 | loaded.parse(arguments.split("\\s+"))
101 | loaded.run()
102 | verify(out.toString)
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/MethodFilterCompleterSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import org.scalatest.matchers.ShouldMatchers
21 | import org.mockito.Mockito._
22 | import instrument.Instrumentation
23 |
24 | /**
25 | * @author zhongl
26 | */
27 |
28 | class MethodFilterCompleterSpec extends FunSpec with ShouldMatchers {
29 | val c = new MethodFilterCompleter {
30 | lazy val inst = {
31 | val m = mock(classOf[Instrumentation])
32 | doReturn(Array(classOf[CCC], classOf[Runnable])).when(m).getAllLoadedClasses
33 | m
34 | }
35 | }
36 |
37 | describe("MethodFilterCompleter") {
38 | it("should complete CCC") {
39 | val candidates = new java.util.ArrayList[CharSequence]()
40 | c.complete("C", 1, candidates) should be(0)
41 | candidates should contain("CCC".asInstanceOf[CharSequence])
42 | }
43 |
44 | it("should complete CCC.m*") {
45 | val candidates = new java.util.ArrayList[CharSequence]()
46 | c.complete("CCC.m", 5, candidates) should be(4)
47 | candidates should {
48 | contain("".asInstanceOf[CharSequence])
49 | contain("m1".asInstanceOf[CharSequence])
50 | contain("m22".asInstanceOf[CharSequence])
51 | }
52 | }
53 |
54 | it("should complete Runnable with +") {
55 | val candidates = new java.util.ArrayList[CharSequence]()
56 | c.complete("R", 1, candidates) should be(0)
57 | candidates should contain("Runnable+".asInstanceOf[CharSequence])
58 | }
59 |
60 | it("should complete Runnable+. ") {
61 | val candidates = new java.util.ArrayList[CharSequence]()
62 | c.complete("Runnable+.", 10, candidates) should be(10)
63 | candidates should contain("run".asInstanceOf[CharSequence])
64 | }
65 |
66 | it("should complete Runnable+.r ") {
67 | val candidates = new java.util.ArrayList[CharSequence]()
68 | c.complete("Runnable+.r", 11, candidates) should be(10)
69 | candidates should contain("run".asInstanceOf[CharSequence])
70 | }
71 | }
72 |
73 | }
74 |
75 | class CCC {
76 | def m1() {}
77 |
78 | def m22() {}
79 | }
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/MethodFilterSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import org.scalatest.matchers.ShouldMatchers
21 |
22 | /**
23 | * @author zhongl
24 | */
25 |
26 | class MethodFilterSpec extends FunSpec with ShouldMatchers {
27 | describe("MethodFilter") {
28 | it("should include R by Runnable+") {
29 | MethodFilter("Runnable+").filter(classOf[R]) should be(true)
30 | }
31 |
32 | it("should include R by Runnable+.run") {
33 | MethodFilter("Runnable+.run").filter(classOf[R]) should be(true)
34 | }
35 |
36 | it("should include R and run by Runnable+.run") {
37 | MethodFilter("Runnable+.run").filter(classOf[R], "run") should be(true)
38 | }
39 |
40 | it("should include R by R.m") {
41 | MethodFilter("R.m").filter(classOf[R]) should be(true)
42 | }
43 | }
44 | }
45 |
46 | class R extends Runnable {
47 | def run() {}
48 |
49 | private def m() {}
50 | }
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/PropSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import org.scalatest.FunSpec
4 | import org.scalatest.matchers.ShouldMatchers
5 | import java.io.ByteArrayOutputStream
6 | import com.github.zhongl.yascli.PrintOut
7 |
8 | /**
9 | * @author zhongl
10 | */
11 | class PropSpec extends FunSpec with ShouldMatchers {
12 | describe("Prop") {
13 | it("should print property with key name") {
14 | val out = new ByteArrayOutputStream()
15 | val prop = new Prop(PrintOut(out))
16 | val name = "java.home"
17 | prop parse (name.split("\\s+"))
18 | prop.run()
19 | out.toString should be(name + " = " + System.getProperty(name) + "\n")
20 | }
21 |
22 | it("should list properties with regex") {
23 | val out = new ByteArrayOutputStream()
24 | val prop = new Prop(PrintOut(out))
25 | val name = "java.home"
26 | prop parse ("-e java\\.ho.*".split("\\s+"))
27 | prop.run()
28 | out.toString should be(name + " = " + System.getProperty(name) + "\n")
29 | }
30 |
31 | it("should complete java.home") {
32 | val candidates = new java.util.ArrayList[CharSequence]()
33 | new Prop(null).complete("java.ho", 7, candidates)
34 | candidates should {
35 | have size (1)
36 | contain("java.home".asInstanceOf[CharSequence])
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/ResourcesSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.command
2 |
3 | import org.scalatest.FunSpec
4 | import org.scalatest.matchers.ShouldMatchers
5 | import java.io.ByteArrayOutputStream
6 | import org.mockito.Mockito._
7 | import instrument.Instrumentation
8 | import com.github.zhongl.test.A
9 | import com.github.zhongl.yascli.PrintOut
10 |
11 | /**
12 | * @author zhongl
13 | */
14 | class ResourcesSpec extends FunSpec with ShouldMatchers{
15 | describe("Resources") {
16 | it("list source path of res.xml") {
17 | val out = new ByteArrayOutputStream
18 | val inst = mock(classOf[Instrumentation])
19 | doReturn(Array(classOf[A])).when(inst).getAllLoadedClasses
20 |
21 | val resources = new Resources(inst, PrintOut(out))
22 |
23 | resources parse("res.xml".split("\\s+"))
24 | resources run()
25 | out.toString should be (classOf[A].getClassLoader.getResource("res.xml") + "\n")
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/TraceSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import org.scalatest.FunSpec
20 | import org.mockito.Mockito._
21 | import instrument.Instrumentation
22 | import com.github.zhongl.yascli.PrintOut
23 | import actors.Actor._
24 | import actors.TIMEOUT
25 | import org.scalatest.matchers.ShouldMatchers
26 | import io.Source
27 | import java.io.{File, ByteArrayOutputStream}
28 | import com.github.zhongl.test._
29 |
30 | /**
31 | * @author zhongl
32 | */
33 |
34 | class TraceSpec extends FunSpec with ShouldMatchers with AdviceReflection {
35 |
36 | def parseAndRun(arguments: String, resultOrException: AnyRef = null)(verify: (String, File, File) => Unit) {
37 | val out = new ByteArrayOutputStream
38 | val inst = mock(classOf[Instrumentation])
39 | doReturn(Array(classOf[A], classOf[String])).when(inst).getAllLoadedClasses
40 |
41 | val trace = new Trace(inst, PrintOut(out))
42 |
43 | trace.parse(arguments.split("\\s+"))
44 |
45 | trace.detailFile.delete()
46 | trace.stackFile.delete()
47 |
48 | val host = self
49 | actor { trace.run(); host ! "exit" }
50 |
51 | var cond = true
52 | while (cond) {
53 | host.receiveWithin(10) {
54 | case TIMEOUT =>
55 | val a = new A
56 | invoke(classOf[A].getName, "", "()V", a, Array.empty[AnyRef], resultOrException)
57 | invoke(classOf[A].getName, "m", "()V", a, Array.empty[AnyRef], resultOrException)
58 | case "exit" => cond = false
59 | }
60 | }
61 |
62 | verify(out.toString, trace.detailFile, trace.stackFile)
63 | }
64 |
65 | describe("Trace") {
66 | it("should display statistics") {
67 | parseAndRun("-t 3 A.m") {
68 | (out, detail, stack) =>
69 | val methodFullName = """[\.\w\(\),\$ <>]+"""
70 | val objectToString = """[\.\w,@\$ \[\]]+"""
71 | val number = """\d+\s+"""
72 | val elapse = """\d+ms"""
73 | out.split("\n").filter(s => !s.startsWith("INFO") && !s.isEmpty).tail foreach {
74 | _ should fullyMatch regex (methodFullName + objectToString + number + elapse + objectToString)
75 | }
76 | }
77 | }
78 |
79 | it("should output invocation details") {
80 | parseAndRun("-d -t 1 -l 1 A") {
81 | (out, detail, stack) =>
82 | out.split("\n") should contain("INFO : You can get invocation detail from " + detail)
83 |
84 | val date = """\d{4}-\d{2}-\d{2}"""
85 | val time = """\d{2}:\d{2}:\d{2}"""
86 | val elapse = """\d+ms"""
87 | val thread = """\[[^\]]+\]"""
88 | val thisObject = """com\.github\.zhongl\.test\.A@[\da-f]+"""
89 | val name = """com\.github\.zhongl\.test\.A\.(m|)"""
90 | val arguments = """\[\]"""
91 | val result = "void"
92 | Source.fromFile(detail).getLines() foreach {
93 | _ should fullyMatch regex ((date :: time :: elapse :: thread :: thisObject :: name :: arguments :: result :: Nil)
94 | .mkString(" "))
95 | }
96 | }
97 | }
98 |
99 | it("should output invocation details with exception stack trace") {
100 | parseAndRun("-d -t 1 -l 1 A", new Exception) {
101 | (out, detail, stack) =>
102 | val date = """\d{4}-\d{2}-\d{2}"""
103 | val time = """\d{2}:\d{2}:\d{2}"""
104 | val elapse = """\d+ms"""
105 | val thread = """\[[^\]]+\]"""
106 | val thisObject = """com\.github\.zhongl\.test\.A@[\da-f]+"""
107 | val name = """com\.github\.zhongl\.test\.A\.(m|)"""
108 | val arguments = """\[\]"""
109 | val result = "java\\.lang\\.Exception"
110 | val log = (date :: time :: elapse :: thread :: thisObject :: name :: arguments :: result :: Nil).mkString(" ")
111 | Source.fromFile(detail).getLines() foreach {
112 | _ should {
113 | fullyMatch regex log or fullyMatch regex ("\\tat .+")
114 | }
115 | }
116 | }
117 | }
118 |
119 | it("should output invocation stack") {
120 | parseAndRun("-s -l 1 A") {
121 | (out, detail, stack) =>
122 | out.split("\n") should contain("INFO : You can get invocation stack from " + stack)
123 |
124 | val lines = Source.fromFile(stack).getLines().toList.dropRight(1)
125 | val head = """com\.github\.zhongl\.test\.A\.m\(\)V call by thread \[[\w-]+\]""".r
126 | val st = """\t\S+\(\S+:\d+\)""".r
127 | lines.tail foreach {
128 | _ match {
129 | case head() =>
130 | case st() =>
131 | case "" =>
132 | case _ => fail()
133 | }
134 | }
135 | }
136 | }
137 |
138 | it("should only include package com.github") {
139 | parseAndRun("-p com\\.github String") {
140 | (out, detail, stack) =>
141 | out should not be ("No matched class")
142 | }
143 | }
144 |
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/command/TransformCommandSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.command
18 |
19 | import instrument.Instrumentation
20 | import org.scalatest.FunSpec
21 | import org.scalatest.matchers.ShouldMatchers
22 | import java.io.ByteArrayOutputStream
23 | import org.mockito.Mockito._
24 | import actors.Actor._
25 | import actors.TIMEOUT
26 | import com.github.zhongl.yascli.PrintOut
27 | import com.github.zhongl.test._
28 | import com.github.zhongl.housemd.duck.Duck
29 | import com.github.zhongl.housemd.instrument.{Context, Hook}
30 |
31 | /**
32 | * @author zhongl
33 | */
34 |
35 | class TransformCommandSpec extends FunSpec with ShouldMatchers with AdviceReflection {
36 |
37 | class Concrete(val inst: Instrumentation, out: PrintOut)
38 | extends TransformCommand("concrete", "test mock.", inst, out) {
39 |
40 | private val methodFilter = parameter[MethodFilter]("method-filter", "")
41 |
42 | protected def hook = new Hook() {
43 | def enterWith(context: Context) {}
44 |
45 | def exitWith(context: Context) {}
46 |
47 | def heartbeat(now: Long) {}
48 |
49 | def finalize(throwable: Option[Throwable]) {}
50 | }
51 |
52 | protected def isCandidate(klass: Class[_]) = methodFilter().filter(klass)
53 |
54 | protected def isDecorating(klass: Class[_], methodName: String) = methodFilter().filter(klass, methodName)
55 | }
56 |
57 | def parseAndRun(arguments: String)(verify: (String) => Unit) {
58 | val out = new ByteArrayOutputStream
59 | val inst = mock(classOf[Instrumentation])
60 |
61 | doReturn(Array(classOf[I], classOf[A], classOf[F], classOf[String], classOf[Duck])).when(inst).getAllLoadedClasses
62 |
63 | val concrete = new Concrete(inst, PrintOut(out))
64 |
65 | concrete.parse(arguments.split("\\s+"))
66 |
67 | val host = self
68 | actor { concrete.run(); host ! "exit" }
69 |
70 | var cond = true
71 | while (cond) {
72 | host.receiveWithin(10) {
73 | case TIMEOUT =>
74 | invoke(classOf[A].getName, "m", "()V", new A, Array.empty[AnyRef], null)
75 | invoke(classOf[F].getName, "m", "()V", new A, Array.empty[AnyRef], null)
76 | case "exit" => cond = false
77 | }
78 | }
79 |
80 | verify(out.toString)
81 | }
82 |
83 |
84 | describe("TransformCommand") {
85 | it("should probe final class") {
86 | parseAndRun("-l 1 F.m") {
87 | out =>
88 | out.split("\n").filter(!_.startsWith("INFO")) foreach {
89 | _ should fullyMatch regex ("F.+")
90 | }
91 | }
92 | }
93 |
94 | it("should reset by overlimit") {
95 | parseAndRun("-l 1 -t 3 A") {
96 | out =>
97 | out.split("\n").dropRight(1).last should be("INFO : Ended by overlimit")
98 | }
99 | }
100 |
101 | it("should reset by timeout") {
102 | parseAndRun("-l 100000 -t 1 A") {
103 | out =>
104 | out.split("\n").dropRight(1).last should be("INFO : Ended by timeout")
105 | }
106 | }
107 |
108 | it("should not probe class loaded by boot classloader") {
109 | parseAndRun("String") {
110 | out =>
111 | out.split("\n").head should be("WARN : Skip " + classOf[String] + " loaded from bootclassloader")
112 | }
113 | }
114 |
115 | it("should not probe interface") {
116 | parseAndRun("I") {
117 | out =>
118 | out.split("\n") should {
119 | contain("WARN : Skip " + classOf[I])
120 | contain("No matched class")
121 | }
122 | }
123 | }
124 |
125 | it("should not probe classes belongs to HouseMD") {
126 | parseAndRun("Duck") {
127 | out =>
128 | out.split("\n") should {
129 | contain("WARN : Skip " + classOf[Duck] + " belongs to HouseMD.")
130 | contain("No matched class")
131 | }
132 | }
133 | }
134 |
135 | it("should probe F by I+") {
136 | parseAndRun("-l 1 I+") {
137 | out =>
138 | val withoutInfo = out.split("\n").filter(!_.startsWith("INFO"))
139 | withoutInfo.head should be("WARN : Skip " + classOf[I] + " ")
140 | withoutInfo.tail foreach {
141 | _ should fullyMatch regex ("F.+")
142 | }
143 | }
144 | }
145 |
146 | }
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/misc/ObjUtilsSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.housemd.misc;
2 |
3 | import org.scalatest.FunSpec
4 | import org.scalatest.matchers.ShouldMatchers
5 |
6 | /**
7 | * @author eagleinfly
8 | */
9 | class ObjUtilsSpec extends FunSpec with ShouldMatchers {
10 | describe("ObjUtils") {
11 | it("should format object in json format") {
12 | ObjUtils.useJsonFormat()
13 | ObjUtils.toString(null) should be("null")
14 | ObjUtils.toString("") should be("\"\"")
15 | ObjUtils.toString("string") should be("\"string\"")
16 | }
17 |
18 | it("should format object in toString format") {
19 | ObjUtils.useToStringFormat()
20 | ObjUtils.toString(null) should be("null")
21 | ObjUtils.toString("") should be("")
22 | ObjUtils.toString("string") should be("string")
23 | }
24 |
25 | it("should format objects with cycle reference in json format") {
26 | class A{
27 | var b: B = null
28 | var intVal: Int = 0
29 | var intObj: java.lang.Integer = null
30 | def setB(b: B) = this.b = b
31 | }
32 |
33 | class B{
34 | var a: A = null
35 | def setA(a: A) = this.a = a
36 | }
37 |
38 | val a = new A
39 | a.b = new B
40 | a.b.a = a
41 | ObjUtils.useJsonFormat()
42 | ObjUtils.toString(a) should not(be(null))
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/misc/ReflectionUtilsSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.misc
18 |
19 | import org.scalatest.FunSpec
20 | import org.scalatest.matchers.ShouldMatchers
21 | import com.github.zhongl.housemd.instrument.Advice
22 | import com.github.zhongl.test.A
23 |
24 | /**
25 | * @author zhongl
26 | */
27 | class ReflectionUtilsSpec extends FunSpec with ShouldMatchers {
28 | describe("ReflectionUtils") {
29 | it("should define Advice") {
30 | ReflectionUtils.loadOrDefine(classOf[Advice], new ClassLoader() {
31 | override def loadClass(name: String) = {
32 | if (name == classOf[Advice].getName) throw new ClassNotFoundException()
33 | else super.loadClass(name)
34 | }
35 | })
36 | }
37 |
38 | it("shoulde get object native string") {
39 | val o = new Object()
40 | ReflectionUtils.toNativeString(o) should be(o.toString)
41 | }
42 |
43 | it("shoulde get or force to native string") {
44 | val o = new Object()
45 | ReflectionUtils.getOrForceToNativeString(o) should be(o.toString)
46 | }
47 |
48 | it("should force to native string") {
49 | val s = "hello"
50 | ReflectionUtils.getOrForceToNativeString(s) should be(ReflectionUtils.toNativeString(s))
51 | }
52 |
53 | it("should include if it is a class") {
54 | ReflectionUtils.constructorAndMethodNamesOf(classOf[A]) should contain("")
55 | }
56 |
57 | it("should not include if it is a interface") {
58 | ReflectionUtils.constructorAndMethodNamesOf(classOf[Runnable]) should not contain ("")
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/housemd/misc/UtilsSpec.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.housemd.misc
18 |
19 | import java.io.ByteArrayInputStream
20 | import org.scalatest.FunSpec
21 | import org.scalatest.matchers.ShouldMatchers
22 | import Utils._
23 |
24 | /**
25 | * @author zhongl
26 | */
27 |
28 | class UtilsSpec extends FunSpec with ShouldMatchers {
29 |
30 | describe("Untils") {
31 | it("should convert input stream to bytes") {
32 | val array = Array('c'.toByte)
33 | val stream = new ByteArrayInputStream(array)
34 | toBytes(stream) should be(array)
35 | }
36 |
37 | it("should get source of a class") {
38 | val location = classOf[UtilsSpec].getProtectionDomain.getCodeSource.getLocation
39 | sourceOf[UtilsSpec] should be(File.unapply(location).get)
40 | }
41 |
42 | it("should get source of class which CodeSource is null") {
43 | val location = classOf[String].getResource(resourceNameOf[String])
44 | sourceOf[String] should be(File.unapply(location).get)
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/test/A.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.test
18 |
19 | /**
20 | * @author zhongl
21 | */
22 |
23 | class A {
24 | def m() {}
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/test/F.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.test
18 |
19 | /**
20 | * @author zhongl
21 | */
22 |
23 | final class F extends I {
24 | def m() {}
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/test/G.scala:
--------------------------------------------------------------------------------
1 | package com.github.zhongl.test
2 |
3 | class G {
4 | val i = 5
5 |
6 | def m() {}
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/zhongl/test/I.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 zhongl
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.zhongl.test
18 |
19 | /**
20 | * @author zhongl
21 | */
22 |
23 | trait I {
24 | def m()
25 | }
26 |
--------------------------------------------------------------------------------