diff options
Diffstat (limited to 'container-core/src/main')
4 files changed, 209 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java index 66fb4c73560..01ff6918f7b 100644 --- a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java +++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java @@ -13,9 +13,12 @@ import com.yahoo.container.core.DiagnosticsConfig; import com.yahoo.container.di.ComponentDeconstructor; import com.yahoo.container.di.Container; import com.yahoo.container.di.componentgraph.core.ComponentGraph; +import com.yahoo.container.di.componentgraph.core.DotGraph; import com.yahoo.container.di.config.SubscriberFactory; import com.yahoo.container.di.osgi.OsgiUtil; +import com.yahoo.container.handler.observability.OverviewHandler; import com.yahoo.container.logging.AccessLog; +import com.yahoo.container.logging.AccessLogInterface; import com.yahoo.jdisc.application.OsgiFramework; import com.yahoo.jdisc.handler.RequestHandler; import com.yahoo.jdisc.service.ClientProvider; @@ -25,12 +28,14 @@ import com.yahoo.language.simple.SimpleLinguistics; import com.yahoo.log.LogLevel; import com.yahoo.osgi.OsgiImpl; import com.yahoo.statistics.Statistics; + import org.osgi.framework.Bundle; import org.osgi.framework.wiring.BundleWiring; import scala.collection.immutable.Set; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Logger; @@ -148,6 +153,7 @@ public class HandlersConfigurerDi { RegistriesHack registriesHack = currentGraph.getInstance(RegistriesHack.class); assert (registriesHack != null); + injectDotGraph(); } @SuppressWarnings("deprecation") @@ -170,6 +176,16 @@ public class HandlersConfigurerDi { }); } + private void injectDotGraph() { + try { + OverviewHandler overviewHandler = currentGraph.getInstance(OverviewHandler.class); + overviewHandler.setDotGraph(DotGraph.generate(currentGraph)); + } catch (Exception e) { + log.fine("No overview handler"); + } + + } + public void reloadConfig(long generation) { container.reloadConfig(generation); } diff --git a/container-core/src/main/scala/com/yahoo/container/handler/observability/Graphviz.scala b/container-core/src/main/scala/com/yahoo/container/handler/observability/Graphviz.scala new file mode 100644 index 00000000000..845f0857323 --- /dev/null +++ b/container-core/src/main/scala/com/yahoo/container/handler/observability/Graphviz.scala @@ -0,0 +1,33 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.observability + +import com.yahoo.text.Utf8 +import com.yahoo.io.IOUtils +import java.io.{IOException, InputStreamReader, InputStream} +import com.google.common.io.ByteStreams + +/** + * @author tonytv + */ + +object Graphviz { + + @throws(classOf[IOException]) + def runDot(outputType: String, graph: String) = { + val process = Runtime.getRuntime.exec(Array("/bin/sh", "-c", "unflatten -l7 | dot -T" + outputType)) + process.getOutputStream.write(Utf8.toBytes(graph)) + process.getOutputStream.close() + + val result = ByteStreams.toByteArray(process.getInputStream) + process.waitFor() match { + case 0 => result + case 127 => throw new RuntimeException("Couldn't find dot, please ensure that Graphviz is installed.") + case _ => throw new RuntimeException("Failed running dot: " + readString(process.getErrorStream)) + } + } + + private def readString(inputStream: InputStream): String = { + IOUtils.readAll(new InputStreamReader(inputStream, "UTF-8")) + } + +} diff --git a/container-core/src/main/scala/com/yahoo/container/handler/observability/HtmlUtil.scala b/container-core/src/main/scala/com/yahoo/container/handler/observability/HtmlUtil.scala new file mode 100644 index 00000000000..6469d4604d2 --- /dev/null +++ b/container-core/src/main/scala/com/yahoo/container/handler/observability/HtmlUtil.scala @@ -0,0 +1,42 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.observability + +import xml.{PrettyPrinter, Elem} + + +/** + * @author gjoranv + * @author tonytv + */ +object HtmlUtil { + def link(target: String, anchor: String): Elem = + <a href={target}>{anchor}</a> + + def link(targetAndAnchor: String): Elem = link(targetAndAnchor, targetAndAnchor) + + def unorderedList(items: Elem*) = + <ul> + {items} + </ul> + + def li[T](children: T*) = + <li>{children}</li> + + def h1(name: String) = + <h1>{name}</h1> + + def html(title: String, body: Elem*) = + <html> + <head> + <title>{title}</title> + </head> + <body> + {body} + </body> + </html> + + def prettyPrintXhtml(elem: Elem): String = { + """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">""" + + "\n" + new PrettyPrinter(120, 2).format(elem) + } +} diff --git a/container-core/src/main/scala/com/yahoo/container/handler/observability/OverviewHandler.scala b/container-core/src/main/scala/com/yahoo/container/handler/observability/OverviewHandler.scala new file mode 100644 index 00000000000..fcb00bb72ef --- /dev/null +++ b/container-core/src/main/scala/com/yahoo/container/handler/observability/OverviewHandler.scala @@ -0,0 +1,118 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.observability + +import java.util.concurrent.Executor + +import HtmlUtil._ +import OverviewHandler._ +import com.yahoo.container.jdisc.{HttpResponse, HttpRequest, ThreadedHttpRequestHandler} +import com.yahoo.text.Utf8 +import java.io.{PrintWriter, OutputStream} + + +/** + * @author gjoranv + * @author tonytv + */ +class OverviewHandler(executor: Executor) extends ThreadedHttpRequestHandler(executor) { + + @volatile + private var dotGraph: String = _ + + def handle(request: HttpRequest): HttpResponse = { + val path = request.getUri.getPath + + try { + if (path.endsWith("/ComponentGraph")) + handleComponentGraph(request) + else if (path.endsWith("/Overview")) + handleOverview(request) + else + null + } catch { + case e: Exception => errorResponse(e.getMessage) + } + + } + + def handleOverview(request: HttpRequest): HttpResponse = { + new HttpResponse(com.yahoo.jdisc.Response.Status.OK) { + def render(stream: OutputStream) { + stream.write(Utf8.toBytes(overviewPageText)) + } + + override def getContentType: String = { + "text/html" + } + } + } + + def errorResponse(message: String): HttpResponse = { + new HttpResponse(com.yahoo.jdisc.Response.Status.BAD_REQUEST) { + def render(stream: OutputStream) { + new PrintWriter(stream).println(message) + } + } + } + + def handleComponentGraph(request: HttpRequest): HttpResponse = { + var graphType = request.getProperty("type"); + if (graphType == null) + graphType = "text" + + graphType match { + case "text" => textualComponentGraph(dotGraph) + case t if componentGraphTypes.contains(t) => graphicalComponentGraph(t, Graphviz.runDot(graphType,dotGraph)) + case t => errorResponse(t) + } + } + + def textualComponentGraph(dotGraph: String) = + new HttpResponse(com.yahoo.jdisc.Response.Status.OK) { + def render(stream: OutputStream) { + stream.write(Utf8.toBytes(dotGraph)) + } + + override def getContentType: String = { + "text/plain" + } + } + + def graphicalComponentGraph(graphType: String, image: Array[Byte] ): HttpResponse = + new HttpResponse(com.yahoo.jdisc.Response.Status.OK) { + def render(output: OutputStream) { + output.write(image) + } + + override def getContentType: String = { + componentGraphTypes(graphType) + } + } + + def setDotGraph(dotGraph: String) { + this.dotGraph = dotGraph + } +} + +object OverviewHandler { + val componentGraphTypes = Map( + "svg" -> "image/svg+xml", + "png" -> "image/png", + "text" -> "text/plain") + + val overviewPageText = prettyPrintXhtml(overviewPage) + + private def overviewPage = { + def componentGraphLink(graphType: String) = link("Overview/ComponentGraph?type=" + graphType, graphType) + + + html( + title = "Container Overview", + body = + h1("Container Overview"), + unorderedList( + li(link("ApplicationStatus")), + li("ComponentGraph" +: (componentGraphTypes.keys map {componentGraphLink}).toSeq :_*))) + } + +} |