diff --git a/build.sbt b/build.sbt index 0c84fe47..b3122f51 100644 --- a/build.sbt +++ b/build.sbt @@ -9,6 +9,7 @@ val jacksonLibs = Seq( "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion, "com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonVersion ) +val opencensusVersion = "0.19.1" val slf4jVersion = "1.7.21" def util(which: String) = "com.twitter" %% ("util-"+which) % releaseVersion @@ -102,6 +103,7 @@ lazy val root = (project in file(".")) .settings(noPublishSettings) .aggregate( twitterServer, + twitterServerOpenCensus, twitterServerSlf4jJdk14, twitterServerSlf4jLog4j12, twitterServerSlf4jLogbackClassic) @@ -132,6 +134,25 @@ lazy val twitterServer = (project in file("server")) ), libraryDependencies ++= jacksonLibs) +lazy val twitterServerOpenCensus = (project in file("opencensus")) + .enablePlugins( + ScalaUnidocPlugin + ) + .settings( + name := "twitter-server-opencensus", + moduleName := "twitter-server-opencensus", + sharedSettings) + .settings( + libraryDependencies ++= Seq( + finagle("core"), + finagle("http"), + "io.opencensus" % "opencensus-api" % opencensusVersion, + "io.opencensus" % "opencensus-contrib-zpages" % opencensusVersion + )) + .dependsOn( + twitterServer) + + lazy val twitterServerSlf4jJdk14 = (project in file("slf4j-jdk14")) .settings( name := "twitter-server-slf4j-jdk14", diff --git a/opencensus/README.md b/opencensus/README.md new file mode 100644 index 00000000..e72f7758 --- /dev/null +++ b/opencensus/README.md @@ -0,0 +1,29 @@ +# OpenCensus zPages + +This module integrates TwitterServer with [OpenCensus zPages](https://opencensus.io/zpages/). + +## Current State + +This library is in an experimental state. + +## Details + +By mixing in `ZPagesAdminRoutes` into a `TwitterServer`, zPages +will be served on admin routes: + + - /rpcz + - /statz + - /tracez + - /traceconfigz + +For example: + +``` +import com.twitter.server.TwitterServer +import com.twitter.server.opencensus.ZPagesAdminRoutes + +object MyServer extends TwitterServer with ZPagesAdminRoutes { + // ... +} +``` + diff --git a/opencensus/src/main/scala/com/twitter/server/opencensus/BUILD b/opencensus/src/main/scala/com/twitter/server/opencensus/BUILD new file mode 100644 index 00000000..909cd8a2 --- /dev/null +++ b/opencensus/src/main/scala/com/twitter/server/opencensus/BUILD @@ -0,0 +1,11 @@ +scala_library( + sources = globs("*.scala"), + compiler_option_sets = {"fatal_warnings"}, + dependencies = [ + "3rdparty/jvm/io/opencensus:opencensus-api", + "3rdparty/jvm/io/opencensus:opencensus-contrib-zpages", + "finagle/finagle-core", + "finagle/finagle-http", + "twitter-server/server/src/main/scala", + ], +) diff --git a/opencensus/src/main/scala/com/twitter/server/opencensus/ZPagesAdminRoutes.scala b/opencensus/src/main/scala/com/twitter/server/opencensus/ZPagesAdminRoutes.scala new file mode 100644 index 00000000..9bf247b5 --- /dev/null +++ b/opencensus/src/main/scala/com/twitter/server/opencensus/ZPagesAdminRoutes.scala @@ -0,0 +1,82 @@ +package com.twitter.server.opencensus + +import com.twitter.finagle.Service +import com.twitter.finagle.http.{Request, Response, Status} +import com.twitter.io.Buf +import com.twitter.server.AdminHttpServer +import com.twitter.util.{Future, FuturePool} +import io.opencensus.contrib.zpages.{ZPageHandler, ZPageHandlers} +import java.io.ByteArrayOutputStream +import scala.collection.JavaConverters._ + +private object ZPagesAdminRoutes { + private def zpageHandlerToService( + handler: ZPageHandler, + name: String + ): Service[Request, Response] = + new Service[Request, Response] { + override def toString: String = s"ZPageHandlerService($name)" + + def apply(request: Request): Future[Response] = { + val requestParams = + request + .getParamNames().asScala.map { name => + name -> request.getParam(name) + }.toMap.asJava + + // process in a FuturePool to handle the possibility + // of zpages having blocking code. + FuturePool.unboundedPool { + val output = new ByteArrayOutputStream() + handler.emitHtml(requestParams, output) + Response(request) + .status(Status.Ok) + .content(Buf.ByteArray.Owned(output.toByteArray)) + } + } + } +} + +/** + * Mix into an [[AdminHttpServer]] to serve OpenCensus zPages on admin routes. + * + * The zPages will be available at: + * - /rpcz + * - /statz + * - /tracez + * - /traceconfigz + * + * For example: + * {{{ + * import com.twitter.server.TwitterServer + * import com.twitter.server.opencensus.ZPagesAdminRoutes + * + * object MyServer extends TwitterServer with ZPagesAdminRoutes { + * // ... + * } + * }}} + * + * @see [[https://opencensus.io/zpages/]] + */ +trait ZPagesAdminRoutes { self: AdminHttpServer => + + addAdminRoutes { + val handlers = + (ZPageHandlers.getRpczZpageHandler, "RPCz") :: + (ZPageHandlers.getStatszZPageHandler, "Statz") :: + (ZPageHandlers.getTraceConfigzZPageHandler, "Trace Configz") :: + (ZPageHandlers.getTracezZPageHandler, "Tracez") :: + Nil + + handlers.map { + case (handler, name) => + AdminHttpServer.mkRoute( + path = handler.getUrlPath, + handler = ZPagesAdminRoutes.zpageHandlerToService(handler, name), + alias = name, + group = Some("OpenCensus"), + includeInIndex = true + ) + } + } +}