├── .gitignore ├── crack └── censum-full.jar ├── javaagent-demo-01 ├── Test01.java ├── pom.xml └── src │ └── main │ └── java │ └── me │ └── geek01 │ └── javaagent │ ├── AgentMain.java │ └── AgentPreMain.java ├── javaagent-demo ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── me │ └── geek01 │ └── javaagent │ ├── AgentMain.java │ ├── Aspect.java │ ├── AspectInfo.java │ ├── BaseAdviceAdapter.java │ ├── BuildConfig.java │ ├── DapperSpanLifecycleListener.java │ ├── HttpRequestTracingUtils.java │ ├── HttpServletReflectUtils.java │ ├── HttpSpanFactory.java │ ├── JedisCmdAdviceAdapter.java │ ├── Log.java │ ├── MessageQueue.java │ ├── MyClassFileTransformer.java │ ├── MyHttpServletAdviceAdapter.java │ ├── OnAfter.java │ ├── OnBefore.java │ ├── Record.java │ ├── RequestWithHeaders.java │ ├── RequestWithHeadersServletAdapter.java │ ├── RootSpanSamplingStrategy.java │ ├── SampleAllTheThingsStrategy.java │ ├── Span.java │ ├── SpanLifecycleListener.java │ ├── SpanModel.java │ ├── TraceAndSpanIdGenerator.java │ ├── TraceClassTransform.java │ ├── TraceHeaders.java │ ├── Tracer.java │ └── Wildcard.java ├── jvm └── bytecode │ ├── Hello.class │ └── Hello.java └── zero_copy ├── zero_copy_basic ├── CMakeLists.txt ├── index.html └── main.c └── zerocopy_sendfile ├── CMakeLists.txt ├── index.html └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | CMakeCache.txt 4 | CMakeFiles 5 | Makefile 6 | cmake-build-debug 7 | cmake_install.cmake 8 | target 9 | *.class 10 | bk 11 | dependency-reduced-pom.xml 12 | libs 13 | -------------------------------------------------------------------------------- /crack/censum-full.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthur-zhang/geek01/776923f0ec91ec235bbe08cfa398cff33c96daa1/crack/censum-full.jar -------------------------------------------------------------------------------- /javaagent-demo-01/Test01.java: -------------------------------------------------------------------------------- 1 | public class Test01 { 2 | public static void main(String[] args) { 3 | System.out.println("in test01 main"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /javaagent-demo-01/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | javaagent-demo 8 | javaagent-demo 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | my-javaagent 14 | 15 | 16 | org.apache.maven.plugins 17 | maven-jar-plugin 18 | 19 | 20 | 21 | me.geek01.javaagent.AgentMain 22 | me.geek01.javaagent.AgentPreMain 23 | true 24 | true 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | 33 | 8 34 | 8 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /javaagent-demo-01/src/main/java/me/geek01/javaagent/AgentMain.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.Instrumentation; 5 | import java.security.ProtectionDomain; 6 | 7 | public class AgentMain { 8 | 9 | public static void agentmain(String agentArgs, Instrumentation inst){ 10 | System.out.println("agentmain called: " + agentArgs); 11 | inst.addTransformer(new ClassFileTransformer() { 12 | 13 | @Override 14 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, 15 | ProtectionDomain protectionDomain, byte[] classfileBuffer) { 16 | System.out.println("agentmain load Class :" + className); 17 | return classfileBuffer; 18 | } 19 | }, true); 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /javaagent-demo-01/src/main/java/me/geek01/javaagent/AgentPreMain.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.io.IOException; 4 | import java.lang.instrument.ClassFileTransformer; 5 | import java.lang.instrument.Instrumentation; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.security.ProtectionDomain; 10 | 11 | /** 12 | * Created By Arthur Zhang at 2018-12-18 13 | */ 14 | public class AgentPreMain { 15 | 16 | public static class ClassLoggerTransformer implements ClassFileTransformer { 17 | @Override 18 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, 19 | ProtectionDomain protectionDomain, byte[] classfileBuffer) { 20 | System.out.println("transform: " + className); 21 | Path path = Paths.get("/tmp/" + className.replaceAll("/", ".") + ".class"); 22 | try { 23 | Files.write(path, classfileBuffer); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | return classfileBuffer; 28 | } 29 | } 30 | 31 | public static void premain(String agentArgument, Instrumentation instrumentation) { 32 | ClassFileTransformer classFileTransformer = new ClassLoggerTransformer(); 33 | instrumentation.addTransformer(classFileTransformer, true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /javaagent-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | javaagent-demo.iml 3 | target/ 4 | dependency-reduced-pom.xml 5 | 6 | -------------------------------------------------------------------------------- /javaagent-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | javaagent-demo 8 | javaagent-demo 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.ow2.asm 14 | asm 15 | 5.2 16 | 17 | 18 | org.ow2.asm 19 | asm-commons 20 | 5.2 21 | 22 | 23 | commons-io 24 | commons-io 25 | 2.5 26 | 27 | 28 | org.apache.commons 29 | commons-lang3 30 | 3.4 31 | 32 | 33 | com.google.guava 34 | guava 35 | 19.0 36 | 37 | 38 | 39 | com.alibaba 40 | fastjson 41 | 1.2.49 42 | 43 | 44 | 45 | my-javaagent 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-jar-plugin 50 | 51 | 52 | 53 | me.geek01.javaagent.AgentMain 54 | me.geek01.javaagent.AgentMain 55 | true 56 | true 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-shade-plugin 64 | 2.4.3 65 | 66 | 67 | package 68 | 69 | 70 | shade 71 | 72 | 73 | 74 | 75 | 76 | org.ow2.asm 77 | me.geek01.javaagent.hidden.org.ow2.asm 78 | 79 | 80 | org.objectweb.asm 81 | me.geek01.javaagent.hidden.org.objectweb.asm 82 | 83 | 84 | com.google 85 | me.geek01.javaagent.hidden.com.google 86 | 87 | 88 | org.apache.commons.lang3 89 | me.geek01.javaagent.hidden.org.apache.commons.lang3 90 | 91 | 92 | okio 93 | me.geek01.javaagent.hidden.okio 94 | 95 | 96 | okhttp3 97 | me.geek01.javaagent.hidden.okhttp3 98 | 99 | 100 | com.alibaba.fastjson 101 | me.geek01.javaagent.hidden.com.alibaba.fastjson 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-compiler-plugin 112 | 113 | 7 114 | 7 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/AgentMain.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import com.google.common.base.Splitter; 4 | 5 | import java.lang.instrument.Instrumentation; 6 | import java.lang.reflect.Method; 7 | import java.util.*; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * Created By Arthur Zhang at 2018-12-18 12 | */ 13 | public class AgentMain { 14 | public static final String TYPE_TOMCAT_WAR = "tomcat"; 15 | public static String agentType = ""; 16 | private static final String AGENT_ID_PARAMS_KEY = "agentId"; 17 | private static final String AGENT_TYPE_PARAMS_KEY = "agentType"; 18 | private static final String APP_ID_PARAMS_KEY = "appId"; 19 | public static String agentId = ""; 20 | public static String appId = ""; 21 | 22 | public static void premain(String agentArgument, Instrumentation instrumentation) { 23 | 24 | if (agentArgument == null) { 25 | return; 26 | } 27 | String[] parts = agentArgument.split(","); 28 | for (String item : parts) { 29 | if (!item.contains(":")) { 30 | continue; 31 | } 32 | String[] keyVals = item.split(":"); 33 | if (keyVals.length != 2) { 34 | continue; 35 | } 36 | if (APP_ID_PARAMS_KEY.equals(keyVals[0])) { 37 | appId = keyVals[1]; 38 | } else if (AGENT_ID_PARAMS_KEY.equals(keyVals[0])) { 39 | agentId = keyVals[1]; 40 | } else if (AGENT_TYPE_PARAMS_KEY.equals(keyVals[0])) { 41 | agentType = keyVals[1]; 42 | } 43 | 44 | } 45 | System.out.println(appId + "\t" + agentType + "\t" + agentId); 46 | Tracer.getInstance().addSpanLifecycleListener(new DapperSpanLifecycleListener()); 47 | 48 | initPlugins(); 49 | instrumentation.addTransformer(new MyClassFileTransformer(), true); 50 | } 51 | 52 | 53 | public static Set pluginClassNameSet = new HashSet<>(); 54 | 55 | public static Map> pluginMaps = new ConcurrentHashMap<>(); 56 | public static void initPlugins() { 57 | 58 | List clz = new ArrayList<>(); 59 | 60 | 61 | Class[] clzCommon = new Class[] { 62 | MyHttpServletAdviceAdapter.class, 63 | JedisCmdAdviceAdapter.class 64 | }; 65 | clz.addAll(Arrays.asList(clzCommon)); 66 | 67 | for (Class cls : clz) { 68 | Aspect anno = (Aspect) cls.getAnnotation(Aspect.class); 69 | if (anno == null) continue; 70 | 71 | String className = anno.className(); 72 | pluginClassNameSet.add(className); 73 | String[] methodList = anno.method(); 74 | List list = new ArrayList<>(); 75 | 76 | for (String item : methodList) { 77 | AspectInfo aspectInfo = new AspectInfo(); 78 | aspectInfo.clz = cls; 79 | List parts = Splitter.on(" ").splitToList(item); 80 | if (parts.size() > 2) { 81 | continue; 82 | } 83 | 84 | if (parts.size() == 2) { 85 | aspectInfo.methodName = parts.get(0) ; 86 | aspectInfo.methodDesc = parts.get(1) ; 87 | } 88 | if (parts.size() == 1) { 89 | aspectInfo.methodName = parts.get(0) ; 90 | aspectInfo.methodDesc = "*"; 91 | } 92 | list.add(aspectInfo); 93 | } 94 | pluginMaps.put(className, list); 95 | 96 | java.lang.reflect.Method[] methods = cls.getDeclaredMethods(); 97 | Method onBeforeMethod = null; 98 | Method onReturnMethod = null; 99 | for (Method method : methods) { 100 | OnBefore onBeforeAnnotation = method.getAnnotation(OnBefore.class); 101 | OnAfter onAfterAnnotation = method.getAnnotation(OnAfter.class); 102 | 103 | if (onBeforeAnnotation != null) { 104 | onBeforeMethod = method; 105 | } 106 | if (onAfterAnnotation != null) { 107 | onReturnMethod = method; 108 | } 109 | } 110 | for (AspectInfo classInfo : list) { 111 | classInfo.onAfterMethod = onReturnMethod; 112 | classInfo.onBeforeMethod = onBeforeMethod; 113 | } 114 | 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Aspect.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface Aspect { 11 | String className(); 12 | String[] method(); 13 | } -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/AspectInfo.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 4 | import org.apache.commons.lang3.builder.ToStringStyle; 5 | import org.objectweb.asm.commons.AdviceAdapter; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Created By Arthur Zhang at 20/02/2017 11 | */ 12 | public class AspectInfo { 13 | public Class clz; 14 | public String methodName; 15 | public String methodDesc; 16 | public Method onBeforeMethod; 17 | public Method onAfterMethod; 18 | 19 | @Override 20 | public String toString() { 21 | return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/BaseAdviceAdapter.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import org.apache.commons.lang3.exception.ExceptionUtils; 4 | import org.objectweb.asm.Label; 5 | import org.objectweb.asm.MethodVisitor; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.commons.AdviceAdapter; 8 | import org.objectweb.asm.commons.Method; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created By Arthur Zhang at 21/02/2017 14 | */ 15 | public abstract class BaseAdviceAdapter extends AdviceAdapter { 16 | private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); 17 | private Label methodStartLabel; 18 | private Label endFinallyLabel; 19 | protected String appId; 20 | protected AspectInfo aspectInfo; 21 | 22 | public BaseAdviceAdapter(MethodVisitor mv, int access, String name, String desc, String appId, AspectInfo aspectInfo) { 23 | super(ASM5, mv, access, name, desc); 24 | methodStartLabel = new Label(); 25 | endFinallyLabel = new Label(); 26 | this.appId = appId; 27 | this.aspectInfo = aspectInfo; 28 | } 29 | 30 | public abstract void loadOnBeforeArgs(); 31 | 32 | public abstract void loadOnAfterArgs(); 33 | 34 | public abstract void loadOnThrowArgs(); 35 | 36 | public void beforeMethodEnter() { 37 | 38 | } 39 | public void afterMethodEnter() { 40 | 41 | } 42 | 43 | public void beforeMethodExit() { 44 | 45 | } 46 | 47 | public void afterMethodExit() { 48 | 49 | } 50 | 51 | public static String getStackTraceFromThrowable(Object uncaughtException) { 52 | String uncaughtExceptionStackTrace = ""; 53 | 54 | try { 55 | if (uncaughtException != null) { 56 | uncaughtExceptionStackTrace = ExceptionUtils.getStackTrace((Throwable) uncaughtException); 57 | } 58 | } catch (Exception e) { 59 | 60 | } 61 | return uncaughtExceptionStackTrace; 62 | } 63 | 64 | public static String getCaughtExceptionStackTraceList(List exceptionList) { 65 | StringBuilder sb = new StringBuilder(); 66 | if (exceptionList == null || exceptionList.size() <= 0) { 67 | return sb.toString(); 68 | } 69 | for (Object item : exceptionList) { 70 | sb.append(getStackTraceFromThrowable(item)); 71 | sb.append("\n"); 72 | } 73 | return sb.toString(); 74 | } 75 | 76 | 77 | @Override 78 | protected void onMethodEnter() { 79 | beforeMethodEnter(); 80 | 81 | visitLabel(methodStartLabel); 82 | 83 | loadOnBeforeArgs(); 84 | invokeOnBeforeMethod(); 85 | 86 | afterMethodEnter(); 87 | } 88 | 89 | @Override 90 | protected void onMethodExit(int opcode) { 91 | if (opcode != ATHROW) { 92 | exitNormal(); 93 | } 94 | } 95 | 96 | @Override 97 | public void visitMaxs(int maxStack, int maxLocals) { 98 | 99 | visitTryCatchBlock(methodStartLabel, endFinallyLabel, endFinallyLabel, THROWABLE_TYPE.getInternalName()); 100 | visitLabel(endFinallyLabel); 101 | 102 | exitThrow(); 103 | super.visitMaxs(maxStack, maxLocals); 104 | } 105 | 106 | private void exitThrow() { 107 | loadOnThrowArgs(); 108 | invokeOnAfterMethod(); 109 | visitInsn(ATHROW); 110 | } 111 | 112 | private void exitNormal() { 113 | loadOnAfterArgs(); 114 | invokeOnAfterMethod(); 115 | } 116 | 117 | public void invokeOnBeforeMethod() { 118 | invokeStatic(Type.getType(this.getClass()), org.objectweb.asm.commons.Method.getMethod(aspectInfo.onBeforeMethod)); 119 | } 120 | 121 | public void invokeOnAfterMethod() { 122 | invokeStatic(Type.getType(this.getClass()), Method.getMethod(aspectInfo.onAfterMethod)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/BuildConfig.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | /** 4 | * Created By Arthur Zhang at 10/10/2016 5 | */ 6 | public class BuildConfig { 7 | public static final boolean DEBUG = true; 8 | public static final boolean LOG_DEBUG = false; 9 | } 10 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/DapperSpanLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | public class DapperSpanLifecycleListener implements SpanLifecycleListener { 5 | 6 | public DapperSpanLifecycleListener() { 7 | } 8 | 9 | @Override 10 | public void spanStarted(Span span) { 11 | // Do nothing 12 | } 13 | 14 | @Override 15 | public void spanSampled(Span span) { 16 | // Do nothing 17 | } 18 | 19 | @Override 20 | public void spanCompleted(Span span) { 21 | Log.info("spanCompleted: " + span); 22 | SpanModel record = SpanModel.fromSpan(span); 23 | MessageQueue.getInstance().add(record); 24 | } 25 | } -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/HttpRequestTracingUtils.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | public class HttpRequestTracingUtils { 4 | 5 | 6 | public static final String UNSPECIFIED_SPAN_NAME = "UNSPECIFIED"; 7 | 8 | private HttpRequestTracingUtils() { 9 | // Nothing to do 10 | } 11 | 12 | public static Span fromRequestWithHeaders(RequestWithHeaders request, String appId) { 13 | if (request == null) 14 | return null; 15 | 16 | String traceId = getTraceId(request); 17 | if (traceId == null) 18 | return null; 19 | 20 | String spanName = getHeaderWithAttributeAsBackup(request, TraceHeaders.SPAN_NAME); 21 | if (spanName == null || spanName.length() == 0) 22 | spanName = UNSPECIFIED_SPAN_NAME; 23 | 24 | return Span.newBuilder(spanName, Span.SpanType.HTTP_REQUEST) 25 | .withAppId(appId) 26 | .withTraceId(traceId) 27 | .withParentSpanId(getSpanIdFromRequest(request, TraceHeaders.PARENT_SPAN_ID, false)) 28 | .withSpanId(getSpanIdFromRequest(request, TraceHeaders.SPAN_ID, true)) 29 | .withSampleable(getSpanSampleableFlag(request)) 30 | .build(); 31 | } 32 | 33 | protected static boolean getSpanSampleableFlag(RequestWithHeaders request) { 34 | String spanSampleableHeaderStr = getHeaderWithAttributeAsBackup(request, TraceHeaders.TRACE_SAMPLED); 35 | // Default to true (enabling trace sampling for requests that don't explicitly exclude it) 36 | boolean result = true; 37 | 38 | if ("0".equals(spanSampleableHeaderStr) || "false".equalsIgnoreCase(spanSampleableHeaderStr)) 39 | result = false; 40 | 41 | return result; 42 | } 43 | 44 | 45 | protected static String getSpanIdFromRequest(RequestWithHeaders request, String headerName, boolean generateNewSpanIdIfNotFoundInRequest) { 46 | String spanIdString = getHeaderWithAttributeAsBackup(request, headerName); 47 | if (spanIdString == null) 48 | return generateNewSpanIdIfNotFoundInRequest ? TraceAndSpanIdGenerator.generateId() : null; 49 | 50 | return spanIdString; 51 | } 52 | 53 | protected static String getTraceId(RequestWithHeaders request) { 54 | String requestTraceId = getHeaderWithAttributeAsBackup(request, TraceHeaders.TRACE_ID); 55 | return requestTraceId; 56 | } 57 | 58 | protected static String getHeaderWithAttributeAsBackup(RequestWithHeaders request, String headerName) { 59 | Object result = request.getHeader(headerName); 60 | 61 | if (result == null || result.toString().trim().length() == 0) 62 | result = request.getAttribute(headerName); 63 | 64 | return (result == null) ? null : result.toString().trim(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/HttpServletReflectUtils.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import org.apache.commons.lang3.reflect.MethodUtils; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | 7 | /** 8 | * Created By Arthur Zhang at 10/02/2017 9 | */ 10 | public class HttpServletReflectUtils { 11 | 12 | public static String getServletPath(Object httpServletRequest) { 13 | return (String) invokeIgnoreException(httpServletRequest, "getServletPath"); 14 | } 15 | 16 | public static String getRequestURI(Object httpServletRequest) { 17 | return (String) invokeIgnoreException(httpServletRequest, "getRequestURI"); 18 | } 19 | 20 | public static String getQueryString(Object httpServletRequest) { 21 | return (String) invokeIgnoreException(httpServletRequest, "getQueryString"); 22 | } 23 | 24 | public static String getMethod(Object httpServletRequest) { 25 | return (String) invokeIgnoreException(httpServletRequest, "getMethod"); 26 | } 27 | 28 | public static String getHeader(Object httpServletRequest, String headerName) { 29 | return (String) invokeIgnoreException(httpServletRequest, "getHeader", headerName); 30 | } 31 | 32 | public static String getContextPath(Object httpServletRequest) { 33 | return (String) invokeIgnoreException(httpServletRequest, "getContextPath"); 34 | } 35 | 36 | public static String getAttribute(Object httpServletRequest, String headerName) { 37 | return (String) invokeIgnoreException(httpServletRequest, "getAttribute", headerName); 38 | } 39 | 40 | public static int getStatus(Object httpServletResponse) { 41 | return (int) invokeIgnoreException(httpServletResponse, "getStatus"); 42 | } 43 | 44 | public static Object invokeIgnoreException(Object httpServletRequest, String methodName, Object... args) { 45 | try { 46 | return MethodUtils.invokeMethod(httpServletRequest, methodName, args); 47 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 48 | e.printStackTrace(); 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/HttpSpanFactory.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | /** 5 | * 6 | */ 7 | public class HttpSpanFactory { 8 | 9 | /** 10 | * Intentionally private to force all access through static methods. 11 | */ 12 | private HttpSpanFactory() { 13 | // Nothing to do 14 | } 15 | 16 | public static Span fromHttpServletRequest(Object servletRequest, String appId) { 17 | if (servletRequest == null) 18 | return null; 19 | 20 | return HttpRequestTracingUtils.fromRequestWithHeaders(new RequestWithHeadersServletAdapter(servletRequest), appId); 21 | } 22 | 23 | /** 24 | * 25 | */ 26 | public static Span fromHttpServletRequestOrCreateRootSpan(Object servletRequest, String appId) { 27 | Span span = fromHttpServletRequest(servletRequest, appId); 28 | 29 | if (span == null) { 30 | span = Span 31 | .generateRootSpanForNewTrace(getSpanName(servletRequest), appId, Span.SpanType.HTTP_REQUEST) 32 | .build(); 33 | } 34 | 35 | return span; 36 | } 37 | 38 | /** 39 | * Attempts to pull a valid ID for the user making the request. 40 | * 41 | * @return The HTTP Header value of the user ID if it exists, null otherwise. The request's headers will be inspected for the user ID using the given list of userIdHeaderKeys 42 | * in list order - the first one found that is not null/empty will be returned. 43 | */ 44 | 45 | /** 46 | * @return Span name appropriate for a new root span for this request 47 | */ 48 | public static String getSpanName(Object request) { 49 | // Try the servlet path first, and fall back to the raw request URI. 50 | 51 | String path = HttpServletReflectUtils.getServletPath(request); 52 | if (path == null || path.trim().length() == 0) 53 | path = HttpServletReflectUtils.getRequestURI(request); 54 | 55 | // Include the HTTP method in the returned value to help delineate which endpoint this request represents. 56 | return HttpServletReflectUtils.getMethod(request) + '_' + path; 57 | } 58 | 59 | public static String getServletSpanName(Object request) { 60 | // Try the servlet path first, and fall back to the raw request URI. 61 | 62 | String path = HttpServletReflectUtils.getServletPath(request); 63 | if (path == null || path.trim().length() == 0) 64 | path = HttpServletReflectUtils.getRequestURI(request); 65 | 66 | // Include the HTTP method in the returned value to help delineate which endpoint this request represents. 67 | return HttpServletReflectUtils.getMethod(request) + ' ' + path; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/JedisCmdAdviceAdapter.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | import com.google.common.base.Joiner; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.commons.lang3.reflect.MethodUtils; 7 | import org.objectweb.asm.MethodVisitor; 8 | import org.objectweb.asm.Type; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @Aspect(className = "redis/clients/jedis/Jedis", 16 | method = { 17 | "get", 18 | "type", 19 | "append", 20 | "keys", 21 | "set", 22 | "exists", 23 | "sort", 24 | "rename", 25 | "hvals", 26 | "scan", 27 | "hexists", 28 | "hmget", 29 | "hincrBy", 30 | "del", 31 | "randomKey", 32 | "renamenx", 33 | "expire", 34 | "expireAt", 35 | "move", 36 | "ttl", 37 | "mget", 38 | "getSet", 39 | "setnx", 40 | "mset", 41 | "setex", 42 | "hlen", 43 | "hkeys", 44 | "hdel", 45 | "zrangeWithScores", 46 | "zrevrangeWithScores", 47 | "zrangeByScoreWithScores", 48 | "zrevrangeByScore", 49 | "zrevrangeByScoreWithScores", 50 | "zremrangeByRank", 51 | "zremrangeByScore", 52 | "objectRefcount", 53 | "objectEncoding", 54 | "objectIdletime", 55 | "incrBy", 56 | "decr", 57 | "incr", 58 | "decrBy", 59 | "msetnx", 60 | "hset", 61 | "substr", 62 | "hget", 63 | "hsetnx", 64 | "hmset", 65 | "hgetAll", 66 | "rpush", 67 | "lpush", 68 | "llen", 69 | "lrange", 70 | "ltrim", 71 | "lindex", 72 | "lset", 73 | "lrem", 74 | "lpop", 75 | "rpop", 76 | "rpoplpush", 77 | "sadd", 78 | "smembers", 79 | "srem", 80 | "spop", 81 | "smove", 82 | "scard", 83 | "sismember", 84 | "sinter", 85 | "sinterstore", 86 | "sunion", 87 | "sunionstore", 88 | "sdiff", 89 | "sdiffstore", 90 | "srandmember", 91 | "zadd", 92 | "zrange", 93 | "zrem", 94 | "zincrby", 95 | "zrank", 96 | "zrevrank", 97 | "zrevrange", 98 | "zcard", 99 | "zscore", 100 | "watch", 101 | "blpop", 102 | "brpop", 103 | "zcount", 104 | "zrangeByScore", 105 | "zunionstore", 106 | "zinterstore", 107 | "strlen", 108 | "lpushx", 109 | "persist", 110 | "rpushx", 111 | "echo", 112 | "linsert", 113 | "brpoplpush", 114 | "setbit", 115 | "getbit", 116 | "setrange", 117 | "getrange", 118 | "configGet", 119 | "configSet", 120 | "eval", 121 | "subscribe", 122 | "publish", 123 | "psubscribe", 124 | "evalsha", 125 | "scriptExists", 126 | "scriptLoad", 127 | "slowlogGet", 128 | "bitcount", 129 | "bitop", 130 | "dump", 131 | "restore", 132 | "pexpire", 133 | "pexpireAt", 134 | "pttl", 135 | "incrByFloat", 136 | "psetex", 137 | "clientKill", 138 | "clientSetname", 139 | "migrate", 140 | "hincrByFloat", 141 | "hscan", 142 | "sscan", 143 | "zscan", 144 | "shutdown", 145 | "debug", 146 | "save", 147 | "sync", 148 | "select", 149 | "configResetStat", 150 | "randomBinaryKey", 151 | "monitor", 152 | "unwatch", 153 | "slowlogReset", 154 | "slowlogLen", 155 | "quit", 156 | "flushDB", 157 | "dbSize", 158 | "flushAll", 159 | "auth", 160 | "bgsave", 161 | "bgrewriteaof", 162 | "lastsave", 163 | "slaveof", 164 | "slaveofNoOne", 165 | "multi", 166 | "scriptFlush", 167 | "scriptKill", 168 | "clientGetname", 169 | "clientList", 170 | "slowlogGetBinary", 171 | "info" 172 | } 173 | ) 174 | public class JedisCmdAdviceAdapter extends BaseAdviceAdapter { 175 | private String methodName; 176 | 177 | public JedisCmdAdviceAdapter(MethodVisitor mv, int acc, String name, String desc, 178 | String appId, AspectInfo aspectInfo) { 179 | super(mv, acc, name, desc, appId, aspectInfo); 180 | methodName = name; 181 | } 182 | 183 | @Override 184 | public void loadOnBeforeArgs() { 185 | push(appId); 186 | push(methodName); 187 | } 188 | 189 | @Override 190 | public void loadOnAfterArgs() { 191 | loadThis(); 192 | push(methodName); 193 | push((Type) null); 194 | loadArgArray(); 195 | } 196 | 197 | @Override 198 | public void loadOnThrowArgs() { 199 | dup(); 200 | int uncaughtExceptionLocal = newLocal(Type.getType(Throwable.class)); 201 | storeLocal(uncaughtExceptionLocal, Type.getType(Throwable.class)); 202 | 203 | loadThis(); 204 | push(methodName); 205 | loadLocal(uncaughtExceptionLocal); 206 | loadArgArray(); 207 | } 208 | 209 | @OnBefore 210 | @SuppressWarnings("unused") 211 | public static void injectSpanStart(String appId, String methodName) { 212 | try { 213 | Span parentSpan = Tracer.getInstance().getCurrentSpan(); 214 | if (parentSpan == null) { 215 | return; 216 | } 217 | Tracer.getInstance().startSubSpan("Redis " + methodName, appId, Span.SpanType.JEDIS_CMD); 218 | } catch (Exception e) { 219 | // ignore 220 | } 221 | } 222 | 223 | @OnAfter 224 | @SuppressWarnings("unused") 225 | public static void injectSpanEnd(Object jedis, String methodName, Object uncaughtException, Object... args) { 226 | String instance = ""; 227 | String cmd = ""; 228 | try { 229 | Object client = MethodUtils.invokeExactMethod(jedis, "getClient"); 230 | Integer port = (Integer) MethodUtils.invokeExactMethod(client, "getPort"); 231 | String host = (String) MethodUtils.invokeExactMethod(client, "getHost"); 232 | 233 | String cmdArgs; 234 | 235 | if (args != null) { 236 | List argsValueList = new ArrayList<>(); 237 | for (Object argsItem : args) { 238 | if (argsItem == null ) continue; 239 | if (argsItem instanceof String[]) { 240 | argsValueList.add(Joiner.on(' ').join((String[])argsItem)); 241 | } else { 242 | argsValueList.add(String.valueOf(argsItem)); 243 | } 244 | } 245 | cmdArgs = Joiner.on(" ").join(argsValueList); 246 | } else { 247 | cmdArgs = ""; 248 | } 249 | cmdArgs = StringUtils.abbreviate(cmdArgs, 200); 250 | 251 | instance = host + ":" + port; 252 | 253 | cmd = methodName + " " + cmdArgs; 254 | 255 | Span parentSpan = Tracer.getInstance().getCurrentSpan(); 256 | if (parentSpan == null) { 257 | return; 258 | } 259 | Map map = new HashMap<>(); 260 | map.put("cmd", cmd); 261 | map.put("instance", instance); 262 | Tracer.getInstance().completeSubSpan((Throwable) uncaughtException, null, map); 263 | } catch (Exception e) { 264 | if (BuildConfig.DEBUG) e.printStackTrace(); 265 | } 266 | } 267 | } 268 | 269 | 270 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Log.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | * Created By Arthur Zhang at 05/10/2016 8 | */ 9 | public class Log { 10 | 11 | public static void trace(String message) { 12 | if (false) System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) 13 | + " " + Thread.currentThread().getName() + " [APM] " + message); 14 | } 15 | 16 | public static void debug(String message) { 17 | if (BuildConfig.DEBUG) System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) 18 | + " " + Thread.currentThread().getName() + " [APM] " + message); 19 | } 20 | 21 | public static void info(String message) { 22 | System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) 23 | + " " + Thread.currentThread().getName() + " [APM] " + message); 24 | } 25 | 26 | public static void info(String message, String... parameters) { 27 | System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) 28 | + " " + Thread.currentThread().getName() + " [APM] " + String.format(message, parameters)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import com.google.common.collect.Queues; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.BlockingQueue; 8 | import java.util.concurrent.LinkedBlockingQueue; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * Created By Arthur Zhang at 08/06/2017 13 | */ 14 | public class MessageQueue { 15 | 16 | private static final int SIZE = 5000; 17 | private static final int BATCH_FETCH_ITEM_COUNT = 200; 18 | private static final int MAX_WAIT_TIMEOUT = 30; 19 | private BlockingQueue queue = new LinkedBlockingQueue<>(SIZE); 20 | 21 | private MessageQueue() { 22 | } 23 | 24 | public int availableSize() { 25 | return queue.remainingCapacity(); 26 | } 27 | 28 | private static class SingletonHolder { 29 | private static MessageQueue helper = new MessageQueue(); 30 | } 31 | 32 | public static MessageQueue getInstance() { 33 | return SingletonHolder.helper; 34 | } 35 | 36 | public boolean add(final Record logItem) { 37 | return queue.offer(logItem); 38 | } 39 | 40 | public List drain() { 41 | List bulkData = new ArrayList<>(); 42 | try { 43 | Queues.drain(queue, bulkData, BATCH_FETCH_ITEM_COUNT, MAX_WAIT_TIMEOUT, TimeUnit.SECONDS); 44 | } catch (InterruptedException e) { 45 | if (BuildConfig.DEBUG) e.printStackTrace(); 46 | } 47 | return bulkData; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/MyClassFileTransformer.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import org.apache.commons.lang3.reflect.ConstructorUtils; 4 | import org.objectweb.asm.*; 5 | import org.objectweb.asm.commons.AdviceAdapter; 6 | 7 | import java.io.File; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | import java.lang.instrument.ClassFileTransformer; 12 | import java.lang.instrument.IllegalClassFormatException; 13 | import java.security.ProtectionDomain; 14 | import java.util.List; 15 | 16 | public class MyClassFileTransformer implements ClassFileTransformer { 17 | @Override 18 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classBytes) throws IllegalClassFormatException { 19 | 20 | // 处理常用库注入 21 | if (AgentMain.pluginClassNameSet.contains(className)) { 22 | Log.info("transform: servlet"); 23 | ClassReader cr = new ClassReader(classBytes); 24 | ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); 25 | ClassVisitor cv = new PluginClassVisitor(cw, className, AgentMain.appId); 26 | cr.accept(cv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); 27 | byte[] bytes = cw.toByteArray(); 28 | File dumpFile = new File("/tmp/" + className.replaceAll("/", "_") + ".class"); 29 | writeByteArrayToFile(bytes, dumpFile); 30 | Log.info("dump file: " + dumpFile); 31 | return bytes; 32 | } 33 | 34 | return classBytes; 35 | } 36 | 37 | public static class PluginClassVisitor extends ClassVisitor { 38 | private String className; 39 | private String appId; 40 | public PluginClassVisitor(ClassVisitor cv, String className, String appId) { 41 | super(Opcodes.ASM5, cv); 42 | this.className = className; 43 | this.appId = appId; 44 | } 45 | 46 | @Override 47 | public MethodVisitor visitMethod(int access, String methodName, String methodDesc, String signature, String[] exceptions) { 48 | MethodVisitor mv = super.visitMethod(access, methodName, methodDesc, signature, exceptions); 49 | Log.info("####################visit method:" + className + '\t' + methodName + "\t" + methodDesc + "\t" + appId); 50 | List aspectInfoList = AgentMain.pluginMaps.get(className); 51 | if (aspectInfoList == null) return mv; 52 | for (AspectInfo item : aspectInfoList) { 53 | String aspectMethodName = item.methodName; 54 | String aspectMethodDesc = item.methodDesc; 55 | if (!Wildcard.equalsOrMatch(methodName, aspectMethodName) || !Wildcard.equalsOrMatch(methodDesc, aspectMethodDesc)) { 56 | continue; 57 | } 58 | Class clz = item.clz; 59 | try { 60 | return ConstructorUtils.invokeConstructor(clz, 61 | mv, access, methodName, methodDesc, appId, item); 62 | } catch (Exception e) { 63 | if (BuildConfig.DEBUG) e.printStackTrace(); 64 | } 65 | return mv; 66 | } 67 | return mv; 68 | } 69 | } 70 | 71 | private static void writeByteArrayToFile(byte[] bytes, File file) { 72 | 73 | OutputStream out = null; 74 | try { 75 | out = new FileOutputStream(file, false); 76 | out.write(bytes, 0, bytes.length); 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } finally { 80 | try { 81 | if (out != null) { 82 | out.close(); 83 | } 84 | } catch (Exception e) { 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/MyHttpServletAdviceAdapter.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | import com.google.common.base.Strings; 5 | import org.apache.commons.lang3.reflect.MethodUtils; 6 | import org.objectweb.asm.Label; 7 | import org.objectweb.asm.MethodVisitor; 8 | import org.objectweb.asm.Type; 9 | import org.objectweb.asm.commons.AdviceAdapter; 10 | import org.objectweb.asm.commons.Method; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | @Aspect(className = "javax/servlet/http/HttpServlet", 16 | method = {"service (Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V"}) 17 | public class MyHttpServletAdviceAdapter extends AdviceAdapter { 18 | 19 | private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); 20 | private Label methodStartLabel; 21 | private Label endFinallyLabel; 22 | protected String appId; 23 | 24 | private int startFlagLocal; 25 | 26 | public MyHttpServletAdviceAdapter(MethodVisitor mv, int access, String name, String desc, String appId, AspectInfo aspectInfo) { 27 | super(ASM5, mv, access, name, desc); 28 | methodStartLabel = new Label(); 29 | endFinallyLabel = new Label(); 30 | startFlagLocal = newLocal(Type.INT_TYPE); 31 | this.appId = appId; 32 | } 33 | 34 | 35 | @Override 36 | protected void onMethodEnter() { 37 | mv.visitInsn(ICONST_0); 38 | storeLocal(startFlagLocal); 39 | 40 | visitLabel(methodStartLabel); 41 | 42 | loadOnBeforeArgs(); 43 | invokeOnBeforeMethod(); 44 | storeLocal(startFlagLocal); 45 | } 46 | 47 | @Override 48 | protected void onMethodExit(int opcode) { 49 | Log.info("onMethodExit"); 50 | if (opcode != ATHROW) { 51 | exitNormal(); 52 | } 53 | } 54 | 55 | @Override 56 | public void visitMaxs(int maxStack, int maxLocals) { 57 | 58 | visitTryCatchBlock(methodStartLabel, endFinallyLabel, endFinallyLabel, THROWABLE_TYPE.getInternalName()); 59 | visitLabel(endFinallyLabel); 60 | 61 | exitThrow(); 62 | 63 | visitInsn(ATHROW); 64 | 65 | super.visitMaxs(maxStack, maxLocals); 66 | } 67 | 68 | private void exitThrow() { 69 | loadOnThrowArgs(); 70 | invokeOnAfterMethod(); 71 | } 72 | 73 | private void exitNormal() { 74 | loadOnAfterArgs(); 75 | invokeOnAfterMethod(); 76 | } 77 | 78 | public void loadOnBeforeArgs() { 79 | push(appId); 80 | loadArg(0); 81 | } 82 | 83 | public void loadOnAfterArgs() { 84 | 85 | loadLocal(startFlagLocal); 86 | loadArgs(); 87 | push((Type) null); 88 | } 89 | 90 | public void loadOnThrowArgs() { 91 | dup(); 92 | int uncaughtExceptionLocal = newLocal(Type.getType(Throwable.class)); 93 | storeLocal(uncaughtExceptionLocal, Type.getType(Throwable.class)); 94 | 95 | loadLocal(startFlagLocal); 96 | loadArgs(); 97 | loadLocal(uncaughtExceptionLocal); 98 | } 99 | 100 | public void invokeOnBeforeMethod() { 101 | invokeStatic(Type.getType(this.getClass()), 102 | new Method("startRequest", 103 | "(Ljava/lang/String;Ljava/lang/Object;)I" 104 | )); 105 | } 106 | 107 | private void invokeOnAfterMethod() { 108 | invokeStatic(Type.getType(this.getClass()), 109 | new Method("completeRequestSpan", 110 | "(ILjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V" 111 | )); 112 | } 113 | 114 | 115 | public static int startRequest(String appId, Object request) { 116 | try { 117 | if (Tracer.getInstance() == null || Tracer.getInstance().getCurrentSpan() != null) { 118 | return 0; 119 | } 120 | if (AgentMain.TYPE_TOMCAT_WAR.equals(AgentMain.agentType)) { 121 | appId = Strings.nullToEmpty((String) MethodUtils.invokeMethod(request, "getContextPath")); 122 | if (appId.startsWith("/")) { 123 | appId = appId.replaceAll("/", ""); 124 | } 125 | } 126 | Tracer tracer = Tracer.getInstance(); 127 | final Span parentSpan = HttpSpanFactory.fromHttpServletRequest(request, appId); 128 | 129 | if (parentSpan != null) { 130 | tracer.startRequestWithChildSpan(appId, parentSpan, HttpSpanFactory.getServletSpanName(request)); 131 | } else { 132 | tracer.startRequestWithRootSpan(HttpSpanFactory.getServletSpanName(request), appId); 133 | } 134 | return 1; 135 | } catch (Exception e) { 136 | if (BuildConfig.DEBUG) e.printStackTrace(); 137 | } 138 | return 0; 139 | } 140 | 141 | public static void completeRequestSpan(int startFlag, Object req, Object resp, Object uncaughtExceptionObj) { 142 | 143 | String url = ""; 144 | String method = ""; 145 | String queryString = ""; 146 | int respCode = 0; 147 | Throwable uncaughtException = null; 148 | try { 149 | method = Strings.nullToEmpty(HttpServletReflectUtils.getMethod(req)); 150 | String servletPath = Strings.nullToEmpty(HttpServletReflectUtils.getServletPath(req)); 151 | String requestURI = Strings.nullToEmpty(HttpServletReflectUtils.getRequestURI(req)); 152 | String contextPath = Strings.nullToEmpty(HttpServletReflectUtils.getContextPath(req)); 153 | queryString = Strings.nullToEmpty(HttpServletReflectUtils.getQueryString(req)); 154 | if (Strings.isNullOrEmpty(servletPath)) { 155 | if (requestURI.length() > 0 && contextPath.length() > 0 && contextPath.length() < requestURI.length()) { 156 | url = requestURI.substring(contextPath.length(), requestURI.length()); 157 | } 158 | } else { 159 | url = servletPath; 160 | } 161 | respCode = HttpServletReflectUtils.getStatus(resp); 162 | 163 | uncaughtException = (Throwable) uncaughtExceptionObj; 164 | 165 | if (respCode == 0) { 166 | respCode = 200; 167 | } 168 | } catch (Exception e) { 169 | if (BuildConfig.DEBUG) e.printStackTrace(); 170 | } finally { 171 | Map map = new HashMap<>(); 172 | map.put("url", url); 173 | map.put("method", method); 174 | map.put("queryString", queryString); 175 | map.put("statusCode", respCode); 176 | Tracer.getInstance().completeRequestSpan(uncaughtException, map); 177 | } 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/OnAfter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2015 the original author or authors. 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 me.geek01.javaagent; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Target(ElementType.METHOD) 24 | @Retention(RetentionPolicy.RUNTIME) 25 | public @interface OnAfter {} 26 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/OnBefore.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface OnBefore {} -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Record.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | import com.alibaba.fastjson.annotation.JSONField; 5 | 6 | public abstract class Record { 7 | 8 | @JSONField(name = "appId") 9 | public String appId = ""; 10 | 11 | @JSONField(name = "agentId") 12 | public String agentId = AgentMain.agentId; 13 | 14 | public Record() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/RequestWithHeaders.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | public interface RequestWithHeaders { 5 | 6 | 7 | String getHeader(String headerName); 8 | 9 | 10 | Object getAttribute(String name); 11 | } 12 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/RequestWithHeadersServletAdapter.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | public class RequestWithHeadersServletAdapter implements RequestWithHeaders { 5 | 6 | private final Object httpServletRequest; 7 | 8 | public RequestWithHeadersServletAdapter(Object httpServletRequest) { 9 | if (httpServletRequest == null) 10 | throw new IllegalArgumentException("httpServletRequest cannot be null"); 11 | 12 | this.httpServletRequest = httpServletRequest; 13 | } 14 | 15 | @Override 16 | public String getHeader(String headerName) { 17 | return HttpServletReflectUtils.getHeader(httpServletRequest, headerName); 18 | } 19 | 20 | @Override 21 | public Object getAttribute(String name) { 22 | return HttpServletReflectUtils.getAttribute(httpServletRequest, name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/RootSpanSamplingStrategy.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | public interface RootSpanSamplingStrategy { 4 | 5 | 6 | boolean isNextRootSpanSampleable(); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/SampleAllTheThingsStrategy.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | public class SampleAllTheThingsStrategy implements RootSpanSamplingStrategy { 5 | 6 | @Override 7 | public boolean isNextRootSpanSampleable() { 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Span.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 5 | import org.apache.commons.lang3.builder.ToStringStyle; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | public class Span { 13 | 14 | private String appId; 15 | private String traceId; 16 | private String spanId; 17 | private String parentSpanId; 18 | private String spanName; 19 | private boolean sampleable; 20 | private SpanType spanType; 21 | private long spanStartTimeEpochTimeMillis; 22 | 23 | private long spanStartTimeNanos; 24 | 25 | private int rootType = 0; 26 | 27 | private Integer timeCost; 28 | 29 | private Throwable uncaughtException; 30 | private List caughtExceptionList; 31 | private boolean rootSpanHasSubSpanError = false; 32 | 33 | private Map extraDataMap; 34 | 35 | public void addExtraData(String key, Object value) { 36 | if (extraDataMap == null) extraDataMap = new HashMap<>(); 37 | extraDataMap.put(key, value); 38 | } 39 | 40 | public Map getExtraDataMap() { 41 | return extraDataMap; 42 | } 43 | 44 | public void setTraceId(String traceId) { 45 | this.traceId = traceId; 46 | } 47 | 48 | public void setParentSpanId(String parentSpanId) { 49 | this.parentSpanId = parentSpanId; 50 | } 51 | 52 | public void setSpanId(String spanId) { 53 | this.spanId = spanId; 54 | } 55 | 56 | public void setSpanName(String spanName) { 57 | this.spanName = spanName; 58 | } 59 | 60 | public void setSpanType(SpanType spanType) { 61 | this.spanType = spanType; 62 | } 63 | 64 | public String getTraceId() { 65 | return traceId; 66 | } 67 | 68 | 69 | public String getSpanId() { 70 | return spanId; 71 | } 72 | 73 | 74 | public String getParentSpanId() { 75 | return parentSpanId; 76 | } 77 | 78 | 79 | public String getSpanName() { 80 | return spanName; 81 | } 82 | 83 | public String getAppId() { 84 | return appId; 85 | } 86 | 87 | 88 | public void setRootType(int rootType) { 89 | this.rootType = rootType; 90 | } 91 | 92 | public int getRootType() { 93 | return rootType; 94 | } 95 | 96 | public boolean isSampleable() { 97 | return sampleable; 98 | } 99 | 100 | public void setUncaughtException(Throwable uncaughtException) { 101 | this.uncaughtException = uncaughtException; 102 | } 103 | 104 | public Throwable getUncaughtException() { 105 | return uncaughtException; 106 | } 107 | 108 | public List getCaughtExceptionList() { 109 | return caughtExceptionList; 110 | } 111 | 112 | public void setCaughtExceptionList(List caughtExceptionList) { 113 | try { 114 | this.caughtExceptionList = caughtExceptionList; 115 | } catch (Exception e) { 116 | // ignore 117 | e.printStackTrace(); 118 | } 119 | } 120 | 121 | public long getSpanStartTimeEpochTimeMillis() { 122 | return spanStartTimeEpochTimeMillis; 123 | } 124 | 125 | 126 | public long getSpanStartTimeNanos() { 127 | return spanStartTimeNanos; 128 | } 129 | 130 | public SpanType getSpanType() { 131 | return spanType; 132 | } 133 | public int getTimeCost() { 134 | return timeCost; 135 | } 136 | 137 | 138 | public void setRootSpanHasSubSpanError(boolean hasError) { 139 | this.rootSpanHasSubSpanError = hasError; 140 | } 141 | 142 | public boolean getRootSpanHasSubSpanError() { 143 | return rootSpanHasSubSpanError; 144 | } 145 | 146 | public enum SpanType { 147 | 148 | HTTP_REQUEST, 149 | 150 | CLIENT, 151 | 152 | LOCAL_ONLY, 153 | JEDIS_POOL, 154 | DRUID_POOL, 155 | DBCP2_POOL, 156 | TOMCAT_JDBC_POOL, 157 | PSD_CONNECTION_POOL, 158 | OK_HTTP, 159 | COMMONS_HTTP, 160 | SPRING_HTTP, 161 | JEDIS_CMD, 162 | JDBC_EXECUTE, 163 | DUBBO_CONSUMER, 164 | DUBBO_PROVIDER, 165 | MONGO, 166 | UNKNOWN 167 | } 168 | 169 | public Span() { 170 | } 171 | 172 | public Span(String appId, String traceId, String parentSpanId, String spanId, String spanName, boolean sampleable, 173 | SpanType spanType, long spanStartTimeEpochTimeMillis, long spanStartTimeNanos, Integer timeCost) { 174 | 175 | this.appId = appId == null ? "" : appId; 176 | if (traceId == null) 177 | throw new IllegalArgumentException("traceId cannot be null"); 178 | 179 | if (spanId == null) 180 | throw new IllegalArgumentException("spanId cannot be null"); 181 | 182 | if (spanName == null) 183 | throw new IllegalArgumentException("spanName cannot be null"); 184 | 185 | this.traceId = traceId; 186 | this.spanId = spanId; 187 | this.parentSpanId = parentSpanId; 188 | this.spanName = spanName; 189 | this.sampleable = sampleable; 190 | this.spanStartTimeEpochTimeMillis = spanStartTimeEpochTimeMillis; 191 | this.spanStartTimeNanos = spanStartTimeNanos; 192 | 193 | this.timeCost = timeCost; 194 | 195 | if (spanType == null) 196 | spanType = SpanType.UNKNOWN; 197 | 198 | this.spanType = spanType; 199 | } 200 | 201 | 202 | 203 | public static Builder generateRootSpanForNewTrace(String spanName, String appId, SpanType spanType) { 204 | return Span.newBuilder(spanName, spanType).withAppId(appId); 205 | } 206 | 207 | 208 | public Span generateChildSpan(String spanName, String appId, SpanType spanType) { 209 | return Span.newBuilder(this) 210 | .withAppId(appId) 211 | .withParentSpanId(this.spanId) 212 | .withSpanName(spanName) 213 | .withSpanId(TraceAndSpanIdGenerator.generateId()) 214 | .withSpanStartTimeEpochTimeMillis(System.currentTimeMillis()) 215 | .withSpanStartTimeNanos(System.nanoTime()) 216 | .withTimeCost(null) 217 | .withSpanPurpose(spanType) 218 | .build(); 219 | } 220 | 221 | 222 | public static Builder newBuilder(String spanName, SpanType spanType) { 223 | return new Builder(spanName, spanType); 224 | } 225 | 226 | 227 | public static Builder newBuilder(Span copy) { 228 | Builder builder = new Builder(copy.spanName, copy.spanType); 229 | builder.traceId = copy.traceId; 230 | builder.appId = copy.appId; 231 | builder.spanId = copy.spanId; 232 | builder.parentSpanId = copy.parentSpanId; 233 | builder.sampleable = copy.sampleable; 234 | builder.spanStartTimeEpochTimeMillis = copy.spanStartTimeEpochTimeMillis; 235 | builder.spanStartTimeNanos = copy.spanStartTimeEpochTimeMillis; 236 | builder.timeCost = copy.timeCost; 237 | return builder; 238 | } 239 | 240 | 241 | public boolean sampleable() { 242 | return sampleable; 243 | } 244 | 245 | 246 | 247 | void complete() { 248 | if (this.timeCost != null) 249 | throw new IllegalStateException("This Span is already completed."); 250 | 251 | this.timeCost = (int)(System.currentTimeMillis() - spanStartTimeEpochTimeMillis); 252 | } 253 | 254 | 255 | public boolean hasComplete() { 256 | return timeCost != null; 257 | } 258 | 259 | 260 | @Override 261 | public String toString() { 262 | return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); 263 | } 264 | 265 | @Override 266 | public boolean equals(Object o) { 267 | if (this == o) { 268 | return true; 269 | } 270 | if (!(o instanceof Span)) { 271 | return false; 272 | } 273 | Span span = (Span) o; 274 | return sampleable == span.sampleable && 275 | spanStartTimeEpochTimeMillis == span.spanStartTimeEpochTimeMillis && 276 | spanType == span.spanType && 277 | Objects.equals(traceId, span.traceId) && 278 | Objects.equals(spanId, span.spanId) && 279 | Objects.equals(parentSpanId, span.parentSpanId) && 280 | Objects.equals(spanName, span.spanName) && 281 | Objects.equals(timeCost, span.timeCost); 282 | } 283 | 284 | @Override 285 | public int hashCode() { 286 | return Objects.hash(traceId, spanId, parentSpanId, spanName, sampleable, spanType, spanStartTimeEpochTimeMillis, spanStartTimeNanos, timeCost); 287 | } 288 | 289 | public static final class Builder { 290 | 291 | private String appId; 292 | private String traceId; 293 | private String spanId; 294 | private String parentSpanId; 295 | private String spanName; 296 | private boolean sampleable = true; 297 | private Long spanStartTimeEpochTimeMillis; 298 | private Long spanStartTimeNanos; 299 | private Integer timeCost; 300 | private SpanType spanType; 301 | 302 | private Builder(String spanName, SpanType spanType) { 303 | this.spanName = spanName; 304 | this.spanType = spanType; 305 | } 306 | 307 | 308 | public Builder withTraceId(String traceId) { 309 | this.traceId = traceId; 310 | return this; 311 | } 312 | 313 | public Builder withAppId(String appId) { 314 | this.appId = appId; 315 | return this; 316 | } 317 | 318 | 319 | public Builder withSpanId(String spanId) { 320 | this.spanId = spanId; 321 | return this; 322 | } 323 | 324 | 325 | public Builder withParentSpanId(String parentSpanId) { 326 | this.parentSpanId = parentSpanId; 327 | return this; 328 | } 329 | 330 | 331 | public Builder withSpanName(String spanName) { 332 | this.spanName = spanName; 333 | return this; 334 | } 335 | 336 | 337 | public Builder withSampleable(boolean sampleable) { 338 | this.sampleable = sampleable; 339 | return this; 340 | } 341 | 342 | 343 | public Builder withSpanStartTimeEpochTimeMillis(Long spanStartTimeEpochMicros) { 344 | this.spanStartTimeEpochTimeMillis = spanStartTimeEpochMicros; 345 | return this; 346 | } 347 | public Builder withSpanStartTimeNanos(Long spanStartTimeNanos) { 348 | this.spanStartTimeNanos = spanStartTimeNanos; 349 | return this; 350 | } 351 | 352 | 353 | public Builder withTimeCost(Integer durationNanos) { 354 | this.timeCost = durationNanos; 355 | return this; 356 | } 357 | 358 | 359 | public Builder withSpanPurpose(SpanType spanType) { 360 | this.spanType = spanType; 361 | return this; 362 | } 363 | 364 | 365 | public Span build() { 366 | if (traceId == null) 367 | traceId = TraceAndSpanIdGenerator.generateId(); 368 | 369 | if (spanId == null) 370 | spanId = TraceAndSpanIdGenerator.generateId(); 371 | 372 | if (spanStartTimeEpochTimeMillis == null) { 373 | spanStartTimeEpochTimeMillis = System.currentTimeMillis(); 374 | } 375 | if (spanStartTimeNanos == null) { 376 | spanStartTimeNanos = System.nanoTime(); 377 | } 378 | 379 | return new Span(appId, traceId, parentSpanId, spanId, spanName, sampleable, spanType, spanStartTimeEpochTimeMillis, spanStartTimeNanos, timeCost); 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/SpanLifecycleListener.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | public interface SpanLifecycleListener { 4 | 5 | 6 | void spanStarted(Span span); 7 | 8 | void spanSampled(Span span); 9 | 10 | void spanCompleted(Span span); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/SpanModel.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import org.apache.commons.lang3.builder.ReflectionToStringBuilder; 5 | import org.apache.commons.lang3.builder.ToStringStyle; 6 | import org.apache.commons.lang3.exception.ExceptionUtils; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created By Arthur Zhang at 11/02/2017 13 | */ 14 | public class SpanModel extends Record { 15 | 16 | @JSONField(name = "traceId") 17 | public String traceId; 18 | @JSONField(name = "spanId") 19 | public String spanId; 20 | @JSONField(name = "parentSpanId") 21 | public String parentSpanId; 22 | @JSONField(name = "spanName") 23 | public String spanName; 24 | @JSONField(name = "spanType") 25 | public String spanType; 26 | @JSONField(name = "spanStartTimeMills") 27 | public long spanStartTimeMills; 28 | 29 | @JSONField(name = "spanStartTimeNanos") 30 | public long spanStartTimeNanos; 31 | 32 | @JSONField(name = "timeCost") 33 | public int timeCost; 34 | 35 | @JSONField(name = "exceptionStackTrace") 36 | public String exceptionStackTrace; 37 | 38 | @JSONField(name = "caughtExceptionStackTrace") 39 | public String caughtExceptionStackTrace; 40 | 41 | @JSONField(name = "rootType") 42 | public int rootType; 43 | @JSONField(name = "extraData") 44 | public Map extraDataMap; 45 | 46 | 47 | public static SpanModel fromSpan(Span span) { 48 | SpanModel spanModel = new SpanModel(); 49 | spanModel.appId = span.getAppId(); 50 | spanModel.traceId = span.getTraceId(); 51 | spanModel.spanId = span.getSpanId(); 52 | spanModel.parentSpanId = span.getParentSpanId(); 53 | spanModel.spanName = span.getSpanName(); 54 | spanModel.spanType = span.getSpanType().name(); 55 | spanModel.spanStartTimeMills = span.getSpanStartTimeEpochTimeMillis(); 56 | spanModel.spanStartTimeNanos = span.getSpanStartTimeNanos(); 57 | spanModel.rootType = span.getRootType(); 58 | spanModel.timeCost = span.getTimeCost(); 59 | spanModel.exceptionStackTrace = getStackTraceFromThrowable(span.getUncaughtException()); 60 | spanModel.caughtExceptionStackTrace = getCaughtExceptionStackTraceList(span.getCaughtExceptionList()); 61 | spanModel.extraDataMap = span.getExtraDataMap(); 62 | return spanModel; 63 | } 64 | public static String getStackTraceFromThrowable(Object uncaughtException) { 65 | String uncaughtExceptionStackTrace = ""; 66 | 67 | try { 68 | if (uncaughtException != null) { 69 | uncaughtExceptionStackTrace = ExceptionUtils.getStackTrace((Throwable) uncaughtException); 70 | } 71 | } catch (Exception e) { 72 | 73 | } 74 | return uncaughtExceptionStackTrace; 75 | } 76 | 77 | public static String getCaughtExceptionStackTraceList(List exceptionList) { 78 | StringBuilder sb = new StringBuilder(); 79 | if (exceptionList == null || exceptionList.size() <= 0) { 80 | return sb.toString(); 81 | } 82 | for (Object item : exceptionList) { 83 | sb.append(getStackTraceFromThrowable(item)); 84 | sb.append("\n"); 85 | } 86 | return sb.toString(); 87 | } 88 | @Override 89 | public String toString() { 90 | return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/TraceAndSpanIdGenerator.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import java.util.UUID; 4 | 5 | 6 | public class TraceAndSpanIdGenerator { 7 | 8 | 9 | private TraceAndSpanIdGenerator() { 10 | 11 | } 12 | 13 | public static String generateId() { 14 | return UUID.randomUUID().toString().replaceAll("-", ""); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/TraceClassTransform.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | import com.google.common.base.Splitter; 4 | import org.objectweb.asm.Label; 5 | import org.objectweb.asm.MethodVisitor; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.commons.AdviceAdapter; 8 | import org.objectweb.asm.commons.Method; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | 14 | /** 15 | * Created By Arthur Zhang at 09/02/2017 16 | */ 17 | public class TraceClassTransform extends AdviceAdapter { 18 | 19 | private static final Type THROWABLE_TYPE = Type.getType(Throwable.class); 20 | private final String appId; 21 | private Label methodStartLabel; 22 | private Label endFinallyLabel; 23 | 24 | private String methodName; 25 | private String className; 26 | private HashMap> matchedHandle = new HashMap<>(); 27 | private int caughtExceptionListLocal; 28 | 29 | public TraceClassTransform(MethodVisitor mv, int acc, String name, String desc, String appId, String className) { 30 | super(ASM5, mv, acc, name, desc); 31 | methodStartLabel = new Label(); 32 | endFinallyLabel = new Label(); 33 | methodName = name; 34 | this.className = className; 35 | this.appId = appId; 36 | 37 | caughtExceptionListLocal = newLocal(Type.getObjectType("java/util/ArrayList")); 38 | } 39 | 40 | @Override 41 | protected void onMethodEnter() { 42 | 43 | visitTypeInsn(NEW, "java/util/ArrayList"); 44 | dup(); 45 | visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "()V", false); 46 | storeLocal(caughtExceptionListLocal, Type.getObjectType("java/util/ArrayList")); 47 | 48 | visitLabel(methodStartLabel); 49 | push(appId); 50 | push(className); 51 | push(methodName); 52 | invokeStatic(Type.getType(this.getClass()), 53 | new Method("startSubSpan", 54 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" 55 | )); 56 | } 57 | 58 | 59 | @Override 60 | protected void onMethodExit(int opcode) { 61 | Log.trace("on method exit: " + opcode); 62 | if (opcode != ATHROW) { 63 | exitMethod(false); 64 | } 65 | } 66 | 67 | @Override 68 | public void visitMaxs(int maxStack, int maxLocals) { 69 | Log.trace("visit max: " + maxStack + "\t" + maxLocals); 70 | 71 | visitTryCatchBlock(methodStartLabel, endFinallyLabel, endFinallyLabel, THROWABLE_TYPE.getInternalName()); 72 | visitLabel(endFinallyLabel); 73 | 74 | exitMethod(true); 75 | super.visitMaxs(maxStack, maxLocals); 76 | } 77 | 78 | @Override 79 | public void visitTryCatchBlock(Label start, Label end, Label handler, String exception) { 80 | super.visitTryCatchBlock(start, end, handler, exception); 81 | if (exception != null) { 82 | List handles = matchedHandle.get(handler); 83 | if (handles == null) handles = new ArrayList<>(); 84 | handles.add(exception); 85 | matchedHandle.put(handler, handles); 86 | 87 | Log.trace("matched handle: " + handler); 88 | Log.trace("reach here: visitTryCatchBlock(): " + exception); 89 | } 90 | } 91 | 92 | @Override 93 | public void visitLabel(Label label) { 94 | super.visitLabel(label); 95 | if (label != null && matchedHandle.get(label) != null && !label.equals(endFinallyLabel)) { 96 | 97 | dup(); // exception 98 | 99 | loadLocal(caughtExceptionListLocal, Type.getObjectType("java/util/ArrayList")); 100 | swap(); // swap exception <-> list 101 | invokeVirtual(Type.getType(ArrayList.class), new Method("add", "(Ljava/lang/Object;)Z")); 102 | pop(); 103 | } 104 | } 105 | 106 | private void exitMethod(boolean throwing) { 107 | 108 | if (throwing) { 109 | dup(); 110 | } else { 111 | push((Type) null); 112 | } 113 | loadLocal(caughtExceptionListLocal); 114 | 115 | invokeStatic(Type.getType(this.getClass()), 116 | new Method("completeSubSpan", 117 | "(Ljava/lang/Object;Ljava/lang/Object;)V" 118 | )); 119 | if (throwing) { 120 | visitInsn(ATHROW); 121 | } 122 | } 123 | 124 | public static void startSubSpan(String appId, String className, String methodName) { 125 | try { 126 | List parts = Splitter.on("/").splitToList(className); 127 | String simpleClassName = parts.get(parts.size() - 1); 128 | 129 | Span parentSpan = Tracer.getInstance().getCurrentSpan(); 130 | if (parentSpan == null) { 131 | return; 132 | } 133 | Tracer.getInstance().startSubSpan(simpleClassName + "." + methodName + "()", appId, Span.SpanType.LOCAL_ONLY); 134 | } catch (Exception e) { 135 | } 136 | } 137 | 138 | public static void completeSubSpan(Object uncaughtException, Object caughtExceptionList) { 139 | try { 140 | Span parentSpan = Tracer.getInstance().getCurrentSpan(); 141 | if (parentSpan == null) { 142 | return; 143 | } 144 | Tracer.getInstance().completeSubSpan((Throwable) uncaughtException, (List)caughtExceptionList, null); 145 | } catch (Exception e) { 146 | 147 | } 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/TraceHeaders.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | public interface TraceHeaders { 4 | 5 | 6 | String TRACE_ID = "X-APM-TraceId"; 7 | 8 | 9 | String SPAN_ID = "X-APM-SpanId"; 10 | 11 | 12 | String PARENT_SPAN_ID = "X-APM-ParentSpanId"; 13 | 14 | 15 | String SPAN_NAME = "X-APM-SpanName"; 16 | 17 | 18 | String TRACE_SAMPLED = "X-APM-Sampled"; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Tracer.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | import java.util.*; 5 | 6 | public class Tracer { 7 | 8 | 9 | 10 | private static final ThreadLocal> currentSpanStackThreadLocal = new ThreadLocal<>(); 11 | 12 | 13 | private static final Tracer INSTANCE = new Tracer(); 14 | 15 | 16 | private RootSpanSamplingStrategy rootSpanSamplingStrategy = new SampleAllTheThingsStrategy(); 17 | 18 | 19 | private final List spanLifecycleListeners = new ArrayList<>(); 20 | 21 | 22 | private Tracer() { } 23 | 24 | 25 | public static Tracer getInstance() { 26 | return INSTANCE; 27 | } 28 | 29 | 30 | public Span getCurrentSpan() { 31 | Deque spanStack = currentSpanStackThreadLocal.get(); 32 | if (spanStack == null || spanStack.isEmpty()) { 33 | spanStack = currentSpanStackThreadLocal.get(); 34 | } 35 | 36 | return (spanStack == null) ? null : spanStack.peek(); 37 | } 38 | 39 | 40 | public Span startRequestWithRootSpan(String spanName, String appId) { 41 | boolean sampleable = isNextRootSpanSampleable(); 42 | String traceId = TraceAndSpanIdGenerator.generateId(); 43 | return doNewRequestSpan(appId, traceId, null, spanName, sampleable, Span.SpanType.HTTP_REQUEST); 44 | } 45 | 46 | public Span startRootSpan(String spanName, String appId, Span.SpanType spanType) { 47 | boolean sampleable = isNextRootSpanSampleable(); 48 | String traceId = TraceAndSpanIdGenerator.generateId(); 49 | return doNewRequestSpan(appId, traceId, null, spanName, sampleable, spanType); 50 | } 51 | 52 | 53 | public Span startRequestWithChildSpan(String appId, Span parentSpan, String childSpanName) { 54 | if (parentSpan == null) { 55 | throw new IllegalArgumentException("parentSpan cannot be null. " + 56 | "If you don't have a parent span then you should call one of the startRequestWithRootSpan(...) methods instead."); 57 | } 58 | 59 | return startRequestWithSpanInfo(appId, parentSpan.getTraceId(), parentSpan.getSpanId(), childSpanName, parentSpan.sampleable(), 60 | Span.SpanType.HTTP_REQUEST); 61 | } 62 | 63 | public Span startWithChildSpan(String appId, Span parentSpan, String childSpanName, Span.SpanType spanType) { 64 | if (parentSpan == null) { 65 | throw new IllegalArgumentException("parentSpan cannot be null. " + 66 | "If you don't have a parent span then you should call one of the startRequestWithRootSpan(...) methods instead."); 67 | } 68 | 69 | return startRequestWithSpanInfo(appId, parentSpan.getTraceId(), parentSpan.getSpanId(), childSpanName, parentSpan.sampleable(), 70 | spanType 71 | ); 72 | } 73 | 74 | public Span startRequestWithSpanInfo(String appId, String traceId, String parentSpanId, String newSpanName, boolean sampleable, Span.SpanType spanType) { 75 | return doNewRequestSpan(appId, traceId, parentSpanId, newSpanName, sampleable, spanType); 76 | } 77 | 78 | 79 | public Span startSubSpan(String spanName, String appId, Span.SpanType spanType) { 80 | 81 | Span parentSpan = getCurrentSpan(); 82 | if (parentSpan == null) { 83 | System.err.println("[" + Thread.currentThread().getName() + "]" + spanName + "\t" + appId + "\t" + spanType); 84 | } 85 | 86 | Span childSpan = (parentSpan != null) 87 | ? parentSpan.generateChildSpan(spanName, appId, spanType) 88 | : Span.generateRootSpanForNewTrace(spanName, appId, spanType).withSampleable(isNextRootSpanSampleable()).build(); 89 | 90 | pushSpanOntoCurrentSpanStack(childSpan); 91 | 92 | notifySpanStarted(childSpan); 93 | notifyIfSpanSampled(childSpan); 94 | 95 | return childSpan; 96 | } 97 | 98 | 99 | protected Span doNewRequestSpan(String appId, String traceId, String parentSpanId, String newSpanName, boolean sampleable, Span.SpanType spanType) { 100 | if (newSpanName == null) 101 | throw new IllegalArgumentException("spanName cannot be null"); 102 | 103 | 104 | if (traceId == null) 105 | traceId = TraceAndSpanIdGenerator.generateId(); 106 | String spanId; 107 | spanId = TraceAndSpanIdGenerator.generateId(); 108 | Integer durationNanos = null; 109 | Long spanStartTimeEpochMicros = System.currentTimeMillis(); 110 | Long spanStartTimeNanos = System.nanoTime(); 111 | 112 | Span span = new Span(appId, traceId, parentSpanId, spanId, newSpanName, sampleable, 113 | spanType, spanStartTimeEpochMicros, spanStartTimeNanos, durationNanos); 114 | 115 | span.setTraceId(traceId); 116 | span.setParentSpanId(parentSpanId); 117 | span.setSpanType(spanType); 118 | span.setRootType(1); 119 | 120 | startNewSpanStack(span); 121 | 122 | notifySpanStarted(span); 123 | notifyIfSpanSampled(span); 124 | 125 | return span; 126 | } 127 | 128 | 129 | protected void startNewSpanStack(Span firstEntry) { 130 | Deque existingStack = currentSpanStackThreadLocal.get(); 131 | if (existingStack != null && !existingStack.isEmpty()) { 132 | boolean first = true; 133 | StringBuilder lostTraceIds = new StringBuilder(); 134 | for (Span span : existingStack) { 135 | if (!first) 136 | lostTraceIds.append(','); 137 | lostTraceIds.append(span.getTraceId()); 138 | first = false; 139 | } 140 | System.err.println( 141 | "[" + Thread.currentThread().getName() + "]" + "USAGE ERROR existing stack size: " 142 | + existingStack.size() + " lost_trace_ids=" + lostTraceIds.toString() + "\t" + firstEntry 143 | ); 144 | } 145 | 146 | currentSpanStackThreadLocal.set(new LinkedList()); 147 | pushSpanOntoCurrentSpanStack(firstEntry); 148 | } 149 | 150 | 151 | 152 | protected void pushSpanOntoCurrentSpanStack(Span pushMe) { 153 | Deque currentStack = currentSpanStackThreadLocal.get(); 154 | if (currentStack == null) { 155 | currentStack = new LinkedList<>(); 156 | currentSpanStackThreadLocal.set(currentStack); 157 | } 158 | currentStack.push(pushMe); 159 | } 160 | 161 | public void completeRequestSpan(Throwable uncaughtException, Map map) { 162 | Log.info("completeRequestSpan: " + map); 163 | Deque currentSpanStack = currentSpanStackThreadLocal.get(); 164 | if (currentSpanStack != null) { 165 | // Keep track of data as we go in case we need to output an error (we should only have 1 span in the stack) 166 | int originalSize = currentSpanStack.size(); 167 | StringBuilder badTraceIds = new StringBuilder(); 168 | 169 | while (!currentSpanStack.isEmpty()) { 170 | // Get the next span on the stack. 171 | Span span = currentSpanStack.pop(); 172 | span.setUncaughtException(uncaughtException); 173 | for (Map.Entry item : map.entrySet()) { 174 | span.addExtraData(item.getKey(), item.getValue()); 175 | } 176 | 177 | // Check if it's a "bad" span (i.e. not the last). 178 | boolean isBadSpan = false; 179 | if (!currentSpanStack.isEmpty()) { 180 | // There's still at least one more span, so this one is "bad". 181 | isBadSpan = true; 182 | if (badTraceIds.length() > 0) 183 | badTraceIds.append(','); 184 | badTraceIds.append(span.getTraceId()); 185 | } 186 | 187 | completeAndLogSpan(span); 188 | } 189 | 190 | // Output an error message if we had any bad spans. 191 | if (originalSize > 1) { 192 | System.err.println( 193 | "USAGE ERROR - We were asked to fully complete a request span" 194 | ); 195 | } 196 | } 197 | 198 | currentSpanStackThreadLocal.remove(); 199 | } 200 | 201 | 202 | public void completeSubSpan(Throwable uncaughtException, List caughtExceptionList, Map map) { 203 | 204 | Deque currentSpanStack = currentSpanStackThreadLocal.get(); 205 | if (currentSpanStack == null || currentSpanStack.size() < 2) { 206 | int stackSize = (currentSpanStack == null) ? 0 : currentSpanStack.size(); 207 | System.err.println( 208 | "[" + Thread.currentThread().getName() + "]" + 209 | "USAGE ERROR - Expected to find a child sub-span on the stack to complete," + 210 | " but the span stack was size " + stackSize); 211 | // Nothing to do 212 | return; 213 | } 214 | 215 | // We have at least two spans. Pop off the child sub-span and complete/log it. 216 | Span subSpan = currentSpanStack.pop(); 217 | 218 | subSpan.setCaughtExceptionList(caughtExceptionList); 219 | subSpan.setUncaughtException(uncaughtException); 220 | if (map != null) { 221 | for (Map.Entry item : map.entrySet()) { 222 | subSpan.addExtraData(item.getKey(), item.getValue()); 223 | } 224 | } 225 | completeAndLogSpan(subSpan); 226 | } 227 | 228 | 229 | protected void completeAndLogSpan(Span span) { 230 | // Complete the span. 231 | if (span.hasComplete()) { 232 | System.err.println( 233 | "USAGE ERROR - An attempt was made to complete a span that was already completed. This call will be ignored. " 234 | + "wingtips_usage_error=true, already_completed_span=true, trace_id={}, span_id={}" 235 | ); 236 | return; 237 | } 238 | else 239 | span.complete(); 240 | 241 | // Notify listeners. 242 | notifySpanCompleted(span); 243 | } 244 | 245 | 246 | protected boolean isNextRootSpanSampleable() { 247 | return rootSpanSamplingStrategy.isNextRootSpanSampleable(); 248 | } 249 | 250 | 251 | public void addSpanLifecycleListener(SpanLifecycleListener listener) { 252 | if (listener != null) 253 | this.spanLifecycleListeners.add(listener); 254 | } 255 | 256 | 257 | protected void notifySpanStarted(Span span) { 258 | for (SpanLifecycleListener tll : spanLifecycleListeners) { 259 | tll.spanStarted(span); 260 | } 261 | } 262 | 263 | 264 | private void notifyIfSpanSampled(Span span) { 265 | if (span.sampleable()) { 266 | for (SpanLifecycleListener tll : spanLifecycleListeners) { 267 | tll.spanSampled(span); 268 | } 269 | } 270 | } 271 | 272 | 273 | protected void notifySpanCompleted(Span span) { 274 | for (SpanLifecycleListener tll : spanLifecycleListeners) { 275 | tll.spanCompleted(span); 276 | } 277 | } 278 | 279 | 280 | } 281 | -------------------------------------------------------------------------------- /javaagent-demo/src/main/java/me/geek01/javaagent/Wildcard.java: -------------------------------------------------------------------------------- 1 | package me.geek01.javaagent; 2 | 3 | 4 | /** 5 | * Checks whether a string or path matches a given wildcard pattern. 6 | * Possible patterns allow to match single characters ('?') or any count of 7 | * characters ('*'). Wildcard characters can be escaped (by an '\'). 8 | * When matching path, deep tree wildcard also can be used ('**'). 9 | *

10 | * This method uses recursive matching, as in linux or windows. regexp works the same. 11 | * This method is very fast, comparing to similar implementations. 12 | */ 13 | public class Wildcard { 14 | 15 | /** 16 | * Checks whether a string matches a given wildcard pattern. 17 | * 18 | * @param string input string 19 | * @param pattern pattern to match 20 | * @return true if string matches the pattern, otherwise false 21 | */ 22 | public static boolean match(CharSequence string, CharSequence pattern) { 23 | return match(string, pattern, 0, 0); 24 | } 25 | 26 | /** 27 | * Checks if two strings are equals or if they {@link #match(CharSequence, CharSequence)}. 28 | * Useful for cases when matching a lot of equal strings and speed is important. 29 | */ 30 | public static boolean equalsOrMatch(CharSequence string, CharSequence pattern) { 31 | if (string.equals(pattern)) { 32 | return true; 33 | } 34 | return match(string, pattern, 0, 0); 35 | } 36 | 37 | /** 38 | * Internal matching recursive function. 39 | */ 40 | private static boolean match(CharSequence string, CharSequence pattern, int sNdx, int pNdx) { 41 | int pLen = pattern.length(); 42 | if (pLen == 1) { 43 | if (pattern.charAt(0) == '*') { // speed-up 44 | return true; 45 | } 46 | } 47 | int sLen = string.length(); 48 | boolean nextIsNotWildcard = false; 49 | 50 | while (true) { 51 | 52 | // check if end of string and/or pattern occurred 53 | if ((sNdx >= sLen)) { // end of string still may have pending '*' in pattern 54 | while ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) { 55 | pNdx++; 56 | } 57 | return pNdx >= pLen; 58 | } 59 | if (pNdx >= pLen) { // end of pattern, but not end of the string 60 | return false; 61 | } 62 | char p = pattern.charAt(pNdx); // pattern char 63 | 64 | // perform logic 65 | if (!nextIsNotWildcard) { 66 | 67 | if (p == '\\') { 68 | pNdx++; 69 | nextIsNotWildcard = true; 70 | continue; 71 | } 72 | if (p == '?') { 73 | sNdx++; 74 | pNdx++; 75 | continue; 76 | } 77 | if (p == '*') { 78 | char pNext = 0; // next pattern char 79 | if (pNdx + 1 < pLen) { 80 | pNext = pattern.charAt(pNdx + 1); 81 | } 82 | if (pNext == '*') { // double '*' have the same effect as one '*' 83 | pNdx++; 84 | continue; 85 | } 86 | int i; 87 | pNdx++; 88 | 89 | // find recursively if there is any substring from the end of the 90 | // line that matches the rest of the pattern !!! 91 | for (i = string.length(); i >= sNdx; i--) { 92 | if (match(string, pattern, i, pNdx)) { 93 | return true; 94 | } 95 | } 96 | return false; 97 | } 98 | } else { 99 | nextIsNotWildcard = false; 100 | } 101 | 102 | // check if pattern char and string char are equals 103 | if (p != string.charAt(sNdx)) { 104 | return false; 105 | } 106 | 107 | // everything matches for now, continue 108 | sNdx++; 109 | pNdx++; 110 | } 111 | } 112 | 113 | 114 | // ---------------------------------------------------------------- utilities 115 | 116 | /** 117 | * Matches string to at least one pattern. 118 | * Returns index of matched pattern, or -1 otherwise. 119 | * 120 | * @see #match(CharSequence, CharSequence) 121 | */ 122 | public static int matchOne(String src, String[] patterns) { 123 | for (int i = 0; i < patterns.length; i++) { 124 | if (match(src, patterns[i])) { 125 | return i; 126 | } 127 | } 128 | return -1; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /jvm/bytecode/Hello.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arthur-zhang/geek01/776923f0ec91ec235bbe08cfa398cff33c96daa1/jvm/bytecode/Hello.class -------------------------------------------------------------------------------- /jvm/bytecode/Hello.java: -------------------------------------------------------------------------------- 1 | public class Hello { 2 | public static void main(String[] args) { 3 | System.out.println("Hello, World"); 4 | } 5 | } -------------------------------------------------------------------------------- /zero_copy/zero_copy_basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(zero_copy C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | add_executable(zero_copy main.c) -------------------------------------------------------------------------------- /zero_copy/zero_copy_basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /zero_copy/zero_copy_basic/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include /* ANSI C header file */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define LISTENQ 1024 /* 2nd argument to listen() */ 21 | #define BUFFER_SIZE 64 /* size of buffer used by setbuf */ 22 | 23 | void 24 | err_quit(const char *fmt) { 25 | printf("%s", fmt); 26 | exit(1); 27 | } 28 | 29 | int main() { 30 | printf("Hello, World!\n"); 31 | 32 | struct sockaddr_in serv_addr; 33 | 34 | int listen_fd = 0; 35 | // socket(int domain, int type, int protocol); 36 | if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 37 | exit(1); 38 | } 39 | bzero(&serv_addr, sizeof(serv_addr)); 40 | 41 | serv_addr.sin_family = AF_INET; 42 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 43 | serv_addr.sin_port = htons(34567); 44 | 45 | int optval = 1; 46 | if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, 47 | sizeof(optval)) == -1) { 48 | close(listen_fd); 49 | err_quit("set socket opt failed"); 50 | } 51 | if (bind(listen_fd, &serv_addr, sizeof(serv_addr)) < 0) 52 | err_quit("bind failed"); 53 | 54 | if (listen(listen_fd, LISTENQ) < 0) 55 | err_quit("listen failed"); 56 | 57 | struct sockaddr_in client_addr; 58 | 59 | int connect_fd = 0; 60 | int fd = open("/Users/arthur/CLionProjects/zero_copy/index.html", O_RDWR); 61 | 62 | if (fd < 0) err_quit("error open file"); 63 | 64 | 65 | for (;;) { 66 | if (lseek(fd, 0, SEEK_SET) < 0) err_quit("error seek file"); 67 | 68 | connect_fd = accept(listen_fd, &serv_addr, &client_addr); 69 | if (connect_fd < 0) err_quit("accept failed"); 70 | 71 | int n = 0; 72 | char buf[BUFFER_SIZE]; 73 | while ((n = read(fd, buf, BUFFER_SIZE)) > 0) { 74 | write(connect_fd, buf, n); 75 | } 76 | close(connect_fd); 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /zero_copy/zerocopy_sendfile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(zerocopy_sendfile C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | add_executable(zerocopy_sendfile main.c) -------------------------------------------------------------------------------- /zero_copy/zerocopy_sendfile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /zero_copy/zerocopy_sendfile/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include /* ANSI C header file */ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define LISTENQ 1024 /* 2nd argument to listen() */ 22 | #define BUFER_SIZE 64 /* size of buffer used by setbuf */ 23 | 24 | void 25 | err_quit(const char *fmt) { 26 | printf("%s", fmt); 27 | exit(1); 28 | } 29 | 30 | int main() { 31 | printf("Hello, World!\n"); 32 | 33 | struct sockaddr_in serv_addr; 34 | 35 | int listen_fd = 0; 36 | // socket(int domain, int type, int protocol); 37 | if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 38 | exit(1); 39 | } 40 | bzero(&serv_addr, sizeof(serv_addr)); 41 | 42 | serv_addr.sin_family = AF_INET; 43 | serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 44 | serv_addr.sin_port = htons(34567); 45 | 46 | int optval = 1; 47 | if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, 48 | sizeof(optval)) == -1) { 49 | close(listen_fd); 50 | err_quit("set socket opt failed"); 51 | } 52 | if (bind(listen_fd, &serv_addr, sizeof(serv_addr)) < 0) 53 | err_quit("bind failed"); 54 | 55 | if (listen(listen_fd, LISTENQ) < 0) 56 | err_quit("listen failed"); 57 | 58 | struct sockaddr_in client_addr; 59 | 60 | int connect_fd = 0; 61 | int fd = open("./index.html", O_RDWR); 62 | 63 | if (fd < 0) err_quit("error open file"); 64 | 65 | 66 | for (;;) { 67 | 68 | connect_fd = accept(listen_fd, &serv_addr, &client_addr); 69 | if (connect_fd < 0) err_quit("accept failed"); 70 | 71 | struct stat stat_buf; 72 | fstat(fd, &stat_buf); 73 | 74 | off_t offset = 0; 75 | 76 | int cnt = 0; 77 | if ((cnt = sendfile(connect_fd, fd, &offset, stat_buf.st_size)) < 0) { 78 | err_quit("send file failed"); 79 | } 80 | close(connect_fd); 81 | } 82 | } 83 | 84 | 85 | 86 | --------------------------------------------------------------------------------