Ability to view spark job urls in each paragraph

This commit is contained in:
karuppayya 2016-11-20 20:30:40 +05:30 committed by Karup
parent 7420f2df75
commit f16422f297
21 changed files with 234 additions and 23 deletions

View file

@ -42,6 +42,8 @@ import org.apache.spark.repl.SparkILoop;
import org.apache.spark.scheduler.ActiveJob;
import org.apache.spark.scheduler.DAGScheduler;
import org.apache.spark.scheduler.Pool;
import org.apache.spark.scheduler.SparkListenerApplicationEnd;
import org.apache.spark.scheduler.SparkListenerJobStart;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.ui.SparkUI;
import org.apache.spark.ui.jobs.JobProgressListener;
@ -57,6 +59,7 @@ import org.apache.zeppelin.interpreter.WrappedInterpreter;
import org.apache.zeppelin.interpreter.util.InterpreterOutputStream;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.resource.WellKnownResourceName;
import org.apache.zeppelin.interpreter.remote.RemoteEventClientWrapper;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.scheduler.Scheduler;
import org.apache.zeppelin.scheduler.SchedulerFactory;
@ -112,7 +115,7 @@ public class SparkInterpreter extends Interpreter {
private InterpreterOutputStream out;
private SparkDependencyResolver dep;
private String sparkUrl;
private static String sparkUrl;
/**
* completer - org.apache.spark.repl.SparkJLineCompletion (scala 2.10)
@ -156,7 +159,43 @@ public class SparkInterpreter extends Interpreter {
}
static JobProgressListener setupListeners(SparkContext context) {
JobProgressListener pl = new JobProgressListener(context.getConf());
JobProgressListener pl = new JobProgressListener(context.getConf()) {
@Override
public synchronized void onJobStart(SparkListenerJobStart jobStart) {
super.onJobStart(jobStart);
int jobId = jobStart.jobId();
String jobGroupId = jobStart.properties().getProperty("spark.jobGroup.id");
String jobUrl = getJobUrl(jobId);
String noteId = getNoteId(jobGroupId);
String paragraphId = getParagraphId(jobGroupId);
if (jobUrl != null && noteId != null && paragraphId != null) {
RemoteEventClientWrapper eventClient = ZeppelinContext.getEventClient();
Map<String, String> infos = new java.util.HashMap<>();
infos.put("jobUrl", jobUrl);
eventClient.onParaInfosReceived(noteId, paragraphId, infos);
}
}
private String getJobUrl(int jobId) {
String jobUrl = null;
if (sparkUrl != null) {
jobUrl = sparkUrl + "/jobs/job?id=" + jobId;
}
return jobUrl;
}
private String getNoteId(String jobgroupId) {
int indexOf = jobgroupId.indexOf("-");
int secondIndex = jobgroupId.indexOf("-", indexOf + 1);
return jobgroupId.substring(indexOf + 1, secondIndex);
}
private String getParagraphId(String jobgroupId) {
int indexOf = jobgroupId.indexOf("-");
int secondIndex = jobgroupId.indexOf("-", indexOf + 1);
return jobgroupId.substring(secondIndex + 1, jobgroupId.length());
}
};
try {
Object listenerBus = context.getClass().getMethod("listenerBus").invoke(context);
@ -973,6 +1012,7 @@ public class SparkInterpreter extends Interpreter {
infos.put("url", sparkUrl);
logger.info("Sending metainfos to Zeppelin server: {}", infos.toString());
if (ctx != null && ctx.getClient() != null) {
getZeppelinContext().setEventClient(ctx.getClient());
ctx.getClient().onMetaInfosReceived(infos);
}
}
@ -1105,8 +1145,8 @@ public class SparkInterpreter extends Interpreter {
return obj;
}
String getJobGroup(InterpreterContext context){
return "zeppelin-" + context.getParagraphId();
String getJobGroup(InterpreterContext context) {
return "zeppelin-" + context.getNoteId() + "-" + context.getParagraphId();
}
/**

View file

@ -102,7 +102,12 @@ public class SparkRInterpreter extends Interpreter {
@Override
public InterpreterResult interpret(String lines, InterpreterContext interpreterContext) {
getSparkInterpreter().populateSparkWebUrl(interpreterContext);
SparkInterpreter sparkInterpreter = getSparkInterpreter();
sparkInterpreter.populateSparkWebUrl(interpreterContext);
String jobGroup = sparkInterpreter.getJobGroup(interpreterContext);
sparkInterpreter.getSparkContext().setJobGroup(jobGroup, "Zeppelin", false);
String imageWidth = getProperty("zeppelin.R.image.width");
String[] sl = lines.split("\n");

View file

@ -45,10 +45,6 @@ public class SparkSqlInterpreter extends Interpreter {
Logger logger = LoggerFactory.getLogger(SparkSqlInterpreter.class);
AtomicInteger num = new AtomicInteger(0);
private String getJobGroup(InterpreterContext context){
return "zeppelin-" + context.getParagraphId();
}
private int maxResult;
public SparkSqlInterpreter(Properties property) {
@ -105,7 +101,7 @@ public class SparkSqlInterpreter extends Interpreter {
sc.setLocalProperty("spark.scheduler.pool", null);
}
sc.setJobGroup(getJobGroup(context), "Zeppelin", false);
sc.setJobGroup(sparkInterpreter.getJobGroup(context), "Zeppelin", false);
Object rdd = null;
try {
// method signature of sqlc.sql() is changed
@ -134,10 +130,11 @@ public class SparkSqlInterpreter extends Interpreter {
@Override
public void cancel(InterpreterContext context) {
SQLContext sqlc = getSparkInterpreter().getSQLContext();
SparkInterpreter sparkInterpreter = getSparkInterpreter();
SQLContext sqlc = sparkInterpreter.getSQLContext();
SparkContext sc = sqlc.sparkContext();
sc.cancelJobGroup(getJobGroup(context));
sc.cancelJobGroup(sparkInterpreter.getJobGroup(context));
}
@Override

View file

@ -46,6 +46,7 @@ import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterHookRegistry;
import org.apache.zeppelin.interpreter.RemoteWorksController;
import org.apache.zeppelin.interpreter.remote.RemoteEventClientWrapper;
import org.apache.zeppelin.spark.dep.SparkDependencyResolver;
import org.apache.zeppelin.resource.Resource;
import org.apache.zeppelin.resource.ResourcePool;
@ -61,6 +62,7 @@ public class ZeppelinContext {
// Map interpreter class name (to be used by hook registry) from
// given replName in parapgraph
private static final Map<String, String> interpreterClassMap;
private static RemoteEventClientWrapper eventClient;
static {
interpreterClassMap = new HashMap<>();
interpreterClassMap.put("spark", "org.apache.zeppelin.spark.SparkInterpreter");
@ -221,7 +223,8 @@ public class ZeppelinContext {
Object df, int maxResult) {
Object[] rows = null;
Method take;
String jobGroup = "zeppelin-" + interpreterContext.getParagraphId();
String jobGroup = "zeppelin-" + interpreterContext.getNoteId() + "-"
+ interpreterContext.getParagraphId();
sc.setJobGroup(jobGroup, "Zeppelin", false);
try {
@ -930,4 +933,21 @@ public class ZeppelinContext {
return resourcePool.getAll();
}
/**
* Get the event client
*/
@ZeppelinApi
public static RemoteEventClientWrapper getEventClient() {
return eventClient;
}
/**
* Set event client
*/
@ZeppelinApi
public void setEventClient(RemoteEventClientWrapper eventClient) {
if (ZeppelinContext.eventClient == null) {
ZeppelinContext.eventClient = eventClient;
}
}
}

