com.palantir.tracing.HtmlFormatter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tracing-test-utils Show documentation
Show all versions of tracing-test-utils Show documentation
Palantir open source project
The newest version!
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.tracing;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.google.common.io.Resources;
import com.palantir.tracing.api.Serialization;
import com.palantir.tracing.api.Span;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.immutables.value.Value;
final class HtmlFormatter {
private RenderConfig config;
private HtmlFormatter(RenderConfig config) {
this.config = config;
}
@Value.Immutable
interface RenderConfig {
Collection spans();
Path path();
String displayName();
LayoutStrategy layoutStrategy();
Set problemSpanIds();
@Value.Derived
default TimeBounds bounds() {
return TimeBounds.fromSpans(spans());
}
class Builder extends ImmutableRenderConfig.Builder {}
static Builder builder() {
return new Builder();
}
}
public static void render(RenderConfig config) throws IOException {
HtmlFormatter formatter = new HtmlFormatter(config);
StringBuilder sb = new StringBuilder();
formatter.header(sb);
switch (config.layoutStrategy()) {
case CHRONOLOGICAL:
formatter.renderChronological(sb);
break;
case SPLIT_BY_TRACE:
formatter.renderSplitByTraceId(sb);
break;
}
formatter.rawSpanJson(sb);
Files.write(config.path(), sb.toString().getBytes(StandardCharsets.UTF_8));
}
private void renderChronological(StringBuilder sb) {
config.spans().stream()
.sorted(Comparator.comparingLong(Span::getStartTimeMicroSeconds))
.forEachOrdered(span -> formatSpan(span, false, sb));
}
private void renderSplitByTraceId(StringBuilder sb) {
Map analyzedByTraceId = SpanAnalyzer.analyzeByTraceId(config.spans());
analyzedByTraceId.entrySet().stream()
.sorted(Comparator.comparing(e -> e.getValue().bounds()))
.forEachOrdered(entry -> {
SpanAnalyzer.Result analysis = entry.getValue();
renderAllSpansForOneTraceId(entry.getKey(), analysis, sb);
});
}
private void renderAllSpansForOneTraceId(String traceId, SpanAnalyzer.Result analysis, StringBuilder sb) {
sb.append("\n");
analysis.orderedSpans().stream()
.filter(s -> !SpanAnalyzer.isSyntheticRoot(s))
.forEach(span -> {
boolean suspectedCollision = analysis.collisions().contains(span);
formatSpan(span, suspectedCollision, sb);
});
sb.append("\n");
}
private void header(StringBuilder sb) throws IOException {
OffsetDateTime startTime = Instant.ofEpochMilli(TimeUnit.MILLISECONDS.convert(
Duration.of(config.bounds().startMicros(), ChronoUnit.MICROS)))
.atOffset(ZoneOffset.UTC);
OffsetDateTime endTime = Instant.ofEpochMilli(TimeUnit.MILLISECONDS.convert(
Duration.ofNanos(config.bounds().endNanos())))
.atOffset(ZoneOffset.UTC);
sb.append(template(
"header.html",
ImmutableMap.builder()
.put("{{DISPLAY_NAME}}", config.displayName())
.put(
"{{START_TIME}}",
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
.format(startTime))
.put(
"{{END_TIME}}",
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
.format(endTime))
.buildOrThrow()));
}
private void formatSpan(Span span, boolean suspectedCollision, StringBuilder sb) {
long transposedStartMicros =
span.getStartTimeMicroSeconds() - config.bounds().startMicros();
long hue = Hashing.adler32()
.hashString(span.getTraceId(), StandardCharsets.UTF_8)
.padToLong()
% 360;
sb.append(template(
"span.html",
ImmutableMap.builder()
.put(
"{{LEFT}}",
Float.toString(Utils.percent(
transposedStartMicros, config.bounds().durationMicros())))
.put(
"{{WIDTH}}",
Float.toString(Utils.percent(
span.getDurationNanoSeconds(),
config.bounds().durationNanos())))
.put("{{HUE}}", Long.toString(hue))
.put("{{SPANID}}", span.getSpanId())
.put("{{CLASS}}", config.problemSpanIds().contains(span.getSpanId()) ? "problem-span" : "")
.put("{{START}}", Utils.renderDuration((double) transposedStartMicros, TimeUnit.MICROSECONDS))
.put(
"{{FINISH}}",
Utils.renderDuration(
(double) (transposedStartMicros
+ TimeUnit.MICROSECONDS.convert(
Duration.ofNanos(span.getDurationNanoSeconds()))),
TimeUnit.MICROSECONDS))
.put("{{OPERATION}}", span.getOperation())
.put(
"{{DURATION}}",
Utils.renderDuration((double) span.getDurationNanoSeconds(), TimeUnit.NANOSECONDS))
.put("{{COLLISION}}", suspectedCollision ? " (collision)" : "")
.buildOrThrow()));
}
private void rawSpanJson(StringBuilder sb) {
sb.append("\n");
config.spans().stream()
.sorted(Comparator.comparingLong(Span::getStartTimeMicroSeconds))
.forEach(s -> {
sb.append('\n');
sb.append(Serialization.toString(s));
});
sb.append("\n
");
}
private static String template(String resourceName, Map values) {
try {
String template = Resources.toString(Resources.getResource(resourceName), StandardCharsets.UTF_8);
for (Map.Entry entry : values.entrySet()) {
template = template.replace(entry.getKey(), entry.getValue());
}
return template;
} catch (IOException e) {
throw new UncheckedIOException("Unable to read resource " + resourceName, e);
}
}
}