Analysis of pinpoint sampling principle

Keywords: Programming

When using pinpoint for full link monitoring, it supports the sampling of requests. Whether a request is sampled depends on the machine at the beginning of the whole link. The machine uses a specific sampling algorithm. The sampled flag will be transmitted through the link all the time. For example, in http, the pinpoint sampled field will be added to the header, and different values will be used to indicate whether to sample or not.

s0: this request is not sampled
s1: sample this request

The following is divided into the receiving end and the sending end, respectively looking at the processing of sampling. In addition, when recording span or spanEvent, you need to pay attention to the judgment of whether to sample or not.

Receiving end sampling processing

After receiving the request, the receiver will check whether there are pinpoint sampled field, pinpoint traceid and other fields in the request. It can be divided into the following situations:

  1. There is a pinpoint sampled field with a value of s0, indicating that this request is not sampled.
  2. There is no pinpoint sampled field, but there are pinpoint traceid and other fields, indicating that the request is sampled.
  3. There is no pinpoint sampled field or pinpoint traceid field. It is considered that the machine receiving the request is the first machine of the whole link, or the link information is lost in the front.

In the first case, the processing is relatively simple. Call the following method to create a DisableTrace, which means that the Trace is not sampled and bound to the thread context.

 

final Trace trace = this.traceContext.disableSampling();
  traceFactory.disableSampling();
    final Trace trace = this.baseTraceFactory.disableSampling();

Note: use indent for nesting

In the second case, call this.baseTraceFactory.continueTraceObject to create a DefaultTrace or AsyncTrace. Specify that the sampling field is true to indicate sampling.

 

continueTrace(request, traceHeader)
  this.traceContext.continueTraceObject(traceId);
    this.baseTraceFactory.continueTraceObject(traceId);

In the third case, when this.baseTraceFactory.newTraceObject() is called, the configured sampler is used for sampling judgment: if sampling, the DefaultTrace object is created, and the sampling field is true; if no sampling, the DisableTrace object is created.

 

final Trace trace = this.baseTraceFactory.newTraceObject(); 

public class DefaultBaseTraceFactory implements BaseTraceFactory {
    ...
    @Override
    public Trace newTraceObject() {
        // TODO need to modify how to inject a datasender
        final boolean sampling = sampler.isSampling();
        if (sampling) {
            final TraceRoot traceRoot = traceRootFactory.newTraceRoot();
            final Span span = spanFactory.newSpan(traceRoot);

            final Storage storage = storageFactory.createStorage(traceRoot);
            final CallStack<SpanEvent> callStack = callStackFactory.newCallStack();

            final TraceId traceId = traceRoot.getTraceId();
            final SpanRecorder spanRecorder = recorderFactory.newSpanRecorder(span, traceId.isRoot(), sampling);
            final WrappedSpanEventRecorder wrappedSpanEventRecorder = recorderFactory.newWrappedSpanEventRecorder(traceRoot);

            final ActiveTraceHandle handle = registerActiveTrace(traceRoot);
            final DefaultTrace trace = new DefaultTrace(span, callStack, storage, sampling, spanRecorder, wrappedSpanEventRecorder, handle);

            return trace;
        } else {
            return newDisableTrace();
        }
    }
    ...
}

The following focuses on the code of SamplingRateSampler class, which inherits the Sampler interface and is relatively simple: use the accumulated count, when the accumulated number is a multiple of the configured samplingRate, it means that the request is sampled.

 

public class SamplingRateSampler implements Sampler {

    private final AtomicInteger counter = new AtomicInteger(0);
    private final int samplingRate;

    public SamplingRateSampler(int samplingRate) {
        if (samplingRate <= 0) {
            throw new IllegalArgumentException("Invalid samplingRate " + samplingRate);
        }
        this.samplingRate = samplingRate;
    }

    @Override
    public boolean isSampling() {
        int samplingCount = MathUtils.fastAbs(counter.getAndIncrement());
        int isSampling = samplingCount % samplingRate;
        return isSampling == 0;
    }
}

Sending end sampling processing

Before the request is sent to the downstream machine, the Trace object will be obtained from the current context and whether to sample or not. Take httprequestexecutorexecutemethodeinterceptor as an example:

 

