Github user andrewor14 commented on a diff in the pull request:

    https://github.com/apache/spark/pull/290#discussion_r11518271
  
    --- Diff: core/src/main/scala/org/apache/spark/ui/WebUI.scala ---
    @@ -17,53 +17,134 @@
     
     package org.apache.spark.ui
     
    -import java.text.SimpleDateFormat
    -import java.util.Date
    +import javax.servlet.http.HttpServletRequest
     
    -private[spark] abstract class WebUI(name: String) {
    +import scala.collection.mutable.ArrayBuffer
    +import scala.xml.Node
    +
    +import org.eclipse.jetty.servlet.ServletContextHandler
    +import org.json4s.JsonAST.{JNothing, JValue}
    +
    +import org.apache.spark.{Logging, SecurityManager, SparkConf}
    +import org.apache.spark.ui.JettyUtils._
    +import org.apache.spark.util.Utils
    +
    +/**
    + * The top level component of the UI hierarchy that contains the server.
    + *
    + * Each WebUI represents a collection of tabs, each of which in turn 
represents a collection of
    + * pages. The use of tabs is optional, however; a WebUI may choose to 
include pages directly.
    + */
    +private[spark] abstract class WebUI(
    +    securityManager: SecurityManager,
    +    port: Int,
    +    conf: SparkConf,
    +    basePath: String = "")
    +  extends Logging {
    +
    +  protected val tabs = ArrayBuffer[WebUITab]()
    +  protected val handlers = ArrayBuffer[ServletContextHandler]()
       protected var serverInfo: Option[ServerInfo] = None
    +  protected val localHostName = Utils.localHostName()
    +  protected val publicHostName = 
Option(System.getenv("SPARK_PUBLIC_DNS")).getOrElse(localHostName)
    +  private val className = Utils.getFormattedClassName(this)
    +
    +  def getTabs: Seq[WebUITab] = tabs.toSeq
    +  def getHandlers: Seq[ServletContextHandler] = handlers.toSeq
     
    -  /**
    -   * Bind to the HTTP server behind this web interface.
    -   * Overridden implementation should set serverInfo.
    -   */
    -  def bind() { }
    +  /** Attach a tab to this UI, along with all of its attached pages. */
    +  def attachTab(tab: WebUITab) {
    +    tab.pages.foreach(attachPage)
    +    tabs += tab
    +  }
    +
    +  /** Attach a page to this UI. */
    +  def attachPage(page: WebUIPage) {
    +    val pagePath = "/" + page.prefix
    +    attachHandler(createServletHandler(pagePath,
    +      (request: HttpServletRequest) => page.render(request), 
securityManager, basePath))
    +    if (page.includeJson) {
    +      attachHandler(createServletHandler(pagePath.stripSuffix("/") + 
"/json",
    +        (request: HttpServletRequest) => page.renderJson(request), 
securityManager, basePath))
    +    }
    +  }
    +
    +  /** Attach a handler to this UI. */
    +  def attachHandler(handler: ServletContextHandler) {
    +    handlers += handler
    +    serverInfo.foreach { info =>
    +      info.rootHandler.addHandler(handler)
    +      if (!handler.isStarted) {
    +        handler.start()
    +      }
    +    }
    +  }
    +
    +  /** Detach a handler from this UI. */
    +  def detachHandler(handler: ServletContextHandler) {
    +    handlers -= handler
    +    serverInfo.foreach { info =>
    +      info.rootHandler.removeHandler(handler)
    +      if (handler.isStarted) {
    +        handler.stop()
    +      }
    +    }
    +  }
    +
    +  /** Initialize all components of the server. */
    +  def initialize()
    +
    +  /** Bind to the HTTP server behind this web interface. */
    +  def bind() {
    +    assert(!serverInfo.isDefined, "Attempted to bind %s more than 
once!".format(className))
    +    try {
    +      serverInfo = Some(startJettyServer("0.0.0.0", port, handlers, conf))
    +      logInfo("Started %s at http://%s:%d".format(className, 
publicHostName, boundPort))
    +    } catch {
    +      case e: Exception =>
    +        logError("Failed to bind %s".format(className), e)
    +        System.exit(1)
    +    }
    +  }
     
       /** Return the actual port to which this server is bound. Only valid 
after bind(). */
       def boundPort: Int = serverInfo.map(_.boundPort).getOrElse(-1)
     
       /** Stop the server behind this web interface. Only valid after bind(). 
*/
       def stop() {
    -    assert(serverInfo.isDefined, "Attempted to stop %s before binding to a 
server!".format(name))
    +    assert(serverInfo.isDefined,
    +      "Attempted to stop %s before binding to a server!".format(className))
         serverInfo.get.server.stop()
       }
     }
     
    +
     /**
    - * Utilities used throughout the web UI.
    + * A tab that represents a collection of pages.
      */
    -private[spark] object WebUI {
    -  // SimpleDateFormat is not thread-safe. Don't expose it to avoid 
improper use.
    -  private val dateFormat = new ThreadLocal[SimpleDateFormat]() {
    -    override def initialValue(): SimpleDateFormat = new 
SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
    +private[spark] abstract class WebUITab(parent: WebUI, val prefix: String) {
    +  val pages = ArrayBuffer[WebUIPage]()
    +  val name = prefix.capitalize
    +
    +  /** Attach a page to this tab. This prepends the page's prefix with the 
tab's own prefix. */
    +  def attachPage(page: WebUIPage) {
    +    page.prefix = (prefix + "/" + page.prefix).stripSuffix("/")
    +    pages += page
       }
     
    -  def formatDate(date: Date): String = dateFormat.get.format(date)
    +  /** Get a list of header tabs from the parent UI. */
    +  def headerTabs: Seq[WebUITab] = parent.getTabs
    +}
     
    -  def formatDate(timestamp: Long): String = dateFormat.get.format(new 
Date(timestamp))
     
    -  def formatDuration(milliseconds: Long): String = {
    -    val seconds = milliseconds.toDouble / 1000
    -    if (seconds < 60) {
    -      return "%.0f s".format(seconds)
    -    }
    -    val minutes = seconds / 60
    -    if (minutes < 10) {
    -      return "%.1f min".format(minutes)
    -    } else if (minutes < 60) {
    -      return "%.0f min".format(minutes)
    -    }
    -    val hours = minutes / 60
    -    "%.1f h".format(hours)
    -  }
    +/**
    + * A page that represents the leaf node in the UI hierarchy.
    + *
    + * The direct parent of a WebUIPage is not specified as it can be either a 
WebUI or a WebUITab.
    + * If includeJson is true, the parent WebUI (direct or indirect) creates 
handlers for both the
    + * HTML and the JSON content, rather than just the former.
    + */
    +private[spark] abstract class WebUIPage(var prefix: String, val 
includeJson: Boolean = false) {
    --- End diff --
    
    Sounds good. I don't like the extra boolean flag either.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at [email protected] or file a JIRA ticket
with INFRA.
---

Reply via email to