/*
* Copyright 2014 NAVER Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.navercorp.pinpoint.profiler.context;
import com.navercorp.pinpoint.bootstrap.context.*;
import com.navercorp.pinpoint.bootstrap.context.scope.TraceScope;
import com.navercorp.pinpoint.profiler.context.id.AsyncIdGenerator;
import com.navercorp.pinpoint.profiler.context.id.DefaultAsyncTraceId;
import com.navercorp.pinpoint.profiler.context.recorder.RecorderFactory;
import com.navercorp.pinpoint.profiler.context.recorder.WrappedSpanEventRecorder;
import com.navercorp.pinpoint.profiler.context.scope.DefaultTraceScopePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.navercorp.pinpoint.exception.PinpointException;
import com.navercorp.pinpoint.profiler.context.storage.Storage;
/**
* @author netspider
* @author emeroad
* @author jaehong.kim
*/
public final class DefaultTrace implements Trace {
private static final Logger logger = LoggerFactory.getLogger(DefaultTrace.class.getName());
private static final boolean isTrace = logger.isTraceEnabled();
private static final boolean isWarn = logger.isWarnEnabled();
private final boolean sampling;
private final long localTransactionId;
private final TraceId traceId;
private final CallStack callStack;
private final Storage storage;
private final Span span;
private final SpanRecorder spanRecorder;
private final WrappedSpanEventRecorder spanEventRecorder;
private final AsyncIdGenerator asyncIdGenerator;
private boolean closed = false;
private Thread bindThread;
private final DefaultTraceScopePool scopePool = new DefaultTraceScopePool();
public DefaultTrace(CallStackFactory callStackFactory, Storage storage, TraceId traceId, long localTransactionId, AsyncIdGenerator asyncIdGenerator, boolean sampling,
SpanFactory spanFactory, RecorderFactory recorderFactory) {
if (storage == null) {
throw new NullPointerException("storage must not be null");
}
if (traceId == null) {
throw new NullPointerException("continueTraceId must not be null");
}
if (asyncIdGenerator == null) {
throw new NullPointerException("asyncIdGenerator must not be null");
}
if (spanFactory == null) {
throw new NullPointerException("spanFactory must not be null");
}
if (recorderFactory == null) {
throw new NullPointerException("recorderFactory must not be null");
}
this.storage = storage;
this.traceId = traceId;
this.localTransactionId = localTransactionId;
this.sampling = sampling;
this.span = spanFactory.newSpan();
this.span.recordTraceId(traceId);
this.spanRecorder = recorderFactory.newSpanRecorder(span, traceId.isRoot(), sampling);
this.spanEventRecorder = recorderFactory.newWrappedSpanEventRecorder();
this.callStack = callStackFactory.newCallStack(span);
this.asyncIdGenerator = asyncIdGenerator;
setCurrentThread();
}
private SpanEventRecorder wrappedSpanEventRecorder(SpanEvent spanEvent) {
final WrappedSpanEventRecorder spanEventRecorder = this.spanEventRecorder;
spanEventRecorder.setWrapped(spanEvent);
return spanEventRecorder;
}
public Span getSpan() {
return span;
}
@Override
public SpanEventRecorder traceBlockBegin() {
return traceBlockBegin(DEFAULT_STACKID);
}
@Override
public SpanEventRecorder traceBlockBegin(final int stackId) {
// Set properties for the case when stackFrame is not used as part of Span.
final SpanEvent spanEvent = new SpanEvent(span);
spanEvent.markStartTime();
spanEvent.setStackId(stackId);
if (this.closed) {
if (isWarn) {
PinpointException exception = new PinpointException("already closed trace.");
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
} else {
callStack.push(spanEvent);
}
return wrappedSpanEventRecorder(spanEvent);
}
@Override
public void traceBlockEnd() {
traceBlockEnd(DEFAULT_STACKID);
}
@Override
public void traceBlockEnd(int stackId) {
if (this.closed) {
if (isWarn) {
final PinpointException exception = new PinpointException("already closed trace.");
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
return;
}
final SpanEvent spanEvent = callStack.pop();
if (spanEvent == null) {
if (isWarn) {
PinpointException exception = new PinpointException("call stack is empty.");
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
return;
}
if (spanEvent.getStackId() != stackId) {
// stack dump will make debugging easy.
if (isWarn) {
PinpointException exception = new PinpointException("not matched stack id. expected=" + stackId + ", current=" + spanEvent.getStackId());
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
}
if (spanEvent.isTimeRecording()) {
spanEvent.markAfterTime();
}
logSpan(spanEvent);
}
@Override
public void close() {
if (closed) {
logger.warn("Already closed trace.");
return;
}
closed = true;
if (!callStack.empty()) {
if (isWarn) {
PinpointException exception = new PinpointException("not empty call stack.");
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
// skip
} else {
if (span.isTimeRecording()) {
span.markAfterTime();
}
logSpan(span);
}
this.storage.close();
}
@Override
public void flush() {
this.storage.flush();
}
/**
* Get current TraceID. If it was not set this will return null.
*
* @return
*/
@Override
public TraceId getTraceId() {
return this.traceId;
}
@Override
public long getId() {
return this.localTransactionId;
}
@Override
public long getStartTime() {
return span.getStartTime();
}
@Override
public Thread getBindThread() {
return bindThread;
}
private void setCurrentThread() {
this.setBindThread(Thread.currentThread());
}
private void setBindThread(Thread thread) {
bindThread = thread;
}
public boolean canSampled() {
return this.sampling;
}
public boolean isRoot() {
return getTraceId().isRoot();
}
private void logSpan(SpanEvent spanEvent) {
if (isTrace) {
final Thread th = Thread.currentThread();
logger.trace("[DefaultTrace] Write {} thread{id={}, name={}}", spanEvent, th.getId(), th.getName());
}
storage.store(spanEvent);
}
private void logSpan(Span span) {
if (isTrace) {
final Thread th = Thread.currentThread();
logger.trace("[DefaultTrace] Write {} thread{id={}, name={}}", span, th.getId(), th.getName());
}
this.storage.store(span);
}
@Override
public boolean isAsync() {
return false;
}
@Override
public boolean isRootStack() {
return callStack.empty();
}
@Override
public AsyncTraceId getAsyncTraceId() {
return getAsyncTraceId(false);
}
@Override
public AsyncTraceId getAsyncTraceId(boolean closeable) {
// ignored closeable.
return new DefaultAsyncTraceId(traceId, asyncIdGenerator.nextAsyncId(), span.getStartTime());
}
@Override
public SpanRecorder getSpanRecorder() {
return spanRecorder;
}
@Override
public SpanEventRecorder currentSpanEventRecorder() {
SpanEvent spanEvent = callStack.peek();
if (spanEvent == null) {
if (isWarn) {
PinpointException exception = new PinpointException("call stack is empty");
logger.warn("[DefaultTrace] Corrupted call stack found.", exception);
}
// make dummy.
spanEvent = new SpanEvent(span);
}
return wrappedSpanEventRecorder(spanEvent);
}
@Override
public int getCallStackFrameId() {
final SpanEvent spanEvent = callStack.peek();
if (spanEvent == null) {
return ROOT_STACKID;
} else {
return spanEvent.getStackId();
}
}
@Override
public TraceScope getScope(String name) {
return scopePool.get(name);
}
@Override
public TraceScope addScope(String name) {
return scopePool.add(name);
}
}