/**
* Copyright 2015-2017 The OpenZipkin Authors
*
* 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 zipkin.internal;
import zipkin.Annotation;
import zipkin.Constants;
import zipkin.Span;
/**
* <h3>Derived timestamp and duration</h3>
*
* <p>Instrumentation should log timestamp and duration in most cases, but since these fields are
* recent (Nov-2015), a lot of tracers will not. They also will not log timestamp or duration in
* one-way spans ("cs", "sr"). This includes a utility to backfill timestamp and duration at query
* time. It also includes a utility to guess a timestamp, which is useful when indexing incomplete
* spans.
*/
public class ApplyTimestampAndDuration {
/**
* For RPC two-way spans, the duration between "cs" and "cr" is authoritative. RPC one-way spans
* lack a response, so the duration is between "cs" and "sr". We special-case this to avoid
* setting incorrect duration when there's skew between the client and the server.
*
* <p>Note: this should only be used for query, not storage commands!
*/
public static Span apply(Span span) {
// Don't overwrite authoritatively set timestamp and duration!
if (span.timestamp != null && span.duration != null) {
return span;
}
// We cannot backfill duration on a span with less than two annotations. However, we can
// backfill timestamp.
if (span.annotations.size() < 2) {
if (span.timestamp != null) return span;
Long guess = guessTimestamp(span);
if (guess == null) return span;
return span.toBuilder().timestamp(guess).build();
}
// Prefer RPC one-way (cs -> sr) vs arbitrary annotations.
Long first = span.annotations.get(0).timestamp;
Long last = span.annotations.get(span.annotations.size() - 1).timestamp;
for (int i = 0, length = span.annotations.size(); i < length; i++) {
Annotation annotation = span.annotations.get(i);
if (annotation.value.equals(Constants.CLIENT_SEND)) {
first = annotation.timestamp;
} else if (annotation.value.equals(Constants.CLIENT_RECV)) {
last = annotation.timestamp;
}
}
long ts = span.timestamp != null ? span.timestamp : first;
Long dur = span.duration != null ? span.duration : last.equals(first) ? null : last - first;
return span.toBuilder().timestamp(ts).duration(dur).build();
}
/**
* Instrumentation should set {@link Span#timestamp} when recording a span so that guess-work
* isn't needed. Since a lot of instrumentation don't, we have to make some guesses.
*
* <pre><ul>
* <li>If there is a {@link Constants#CLIENT_SEND}, use that</li>
* <li>Fall back to {@link Constants#SERVER_RECV}</li>
* <li>Otherwise, return null</li>
* </ul></pre>
*/
public static Long guessTimestamp(Span span) {
if (span.timestamp != null || span.annotations.isEmpty()) return span.timestamp;
Long rootServerRecv = null;
for (int i = 0, length = span.annotations.size(); i < length; i++) {
Annotation annotation = span.annotations.get(i);
if (annotation.value.equals(Constants.CLIENT_SEND)) {
return annotation.timestamp;
} else if (annotation.value.equals(Constants.SERVER_RECV)) {
rootServerRecv = annotation.timestamp;
}
}
return rootServerRecv;
}
/** When performing updates, don't overwrite an authoritative timestamp with a guess! */
public static Long authoritativeTimestamp(Span span) {
if (span.timestamp != null) return span.timestamp;
for (int i = 0, length = span.annotations.size(); i < length; i++) {
Annotation a = span.annotations.get(i);
if (a.value.equals(Constants.CLIENT_SEND)) {
return a.timestamp;
}
}
return null;
}
private ApplyTimestampAndDuration() {
}
}