You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2016/11/15 16:42:02 UTC

zeppelin git commit: ZEPPELIN-1643:Make spark web UI accesible from interpreters page

Repository: zeppelin
Updated Branches:
  refs/heads/master 621c527f2 -> 96ca84da1


ZEPPELIN-1643:Make spark web UI accesible from interpreters page

### What is this PR for?
Make spark web UI accesible from interpreters page

### What type of PR is it?
Improvement

### Todos
NA

### What is the Jira issue?
ZEPPELIN-1643

### How should this be tested?
Start sparkcontext.
Goto interpreters page. Corresponding to the spark interpreter used in notebook, there will be *spark ui* button.
Clicking the button should open a new tab with the application's spark web UI.

### Screenshots (if appropriate)
![zscpr58qdi](https://cloud.githubusercontent.com/assets/5082742/20110797/c6852202-a60b-11e6-8264-93437a58f752.gif)

### Questions:
* Does the licenses files need update? NA
* Is there breaking changes for older versions? No
* Does this needs documentation? No

Author: karuppayya <ka...@gmail.com>
Author: Karup <ka...@outlook.com>

Closes #1613 from karup1990/ZEPPELIN-1643 and squashes the following commits:

29e9812 [karuppayya] Fix checkstyle
350d665 [karuppayya] Fix coding err
42dde76 [Karup] Fix typo
5cfed2a [karuppayya] Fix test failure
55c45c9 [karuppayya] Address feedback
4d97196 [karuppayya] Remove comments
a1304a2 [karuppayya] Change to make spark web UI accesible from interpreters page


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/96ca84da
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/96ca84da
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/96ca84da

Branch: refs/heads/master
Commit: 96ca84da156522bab33f734c89f0cc8b398e5334
Parents: 621c527
Author: karuppayya <ka...@gmail.com>
Authored: Tue Nov 15 09:28:23 2016 +0530
Committer: Lee moon soo <mo...@apache.org>
Committed: Tue Nov 15 08:41:59 2016 -0800

----------------------------------------------------------------------
 .../zeppelin/spark/PySparkInterpreter.java      |  1 +
 .../apache/zeppelin/spark/SparkInterpreter.java | 27 ++++++++++++++++--
 .../zeppelin/spark/SparkRInterpreter.java       |  1 +
 .../zeppelin/spark/SparkSqlInterpreter.java     |  1 +
 .../interpreter/InterpreterContext.java         | 25 ++++++++++++++++-
 .../interpreter/remote/RemoteEventClient.java   | 24 ++++++++++++++++
 .../remote/RemoteEventClientWrapper.java        | 15 ++++++++++
 .../remote/RemoteInterpreterEventClient.java    |  5 ++++
 .../remote/RemoteInterpreterEventPoller.java    |  8 ++++++
 .../RemoteInterpreterProcessListener.java       |  3 ++
 .../remote/RemoteInterpreterServer.java         |  2 +-
 .../thrift/RemoteInterpreterEventType.java      |  8 +++---
 .../RemoteInterpreterOutputTestStream.java      |  6 ++++
 .../zeppelin/scheduler/RemoteSchedulerTest.java |  5 ++++
 .../zeppelin/rest/InterpreterRestApi.java       | 29 ++++++++++++++++++++
 .../apache/zeppelin/socket/NotebookServer.java  |  7 +++++
 .../app/interpreter/interpreter.controller.js   | 16 +++++++++++
 .../src/app/interpreter/interpreter.html        |  4 +++
 .../interpreter/InterpreterFactory.java         |  2 ++
 .../interpreter/InterpreterSetting.java         | 10 +++++++
 20 files changed, 190 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java
----------------------------------------------------------------------
diff --git a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java
index 13407b2..b682a03 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/PySparkInterpreter.java
@@ -316,6 +316,7 @@ public class PySparkInterpreter extends Interpreter implements ExecuteResultHand
   @Override
   public InterpreterResult interpret(String st, InterpreterContext context) {
     SparkInterpreter sparkInterpreter = getSparkInterpreter();
+    sparkInterpreter.populateSparkWebUrl(context);
     if (sparkInterpreter.getSparkVersion().isUnsupportedVersion()) {
       return new InterpreterResult(Code.ERROR, "Spark "
           + sparkInterpreter.getSparkVersion().toString() + " is not supported");

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java
----------------------------------------------------------------------
diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java
index 41e83ef..1cd6c71 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkInterpreter.java
@@ -27,8 +27,6 @@ import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.*;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import com.google.common.base.Joiner;
@@ -45,6 +43,7 @@ import org.apache.spark.scheduler.ActiveJob;
 import org.apache.spark.scheduler.DAGScheduler;
 import org.apache.spark.scheduler.Pool;
 import org.apache.spark.sql.SQLContext;
+import org.apache.spark.ui.SparkUI;
 import org.apache.spark.ui.jobs.JobProgressListener;
 import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.InterpreterContext;
@@ -113,6 +112,7 @@ public class SparkInterpreter extends Interpreter {
 
   private InterpreterOutputStream out;
   private SparkDependencyResolver dep;
+  private String sparkUrl;
 
   /**
    * completer - org.apache.spark.repl.SparkJLineCompletion (scala 2.10)
@@ -939,6 +939,13 @@ public class SparkInterpreter extends Interpreter {
     numReferenceOfSparkContext.incrementAndGet();
   }
 
+  private String getSparkUIUrl() {
+    Option<SparkUI> sparkUiOption = (Option<SparkUI>) Utils.invokeMethod(sc, "ui");
+    SparkUI sparkUi = sparkUiOption.get();
+    String sparkWebUrl = sparkUi.appUIAddress();
+    return sparkWebUrl;
+  }
+
   private Results.Result interpret(String line) {
     return (Results.Result) Utils.invokeMethod(
         intp,
@@ -947,6 +954,20 @@ public class SparkInterpreter extends Interpreter {
         new Object[] {line});
   }
 
+  public void populateSparkWebUrl(InterpreterContext ctx) {
+    if (sparkUrl == null) {
+      sparkUrl = getSparkUIUrl();
+      Map<String, String> infos = new java.util.HashMap<>();
+      if (sparkUrl != null) {
+        infos.put("url", sparkUrl);
+        logger.info("Sending metainfos to Zeppelin server: {}", infos.toString());
+        if (ctx != null && ctx.getClient() != null) {
+          ctx.getClient().onMetaInfosReceived(infos);
+        }
+      }
+    }
+  }
+
   private List<File> currentClassPath() {
     List<File> paths = classPath(Thread.currentThread().getContextClassLoader());
     String[] cps = System.getProperty("java.class.path").split(File.pathSeparator);
@@ -1086,7 +1107,7 @@ public class SparkInterpreter extends Interpreter {
       return new InterpreterResult(Code.ERROR, "Spark " + sparkVersion.toString()
           + " is not supported");
     }
-
+    populateSparkWebUrl(context);
     z.setInterpreterContext(context);
     if (line == null || line.trim().length() == 0) {
       return new InterpreterResult(Code.SUCCESS);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java
----------------------------------------------------------------------
diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java
index 0613949..15ce658 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkRInterpreter.java
@@ -97,6 +97,7 @@ public class SparkRInterpreter extends Interpreter {
   @Override
   public InterpreterResult interpret(String lines, InterpreterContext interpreterContext) {
 
+    getSparkInterpreter().populateSparkWebUrl(interpreterContext);
     String imageWidth = getProperty("zeppelin.R.image.width");
 
     String[] sl = lines.split("\n");

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/spark/src/main/java/org/apache/zeppelin/spark/SparkSqlInterpreter.java
----------------------------------------------------------------------
diff --git a/spark/src/main/java/org/apache/zeppelin/spark/SparkSqlInterpreter.java b/spark/src/main/java/org/apache/zeppelin/spark/SparkSqlInterpreter.java
index fc8923c..e6fe137 100644
--- a/spark/src/main/java/org/apache/zeppelin/spark/SparkSqlInterpreter.java
+++ b/spark/src/main/java/org/apache/zeppelin/spark/SparkSqlInterpreter.java
@@ -96,6 +96,7 @@ public class SparkSqlInterpreter extends Interpreter {
           + sparkInterpreter.getSparkVersion().toString() + " is not supported");
     }
 
+    sparkInterpreter.populateSparkWebUrl(context);
     sqlc = getSparkInterpreter().getSQLContext();
     SparkContext sc = sqlc.sparkContext();
     if (concurrentSQL()) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterContext.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterContext.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterContext.java
index bf3cfcb..f8c9032 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterContext.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterContext.java
@@ -20,10 +20,12 @@ package org.apache.zeppelin.interpreter;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.zeppelin.annotation.ZeppelinApi;
 import org.apache.zeppelin.display.AngularObjectRegistry;
 import org.apache.zeppelin.user.AuthenticationInfo;
 import org.apache.zeppelin.display.GUI;
+import org.apache.zeppelin.interpreter.remote.RemoteEventClientWrapper;
+import org.apache.zeppelin.interpreter.remote.RemoteEventClient;
+import org.apache.zeppelin.interpreter.remote.RemoteInterpreterEventClient;
 import org.apache.zeppelin.resource.ResourcePool;
 
 /**
@@ -57,6 +59,7 @@ public class InterpreterContext {
   private ResourcePool resourcePool;
   private List<InterpreterContextRunner> runners;
   private String className;
+  private RemoteEventClientWrapper client;
 
   public InterpreterContext(String noteId,
                             String paragraphId,
@@ -83,6 +86,22 @@ public class InterpreterContext {
     this.out = out;
   }
 
+  public InterpreterContext(String noteId,
+                            String paragraphId,
+                            String paragraphTitle,
+                            String paragraphText,
+                            AuthenticationInfo authenticationInfo,
+                            Map<String, Object> config,
+                            GUI gui,
+                            AngularObjectRegistry angularObjectRegistry,
+                            ResourcePool resourcePool,
+                            List<InterpreterContextRunner> contextRunners,
+                            InterpreterOutput output,
+                            RemoteInterpreterEventClient eventClient) {
+    this(noteId, paragraphId, paragraphTitle, paragraphText, authenticationInfo, config, gui,
+        angularObjectRegistry, resourcePool, contextRunners, output);
+    this.client = new RemoteEventClient(eventClient);
+  }
 
   public String getNoteId() {
     return noteId;
@@ -131,4 +150,8 @@ public class InterpreterContext {
   public void setClassName(String className) {
     this.className = className;
   }
+
+  public RemoteEventClientWrapper getClient() {
+    return client;
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClient.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClient.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClient.java
new file mode 100644
index 0000000..3585a59
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClient.java
@@ -0,0 +1,24 @@
+package org.apache.zeppelin.interpreter.remote;
+
+import java.util.Map;
+
+/**
+ * 
+ * Wrapper arnd RemoteInterpreterEventClient
+ * to expose methods in the client
+ *
+ */
+public class RemoteEventClient implements RemoteEventClientWrapper {
+
+  private RemoteInterpreterEventClient client;
+
+  public RemoteEventClient(RemoteInterpreterEventClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public void onMetaInfosReceived(Map<String, String> infos) {
+    client.onMetaInfosReceived(infos);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClientWrapper.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClientWrapper.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClientWrapper.java
new file mode 100644
index 0000000..339f771
--- /dev/null
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteEventClientWrapper.java
@@ -0,0 +1,15 @@
+package org.apache.zeppelin.interpreter.remote;
+
+import java.util.Map;
+
+/**
+ * 
+ * Wrapper interface for RemoterInterpreterEventClient
+ * to expose only required methods from EventClient
+ *
+ */
+public interface RemoteEventClientWrapper {
+
+  public void onMetaInfosReceived(Map<String, String> infos);
+
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventClient.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventClient.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventClient.java
index c59a6f6..ae38ee8 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventClient.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventClient.java
@@ -279,6 +279,11 @@ public class RemoteInterpreterEventClient implements ResourcePoolConnector {
         gson.toJson(appendOutput)));
   }
 
+  public void onMetaInfosReceived(Map<String, String> infos) {
+    sendEvent(new RemoteInterpreterEvent(RemoteInterpreterEventType.META_INFOS,
+        gson.toJson(infos)));
+  }
+
   /**
    * Wait for eventQueue becomes empty
    */

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java
index f7fee49..b75e5fa 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterEventPoller.java
@@ -195,6 +195,14 @@ public class RemoteInterpreterEventPoller extends Thread {
           String status = appStatusUpdate.get("status");
 
           appListener.onStatusChange(noteId, paragraphId, appId, status);
+        } else if (event.getType() == RemoteInterpreterEventType.META_INFOS) {
+          Map<String, String> metaInfos = gson.fromJson(event.getData(),
+              new TypeToken<Map<String, String>>() {
+              }.getType());
+          String id = interpreterGroup.getId();
+          int indexOfColon = id.indexOf(":");
+          String settingId = id.substring(0, indexOfColon);
+          listener.onMetaInfosReceived(settingId, metaInfos);
         }
         logger.debug("Event from remoteproceess {}", event.getType());
       } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessListener.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessListener.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessListener.java
index da6ac63..d25683f 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessListener.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterProcessListener.java
@@ -16,10 +16,13 @@
  */
 package org.apache.zeppelin.interpreter.remote;
 
+import java.util.Map;
+
 /**
  * Event from remoteInterpreterProcess
  */
 public interface RemoteInterpreterProcessListener {
   public void onOutputAppend(String noteId, String paragraphId, String output);
   public void onOutputUpdated(String noteId, String paragraphId, String output);
+  public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos);
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
index cde6a7b..4d6f3ba 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterServer.java
@@ -552,7 +552,7 @@ public class RemoteInterpreterServer
         gson.fromJson(ric.getGui(), GUI.class),
         interpreterGroup.getAngularObjectRegistry(),
         interpreterGroup.getResourcePool(),
-        contextRunners, output);
+        contextRunners, output, eventClient);
   }
 
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/thrift/RemoteInterpreterEventType.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/thrift/RemoteInterpreterEventType.java b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/thrift/RemoteInterpreterEventType.java
index 9554619..75692c0 100644
--- a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/thrift/RemoteInterpreterEventType.java
+++ b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/thrift/RemoteInterpreterEventType.java
@@ -24,9 +24,6 @@
 package org.apache.zeppelin.interpreter.thrift;
 
 
-import java.util.Map;
-import java.util.HashMap;
-import org.apache.thrift.TEnum;
 
 public enum RemoteInterpreterEventType implements org.apache.thrift.TEnum {
   NO_OP(1),
@@ -39,7 +36,8 @@ public enum RemoteInterpreterEventType implements org.apache.thrift.TEnum {
   OUTPUT_APPEND(8),
   OUTPUT_UPDATE(9),
   ANGULAR_REGISTRY_PUSH(10),
-  APP_STATUS_UPDATE(11);
+  APP_STATUS_UPDATE(11),
+  META_INFOS(12);
 
   private final int value;
 
@@ -82,6 +80,8 @@ public enum RemoteInterpreterEventType implements org.apache.thrift.TEnum {
         return ANGULAR_REGISTRY_PUSH;
       case 11:
         return APP_STATUS_UPDATE;
+      case 12:
+        return META_INFOS;
       default:
         return null;
     }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
index 2ba62c3..27e3808 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/interpreter/remote/RemoteInterpreterOutputTestStream.java
@@ -29,6 +29,7 @@ import org.junit.Test;
 import java.io.File;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Properties;
 
 import static org.junit.Assert.assertEquals;
@@ -154,4 +155,9 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
   public void onOutputUpdated(String noteId, String paragraphId, String output) {
 
   }
+
+  @Override
+  public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos) {
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
----------------------------------------------------------------------
diff --git a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
index 49de4a7..54cc25d 100644
--- a/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
+++ b/zeppelin-interpreter/src/test/java/org/apache/zeppelin/scheduler/RemoteSchedulerTest.java
@@ -299,4 +299,9 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
   public void onOutputUpdated(String noteId, String paragraphId, String output) {
 
   }
+
+  @Override
+  public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos) {
+
+  }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java
index c9094eb..cb50a8a 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/InterpreterRestApi.java
@@ -18,9 +18,12 @@
 package org.apache.zeppelin.rest;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
@@ -28,6 +31,7 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
@@ -216,6 +220,31 @@ public class InterpreterRestApi {
   }
 
   /**
+   * get the metainfo property value
+   */
+  @GET
+  @Path("getmetainfos/{settingId}")
+  public Response getMetaInfo(@Context HttpServletRequest req,
+      @PathParam("settingId") String settingId) {
+    String propName = req.getParameter("propName");
+    if (propName == null) {
+      return new JsonResponse<>(Status.BAD_REQUEST).build();
+    }
+    String propValue = null;
+    InterpreterSetting interpreterSetting = interpreterFactory.get(settingId);
+    Map<String, String> infos = interpreterSetting.getInfos();
+    if (infos != null) {
+      propValue = infos.get(propName);
+    }
+    Map<String, String> respMap = new HashMap<>();
+    respMap.put(propName, propValue);
+    logger.debug("Get meta info");
+    logger.debug("Interpretersetting Id: {}, property Name:{}, property value: {}", settingId,
+        propName, propValue);
+    return new JsonResponse<>(Status.OK, respMap).build();
+  }
+
+  /**
    * Delete repository
    *
    * @param repoId ID of repository

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
----------------------------------------------------------------------
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index b34a853..bc0d7f5 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -1764,5 +1764,12 @@ public class NotebookServer extends WebSocketServlet implements
             .put("interpreterSettings", availableSettings)));
   }
 
+  @Override
+  public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos) {
+    InterpreterSetting interpreterSetting = notebook().getInterpreterFactory()
+        .get(settingId);
+    interpreterSetting.setInfos(metaInfos);
+  }
+
 }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-web/src/app/interpreter/interpreter.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter.controller.js b/zeppelin-web/src/app/interpreter/interpreter.controller.js
index c503030..a846cf2 100644
--- a/zeppelin-web/src/app/interpreter/interpreter.controller.js
+++ b/zeppelin-web/src/app/interpreter/interpreter.controller.js
@@ -692,6 +692,22 @@
       getRepositories();
     };
 
+    $scope.showSparkUI = function(settingId) {
+      $http.get(baseUrlSrv.getRestApiBase() + '/interpreter/getmetainfos/' + settingId + '?propName=url')
+        .success(function(data, status, headers, config) {
+          var url = data.body.url;
+          if (!url) {
+            BootstrapDialog.alert({
+              message: 'No spark application running'
+            });
+            return;
+          }
+          window.open(url, '_blank');
+        }).error(function(data, status, headers, config) {
+         console.log('Error %o %o', status, data.message);
+       });
+    };
+
     init();
   }
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-web/src/app/interpreter/interpreter.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/interpreter/interpreter.html b/zeppelin-web/src/app/interpreter/interpreter.html
index abb83d8..e0cebba 100644
--- a/zeppelin-web/src/app/interpreter/interpreter.html
+++ b/zeppelin-web/src/app/interpreter/interpreter.html
@@ -129,6 +129,10 @@ limitations under the License.
         </h3>
         <span style="float:right" ng-show="!valueform.$visible" >
           <button class="btn btn-default btn-xs"
+                  ng-click="showSparkUI(setting.id)"
+                  ng-show="setting.group == 'spark'">
+            <span class="fa fa-external-link"></span> spark ui</button>
+          <button class="btn btn-default btn-xs"
                   ng-click="valueform.$show();
                   copyOriginInterpreterSettingProperties(setting.id)">
             <span class="fa fa-pencil"></span> edit</button>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java
index ce740b7..61adad9 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterFactory.java
@@ -982,6 +982,8 @@ public class InterpreterFactory implements InterpreterGroupFactory {
       // Check if dependency in specified path is changed
       // If it did, overwrite old dependency jar with new one
       if (intpSetting != null) {
+        //clean up metaInfos
+        intpSetting.setInfos(null);
         copyDependenciesFromLocalPath(intpSetting);
 
         stopJobAllInterpreter(intpSetting);

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/96ca84da/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java
----------------------------------------------------------------------
diff --git a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java
index 47d0ef9..cdc6d0d 100644
--- a/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java
+++ b/zeppelin-zengine/src/main/java/org/apache/zeppelin/interpreter/InterpreterSetting.java
@@ -44,6 +44,8 @@ public class InterpreterSetting {
   private String name;
   // always be null in case of InterpreterSettingRef
   private String group;
+  private transient Map<String, String> infos;
+
   /**
    * properties can be either Properties or Map<String, InterpreterProperty>
    * properties should be:
@@ -276,4 +278,12 @@ public class InterpreterSetting {
   public void setErrorReason(String errorReason) {
     this.errorReason = errorReason;
   }
+
+  public void setInfos(Map<String, String> infos) {
+    this.infos = infos;
+  }
+
+  public Map<String, String> getInfos() {
+    return infos;
+  }
 }