Skip to content

Commit e423a46

Browse files
Mechanism for Auditing Network Access Not Isolated by Hystrix
Netflix#116 - Java agent for instrumenting blocking and non-blocking IO and notifying of these events to a registered listener. - Hystrix.getCurrentThreadExecutingCommand to allow querying if currently inside a command
1 parent 86e75ce commit e423a46

File tree

7 files changed

+478
-21
lines changed

7 files changed

+478
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apply plugin: 'java'
2+
dependencies {
3+
compile 'org.javassist:javassist:3.17.1-GA'
4+
5+
jar {
6+
// make a fatjar otherwise it's painful getting the boot-class-path correct when deploying
7+
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
8+
manifest {
9+
attributes(
10+
"Agent-Class": "com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent",
11+
"Can-Redefine-Classes": true,
12+
"Can-Retransform-Classes": true,
13+
"Boot-Class-Path": "hystrix-network-auditor-agent-" + version + ".jar",
14+
"Premain-Class": "com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent")
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
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+
package com.netflix.hystrix.contrib.networkauditor;
17+
18+
import java.lang.instrument.Instrumentation;
19+
20+
/**
21+
* Java Agent to instrument network code in the java.* libraries and use Hystrix state to determine if calls are Hystrix-isolated or not.
22+
*/
23+
public class HystrixNetworkAuditorAgent {
24+
25+
private static final HystrixNetworkAuditorEventListener DEFAULT_BRIDGE = new HystrixNetworkAuditorEventListener() {
26+
27+
@Override
28+
public void handleNetworkEvent() {
29+
// do nothing
30+
}
31+
32+
};
33+
private static volatile HystrixNetworkAuditorEventListener bridge = DEFAULT_BRIDGE;
34+
35+
public static void premain(String agentArgs, Instrumentation inst) {
36+
inst.addTransformer(new NetworkClassTransform());
37+
}
38+
39+
/**
40+
* Invoked by instrumented code when network access occurs.
41+
*/
42+
public static void notifyOfNetworkEvent() {
43+
bridge.handleNetworkEvent();
44+
}
45+
46+
/**
47+
* Register the implementation of the {@link HystrixNetworkAuditorEventListener} that will receive the events.
48+
*
49+
* @param bridge
50+
* {@link HystrixNetworkAuditorEventListener} implementation
51+
*/
52+
public static void registerEventListener(HystrixNetworkAuditorEventListener bridge) {
53+
if (bridge == null) {
54+
throw new IllegalArgumentException("HystrixNetworkAuditorEventListener can not be NULL");
55+
}
56+
HystrixNetworkAuditorAgent.bridge = bridge;
57+
}
58+
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
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+
package com.netflix.hystrix.contrib.networkauditor;
17+
18+
/**
19+
* Event listener that gets implemented and registered with {@link HystrixNetworkAuditorAgent#registerEventListener(HystrixNetworkAuditorEventListener)} by the application
20+
* running Hystrix in the application classloader into this JavaAgent running in the boot classloader so events can be invoked on code inside the application classloader.
21+
*/
22+
public interface HystrixNetworkAuditorEventListener {
23+
24+
/**
25+
* Invoked by the {@link HystrixNetworkAuditorAgent} when network events occur.
26+
* <p>
27+
* An event may be the opening of a socket, channel or reading/writing bytes. It is not guaranteed to be a one-to-one relationship with a request/response.
28+
* <p>
29+
* No arguments are returned as it is left to the implementation to decide whether the current thread stacktrace should be captured or not.
30+
* <p>
31+
* Typical implementations will want to filter to non-Hystrix isolated network traffic with code such as this:
32+
*
33+
* <pre> {@code
34+
*
35+
* if (Hystrix.getCurrentThreadExecutingCommand() == null) {
36+
* // this event is not inside a Hystrix context (according to ThreadLocal variables)
37+
* StackTraceElement[] stack = Thread.currentThread().getStackTrace();
38+
* // increment counters, fire alerts, log stack traces etc
39+
* } </pre>
40+
*
41+
*/
42+
public void handleNetworkEvent();
43+
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
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+
package com.netflix.hystrix.contrib.networkauditor;
17+
18+
import java.io.IOException;
19+
import java.lang.instrument.ClassFileTransformer;
20+
import java.lang.instrument.IllegalClassFormatException;
21+
import java.security.ProtectionDomain;
22+
23+
import javassist.CannotCompileException;
24+
import javassist.ClassPool;
25+
import javassist.CtClass;
26+
import javassist.CtConstructor;
27+
import javassist.CtMethod;
28+
import javassist.NotFoundException;
29+
30+
/**
31+
* Bytecode ClassFileTransformer used by the Java Agent to instrument network code in the java.* libraries and use Hystrix state to determine if calls are Hystrix-isolated or not.
32+
*/
33+
public class NetworkClassTransform implements ClassFileTransformer {
34+
35+
@Override
36+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
37+
String name = className.replace('/', '.');
38+
try {
39+
/*
40+
* These hook points were found through trial-and-error after wrapping all java.net/java.io/java.nio classes and methods and finding reliable
41+
* notifications that matched statistics and events in an application.
42+
*
43+
* There may very well be problems here or code paths that don't correctly trigger an event.
44+
*
45+
* If someone can provide a more reliable and cleaner hook point or if there are examples of code paths that don't trigger either of these
46+
* then please file a bug or submit a pull request at https://github.com/Netflix/Hystrix
47+
*/
48+
if (name.equals("java.net.Socket$2")) {
49+
// this one seems to be fairly reliable in counting each time a request/response occurs on blocking IO
50+
return wrapConstructorsOfClass(name);
51+
} else if (name.equals("java.nio.channels.SocketChannel")) {
52+
// handle NIO
53+
return wrapConstructorsOfClass(name);
54+
}
55+
} catch (Exception e) {
56+
throw new RuntimeException("Failed trying to wrap class: " + className, e);
57+
}
58+
59+
// we didn't transform anything so return null to leave untouched
60+
return null;
61+
}
62+
63+
/**
64+
* Wrap all constructors of a given class
65+
*
66+
* @param className
67+
* @throws NotFoundException
68+
* @throws CannotCompileException
69+
* @throws IOException
70+
*/
71+
private byte[] wrapConstructorsOfClass(String className) throws NotFoundException, IOException, CannotCompileException {
72+
return wrapClass(className, true);
73+
}
74+
75+
/**
76+
* Wrap all signatures of a given method name.
77+
*
78+
* @param className
79+
* @param methodName
80+
* @throws NotFoundException
81+
* @throws CannotCompileException
82+
* @throws IOException
83+
*/
84+
private byte[] wrapClass(String className, boolean wrapConstructors, String... methodNames) throws NotFoundException, IOException, CannotCompileException {
85+
ClassPool cp = ClassPool.getDefault();
86+
CtClass ctClazz = cp.get(className);
87+
// constructors
88+
if (wrapConstructors) {
89+
CtConstructor[] constructors = ctClazz.getConstructors();
90+
for (CtConstructor constructor : constructors) {
91+
try {
92+
constructor.insertBefore("{ com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent.notifyOfNetworkEvent(); }");
93+
} catch (Exception e) {
94+
throw new RuntimeException("Failed trying to wrap constructor of class: " + className, e);
95+
}
96+
97+
}
98+
}
99+
// methods
100+
CtMethod[] methods = ctClazz.getDeclaredMethods();
101+
for (CtMethod method : methods) {
102+
try {
103+
for (String methodName : methodNames) {
104+
if (method.getName().equals(methodName)) {
105+
method.insertBefore("{ com.netflix.hystrix.contrib.networkauditor.HystrixNetworkAuditorAgent.handleNetworkEvent(); }");
106+
}
107+
}
108+
} catch (Exception e) {
109+
throw new RuntimeException("Failed trying to wrap method [" + method.getName() + "] of class: " + className, e);
110+
}
111+
}
112+
return ctClazz.toBytecode();
113+
}
114+
115+
}

0 commit comments

Comments
 (0)