Implemented index page for zPages (#1423)

* Added two abstract getter method for obtaining page information

* Added page information getter method

* Implemented index zPage handler

* Implemented index page for zPages

* Added unit test for index zPage

* Small style and comment fixes

* Increased font size, code style fix

* Fixed font size issue
This commit is contained in:
Terry (Tianyu) Wang 2020-07-16 22:55:42 +00:00 committed by GitHub
parent 98cb75695d
commit df14623402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 8 deletions

View File

@ -0,0 +1,108 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.sdk.extensions.zpages;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
final class IndexZPageHandler extends ZPageHandler {
private static final String INDEX_URL = "/";
private static final String INDEX_NAME = "Index";
private static final String INDEX_DESCRITION = "Index page of zPages";
private static final Logger logger = Logger.getLogger(IndexZPageHandler.class.getName());
@Nullable private final List<ZPageHandler> availableHandlers;
IndexZPageHandler(@Nullable List<ZPageHandler> availableHandlers) {
this.availableHandlers = availableHandlers;
}
@Override
public String getUrlPath() {
return INDEX_URL;
}
@Override
public String getPageName() {
return INDEX_NAME;
}
@Override
public String getPageDescription() {
return INDEX_DESCRITION;
}
private static void emitPageLinkAndInfo(PrintStream out, ZPageHandler handler) {
out.print("<a href=\"" + handler.getUrlPath() + "\">");
out.print("<h2 style=\"text-align: left;\">" + handler.getPageName() + "</h2>");
out.print("</a>");
out.print("<p>" + handler.getPageDescription() + "</p>");
}
@Override
public void emitHtml(Map<String, String> queryMap, OutputStream outputStream) {
// PrintStream for emiting HTML contents
try (PrintStream out = new PrintStream(outputStream, /* autoFlush= */ false, "UTF-8")) {
out.print("<!DOCTYPE html>");
out.print("<html lang=\"en\">");
out.print("<head>");
out.print("<meta charset=\"UTF-8\">");
out.print(
"<link rel=\"shortcut icon\" href=\"data:image/png;base64,"
+ ZPageLogo.getFaviconBase64()
+ "\" type=\"image/png\">");
out.print(
"<link href=\"https://fonts.googleapis.com/css?family=Open+Sans:300\""
+ "rel=\"stylesheet\">");
out.print(
"<link href=\"https://fonts.googleapis.com/css?family=Roboto\" rel=\"stylesheet\">");
out.print("<title>zPages</title>");
out.print("<style>");
out.print(ZPageStyle.style);
out.print("</style>");
out.print("</head>");
out.print("<body>");
out.print(
"<a href=\"/\"><img style=\"height: 90px;\" src=\"data:image/png;base64,"
+ ZPageLogo.getLogoBase64()
+ "\" /></a>");
out.print("<h1 style=\"text-align: left;\">zPages</h1>");
out.print(
"<p>OpenTelemetry provides in-process web pages that display collected data from"
+ " the process that they are attached to. These are called \"zPages\"."
+ " They are useful for in-process diagnostics without having to depend on"
+ " any backend to examine traces or metrics.</p>");
out.print(
"<p>zPages can be useful during the development time or "
+ "when the process to be inspected is known in production.</p>");
if (this.availableHandlers != null) {
for (ZPageHandler handler : this.availableHandlers) {
emitPageLinkAndInfo(out, handler);
}
}
out.print("</body>");
out.print("</html>");
} catch (Throwable t) {
logger.log(Level.WARNING, "error while generating HTML", t);
}
}
}

View File

@ -82,6 +82,10 @@ final class TracezZPageHandler extends ZPageHandler {
}
private static final String TRACEZ_URL = "/tracez";
private static final String TRACEZ_NAME = "TraceZ";
private static final String TRACEZ_DESCRIPTION =
"TraceZ displays information about all the running spans"
+ " and all the sampled spans based on latency and errors";
// Background color used for zebra striping rows of summary table
private static final String ZEBRA_STRIPE_COLOR = "#e6e6e6";
// Color for sampled traceIds
@ -114,6 +118,16 @@ final class TracezZPageHandler extends ZPageHandler {
return TRACEZ_URL;
}
@Override
public String getPageName() {
return TRACEZ_NAME;
}
@Override
public String getPageDescription() {
return TRACEZ_DESCRIPTION;
}
/**
* Emits CSS Styles to the {@link PrintStream} {@code out}. Content emitted by this function
* should be enclosed by <head></head> tag.
@ -452,9 +466,9 @@ final class TracezZPageHandler extends ZPageHandler {
return;
}
out.print(
"<img style=\"height: 90px;\" src=\"data:image/png;base64,"
"<a href=\"/\"><img style=\"height: 90px;\" src=\"data:image/png;base64,"
+ ZPageLogo.getLogoBase64()
+ "\" />");
+ "\" /></a>");
out.print("<h1>TraceZ Summary</h1>");
emitSummaryTable(out);
// spanName will be null if the query parameter doesn't exist in the URL

View File

@ -32,6 +32,20 @@ public abstract class ZPageHandler {
*/
public abstract String getUrlPath();
/**
* Returns the name of the zPage.
*
* @return the name of the zPage.
*/
public abstract String getPageName();
/**
* Returns the description of the zPage.
*
* @return the description of the zPage.
*/
public abstract String getPageDescription();
/**
* Emits the generated HTML page to the {@code outputStream}.
*

View File

@ -19,6 +19,7 @@ package io.opentelemetry.sdk.extensions.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.TracerSdkProvider;
@ -71,6 +72,9 @@ public final class ZPageServer {
// Handler for /tracez page
private static final ZPageHandler tracezZPageHandler =
new TracezZPageHandler(tracezDataAggregator);
// Handler for index page, **please include all available zPages in the constructor**
private static final ZPageHandler indexZPageHandler =
new IndexZPageHandler(ImmutableList.of(tracezZPageHandler));
private static final Object mutex = new Object();
private static final AtomicBoolean isTracezSpanProcesserAdded = new AtomicBoolean(false);
@ -88,7 +92,17 @@ public final class ZPageServer {
}
/**
* Registers a {@code ZPageHandler} for tracing debug to the server. The page displays information
* Registers a {@link ZPageHandler} for the index page of zPages. The page displays information
* about all available zPages with links to those zPages.
*
* @param server the {@link HttpServer} for the page to register to.
*/
static void registerIndexZPageHandler(HttpServer server) {
server.createContext(indexZPageHandler.getUrlPath(), new ZPageHttpHandler(indexZPageHandler));
}
/**
* Registers a {@link ZPageHandler} for tracing debug to the server. The page displays information
* about all running spans and all sampled spans based on latency and error.
*
* <p>It displays a summary table which contains one row for each span name and data about number
@ -99,6 +113,8 @@ public final class ZPageServer {
*
* <p>This method will add the TracezSpanProcessor to the tracerProvider, it should only be called
* once.
*
* @param server the {@link HttpServer} for the page to register to.
*/
static void registerTracezZPageHandler(HttpServer server) {
addTracezSpanProcessor();
@ -108,10 +124,11 @@ public final class ZPageServer {
/**
* Registers all zPages to the given {@link HttpServer} {@code server}.
*
* @param server the server that exports the zPages.
* @param server the {@link HttpServer} for the page to register to.
*/
public static void registerAllPagesToHttpServer(HttpServer server) {
// For future zPages, register them to the server in here
registerIndexZPageHandler(server);
registerTracezZPageHandler(server);
}

View File

@ -22,11 +22,11 @@ final class ZPageStyle {
/** Style here will be applied to the generated HTML pages for all zPages. */
static String style =
"body{font-family: \"Roboto\", sans-serif; font-size: 14px;"
"body{font-family: \"Roboto\", sans-serif; font-size: 16px;"
+ "background-color: #fff;}"
+ "h1{color: #363636; text-align: center; margin-bottom 20px;}"
+ "h2{color: #363636; text-align: center; margin-top: 30px;}"
+ "p{padding: 0 0.5em; color: #363636;}"
+ "h1{padding: 0 20px; color: #363636; text-align: center; margin-bottom: 20px;}"
+ "h2{padding: 0 20px; color: #363636; text-align: center; margin-bottom: 20px;}"
+ "p{padding: 0 20px; color: #363636;}"
+ "tr.bg-color{background-color: #4b5fab;}"
+ "table{margin: 0 auto;}"
+ "th{padding: 0 1em; line-height: 2.0}"

View File

@ -0,0 +1,49 @@
/*
* Copyright 2020, OpenTelemetry 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 io.opentelemetry.sdk.extensions.zpages;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link IndexZPageHandler}. */
@RunWith(JUnit4.class)
public final class IndexZPageHandlerTest {
private static final ZPageHandler tracezZPageHandler = new TracezZPageHandler(null);
private final Map<String, String> emptyQueryMap = ImmutableMap.of();
@Test
public void emitHtmlCorrectly() {
OutputStream output = new ByteArrayOutputStream();
IndexZPageHandler indexZPageHandler =
new IndexZPageHandler(ImmutableList.of(tracezZPageHandler));
indexZPageHandler.emitHtml(emptyQueryMap, output);
assertThat(output.toString()).contains("<a href=\"" + tracezZPageHandler.getUrlPath() + "\">");
assertThat(output.toString()).contains(">" + tracezZPageHandler.getPageName() + "</h2></a>");
assertThat(output.toString())
.contains("<p>" + tracezZPageHandler.getPageDescription() + "</p>");
}
}