skywalking的agent如何创建span(二)
- 在tomcat插件中的beforeMethod方法中可以看到ContextManager.createEntrySpan。查看源码,重要的有两部分,第一部分为创建一个AbstractTracerContext,通过一步一步调试可以看到是创建了一个TracingContext(实现类),第二部分为创建EntrySpan。
private static ThreadLocal<AbstractTracerContext> CONTEXT = new ThreadLocal<AbstractTracerContext>();
public static AbstractSpan createEntrySpan(String operationName, ContextCarrier carrier) {
AbstractSpan span;
AbstractTracerContext context;
operationName = StringUtil.cut(operationName, OPERATION_NAME_THRESHOLD);
if (carrier != null && carrier.isValid()) {
SamplingService samplingService = ServiceManager.INSTANCE.findService(SamplingService.class);
samplingService.forceSampled();
context = getOrCreate(operationName, true);
span = context.createEntrySpan(operationName);
context.extract(carrier);
} else {
context = getOrCreate(operationName, false); //①
span = context.createEntrySpan(operationName); //②
}
return span;
}
private static AbstractTracerContext getOrCreate(String operationName, boolean forceSampling) {
AbstractTracerContext context = CONTEXT.get();
if (context == null) {
if (StringUtil.isEmpty(operationName)) {
if (LOGGER.isDebugEnable()) {
LOGGER.debug("No operation name, ignore this trace.");
}
context = new IgnoredTracerContext();
} else {
if (EXTEND_SERVICE == null) {
EXTEND_SERVICE = ServiceManager.INSTANCE.findService(ContextManagerExtendService.class);
}
context = EXTEND_SERVICE.createTraceContext(operationName, forceSampling);
}
CONTEXT.set(context);
}
return context;
}
步骤一种创建的context会被存储到 ThreadLocal中,ThreadLocal保证了每个线程中的数据是独立的
在TracingContext中定义了TraceSegment,在TraceSegment中定义了一个private List<AbstractTracingSpan> spans,用于存储span信息。
/**
* Initialize all fields with default value.
*/
TracingContext(String firstOPName, SpanLimitWatcher spanLimitWatcher) {
this.segment = new TraceSegment();
this.spanIdGenerator = 0;
isRunningInAsyncMode = false;
createTime = System.currentTimeMillis();
running = true;
// profiling status
if (PROFILE_TASK_EXECUTION_SERVICE == null) {
PROFILE_TASK_EXECUTION_SERVICE = ServiceManager.INSTANCE.findService(ProfileTaskExecutionService.class);
}
this.profileStatus = PROFILE_TASK_EXECUTION_SERVICE.addProfiling(
this, segment.getTraceSegmentId(), firstOPName);
this.correlationContext = new CorrelationContext();
this.extensionContext = new ExtensionContext();
this.spanLimitWatcher = spanLimitWatcher;
}
private List<AbstractTracingSpan> spans;
public TraceSegment() {
this.traceSegmentId = GlobalIdGenerator.generate();
this.spans = new LinkedList<>();
this.relatedGlobalTraceId = new NewDistributedTraceId();
this.createTime = System.currentTimeMillis();
//收集线程信息
threadID = Thread.currentThread().getId();
threadName = Thread.currentThread().getName();
ThreadGroup group = Thread.currentThread().getThreadGroup();
threadGroup = (group == null ? null : group.getName());
}
在步骤二中创建span,该span会被存储到activeSpanStack ,span信息会在afterMethod中获取出来,并对span信息补充。
private LinkedList<AbstractSpan> activeSpanStack = new LinkedList<>();
/**
* Add a new Span at the top of 'ActiveSpanStack'
*
* @param span the {@code span} to push
*/
private AbstractSpan push(AbstractSpan span) {
if (firstSpan == null) {
firstSpan = span;
}
activeSpanStack.addLast(span);
this.extensionContext.handle(span);
return span;
}
afterMethod源码可以看到会获取activeSpan,也就是activeSpanStack 中的span信息,在afterMethod中完成对span信息的补充后,会调用stopSpan
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
Request request = (Request) allArguments[0];
HttpServletResponse response = (HttpServletResponse) allArguments[1];
AbstractSpan span = ContextManager.activeSpan();
//设置所处的类信息
Tags.LOCATION.set(span, objInst.getClass().getName());
if (IS_SERVLET_GET_STATUS_METHOD_EXIST && response.getStatus() >= 400) {
span.errorOccurred();
Tags.STATUS_CODE.set(span, Integer.toString(response.getStatus()));
}
// Active HTTP parameter collection automatically in the profiling context.
if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) {
collectHttpParam(request, span);
}
ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
ContextManager.stopSpan();
return ret;
}
继续跟踪stopSpan,可以看到在执行玩stopSpan后,会把对应的CONTEXT(也就是之前说的TracingContext)从ThreadLocal中移除
private static void stopSpan(AbstractSpan span, final AbstractTracerContext context) {
if (context.stopSpan(span)) {
CONTEXT.remove();
RUNTIME_CONTEXT.remove();
}
}
继续追踪stopSpan到finish方法,可以看到第一部分的红色代码,判断activeSpanStack是否还有span信息,该结果作为判断是否调用notifyFinish实现数据向collector
发送的条件之一。这么做的意义在哪里呢?可以想象一下,Tomcat作为入口,探针为其创建了EntrySpan,在tomcat应用中可能会通过jdbc调用数据库(ExitSpan)。这就像括号匹配一样
"{[()]}"每个span都有beforeMethod和AfterMethod,只有当所有的afterMethod执行完后才能组建成一个完整的TraceSegment(数据的发送是以每个TraceSegment为单位的)。
/**
* Finish this context, and notify all {@link TracingContextListener}s, managed by {@link
* TracingContext.ListenerManager} and {@link TracingContext.TracingThreadListenerManager}
*/
private void finish() {
if (isRunningInAsyncMode) {
asyncFinishLock.lock();
}
try {
boolean isFinishedInMainThread = activeSpanStack.isEmpty() && running;
if (isFinishedInMainThread) {
/*
* Notify after tracing finished in the main thread.
*/
TracingThreadListenerManager.notifyFinish(this);
}
if (isFinishedInMainThread && (!isRunningInAsyncMode || asyncSpanCounter == 0)) {
TraceSegment finishedSegment = segment.finish(isLimitMechanismWorking());
TracingContext.ListenerManager.notifyFinish(finishedSegment);
running = false;
}
} finally {
if (isRunningInAsyncMode) {
asyncFinishLock.unlock();
}
}
}
追踪notifyFinish方法一直到Channels类的save方法,数据会存放在本地缓存bufferChannels中。
public class Channels<T> {
private final QueueBuffer<T>[] bufferChannels;
......
......
public boolean save(T data) {
int index = dataPartitioner.partition(bufferChannels.length, data);
int retryCountDown = 1;
if (BufferStrategy.IF_POSSIBLE.equals(strategy)) {
int maxRetryCount = dataPartitioner.maxRetryCount();
if (maxRetryCount > 1) {
retryCountDown = maxRetryCount;
}
}
for (; retryCountDown > 0; retryCountDown--) {
if (bufferChannels[index].save(data)) {
return true;
}
}
return false;
}
......
......
}
该缓存会通过另外一个线程实现数据的发送
@Override
public void run() {
running = true;
final List consumeList = new ArrayList(2000);
while (running) {
boolean hasData = false;
for (Group target : consumeTargets) {
boolean consume = consume(target, consumeList);
hasData = hasData || consume;
}
if (!hasData) {
try {
Thread.sleep(consumeCycle);
} catch (InterruptedException e) {
}
}
}
// consumer thread is going to stop
// consume the last time
for (Group target : consumeTargets) {
consume(target, consumeList);
target.consumer.onExit();
}
}