public class HttpRequestExecutorExecuteMethodInterceptor implements AroundInterceptor {
    public void before(Object target, Object[] args) {
         ....
        final Trace trace = traceContext.currentRawTraceObject();
        if (trace == null) {
            return;
        }

        final HttpRequest httpRequest = getHttpRequest(args);
        final NameIntValuePair<String> host = getHost();
        final boolean sampling = trace.canSampled();
        if (!sampling) {
            if (httpRequest != null) {
                this.requestTraceWriter.write(httpRequest);
            }
            return;
        }

        final SpanEventRecorder recorder = trace.traceBlockBegin();
        TraceId nextId = trace.getTraceId().getNextTraceId();
        recorder.recordNextSpanId(nextId.getSpanId());
        recorder.recordServiceType(HttpClient4Constants.HTTP_CLIENT_4);
        if (httpRequest != null) {
            final String hostString = getHostString(host.getName(), host.getValue());
            this.requestTraceWriter.write(httpRequest, nextId, hostString);
        }

        InterceptorScopeInvocation invocation = interceptorScope.getCurrentInvocation();
        if (invocation != null) {
            invocation.getOrCreateAttachment(HttpCallContextFactory.HTTPCALL_CONTEXT_FACTORY);
        }
    }
  ...
}


public class DefaultRequestTraceWriter<T> implements RequestTraceWriter<T> {
    ...
    @Override
    public void write(T header, final TraceId traceId, final String host) {
        Assert.requireNonNull(traceId, "traceId must not be null");

        if (isDebug) {
            logger.debug("Set request header. traceId={}, applicationName={}, serverTypeCode={}, applicationNamespace={}", traceId, applicationName, serverTypeCode, applicationNamespace);
        }
        clientHeaderAdaptor.setHeader(header, Header.HTTP_TRACE_ID.toString(), traceId.getTransactionId());
        clientHeaderAdaptor.setHeader(header, Header.HTTP_SPAN_ID.toString(), String.valueOf(traceId.getSpanId()));
        clientHeaderAdaptor.setHeader(header, Header.HTTP_PARENT_SPAN_ID.toString(), String.valueOf(traceId.getParentSpanId()));
        clientHeaderAdaptor.setHeader(header, Header.HTTP_FLAGS.toString(), String.valueOf(traceId.getFlags()));
        clientHeaderAdaptor.setHeader(header, Header.HTTP_PARENT_APPLICATION_NAME.toString(), applicationName);
        clientHeaderAdaptor.setHeader(header, Header.HTTP_PARENT_APPLICATION_TYPE.toString(), Short.toString(serverTypeCode));

        if (applicationNamespace != null) {
            clientHeaderAdaptor.setHeader(header, Header.HTTP_PARENT_APPLICATION_NAMESPACE.toString(), applicationNamespace);
        }

        if (host != null) {
            clientHeaderAdaptor.setHeader(header, Header.HTTP_HOST.toString(), host);
        }
    }
   ...
}

If the current trace's sampling = false is written directly to the HTTP header, the other information will not be passed. If it is true, the pinpoint sample field is not set, but only other trace related fields, such as Header.HTTP_TRACE_ID, are set.

For the judgment of sampling:

If the sampling of Trace = false, the Trace does not support recording span and spanEvent. Therefore, where span and spanEvent need to be used, it is necessary to determine whether the current Trace is sampled, such as:

 

        final Trace trace = createTrace(request);
        if (trace == null) {
            return;
        }

        if (!trace.canSampled()) {
            return;
        }

        ....
        final Trace trace = this.requestTraceReader.read(request);
        if (trace.canSampled()) {
            final SpanRecorder recorder = trace.getSpanRecorder();
            // record root span
            recorder.recordServiceType(this.serviceType);
            recorder.recordApi(SERVLET_SYNC_METHOD_DESCRIPTOR);
            this.serverRequestRecorder.record(recorder, request);
            // record proxy HTTP header.
            this.proxyHttpHeaderRecorder.record(recorder, request);
        }
        return trace;


 

Posted by DarkWater on Wed, 11 Dec 2019 19:51:09 -0800