View file

@ -1,5 +1,6 @@
package org.apache.zeppelin.interpreter.remote;
import java.util.HashMap;
import java.util.Map;
/**
@ -21,4 +22,13 @@ public class RemoteEventClient implements RemoteEventClientWrapper {
client.onMetaInfosReceived(infos);
}
@Override
public void onParaInfosReceived(String noteId, String paragraphId, Map<String, String> infos) {
Map<String, String> paraInfos = new HashMap<String, String>(infos);
paraInfos.put("noteId", noteId);
paraInfos.put("paraId", paragraphId);
client.onParaInfosReceived(paraInfos);
}
}

View file

@ -12,4 +12,7 @@ public interface RemoteEventClientWrapper {
public void onMetaInfosReceived(Map<String, String> infos);
public void onParaInfosReceived(String noteId, String paragraphId,
Map<String, String> infos);
}

View file

@ -470,6 +470,10 @@ public class RemoteInterpreterEventClient implements ResourcePoolConnector {
gson.toJson(infos)));
}
public void onParaInfosReceived(Map<String, String> infos) {
sendEvent(new RemoteInterpreterEvent(RemoteInterpreterEventType.PARA_INFOS,
gson.toJson(infos)));
}
/**
* Wait for eventQueue becomes empty
*/

View file

