diff --git a/sdk-extensions/zpages/build.gradle b/sdk-extensions/zpages/build.gradle index fe5983c064..ecb076408f 100644 --- a/sdk-extensions/zpages/build.gradle +++ b/sdk-extensions/zpages/build.gradle @@ -13,7 +13,7 @@ dependencies { implementation project(':opentelemetry-api'), project(':opentelemetry-sdk') - implementation libraries.guava + testImplementation libraries.guava compileOnly 'com.sun.net.httpserver:http:20070405' } diff --git a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezSpanBuckets.java b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezSpanBuckets.java index ef7236fdf0..253ff457bf 100644 --- a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezSpanBuckets.java +++ b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezSpanBuckets.java @@ -5,33 +5,28 @@ package io.opentelemetry.sdk.extension.zpages; -import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.data.SpanData; import java.util.ArrayList; import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; final class TracezSpanBuckets { - private final ImmutableMap latencyBuckets; - private final ImmutableMap errorBuckets; + private final Map latencyBuckets = new HashMap<>(); + private final Map errorBuckets = new HashMap<>(); TracezSpanBuckets() { - ImmutableMap.Builder latencyBucketsBuilder = - ImmutableMap.builder(); for (LatencyBoundary bucket : LatencyBoundary.values()) { - latencyBucketsBuilder.put(bucket, new SpanBucket(/* isLatencyBucket= */ true)); + latencyBuckets.put(bucket, new SpanBucket(/* isLatencyBucket= */ true)); } - latencyBuckets = latencyBucketsBuilder.build(); - ImmutableMap.Builder errorBucketsBuilder = ImmutableMap.builder(); for (StatusCode code : StatusCode.values()) { if (code == StatusCode.ERROR) { - errorBucketsBuilder.put(code, new SpanBucket(/* isLatencyBucket= */ false)); + errorBuckets.put(code, new SpanBucket(/* isLatencyBucket= */ false)); } } - errorBuckets = errorBucketsBuilder.build(); } void addToBucket(ReadableSpan span) { diff --git a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandler.java b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandler.java index 9e37ceea95..a954e104c1 100644 --- a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandler.java +++ b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandler.java @@ -5,11 +5,6 @@ package io.opentelemetry.sdk.extension.zpages; -import static com.google.common.html.HtmlEscapers.htmlEscaper; -import static com.google.common.net.UrlEscapers.urlFormParameterEscaper; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeConsumer; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.ReadableAttributes; @@ -19,11 +14,11 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.data.SpanData.Event; import java.io.OutputStream; import java.io.PrintStream; -import java.io.Serializable; -import java.util.ArrayList; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Calendar; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.Formatter; import java.util.HashMap; @@ -34,6 +29,7 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.annotation.Nullable; final class TracezZPageHandler extends ZPageHandler { @@ -90,7 +86,7 @@ final class TracezZPageHandler extends ZPageHandler { // * for error based sampled spans [0, 15], 0 means all, otherwise the error code private static final String PARAM_SAMPLE_SUB_TYPE = "zsubtype"; // Map from LatencyBoundary to human readable string on the UI - private static final ImmutableMap LATENCY_BOUNDARIES_STRING_MAP = + private static final Map LATENCY_BOUNDARIES_STRING_MAP = buildLatencyBoundaryStringMap(); private static final Logger logger = Logger.getLogger(TracezZPageHandler.class.getName()); @Nullable private final TracezDataAggregator dataAggregator; @@ -172,7 +168,14 @@ final class TracezZPageHandler extends ZPageHandler { // If numOfSamples is smaller than 0, print the text "N/A", otherwise print the text "0" if (numOfSamples > 0) { out.print("" + numOfSamples + ""); @@ -210,7 +213,7 @@ final class TracezZPageHandler extends ZPageHandler { out.print(""); } zebraStripe = !zebraStripe; - out.print("" + htmlEscaper().escape(spanName) + ""); + out.print("" + escapeHtml(spanName) + ""); // Running spans column int numOfRunningSpans = @@ -241,8 +244,7 @@ final class TracezZPageHandler extends ZPageHandler { private static void emitSpanNameAndCount( PrintStream out, String spanName, int count, SampleType type) { - out.print( - "

Span Name: " + htmlEscaper().escape(spanName) + "

"); + out.print("

Span Name: " + escapeHtml(spanName) + "

"); String typeString = type == SampleType.RUNNING ? "running" @@ -306,8 +308,10 @@ final class TracezZPageHandler extends ZPageHandler { int lastEntryDayOfYear = calendar.get(Calendar.DAY_OF_YEAR); long lastEpochNanos = span.getStartEpochNanos(); - List timedEvents = new ArrayList<>(span.getEvents()); - Collections.sort(timedEvents, new EventComparator()); + List timedEvents = + span.getEvents().stream() + .sorted(Comparator.comparingLong(Event::getEpochNanos)) + .collect(Collectors.toList()); for (Event event : timedEvents) { calendar.setTimeInMillis(TimeUnit.NANOSECONDS.toMillis(event.getEpochNanos())); formatter.format( @@ -326,9 +330,9 @@ final class TracezZPageHandler extends ZPageHandler { zebraStripe ? ZEBRA_STRIPE_COLOR : "#fff"); SpanData.Status status = span.getStatus(); if (status != null) { - formatter.format("%s | ", htmlEscaper().escape(status.toString())); + formatter.format("%s | ", escapeHtml(status.toString())); } - formatter.format("%s", htmlEscaper().escape(renderAttributes(span.getAttributes()))); + formatter.format("%s", escapeHtml(renderAttributes(span.getAttributes()))); zebraStripe = !zebraStripe; return zebraStripe; } @@ -375,7 +379,7 @@ final class TracezZPageHandler extends ZPageHandler { calendar.get(Calendar.SECOND), microsField, deltaString, - htmlEscaper().escape(renderEvent(event))); + escapeHtml(renderEvent(event))); } private static String renderAttributes(ReadableAttributes attributes) { @@ -475,7 +479,9 @@ final class TracezZPageHandler extends ZPageHandler { if (spans != null) { Formatter formatter = new Formatter(out, Locale.US); spans = - ImmutableList.sortedCopyOf(new SpanDataComparator(/* incremental= */ true), spans); + spans.stream() + .sorted(Comparator.comparingLong(SpanData::getStartEpochNanos)) + .collect(Collectors.toList()); emitSpanDetails(out, formatter, spans); } } @@ -540,41 +546,55 @@ final class TracezZPageHandler extends ZPageHandler { throw new IllegalArgumentException("No value string available for: " + latencyBoundary); } - private static ImmutableMap buildLatencyBoundaryStringMap() { + private static Map buildLatencyBoundaryStringMap() { Map latencyBoundaryMap = new HashMap<>(); for (LatencyBoundary latencyBoundary : LatencyBoundary.values()) { latencyBoundaryMap.put(latencyBoundary, latencyBoundaryToString(latencyBoundary)); } - return ImmutableMap.copyOf(latencyBoundaryMap); + return latencyBoundaryMap; } - private static final class EventComparator implements Comparator, Serializable { - private static final long serialVersionUID = 0; - - @Override - public int compare(Event e1, Event e2) { - return Long.compare(e1.getEpochNanos(), e2.getEpochNanos()); + private static String escapeHtml(String html) { + StringBuilder escaped = null; + for (int i = 0; i < html.length(); i++) { + char c = html.charAt(i); + switch (c) { + case '"': + escaped = lazyStringBuilder(escaped, html, i); + escaped.append("""); + break; + case '\'': + escaped = lazyStringBuilder(escaped, html, i); + escaped.append("'"); + break; + case '&': + escaped = lazyStringBuilder(escaped, html, i); + escaped.append("&"); + break; + case '<': + escaped = lazyStringBuilder(escaped, html, i); + escaped.append("<"); + break; + case '>': + escaped = lazyStringBuilder(escaped, html, i); + escaped.append(">"); + break; + default: + if (escaped != null) { + escaped.append(c); + } + } } + return escaped != null ? escaped.toString() : html; } - private static final class SpanDataComparator implements Comparator, Serializable { - private static final long serialVersionUID = 0; - private final boolean incremental; - - /** - * Returns a new {@code SpanDataComparator}. - * - * @param incremental {@code true} if sorting spans incrementally - */ - private SpanDataComparator(boolean incremental) { - this.incremental = incremental; - } - - @Override - public int compare(SpanData s1, SpanData s2) { - return incremental - ? Long.compare(s1.getStartEpochNanos(), s2.getStartEpochNanos()) - : Long.compare(s2.getStartEpochNanos(), s1.getEndEpochNanos()); + private static StringBuilder lazyStringBuilder( + @Nullable StringBuilder sb, String str, int currentCharIdx) { + if (sb != null) { + return sb; } + sb = new StringBuilder(str.length()); + sb.append(str.substring(0, currentCharIdx - 1)); + return sb; } } diff --git a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageHttpHandler.java b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageHttpHandler.java index bacc34dc94..603b7deef6 100644 --- a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageHttpHandler.java +++ b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageHttpHandler.java @@ -5,27 +5,22 @@ package io.opentelemetry.sdk.extension.zpages; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.CharStreams; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** An {@link HttpHandler} that will be used to render HTML pages using any {@code ZPageHandler}. */ final class ZPageHttpHandler implements HttpHandler { - // Splitter for splitting URL query parameters - private static final Splitter QUERY_SPLITTER = Splitter.on("&").trimResults().omitEmptyStrings(); - // Splitter for splitting URL query parameters' key value - private static final Splitter QUERY_KEYVAL_SPLITTER = - Splitter.on("=").trimResults().omitEmptyStrings(); // Query string parameter name for span name private static final String PARAM_SPAN_NAME = "zspanname"; // The corresponding ZPageHandler for the zPage (e.g. TracezZPageHandler) @@ -42,24 +37,36 @@ final class ZPageHttpHandler implements HttpHandler { * @param queryString the query string for buiding the query map. * @return the query map built based on the query string. */ - @VisibleForTesting - static ImmutableMap parseQueryString(String queryString) - throws UnsupportedEncodingException { + // Visible for testing + static Map parseQueryString(String queryString) { if (queryString == null) { - return ImmutableMap.of(); + return Collections.emptyMap(); } Map queryMap = new HashMap(); - for (String param : QUERY_SPLITTER.split(queryString)) { - List keyValuePair = QUERY_KEYVAL_SPLITTER.splitToList(param); - if (keyValuePair.size() > 1) { - if (keyValuePair.get(0).equals(PARAM_SPAN_NAME)) { - queryMap.put(keyValuePair.get(0), URLDecoder.decode(keyValuePair.get(1), "UTF-8")); - } else { - queryMap.put(keyValuePair.get(0), keyValuePair.get(1)); - } - } - } - return ImmutableMap.copyOf(queryMap); + Arrays.stream(queryString.split("&")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .forEach( + param -> { + List keyValuePair = + Arrays.stream(param.split("=")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + if (keyValuePair.size() > 1) { + if (keyValuePair.get(0).equals(PARAM_SPAN_NAME)) { + try { + queryMap.put( + keyValuePair.get(0), URLDecoder.decode(keyValuePair.get(1), "UTF-8")); + } catch (UnsupportedEncodingException e) { + // Ignore encoding exception. + } + } else { + queryMap.put(keyValuePair.get(0), keyValuePair.get(1)); + } + } + }); + return Collections.unmodifiableMap(queryMap); } @Override @@ -73,9 +80,10 @@ final class ZPageHttpHandler implements HttpHandler { httpExchange.getResponseBody()); } else { final String queryString; - try (InputStreamReader requestBodyReader = - new InputStreamReader(httpExchange.getRequestBody(), "utf-8")) { - queryString = CharStreams.toString(requestBodyReader); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(httpExchange.getRequestBody(), "utf-8"))) { + // Query strings can only have one line + queryString = reader.readLine(); } boolean error = zpageHandler.processRequest( diff --git a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageLogo.java b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageLogo.java index 22c184b1ed..a3c0b02c28 100644 --- a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageLogo.java +++ b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageLogo.java @@ -5,9 +5,10 @@ package io.opentelemetry.sdk.extension.zpages; -import com.google.common.io.BaseEncoding; -import com.google.common.io.ByteStreams; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.util.Base64; import java.util.logging.Level; import java.util.logging.Logger; @@ -22,14 +23,17 @@ final class ZPageLogo { * @return OpenTelemetry logo in base64 encoding. */ public static String getLogoBase64() { + ByteArrayOutputStream os = new ByteArrayOutputStream(); try { - InputStream in = ZPageLogo.class.getClassLoader().getResourceAsStream("logo.png"); - byte[] bytes = ByteStreams.toByteArray(in); - return BaseEncoding.base64().encode(bytes); + try (InputStream is = ZPageLogo.class.getClassLoader().getResourceAsStream("logo.png")) { + readTo(is, os); + } } catch (Throwable t) { logger.log(Level.WARNING, "error while getting OpenTelemetry Logo", t); return ""; } + byte[] bytes = os.toByteArray(); + return Base64.getEncoder().encodeToString(bytes); } /** @@ -38,14 +42,21 @@ final class ZPageLogo { * @return OpenTelemetry favicon in base64 encoding. */ public static String getFaviconBase64() { - try { - - InputStream in = ZPageLogo.class.getClassLoader().getResourceAsStream("favicon.png"); - byte[] bytes = ByteStreams.toByteArray(in); - return BaseEncoding.base64().encode(bytes); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try (InputStream is = ZPageLogo.class.getClassLoader().getResourceAsStream("favicon.png")) { + readTo(is, os); } catch (Throwable t) { logger.log(Level.WARNING, "error while getting OpenTelemetry Logo", t); return ""; } + byte[] bytes = os.toByteArray(); + return Base64.getEncoder().encodeToString(bytes); + } + + private static void readTo(InputStream is, ByteArrayOutputStream os) throws IOException { + int b; + while ((b = is.read()) != -1) { + os.write(b); + } } } diff --git a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageServer.java b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageServer.java index a739a9a041..7d3728bfdb 100644 --- a/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageServer.java +++ b/sdk-extensions/zpages/src/main/java/io/opentelemetry/sdk/extension/zpages/ZPageServer.java @@ -5,15 +5,12 @@ package io.opentelemetry.sdk.extension.zpages; -import static com.google.common.base.Preconditions.checkState; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; import com.sun.net.httpserver.HttpServer; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.trace.TracerSdkManagement; import java.io.IOException; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -68,7 +65,7 @@ public final class ZPageServer { new TraceConfigzZPageHandler(TRACER_SDK_MANAGEMENT); // Handler for index page, **please include all available ZPageHandlers in the constructor** private static final ZPageHandler indexZPageHandler = - new IndexZPageHandler(ImmutableList.of(tracezZPageHandler, traceConfigzZPageHandler)); + new IndexZPageHandler(Arrays.asList(tracezZPageHandler, traceConfigzZPageHandler)); private static final Object mutex = new Object(); private static final AtomicBoolean isTracezSpanProcesserAdded = new AtomicBoolean(false); @@ -166,7 +163,9 @@ public final class ZPageServer { */ public static void startHttpServerAndRegisterAllPages(int port) throws IOException { synchronized (mutex) { - checkState(server == null, "The HttpServer is already started."); + if (server != null) { + throw new IllegalStateException("The HttpServer is already started."); + } server = HttpServer.create(new InetSocketAddress(port), HTTPSERVER_BACKLOG); ZPageServer.registerAllPagesToHttpServer(server); server.start(); @@ -187,7 +186,7 @@ public final class ZPageServer { * * @return the boolean indicating if TracezSpanProcessor is added. */ - @VisibleForTesting + // Visible for testing static boolean getIsTracezSpanProcesserAdded() { return isTracezSpanProcesserAdded.get(); } diff --git a/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java b/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java index e91dab936e..b0f6bbc89f 100644 --- a/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java +++ b/sdk-extensions/zpages/src/test/java/io/opentelemetry/sdk/extension/zpages/TracezZPageHandlerTest.java @@ -343,9 +343,8 @@ class TracezZPageHandlerTest { assertThat(output.toString()).doesNotContain(" Span Name: Span"); } - private static ImmutableMap generateQueryMap( - String spanName, String type, String subtype) - throws UnsupportedEncodingException, URISyntaxException { + private static Map generateQueryMap(String spanName, String type, String subtype) + throws URISyntaxException { return ZPageHttpHandler.parseQueryString( new URI( "tracez?zspanname="