@ -243,10 +243,18 @@ public class RemoteInterpreterEventPoller extends Thread {
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);
String settingId = getInterpreterSettingId();
listener.onMetaInfosReceived(settingId, metaInfos);
} else if (event.getType() == RemoteInterpreterEventType.PARA_INFOS) {
Map<String, String> paraInfos = gson.fromJson(event.getData(),
new TypeToken<Map<String, String>>() {
}.getType());
String noteId = paraInfos.get("noteId");
String paraId = paraInfos.get("paraId");
String settingId = getInterpreterSettingId();
if (noteId != null && paraId != null && settingId != null) {
listener.onParaInfosReceived(noteId, paraId, settingId, paraInfos);
}
}
logger.debug("Event from remote process {}", event.getType());
} catch (Exception e) {
@ -334,6 +342,12 @@ public class RemoteInterpreterEventPoller extends Thread {
}
}
private String getInterpreterSettingId() {
String id = interpreterGroup.getId();
int indexOfColon = id.indexOf(":");
return id.substring(0, indexOfColon);
}
private void sendResourcePoolResponseGetAll(ResourceSet resourceSet) {
Client client = null;
boolean broken = false;

View file

@ -40,4 +40,6 @@ public interface RemoteInterpreterProcessListener {
public void onFinished(Object resultObject);
public void onError();
}
public void onParaInfosReceived(String noteId, String paragraphId,
String interpreterSettingId, Map<String, String> metaInfos);
}

View file

@ -43,7 +43,8 @@ public enum RemoteInterpreterEventType implements org.apache.thrift.TEnum {
APP_STATUS_UPDATE(12),
META_INFOS(13),
REMOTE_ZEPPELIN_SERVER_RESOURCE(14),
RESOURCE_INVOKE_METHOD(15);
RESOURCE_INVOKE_METHOD(15),
PARA_INFOS(16);
private final int value;
@ -94,6 +95,8 @@ public enum RemoteInterpreterEventType implements org.apache.thrift.TEnum {
return REMOTE_ZEPPELIN_SERVER_RESOURCE;
case 15:
return RESOURCE_INVOKE_METHOD;
case 16:
return PARA_INFOS;
default:
return null;
}

View file

@ -56,7 +56,8 @@ enum RemoteInterpreterEventType {
APP_STATUS_UPDATE = 12,
META_INFOS = 13,
REMOTE_ZEPPELIN_SERVER_RESOURCE = 14,
RESOURCE_INVOKE_METHOD = 15
RESOURCE_INVOKE_METHOD = 15,
PARA_INFOS = 16
}

View file

@ -182,4 +182,10 @@ public class RemoteInterpreterOutputTestStream implements RemoteInterpreterProce
public void onRemoteRunParagraph(String noteId, String ParagraphID) throws Exception {
}
@Override
public void onParaInfosReceived(String noteId, String paragraphId,
String interpreterSettingId, Map<String, String> metaInfos) {
}
}

View file

@ -355,6 +355,10 @@ public class RemoteSchedulerTest implements RemoteInterpreterProcessListener {
@Override
public void onRemoteRunParagraph(String noteId, String PsaragraphID) throws Exception {
}
@Override
public void onParaInfosReceived(String noteId, String paragraphId,
String interpreterSettingId, Map<String, String> metaInfos) {
}
}

View file

@ -51,6 +51,7 @@ import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.rest.message.NewInterpreterSettingRequest;
import org.apache.zeppelin.rest.message.UpdateInterpreterSettingRequest;
import org.apache.zeppelin.server.JsonResponse;
import org.apache.zeppelin.socket.NotebookServer;
/**
* Interpreter Rest API
@ -61,14 +62,17 @@ public class InterpreterRestApi {
private static final Logger logger = LoggerFactory.getLogger(InterpreterRestApi.class);
private InterpreterFactory interpreterFactory;
private NotebookServer notebookServer;
Gson gson = new Gson();
public InterpreterRestApi() {
}
public InterpreterRestApi(InterpreterFactory interpreterFactory) {
public InterpreterRestApi(InterpreterFactory interpreterFactory,
NotebookServer notebookWsServer) {
this.interpreterFactory = interpreterFactory;
this.notebookServer = notebookWsServer;
}
/**
@ -179,18 +183,20 @@ public class InterpreterRestApi {
@ZeppelinApi
public Response restartSetting(String message, @PathParam("settingId") String settingId) {
logger.info("Restart interpreterSetting {}, msg={}", settingId, message);
InterpreterSetting setting = interpreterFactory.get(settingId);
try {
RestartInterpreterRequest request = gson.fromJson(message, RestartInterpreterRequest.class);
String noteId = request == null ? null : request.getNoteId();
interpreterFactory.restart(settingId, noteId, SecurityUtils.getPrincipal());
notebookServer.clearParagraphRuntimeInfo(setting);
} catch (InterpreterException e) {
logger.error("Exception in InterpreterRestApi while restartSetting ", e);
return new JsonResponse<>(Status.NOT_FOUND, e.getMessage(), ExceptionUtils.getStackTrace(e))
.build();
}
InterpreterSetting setting = interpreterFactory.get(settingId);
if (setting == null) {
return new JsonResponse<>(Status.NOT_FOUND, "", settingId).build();
}

View file

@ -374,7 +374,7 @@ public class ZeppelinServer extends Application {
HeliumRestApi heliumApi = new HeliumRestApi(helium, notebook);
singletons.add(heliumApi);
InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory);
InterpreterRestApi interpreterApi = new InterpreterRestApi(replFactory, notebookWsServer);
singletons.add(interpreterApi);
CredentialRestApi credentialApi = new CredentialRestApi(credentials);

View file

@ -2314,4 +2314,43 @@ public class NotebookServer extends WebSocketServlet
}
}
}
@Override
public void onParaInfosReceived(String noteId, String paragraphId,
String interpreterSettingId, Map<String, String> metaInfos) {
Note note = notebook().getNote(noteId);
if (note != null) {
Paragraph paragraph = note.getParagraph(paragraphId);
if (paragraph != null) {
InterpreterSetting setting = notebook().getInterpreterFactory()
.get(interpreterSettingId);
setting.addNoteToPara(noteId, paragraphId);
metaInfos.remove("noteId");
metaInfos.remove("paraId");
paragraph.updateRuntimeInfos(metaInfos);
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", paragraph));
}
}
}
public void clearParagraphRuntimeInfo(InterpreterSetting setting) {
Map<String, Set<String>> noteIdAndParaMap = setting.getNoteIdAndParaMap();
if (noteIdAndParaMap != null && !noteIdAndParaMap.isEmpty()) {
for (String noteId : noteIdAndParaMap.keySet()) {
Set<String> paraIdSet = noteIdAndParaMap.get(noteId);
if (paraIdSet != null && !paraIdSet.isEmpty()) {
for (String paraId : paraIdSet) {
Note note = notebook().getNote(noteId);
if (note != null) {
Paragraph paragraph = note.getParagraph(paraId);
paragraph.clearRuntimeInfo();
if (paragraph != null) {
broadcast(noteId, new Message(OP.PARAGRAPH).put("paragraph", paragraph));
}
}
}
}
}
}
}
}

View file

@ -31,6 +31,20 @@ limitations under the License.
tooltip="Cancel (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+c)"
ng-click="cancelParagraph(paragraph)"
ng-show="paragraph.status=='RUNNING' || paragraph.status=='PENDING'"></span>
<span ng-show="paragraph.runtimeInfos.jobUrl.length == 1">
<a href="{{paragraph.runtimeInfos.jobUrl[0]}}" target="_blank"><span class="fa fa-tasks"></span> Spark job </a>
</span>
<span class="dropdown" ng-show="paragraph.runtimeInfos.jobUrl.length > 1">
<span class="fa fa-tasks" style="cursor:pointer;color:#3071A9" tooltip-placement="top" tooltip="Run this paragraph (Shift+Enter)"
data-toggle="dropdown"
type="button"> Spark Jobs
</span>
<ul class="dropdown-menu" role="menu" style="width:200px;z-index:1002">
<li ng-class="{'option-disabled': !noteOperationsAllowed()}" ng-repeat="url in paragraph.runtimeInfos.jobUrl">
<a href="{{url}}" target="_blank"><span class="fa fa-external-link-square"></span> Jobs #{{$index}}</a>
</li>
</ul>
</span>
<span class="{{paragraph.config.editorHide ? 'icon-size-fullscreen' : 'icon-size-actual'}}" style="cursor:pointer" tooltip-placement="top"
tooltip="{{(paragraph.config.editorHide ? 'Show' : 'Hide')}} editor (Ctrl+{{ (isMac ? 'Option' : 'Alt') }}+e)"
ng-click="toggleEditor(paragraph)"></span>

View file

@ -1083,7 +1083,8 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
isEmpty(newPara.results) !== isEmpty(oldPara.results) ||
newPara.errorMessage !== oldPara.errorMessage ||
!angular.equals(newPara.settings, oldPara.settings) ||
!angular.equals(newPara.config, oldPara.config)))
!angular.equals(newPara.config, oldPara.config) ||
!angular.equals(newPara.runtimeInfos, oldPara.runtimeInfos)))
}
$scope.updateAllScopeTexts = function(oldPara, newPara) {
@ -1126,6 +1127,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
$scope.paragraph.results = newPara.results;
}
$scope.paragraph.settings = newPara.settings;
$scope.paragraph.runtimeInfos = newPara.runtimeInfos;
if ($scope.editor) {
$scope.editor.setReadOnly($scope.isRunning(newPara));
}
@ -1139,7 +1141,6 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
$scope.paragraph.config = newPara.config;
}
};
$scope.updateParagraph = function(oldPara, newPara, updateCallback) {
// 1. get status, refreshed
const statusChanged = (newPara.status !== oldPara.status);

View file

@ -25,6 +25,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.google.gson.annotations.SerializedName;
@ -47,6 +48,7 @@ public class InterpreterSetting {
// always be null in case of InterpreterSettingRef
private String group;
private transient Map<String, String> infos;
private transient Map<String, Set<String>> noteIdToParaIdsetMap;
/**
* properties can be either Properties or Map<String, InterpreterProperty>
@ -394,11 +396,28 @@ public class InterpreterSetting {
return infos;
}
<<<<<<< b1e06919fada4240f5440430f2c27c02e4e5626a
public InterpreterRunner getInterpreterRunner() {
return interpreterRunner;
}
public void setInterpreterRunner(InterpreterRunner interpreterRunner) {
this.interpreterRunner = interpreterRunner;
=======
public void addNoteToPara(String noteId, String paraId) {
if(noteIdToParaIdsetMap == null) {
noteIdToParaIdsetMap = new HashMap<>();
}
Set<String> paraIdSet = noteIdToParaIdsetMap.get(noteId);
if(paraIdSet == null) {
paraIdSet = new HashSet<>();
noteIdToParaIdsetMap.put(noteId, paraIdSet);
}
paraIdSet.add(paraId);
}
public Map<String, Set<String>> getNoteIdAndParaMap() {
return noteIdToParaIdsetMap;
>>>>>>> Ability to view spark job urls in each paragraph
}
}

View file

@ -480,6 +480,7 @@ public class Notebook implements NoteEventListener {
if (p.getDateFinished() != null && lastUpdatedDate.before(p.getDateFinished())) {
lastUpdatedDate = p.getDateFinished();
}
p.clearRuntimeInfo();
}
Map<String, List<AngularObject>> savedObjects = note.getAngularObjects();

View file

@ -70,6 +70,7 @@ public class Paragraph extends Job implements Serializable, Cloneable {
// For backward compatibility of note.json format after ZEPPELIN-212
Object result;
private Map<String, Set<String>> runtimeInfos;
/**
* Applicaiton states in this paragraph
@ -676,4 +677,25 @@ public class Paragraph extends Job implements Serializable, Cloneable {
return false;
}
}
public void updateRuntimeInfos(Map<String, String> infos) {
if (this.runtimeInfos == null) {
this.runtimeInfos = new HashMap<String, Set<String>>();
}
if (infos != null) {
for (String key : infos.keySet()) {
Set<String> values = this.runtimeInfos.get(key);
if (values == null) {
values = new HashSet<>();
this.runtimeInfos.put(key, values);
}
values.add(infos.get(key));
}
}
}
public void clearRuntimeInfo() {
this.runtimeInfos = null;
}
}