You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@giraph.apache.org by ed...@apache.org on 2014/12/08 20:21:38 UTC
[3/9] git commit: updated refs/heads/trunk to 8675c84
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/MsgIntegrityViolationWrapper.java
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/MsgIntegrityViolationWrapper.java b/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/MsgIntegrityViolationWrapper.java
new file mode 100644
index 0000000..423a745
--- /dev/null
+++ b/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/MsgIntegrityViolationWrapper.java
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.giraph.debugger.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.giraph.debugger.Integrity.MessageIntegrityViolation;
+import org.apache.giraph.debugger.Integrity.MessageIntegrityViolation.ExtendedOutgoingMessage;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.io.WritableComparable;
+
+import com.google.protobuf.GeneratedMessage;
+
+/**
+ * A wrapper class around the contents of MessageIntegrityViolation inside
+ * integrity.proto. In scenario.proto most things are stored as serialized byte
+ * arrays and this class gives them access through the java classes that those
+ * byte arrays serialize.
+ *
+ * @param <I>
+ * vertex ID class.
+ * @param <M2>
+ * outgoing message class.
+ *
+ * author Semih Salihoglu
+ */
+@SuppressWarnings("rawtypes")
+public class MsgIntegrityViolationWrapper<I extends WritableComparable,
+ M2 extends Writable>
+ extends BaseScenarioAndIntegrityWrapper<I> {
+
+ /**
+ * Outgoing message class.
+ */
+ private Class<M2> outgoingMessageClass;
+ /**
+ * List of captured outgoing messages.
+ */
+ private final List<ExtendedOutgoingMessageWrapper>
+ extendedOutgoingMessageWrappers = new ArrayList<>();
+ /**
+ * The superstep number at which these message violations were found.
+ */
+ private long superstepNo;
+
+ /**
+ * Empty constructor to be used for loading from HDFS.
+ */
+ public MsgIntegrityViolationWrapper() {
+ }
+
+ /**
+ * Constructor with field values.
+ *
+ * @param vertexIdClass Vertex id class.
+ * @param outgoingMessageClass Outgoing message class.
+ */
+ public MsgIntegrityViolationWrapper(Class<I> vertexIdClass,
+ Class<M2> outgoingMessageClass) {
+ initialize(vertexIdClass, outgoingMessageClass);
+ }
+
+ /**
+ * Initializes this instance.
+ *
+ * @param vertexIdClass Vertex id class.
+ * @param outgoingMessageClass Outgoing message class.
+ */
+ private void initialize(Class<I> vertexIdClass, Class<M2>
+ outgoingMessageClass) {
+ super.initialize(vertexIdClass);
+ this.outgoingMessageClass = outgoingMessageClass;
+ }
+
+ public Collection<ExtendedOutgoingMessageWrapper>
+ getExtendedOutgoingMessageWrappers() {
+ return extendedOutgoingMessageWrappers;
+ }
+
+ /**
+ * Captures an outgoing message.
+ *
+ * @param srcId Sending vertex id.
+ * @param destinationId Receiving vertex id.
+ * @param message The message being sent to capture.
+ */
+ public void addMsgWrapper(I srcId, I destinationId, M2 message) {
+ extendedOutgoingMessageWrappers.add(new ExtendedOutgoingMessageWrapper(
+ DebuggerUtils.makeCloneOf(srcId, vertexIdClass), DebuggerUtils
+ .makeCloneOf(destinationId, vertexIdClass), DebuggerUtils.makeCloneOf(
+ message, outgoingMessageClass)));
+ }
+
+ /**
+ * @return The number of captured messages so far.
+ */
+ public int numMsgWrappers() {
+ return extendedOutgoingMessageWrappers.size();
+ }
+
+ public Class<M2> getOutgoingMessageClass() {
+ return outgoingMessageClass;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(super.toString());
+ stringBuilder.append("\noutgoingMessageClass: " +
+ getOutgoingMessageClass().getCanonicalName());
+ for (ExtendedOutgoingMessageWrapper extendedOutgoingMessageWrapper :
+ getExtendedOutgoingMessageWrappers()) {
+ stringBuilder.append("\n" + extendedOutgoingMessageWrapper);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Class for capturing outgoing messages as well as the sending vertex id.
+ */
+ public class ExtendedOutgoingMessageWrapper extends BaseWrapper {
+ /**
+ * Sending vertex id.
+ */
+ private I srcId;
+ /**
+ * Receiving vertex id.
+ */
+ private I destinationId;
+ /**
+ * Message being sent.
+ */
+ private M2 message;
+
+ /**
+ * Constructor with field values.
+ *
+ * @param srcId Sending vertex id.
+ * @param destinationId Receiving vertex id.
+ * @param message Message being sent.
+ */
+ public ExtendedOutgoingMessageWrapper(I srcId, I destinationId, M2 message)
+ {
+ this.setSrcId(srcId);
+ this.setDestinationId(destinationId);
+ this.setMessage(message);
+ }
+
+ /**
+ * Default constructor.
+ */
+ public ExtendedOutgoingMessageWrapper() {
+ }
+
+ @Override
+ public String toString() {
+ return "extendedOutgoingMessage: srcId: " + getSrcId() +
+ " destinationId: " + getDestinationId() + " message: " + getMessage();
+ }
+
+ @Override
+ public GeneratedMessage buildProtoObject() {
+ ExtendedOutgoingMessage.Builder extendedOutgoingMessageBuilder =
+ ExtendedOutgoingMessage.newBuilder();
+ extendedOutgoingMessageBuilder.setSrcId(toByteString(getSrcId()));
+ extendedOutgoingMessageBuilder
+ .setDestinationId(toByteString(getDestinationId()));
+ extendedOutgoingMessageBuilder.setMsgData(toByteString(getMessage()));
+ return extendedOutgoingMessageBuilder.build();
+ }
+
+ @Override
+ public GeneratedMessage parseProtoFromInputStream(InputStream inputStream)
+ throws IOException {
+ return ExtendedOutgoingMessage.parseFrom(inputStream);
+ }
+
+ @Override
+ public void loadFromProto(GeneratedMessage generatedMessage)
+ throws ClassNotFoundException, IOException, InstantiationException,
+ IllegalAccessException {
+ ExtendedOutgoingMessage extendedOutgoingMessage =
+ (ExtendedOutgoingMessage) generatedMessage;
+ this.setSrcId(DebuggerUtils.newInstance(vertexIdClass));
+ fromByteString(extendedOutgoingMessage.getSrcId(), this.getSrcId());
+ this.setDestinationId(DebuggerUtils.newInstance(vertexIdClass));
+ fromByteString(extendedOutgoingMessage.getDestinationId(),
+ this.getDestinationId());
+ this.setMessage(DebuggerUtils.newInstance(outgoingMessageClass));
+ fromByteString(extendedOutgoingMessage.getMsgData(), this.getMessage());
+ }
+
+ /**
+ * @return the srcId
+ */
+ public I getSrcId() {
+ return srcId;
+ }
+
+ /**
+ * @param srcId the srcId to set
+ */
+ public void setSrcId(I srcId) {
+ this.srcId = srcId;
+ }
+
+ /**
+ * @return the destinationId
+ */
+ public I getDestinationId() {
+ return destinationId;
+ }
+
+ /**
+ * @param destinationId the destinationId to set
+ */
+ public void setDestinationId(I destinationId) {
+ this.destinationId = destinationId;
+ }
+
+ /**
+ * @return the message
+ */
+ public M2 getMessage() {
+ return message;
+ }
+
+ /**
+ * @param message the message to set
+ */
+ public void setMessage(M2 message) {
+ this.message = message;
+ }
+ }
+
+ public long getSuperstepNo() {
+ return superstepNo;
+ }
+
+ public void setSuperstepNo(long superstepNo) {
+ this.superstepNo = superstepNo;
+ }
+
+ @Override
+ public GeneratedMessage buildProtoObject() {
+ MessageIntegrityViolation.Builder messageIntegrityViolationBuilder =
+ MessageIntegrityViolation.newBuilder();
+ messageIntegrityViolationBuilder.setVertexIdClass(getVertexIdClass()
+ .getName());
+ messageIntegrityViolationBuilder
+ .setOutgoingMessageClass(getOutgoingMessageClass().getName());
+ messageIntegrityViolationBuilder.setSuperstepNo(getSuperstepNo());
+ for (ExtendedOutgoingMessageWrapper extendedOutgoingMessageWrapper :
+ extendedOutgoingMessageWrappers) {
+ messageIntegrityViolationBuilder
+ .addMessage((ExtendedOutgoingMessage) extendedOutgoingMessageWrapper
+ .buildProtoObject());
+ }
+ return messageIntegrityViolationBuilder.build();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void loadFromProto(GeneratedMessage generatedMessage)
+ throws ClassNotFoundException, IOException, InstantiationException,
+ IllegalAccessException {
+ MessageIntegrityViolation msgIntegrityViolation =
+ (MessageIntegrityViolation) generatedMessage;
+ Class<I> vertexIdClass = (Class<I>) castClassToUpperBound(
+ Class.forName(msgIntegrityViolation.getVertexIdClass()),
+ WritableComparable.class);
+
+ Class<M2> outgoingMessageClazz = (Class<M2>) castClassToUpperBound(
+ Class.forName(msgIntegrityViolation.getOutgoingMessageClass()),
+ Writable.class);
+
+ initialize(vertexIdClass, outgoingMessageClazz);
+ setSuperstepNo(msgIntegrityViolation.getSuperstepNo());
+
+ for (ExtendedOutgoingMessage extendOutgoingMessage : msgIntegrityViolation
+ .getMessageList()) {
+ ExtendedOutgoingMessageWrapper extendedOutgoingMessageWrapper = new
+ ExtendedOutgoingMessageWrapper();
+ extendedOutgoingMessageWrapper.loadFromProto(extendOutgoingMessage);
+ extendedOutgoingMessageWrappers.add(extendedOutgoingMessageWrapper);
+ }
+ }
+
+ @Override
+ public GeneratedMessage parseProtoFromInputStream(InputStream inputStream)
+ throws IOException {
+ return MessageIntegrityViolation.parseFrom(inputStream);
+ }
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/package-info.java
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/package-info.java b/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/package-info.java
new file mode 100644
index 0000000..b5a9125
--- /dev/null
+++ b/giraph-debugger/src/main/java/org/apache/giraph/debugger/utils/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Utility classes for Giraph debugger, Graft.
+ */
+package org.apache.giraph.debugger.utils;
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/protobuf/giraph_aggregator.proto
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/protobuf/giraph_aggregator.proto b/giraph-debugger/src/main/protobuf/giraph_aggregator.proto
new file mode 100644
index 0000000..7539865
--- /dev/null
+++ b/giraph-debugger/src/main/protobuf/giraph_aggregator.proto
@@ -0,0 +1,12 @@
+package org.apache.giraph.debugger;
+
+message Aggregator {
+ required string aggregatorClass = 1;
+ required AggregatedValue aggregatedValue = 2;
+}
+
+message AggregatedValue {
+ required string writableClass = 1;
+ required string key = 2;
+ required bytes value = 3;
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/protobuf/integrity.proto
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/protobuf/integrity.proto b/giraph-debugger/src/main/protobuf/integrity.proto
new file mode 100644
index 0000000..8d05b64
--- /dev/null
+++ b/giraph-debugger/src/main/protobuf/integrity.proto
@@ -0,0 +1,18 @@
+package org.apache.giraph.debugger;
+
+// Stores a list of messages that are violating an
+// integrity constraint designed by the user.
+message MessageIntegrityViolation {
+ required string vertexIdClass = 1;
+ required string outgoingMessageClass = 2;
+ required int64 superstepNo = 3;
+ // Called Extended because in scenario.proto there is another
+ // message called OutgoingMessage, which does not have the srcId.
+ repeated ExtendedOutgoingMessage message = 4;
+
+ message ExtendedOutgoingMessage {
+ required bytes srcId = 1;
+ required bytes destinationId = 2;
+ required bytes msgData = 3;
+ }
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/protobuf/scenario.proto
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/protobuf/scenario.proto b/giraph-debugger/src/main/protobuf/scenario.proto
new file mode 100644
index 0000000..42a47a3d
--- /dev/null
+++ b/giraph-debugger/src/main/protobuf/scenario.proto
@@ -0,0 +1,81 @@
+package org.apache.giraph.debugger;
+
+import "giraph_aggregator.proto";
+
+// GiraphVertexScenario captures necessary information to replicate
+// the environment under which user's Computation.compute() function was
+// called on a particular vertex and superstep.
+message GiraphVertexScenario {
+ required VertexScenarioClasses vertexScenarioClasses = 1;
+ required VertexContext context = 2;
+ optional Exception exception = 3;
+
+ // VertexScenarioClasses contains the names of the user's Computation,
+ // Vertex, Vertex ID, Vertex Value, Edge Value, Incoming Message,
+ // and Outgoing Message.
+ message VertexScenarioClasses {
+ // The class under test. Must implement org.apache.giraph.graph.Computation interface.
+ required string classUnderTest = 1;
+ // The vertex ID class (usually denoted as I). Must implement org.apache.hadoop.io.WritableComparable.
+ required string vertexIdClass = 2;
+ // The vertex value class (usually denoted as V). Must implement org.apache.hadoop.io.Writable.
+ required string vertexValueClass = 3;
+ // The edge value class (usually denoted as E). Must implement org.apache.hadoop.io.Writable.
+ required string edgeValueClass = 4;
+ // The incoming message class (usually denoted as M1). Must implement org.apache.hadoop.io.Writable.
+ required string incomingMessageClass = 5;
+ // The outgoing message class (usually denoted as M2). Must implement org.apache.hadoop.io.Writable.
+ required string outgoingMessageClass = 6;
+ }
+
+ // VertexContext encapsulates a particular pair of inputs and outputs of Computation.compute().
+ message VertexContext {
+ required CommonVertexMasterContext commonContext = 1;
+ required bytes vertexId = 2;
+ required bytes vertexValueBefore = 3;
+ optional bytes vertexValueAfter = 4;
+ // TODO: We might have to break neighbor also to
+ // neighborsBefore and neighborsAfter.
+ repeated Neighbor neighbor = 5;
+ repeated bytes inMessage = 6;
+ repeated OutgoingMessage outMessage = 7;
+
+ // Messages sent by the current vertex.
+ message OutgoingMessage {
+ required bytes destinationId = 1;
+ required bytes msgData = 2;
+ }
+
+ // The outgoing neighbors of the current vertex.
+ message Neighbor {
+ required bytes neighborId = 1;
+ optional bytes edgeValue = 2;
+ }
+ }
+}
+
+
+// GiraphMasterScenario captures the necessary information to
+// replicate the environment under which user's Master.compute()
+// function was called.
+message GiraphMasterScenario {
+ required string masterClassUnderTest = 1;
+ required CommonVertexMasterContext commonContext = 2;
+ optional Exception exception = 3;
+}
+
+// Information related to the exception thrown.
+message Exception {
+ required string message = 1;
+ required string stackTrace = 2;
+}
+
+// Contains common fiels between GiraphVertexScenario.VertexContext
+// and GiraphMasterScenario.
+message CommonVertexMasterContext {
+ required bytes conf = 1;
+ required int64 superstepNo = 2;
+ required int64 totalNumVertices = 3;
+ required int64 totalNumEdges = 4;
+ repeated AggregatedValue previousAggregatedValue = 5;
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/app.css
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/app.css b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/app.css
new file mode 100644
index 0000000..cbaf5ad
--- /dev/null
+++ b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/app.css
@@ -0,0 +1,254 @@
+/*
+ * Style for debugger.
+ */
+
+/* NOTE: This is required because Chrome has a bug that triggers the mouse enter
+ * for element at position 0,0 if a select option is selected anywhere on the
+ * document. Read more here : https://code.google.com/p/chromium/issues/detail?id=112455
+ */
+html {
+ margin:1px;
+}
+
+body {
+ background-color:#F5F5F5;
+}
+
+h1 {
+ margin-top: 10px;
+}
+
+svg {
+ background-color: #FFF;
+ cursor: default;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+svg:not(.active):not(.ctrl) {
+ cursor: crosshair;
+}
+
+path.link {
+ fill: none;
+ stroke: #000;
+ stroke-width: 4px;
+ cursor: default;
+}
+
+svg:not(.active):not(.ctrl) path.link {
+ cursor: pointer;
+}
+
+path.link.selected {
+ stroke-dasharray: 10,2;
+}
+
+path.link.dragline {
+ pointer-events: none;
+}
+
+path.link.hidden {
+ stroke-width: 0;
+}
+
+circle.node {
+ stroke-width: 1.5px;
+ cursor: pointer;
+}
+
+circle.node.reflexive {
+ stroke: #000 !important;
+ stroke-width: 2.5px;
+}
+
+circle.node.selected {
+ stroke: #000 !important;
+ stroke-width: 3.5px;
+}
+
+text {
+ font: 12px sans-serif;
+ pointer-events: none;
+}
+
+text.tid {
+ text-anchor: middle;
+ font-weight: bold;
+}
+
+.editor {
+ width:100%;
+ height:100%;
+}
+
+#editor-container {
+ overflow: hidden;
+ border-bottom: solid black 1px;
+ border-top: solid black 1px;
+ margin-bottom:20px;
+}
+
+.ui-dialog {
+ moz-box-shadow: 0 0 90px 5px #000;
+ -webkit-box-shadow: 0 0 40px 0px #000;
+}
+
+.ui-widget {
+ font-size:12px;
+}
+
+.ui-widget-overlay {
+ background:#111;
+ opacity:0.6;
+}
+
+.ui-dialog-titlebar {
+ font-family:Arial;
+ font-size:14px;
+}
+
+.ui-widget-header {
+ background:#eee;
+ border:none;
+}
+
+#footer {
+ background-color:#3C0C0C;
+ height:50px;
+ padding:10px;
+ margin-top:20px;
+ color:white;
+}
+
+#attr-view {
+ overflow:hidden;
+}
+
+#attr-view-container {
+ overflow:auto;
+ max-height:500px;
+}
+
+#attr-view th, td {
+ padding:8px;
+ text-align:center;
+ vertical-align:middle;
+}
+
+#node-attr .form-horizontal .control-label {
+ text-align: left;
+}
+
+#node-attr {
+ overflow: hidden;
+}
+
+#instructions {
+ margin-top: 20px;
+}
+
+#controls h1,
+#controls h2,
+#controls h3,
+#controls h4 {
+ display: inline-block;
+ vertical-align: middle;
+ margin: auto;
+ margin: 0px 10px 0px 10px;
+}
+
+.editor-controls * {
+ margin-right: 8px;
+}
+
+#controls-container {
+ float:right;
+}
+
+#controls-container .form-inline {
+ clear: both;
+ float:right;
+ margin-bottom:10px;
+}
+
+.valpanel {
+ float: left;
+ height: 100%;
+ border-top: black 1px solid;
+ border-bottom: black 1px solid;
+ border-right: black 2px solid;
+ box-shadow: rgb(136, 136, 136) 5px 0px 15px;
+ background-color:white;
+}
+
+/* Applies to main controls of debugger window
+ * like graph editor and validation panel
+ */
+.debug-control {
+ height: 100%;
+}
+
+
+#debugger-container {
+ height: 504px;
+ margin-bottom: 20px;
+}
+
+#node-attr-flow td {
+ vertical-align: middle;
+}
+
+.editor-aggregators-heading {
+ font: 16px sans-serif;
+}
+
+/* Graph Editor tabular view styles */
+.editor-tablet {
+ width:100%;
+ height:100%;
+ padding: 10px;
+ overflow: auto;
+}
+
+td.tablet-details-control {
+ background: url('../img/details_open.png') no-repeat center center;
+ cursor: pointer;
+}
+tr.shown td.tablet-details-control {
+ background: url('../img/details_close.png') no-repeat center center;
+}
+
+.tablet-data-container {
+ padding: 5px;
+ background-color : #FFDFDF;
+}
+
+.tablet-data-container table {
+ margin:0px;
+}
+
+#code-controls {
+ padding: 5px 0px 5px 0px;
+}
+
+/* Bootstrap overrides */
+.form-control {
+ margin-left:5px;
+}
+
+.nav-tabs li {
+ cursor: pointer;
+}
+
+.slider {
+ width: 100%;
+ margin-left: 15px;
+}
+
+.table th {
+ text-align: center;
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/slider/slider.css
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/slider/slider.css b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/slider/slider.css
new file mode 100644
index 0000000..fefdd58
--- /dev/null
+++ b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/slider/slider.css
@@ -0,0 +1,140 @@
+/*!
+ * Slider for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * NOTE: We are using this code to show a slider for changing link distance
+ * between nodes, among other things.
+ */
+.slider {
+ display: inline-block;
+ vertical-align: middle;
+ position: relative;
+}
+.slider.slider-horizontal {
+ width: 210px;
+ height: 20px;
+}
+.slider.slider-horizontal .slider-track {
+ height: 10px;
+ width: 100%;
+ margin-top: -5px;
+ top: 50%;
+ left: 0;
+}
+.slider.slider-horizontal .slider-selection {
+ height: 100%;
+ top: 0;
+ bottom: 0;
+}
+.slider.slider-horizontal .slider-handle {
+ margin-left: -10px;
+ margin-top: -5px;
+}
+.slider.slider-horizontal .slider-handle.triangle {
+ border-width: 0 10px 10px 10px;
+ width: 0;
+ height: 0;
+ border-bottom-color: #0480be;
+ margin-top: 0;
+}
+.slider.slider-vertical {
+ height: 210px;
+ width: 20px;
+}
+.slider.slider-vertical .slider-track {
+ width: 10px;
+ height: 100%;
+ margin-left: -5px;
+ left: 50%;
+ top: 0;
+}
+.slider.slider-vertical .slider-selection {
+ width: 100%;
+ left: 0;
+ top: 0;
+ bottom: 0;
+}
+.slider.slider-vertical .slider-handle {
+ margin-left: -5px;
+ margin-top: -10px;
+}
+.slider.slider-vertical .slider-handle.triangle {
+ border-width: 10px 0 10px 10px;
+ width: 1px;
+ height: 1px;
+ border-left-color: #0480be;
+ margin-left: 0;
+}
+.slider input {
+ display: none;
+}
+.slider .tooltip-inner {
+ white-space: nowrap;
+}
+.slider-track {
+ position: absolute;
+ cursor: pointer;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+ background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.slider-selection {
+ position: absolute;
+ background-color: #f7f7f7;
+ background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
+ background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
+ background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
+ background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.slider-handle {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ background-color: #0e90d2;
+ background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+ background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+ background-image: -o-linear-gradient(top, #149bdf, #0480be);
+ background-image: linear-gradient(to bottom, #149bdf, #0480be);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+ opacity: 0.8;
+ border: 0px solid transparent;
+}
+.slider-handle.round {
+ -webkit-border-radius: 20px;
+ -moz-border-radius: 20px;
+ border-radius: 20px;
+}
+.slider-handle.triangle {
+ background: transparent none;
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/valpanel.css
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/valpanel.css b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/valpanel.css
new file mode 100644
index 0000000..1bd7da8
--- /dev/null
+++ b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/css/valpanel.css
@@ -0,0 +1,72 @@
+.valpanel-icons-container {
+ height: 30px;
+ width: 100%;
+ margin: 5px 10px 5px 0px;
+}
+
+.valpanel-icons-container * {
+ margin: 5px;
+ float:right;
+}
+
+.valpanel-btn-container {
+ margin : 2px 10px 10px 5px;
+ float: left;
+}
+
+.valpanel-btn-container button {
+ border-radius: 0;
+ margin: 5px 10px 5px 5px;
+ text-align: left;
+ width: 100%;
+ max-width: 140px; /* Must match .preview, .expand width */
+}
+
+.valpanel-btn-container.compact {
+ width : 40px;
+}
+
+.valpanel-btn-container button:disabled {
+ background-color: #ebebeb;
+ color:black;
+ opacity: 0.4;
+ border: none;
+}
+
+.valpanel-btn-container button.btn-success {
+ pointer-events:none;
+}
+
+.valpanel-btn-container.preview, .expand {
+ width : 140px;
+}
+
+.valpanel-btn-close {
+ cursor:pointer;
+}
+
+.valpanel-content-container {
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding: 0px 10px 10px 5px;
+ height: 85%;
+ margin-left: 10px;
+}
+
+.valpanel-preloader {
+ background-image: url('../img/preloader.gif');
+ position: absolute;
+ top: 35%;
+ left: 50%;
+ margin: 0 auto;
+ width: 128px;
+ height: 128px;
+}
+
+.valpanel-content-container table th {
+ text-align: center;
+}
+
+.valpanel-content-container table {
+ table-layout: fixed;
+}
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/index.html
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/index.html b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/index.html
new file mode 100644
index 0000000..b58ec98
--- /dev/null
+++ b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/index.html
@@ -0,0 +1,319 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Graft</title>
+ <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
+ <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css" />
+ <link rel="stylesheet" href="http://cdn.datatables.net/1.10.0/css/jquery.dataTables.css" />
+ <link rel="stylesheet" href="http://yandex.st/highlightjs/8.0/styles/default.min.css">
+ <link rel="stylesheet" href="css/valpanel.css">
+ <link rel="stylesheet" href="css/slider/slider.css">
+ <link rel="stylesheet" href="css/app.css">
+ <!--TODO(vikesh) : * Load all JS files asychronously in a single script - requirejs.
+ * Download external JS files during mvn compile.
+ * Remove http from src to automatically load based on the protocol used to launch this file.
+ -->
+ <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
+ <script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
+ <script src="http://d3js.org/d3.v3.min.js"></script>
+ <script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+ <script src="http://cdn.datatables.net/1.10.0/js/jquery.dataTables.js"></script>
+ <script src="http://yandex.st/highlightjs/8.0/highlight.min.js"></script>
+ <script src="http://cdn.jsdelivr.net/noty/2.2.2/packaged/jquery.noty.packaged.min.js" />
+ <script src="js/slider/bootstrap-slider.js"></script>
+ <script src="js/slider/bootstrap-slider.js"></script>
+ <script src="js/utils.js"></script>
+ <script src="js/utils.sampleGraphs.js"></script>
+ <script src="js/editor.core.js"></script>
+ <script src="js/editor.utils.js"></script>
+ <script src="js/valpanel.js"></script>
+ <script src="js/debugger.js"></script>
+
+ <script type="text/javascript">
+ var giraphDebugger;
+ var currentSuperstep = -1;
+
+ $(document).ready(function(){
+ var currentCodeUrl = null;
+ // Initialize highlighting.
+ hljs.initHighlightingOnLoad();
+
+ // Performs syntax highlighting for dynamically loaded content in pre code.
+ function highlightAll() {
+ $('pre code').each(function(i, e) {hljs.highlightBlock(e)});
+ }
+
+ /*
+ * Sets the currentCode string and the code-container div.
+ * @param {object} data - data.code is the code content and data.url is the
+ * downloadable link of the code (text/plain with content-disposition).
+ * NOTE: All code setting should be performed through this method to ensure that
+ * currentCodeUrl is set properly.
+ */
+ function setCode(data) {
+ currentCodeUrl = data ? data.url : null;
+ var code = data ? data.code : null;
+ $('#code-container').html(code);
+ $('#btn-code-download').prop('disabled', code === null);
+ highlightAll();
+ }
+
+ function handleTestGraphSuccess(response) {
+ setCode(response);
+ noty({text : 'Fetched the test graph successfully.', type : 'success', timeout: 1000});
+ }
+
+ function handleTestGraphFail(responseText) {
+ setCode();
+ noty({text : 'Failed to fetch the test graph. Message from server : ' + responseText, type : 'error'});
+ }
+
+ $("#node-attr-id").keyup(function(event){
+ if(event.keyCode===13){
+ $("#btn-node-attr-save").click();
+ }
+ });
+
+ $("#btn-adj-list-get").click(function() {
+ var adjList = Utils.getAdjListStr(editor.getAdjList());
+ Utils.downloadFile(adjList, 'adjList.txt');
+ });
+
+ // Generate Test Graph - Fetch the test graph and display the code.
+ $('#btn-gen-test-graph').click(function() {
+ Utils.fetchTestGraph(giraphDebugger.debuggerServerRoot, Utils.getAdjListStrForTestGraph(giraphDebugger.editor.getAdjList()))
+ .done(function(response) {
+ handleTestGraphSuccess(response);
+ })
+ .fail(function(responseText) {
+ handleTestGraphFail(responseText);
+ });
+ });
+
+ giraphDebugger = new GiraphDebugger({
+ debuggerContainer : '#debugger-container',
+ nodeAttrContainer : '#node-attr-container',
+ superstepControlsContainer : '#controls-container',
+ });
+
+ // Attach events to catpure scenario - vertex.
+ giraphDebugger.onCaptureVertex(function(response) {
+ setCode(response);
+ noty({text : 'Fetched the vertex scenario successfully.', type : 'success', timeout: 1000});
+ },
+ function(response) {
+ setCode();
+ noty({text : 'Failed to fetch the vertex scenario. Message from server : ' + response, type : 'error'});
+ });
+
+ // Attach events to catpure scenario - master.
+ giraphDebugger.onCaptureMaster(function(response) {
+ setCode(response);
+ noty({text : 'Fetched the master scenario successfully.', type : 'success', timeout: 1000});
+ },
+ function(response) {
+ setCode();
+ noty({text : 'Failed to fetch the master scenario. Message from server : ' + response, type : 'error'});
+ });
+
+ // Attach events to generate test graph.
+ giraphDebugger.onGenerateTestGraph(handleTestGraphSuccess, handleTestGraphFail);
+
+ var editor = giraphDebugger.editor;
+ // Undirected behaviour.
+ $(".graph-type").change(function(event) {
+ editor.undirected = event.target.value === "undirected" ? true : false;
+ editor.restartGraph();
+ });
+
+ // Btn attr-view
+ $("#btn-attr-view").click(function() {
+ $("#attr-view-content").empty();
+ $("#attr-view-content").append("<tr><th>Node ID</th><th>Attributes</th></tr><tr>");
+
+ // Attribute view
+ for (var i = 0; i < editor.nodes.length; i++) {
+ $("#attr-view-content").append("<tr></tr>");
+ }
+
+ var rows = d3.select("#attr-view-content").selectAll("tr")
+ .filter(function(d,i) { return i!=0; })
+ .data(editor.nodes);
+
+ rows.append("td").text(function(d){ return d.id; });
+ rows.append("td").append("textarea")
+ .attr("class", "form-control")
+ .attr("rows","1")
+ .text(function(d){ return d.attrs.join(); });
+
+ // show the dialog
+ $("#attr-view").dialog({
+ modal:true,
+ title:"Node Attributes View",
+ closeText:"close",
+ maxHeight:600,
+ closeOnEscape:true,
+ hide : {effect : "fade", duration:100}
+ });
+
+ $(".ui-widget-overlay").click(function(){ $("#attr-view").dialog("close"); });
+ });
+
+ $("#btn-attr-view-save").unbind("click");
+ $("#btn-attr-view-save").click(function(){
+ var rows = $("#attr-view-content textarea");
+
+ for(var i=0;i<rows.length;i++) {
+ if (rows[i]!="") {
+ editor.nodes[i].attrs = rows[i].value.split(",");
+ }
+ }
+ editor.restartGraph();
+ $("#attr-view").dialog("close");
+ });
+
+ $("#btn-attr-view-cancel").unbind("click");
+ $("#btn-attr-view-cancel").click(function() {
+ $("#attr-view").dialog("close");
+ });
+
+ // Slider for linkDistance
+ $('#slider-link-distance').slider({
+ min : editor.linkDistance,
+ value : editor.linkDistance,
+ max : 500
+ })
+ .on('slideStop', function(ev) {
+ // resize the linkDistance of editor
+ editor.linkDistance = ev.value;
+ editor.restartGraph();
+ });
+
+ // Code select handler.
+ $('#btn-code-select').click(function(event) {
+ selectText('code-container');
+ });
+
+ // Code download handler.
+ $('#btn-code-download').click(function(event) {
+ location.href = currentCodeUrl;
+ });
+ });
+ </script>
+ </head>
+
+ <body>
+ <div id="attr-view" style="display:none">
+ <div id="attr-view-container">
+ <table>
+ <tbody id="attr-view-content">
+ </tbody>
+ </table>
+ </div>
+ <form role="form">
+ <div class="form-group">
+ <button type="button" class="btn btn-primary btn-sm editable-submit" id="btn-attr-view-save"><i class="glyphicon glyphicon-ok"></i></button>
+ <button type="button" class="btn btn-default btn-sm editable-cancel" id="btn-attr-view-cancel"><i class="glyphicon glyphicon-remove"></i></button>
+ </div>
+ </form>
+ </div>
+
+ <div class="container bs-docs-container">
+ <h1>Graft <small>GUI</small></h1>
+ </div>
+
+ <div id="debugger-container">
+ </div>
+
+ <!--Container begins-->
+ <div class="container bs-docs-container">
+ <div class="row"> <!--Row1 begins-->
+ <!--Column for left side controls-->
+ <div class="col-md-4">
+ <!-- This is a row for the directed/undirected controls-->
+ <div class="row">
+ <!--Internal col begins-->
+ <div class="col-md-12">
+ <!--Form begins-->
+ <form role="form" class="form-inline editor-controls">
+ <div class="form-group">
+ <label>
+ <input type="radio" name="options" id="options_directed" class="graph-type form-control" value="directed" checked /> Directed
+ </label>
+ </div>
+ <div class="form-group">
+ <label>
+ <input type="radio" name="options" id="options_undirected" class="graph-type form-control" value="undirected" /> Undirected
+ </label>
+ </div>
+ <!--<button type="button" class="btn btn-primary btn-danger form-control btn-sm" id="btn-attr-view">Attribute view</button>-->
+ </form> <!--Form ends-->
+ </div><!--Internal column ends-->
+ </div><!--Directed/Undirected Row ends-->
+ <!-- This is a row for the download buttons-->
+ <div class="row">
+ <!--Internal column begins-->
+ <div class="col-md-12">
+ <button type="button" class="btn btn-primary" id="btn-adj-list-get">Download Adjacency List</button>
+ <button type="button" class="btn btn-danger" id="btn-gen-test-graph">Generate Test Graph</button>
+ </div><!--Internal column ends-->
+ </div>
+ </div>
+ <div class="col-md-8">
+ <!--Debugger Superstep controls container begin-->
+ <div id="controls-container">
+ </div>
+ <!--Superstep controls end-->
+ </div>
+ </div> <!--Row1 ends-->
+
+ <!-- Row2 begins-->
+ <div class="row" style="margin-top:20px;">
+ <div class="col-md-6">
+ <!-- Slider for linkDistance-->
+ <label>Link Distance : </label> <div class="slider" id="slider-link-distance"></div>
+ </div>
+ </div> <!--Row2 ends-->
+
+ <hr />
+ <div style="margin-top:20px;">
+ <div id="code-controls">
+ <h3>Test Code</h3>
+ <button type="button" class="btn btn-primary btn-sm" id="btn-code-select">Select All</button>
+ <button type="button" class="btn btn-danger btn-sm" id="btn-code-download" disabled>Download</button>
+ </div>
+ <pre>
+ <code id="code-container"></code>
+ </pre>
+ </div>
+
+ <!-- Instructions -->
+ <div id="instructions">
+ <hr style="border-top:1px solid black;" />
+ <p class="lead">
+ <mark>Click</mark> in the open space to <strong><mark>add a node</mark></strong>, drag from one node to another to <strong>add an edge</strong>. <br>
+ <mark>Shift-drag</mark> a node to <strong><mark>move</mark></strong> the graph layout. <br>
+ <mark>Click</mark> a node or an edge to <strong><mark>select</mark></strong> it.<br>
+ <mark>Double-click</mark> a node to open the <strong><mark>node editor</mark></strong>.<br>
+ <mark>Click</mark> on the Attributes View to open an editable list of <strong><mark>node attributes</mark></strong>.<br>
+ <mark>Shift-Click</mark> an edge to edit <strong>edge value</strong>.
+ </p>
+ <p class="lead">
+ When a node is selected: <strong>R</strong> toggles reflexivity, <strong>Delete</strong> removes the node. <br>
+ When an edge is selected: <strong>L</strong>(eft), <strong>R</strong>(ight), <strong>B</strong>(oth) change direction, <strong>Delete</strong> removes the edge.</p>
+ </div> <!-- Instructions end-->
+ </div><!--Container ends-->
+
+ <!--Footer-->
+ <div id="footer">
+ <div class="container">
+ <p class="muted credit">
+ Based on <a href="http://bl.ocks.org/rkirsling/5001347" target="_blank">Directed Graph Editor</a> by rkirsling.
+ <span style="float:right">Code on <a href="https://github.com/vikeshkhanna/graph-editor" target="_blank">GitHub</a></span>
+ </p>
+ </div>
+ </div><!--Footer-->
+ </body>
+</html>
http://git-wip-us.apache.org/repos/asf/giraph/blob/8675c84a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/debugger.js
----------------------------------------------------------------------
diff --git a/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/debugger.js b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/debugger.js
new file mode 100644
index 0000000..d67f7fc
--- /dev/null
+++ b/giraph-debugger/src/main/resources/org/apache/giraph/debugger/gui/js/debugger.js
@@ -0,0 +1,870 @@
+/*
+ * Abstracts the debugger controls.
+ */
+
+/*
+ * Debugger is a class that encapsulates the graph editor and debugging controls.
+ * @param {debuggerContainer} options - Initialize debugger with these options.
+ * @param options.debuggerContainer - Selector for the container of the main debugger area. (Editor & Valpanel)
+ * @param options.superstepControlsContainer - Selector for the container of the superstep controls.
+ * @constructor
+ */
+function GiraphDebugger(options) {
+ // Initialize (and reset) variables for jobs.
+ this.resetVars();
+ this.debuggerServerRoot = (location.protocol === 'file:' ? 'http://localhost:8000' : '');
+ this.mode = GiraphDebugger.ModeEnum.EDIT;
+ this.init(options);
+ return this;
+}
+
+/*
+ * Denotes the mode.
+ * Debug Mode - Editor is in readonly, walking through the supersteps of a giraph job.
+ * Edit Mode - Create new graphs in the editor.
+ */
+GiraphDebugger.ModeEnum = {
+ DEBUG : 'debug',
+ EDIT : 'edit'
+}
+
+/*
+ * Initializes the graph editor, node attr modal DOM elements.
+ */
+GiraphDebugger.prototype.init = function(options) {
+ this.editorContainerId = 'editor-container';
+ this.valpanelId = 'valpanel-container';
+
+ // Create divs for valpanel and editor.
+ var valpanelContainer = $('<div />')
+ .attr('class', 'valpanel debug-control')
+ .attr('id', this.valpanelId)
+ .appendTo(options.debuggerContainer);
+
+ var editorContainer = $('<div />')
+ .attr('id', this.editorContainerId)
+ .attr('class', 'debug-control')
+ .appendTo(options.debuggerContainer);
+
+ // Instantiate the editor object.
+ this.editor = new Editor({
+ 'container' : '#' + this.editorContainerId,
+ onOpenNode : this.openNodeAttrs.bind(this),
+ onOpenEdge : this.openEdgeVals.bind(this)
+ });
+
+
+ // Add toggle view event handler.
+ this.editor.onToggleView((function(editorView) {
+ if (editorView === Editor.ViewEnum.TABLET) {
+ this.btnToggleViewSpan.html(' Graph View');
+ } else {
+ this.btnToggleViewSpan.html(' Table View');
+ }
+ }).bind(this));
+
+ // Instantiate the valpanel object.
+ this.valpanel = new ValidationPanel({
+ 'container' : '#' + this.valpanelId,
+ 'editor' : this.editor,
+ 'debuggerServerRoot' : this.debuggerServerRoot,
+ 'resizeCallback' : (function() {
+ this.editor.restartGraph();
+ }).bind(this)
+ });
+
+ this.initIds();
+ // Must initialize these members as they are used by subsequent methods.
+ this.superstepControlsContainer = options.superstepControlsContainer;
+ this.initElements(options);
+}
+
+/*
+ * Deferred callbacks for capture scenario
+ */
+GiraphDebugger.prototype.onCaptureVertex = function(done, fail) {
+ this.onCaptureVertex.done = this.valpanel.onCaptureVertex.done = done;
+ this.onCaptureVertex.fail = this.valpanel.onCaptureVertex.fail = fail;
+}
+
+/*
+ * Deferred callbacks for generate test graph.
+ */
+GiraphDebugger.prototype.onGenerateTestGraph = function(done, fail) {
+ this.onGenerateTestGraph.done = done;
+ this.onGenerateTestGraph.fail = fail;
+}
+
+/*
+ * Deferred callbacks for capture scenario
+ */
+GiraphDebugger.prototype.onCaptureMaster = function(done, fail) {
+ this.onCaptureMaster.done = done;
+ this.onCaptureMaster.fail = fail;
+}
+
+/*
+ * Reset job-related vars to the initial state.
+ */
+GiraphDebugger.prototype.resetVars = function() {
+ // Node that is currently double clicked.
+ this.selectedNodeId = null;
+ // Initialize current superstep to -2 (Not in debug mode)
+ this.currentSuperstepNumber = -2;
+ // ID of the job currently being debugged.
+ this.currentJobId = null;
+ // Minimum value of superstepNumber
+ this.minSuperstepNumber = -1;
+ // Maximum value of superstepNumber - Depends on the job.
+ // TODO(vikesh): Fetch from debugger server in some AJAX call. Replace constant below.
+ this.maxSuperstepNumber = 15;
+ // Caches the scenarios to show correct information when going backwards.
+ // Cumulatively builds the state of the graph starting from the first superstep by merging
+ // scenarios on top of each other.
+ this.stateCache = {"-1": {}};
+}
+
+/*
+ * Initialize DOM element Id constants
+ */
+GiraphDebugger.prototype.initIds = function() {
+ this.ids = {
+ // IDs of elements in node attribute modal.
+ _nodeAttrModal : 'node-attr',
+ _nodeAttrId : 'node-attr-id',
+ _nodeAttrAttrs : 'node-attr-attrs',
+ _nodeAttrGroupError : 'node-attr-group-error',
+ _nodeAttrError : 'node-attr-error',
+ _btnNodeAttrSave : 'btn-node-attr-save',
+ _btnNodeAttrCancel : 'btn-node-attr-cancel',
+ // IDs of elements in edge values modal
+ _edgeValModal : 'edge-vals',
+ // IDs of elements in Edit Mode controls.
+ _selectSampleGraphs : 'sel-sample-graphs',
+ // IDs of elements in Superstep controls.
+ _btnPrevStep : 'btn-prev-step',
+ _btnNextStep : 'btn-next-step',
+ _btnEditMode : 'btn-edit-mode',
+ _btnFetchJob : 'btn-fetch-job',
+ _btnCaptureVertexScenario : 'btn-capture-scenario',
+ _btnCaptureMasterScenario : 'btn-capture-scenario',
+ _btnToggleView : 'btn-toggle-view'
+ };
+}
+
+/*
+ * Initializes the input elements inside the node attribute modal form.
+ * @param nodeAttrForm - Form DOM object.
+ */
+GiraphDebugger.prototype.initInputElements = function(nodeAttrForm) {
+ // Create form group for ID label and text box.
+ var formGroup1 = $('<div />')
+ .addClass('form-group')
+ .appendTo(nodeAttrForm);
+
+ // Create node ID label.
+ var nodeAttrIdLabel = $('<label />')
+ .attr('for', this.ids._nodeAttrId)
+ .addClass('control-label col-sm-4')
+ .html('Node ID:')
+ .appendTo(formGroup1);
+
+ // Create the ID input textbox.
+ // Add it to a column div, which in turn is added to formgroup2.
+ this.nodeAttrIdInput = $('<input>')
+ .attr('type', 'text')
+ .attr('id', this.ids._nodeAttrId)
+ .addClass('form-control')
+ .appendTo($('<div>').addClass('col-sm-8').appendTo(formGroup1));
+
+ // Create the form group for attributes label and input.
+ var formGroup2 = $('<div />')
+ .addClass('form-group')
+ .appendTo(nodeAttrForm);
+
+ var nodeAttrAttributeLabel = $('<label />')
+ .attr('for', this.ids._nodeAttrAttrs)
+ .addClass('control-label col-sm-4')
+ .html('Attributes: ')
+ .appendTo(formGroup2);
+
+ // Create the Attributes input textbox.
+ // Add it to a column div, which in turn is added to formgroup2.
+ this.nodeAttrAttrsInput = $('<input>')
+ .attr('type', 'text')
+ .attr('id', this._nodeAttrAttrs)
+ .addClass('form-control')
+ .appendTo($('<div>').addClass('col-sm-8').appendTo(formGroup2));
+
+ // Create form group for buttons.
+ var formGroupButtons = $('<div />')
+ .addClass('form-group')
+ .appendTo(nodeAttrForm);
+
+ var buttonsContainer = $('<div />')
+ .addClass('col-sm-12')
+ .appendTo(formGroupButtons);
+
+ this.btnNodeAttrSubmit = Utils.getBtnSubmitSm()
+ .attr('type', 'submit')
+ .attr('id', this.ids._btnNodeAttrSave)
+ .appendTo(buttonsContainer);
+
+ this.btnNodeAttrCancel = Utils.getBtnCancelSm()
+ .attr('id', this.ids._btnNodeAttrCancel)
+ .appendTo(buttonsContainer);
+
+ this.nodeAttrGroupError = $('<div />')
+ .addClass('form-group has-error')
+ .attr('id', this.ids._nodeAttrGroupError)
+ .hide()
+ .appendTo(nodeAttrForm);
+
+ var errorLabel = $('<label />')
+ .addClass('control-label')
+ .attr('id', this.ids._nodeAttrError)
+ .html('Node ID must be unique')
+ .appendTo($('<div class="col-sm-12"></div>').appendTo(this.nodeAttrGroupError));
+}
+
+/*
+ * Initializes the message container and all elements within it.
+ * Returns the message container DOM object.
+ * @param nodeAttrForm - Form DOM object.
+ */
+GiraphDebugger.prototype.initMessageElements = function(nodeAttrForm) {
+ var messageContainer = $('<div />')
+ .appendTo(nodeAttrForm)
+
+ var messageTabs = $('<ul />')
+ .addClass('nav nav-tabs')
+ .html('<li class="active"><a id="node-attr-received" class="nav-msg" href="#!">Received</a></li>' +
+ '<li><a id="node-attr-sent" class="nav-msg" href="#!">Sent</a></li>' +
+ '<li><a id="node-attr-edgevals" class="nav-msg" href="#!">Edge Values</a></li>')
+ .appendTo(messageContainer);
+
+ var tableContainer = $('<div />')
+ .addClass('highlight')
+ .appendTo(messageContainer);
+
+ this.flowTable = $('<table />')
+ .addClass('table')
+ .attr('id', 'node-attr-flow')
+ .appendTo(messageContainer);
+}
+
+/*
+ * Initializes Superstep controls.
+ * @param superstepControlsContainer - Selector for the superstep controls container.
+ */
+GiraphDebugger.prototype.initSuperstepControls = function(superstepControlsContainer) {
+ /*** Edit Mode controls ***/
+ // Create the div with controls visible in Edit Mode
+ this.editModeGroup = $('<div />')
+ .appendTo(superstepControlsContainer);
+
+ // Create the form that fetches the superstep data from debugger server.
+ var formFetchJob = $('<div />')
+ .attr('class', 'form-inline')
+ .appendTo(this.editModeGroup);
+
+ // Fetch job details for job id textbox.
+ this.fetchJobIdInput = $('<input>')
+ .attr('type', 'text')
+ .attr('class', 'form-control ')
+ .attr('placeholder', 'Job ID')
+ .appendTo(formFetchJob);
+
+ this.btnFetchJob = $('<button />')
+ .attr('id', this.ids._btnFetchJob)
+ .attr('type', 'button')
+ .attr('class', 'btn btn-danger form-control')
+ .html('Fetch')
+ .appendTo(formFetchJob);
+
+ // Create the control for creating sample graphs.
+ var formSampleGraphs = $('<div />')
+ .attr('class', 'form-inline')
+ .appendTo(this.editModeGroup);
+
+ this.selectSampleGraphs = $('<select />')
+ .attr('class', 'form-control')
+ .attr('id', this.ids._selectSampleGraphs)
+ .appendTo(formSampleGraphs);
+
+ // Add the graph names to the select drop down.
+ $.each(Utils.sampleGraphs, (function (key, value) {
+ $(this.selectSampleGraphs).append($('<option />').attr('value', key).html(key));
+ }).bind(this));
+
+ this.sampleGraphsInput = $('<input />')
+ .attr('class', 'form-control')
+ .attr('placeholder', '# of vertices')
+ .appendTo(formSampleGraphs);
+
+ this.btnSampleGraph = $('<button />')
+ .attr('class', 'btn btn-primary form-control')
+ .html('Generate')
+ .appendTo(formSampleGraphs);
+
+ /*** DEBUG MODE controls ***/
+ this.debugModeGroup = $('<div />')
+ .appendTo(superstepControlsContainer)
+ .hide();
+
+ // Initialize the actual controls.
+ var formControls = $('<div />')
+ .attr('id', 'controls')
+ .attr('class', 'form-inline')
+ .appendTo(this.debugModeGroup);
+
+ this.btnPrevStep = $('<button />')
+ .attr('class', 'btn btn-default bt-step form-control')
+ .attr('id', this.ids._btnPrevStep)
+ .attr('disabled', 'true')
+ .append(
+ $('<span />')
+ .attr('class', 'glyphicon glyphicon-chevron-left')
+ .html(' Previous')
+ )
+ .appendTo(formControls);
+
+ var superstepLabel = $('<h2><span id="superstep">-1</span>' +
+ '<small> Superstep</small></h2>')
+ .appendTo(formControls);
+
+ // Set this.superstepLabel to the actual label that will be updated.
+ this.superstepLabel = $('#superstep');
+
+ this.btnNextStep = $('<button />')
+ .attr('class', 'btn btn-default btn-step form-control')
+ .attr('id', this.ids._btnNextStep)
+ .append(
+ $('<span />')
+ .attr('class', 'glyphicon glyphicon-chevron-right')
+ .html(' Next')
+ )
+ .appendTo(formControls);
+
+ // Return to the edit mode - Exiting the debug mode.
+ this.btnEditMode = $('<button />')
+ .attr('class', 'btn btn-default btn-step form-control')
+ .attr('id', this.ids._btnEditMode)
+ .append(
+ $('<span />')
+ .attr('class', 'glyphicon glyphicon-pencil')
+ .html(' Edit Mode')
+ )
+ .appendTo(formControls);
+
+ // Change the text value of this span when toggling views.
+ this.btnToggleViewSpan = $('<span />')
+ .attr('class', 'glyphicon glyphicon-cog')
+ .html(' Table View');
+
+ // Toggle the editor between the table and graph view.
+ this.btnToggleView = $('<button />')
+ .attr('class', 'btn btn-default btn-step form-control')
+ .attr('id', this.ids._btnToggleView)
+ .append(this.btnToggleViewSpan)
+ .appendTo(formControls);
+
+ // Capture Scenario group
+ var captureScenarioForm = $('<div />')
+ .attr('class', 'form-inline')
+ .appendTo(this.debugModeGroup);
+
+ // Input text box to input the vertexId
+ this.captureVertexIdInput = $('<input>')
+ .attr('type', 'text')
+ .attr('class', 'form-control ')
+ .attr('placeholder', 'Vertex ID')
+ .appendTo(captureScenarioForm);
+
+ // Capture Vertex Scenario Scenario button.
+ this.btnCaptureVertexScenario = $('<button>')
+ .attr('type', 'button')
+ .attr('id', this.ids._btnCaptureVertexScenario)
+ .attr('class', 'btn btn-primary form-control')
+ .html('Capture Vertex')
+ .appendTo(captureScenarioForm);
+
+ // Capture Master
+ this.btnCaptureMasterScenario = $('<button>')
+ .attr('type', 'button')
+ .attr('id', this.ids._btnCaptureMasterScenario)
+ .attr('class', 'btn btn-danger form-control')
+ .html('Capture Master')
+ .appendTo(captureScenarioForm);
+
+ // Initialize handlers for events
+ this.initSuperstepControlEvents();
+}
+
+/*
+ * Initializes the handlers of the elements on superstep controls.
+ */
+GiraphDebugger.prototype.initSuperstepControlEvents = function() {
+ // On clicking Fetch button, send a request to the debugger server
+ // Fetch the scenario for this job for superstep -1
+ $(this.btnFetchJob).click((function(event) {
+ this.currentJobId = $(this.fetchJobIdInput).val();
+ this.currentSuperstepNumber = 0;
+ this.changeSuperstep(this.currentJobId, this.currentSuperstepNumber);
+ this.toggleMode();
+ }).bind(this));
+ // On clicking the edit mode button, hide the superstep controls and show fetch form.
+ $(this.btnEditMode).click((function(event) {
+ this.toggleMode();
+ }).bind(this));
+
+ // Handle the next and previous buttons on the superstep controls.
+ $(this.btnNextStep).click((function(event) {
+ this.currentSuperstepNumber += 1;
+ this.changeSuperstep(this.currentJobId, this.currentSuperstepNumber);
+ }).bind(this));
+
+ $(this.btnPrevStep).click((function(event) {
+ this.currentSuperstepNumber -= 1;
+ this.changeSuperstep(this.currentJobId, this.currentSuperstepNumber);
+ }).bind(this));
+
+ // Handle the capture scenario button the superstep controls.
+ $(this.btnCaptureVertexScenario).click((function(event){
+ // Get the deferred object.
+ var vertexId = $(this.captureVertexIdInput).val();
+ Utils.fetchVertexTest(this.debuggerServerRoot, this.currentJobId,
+ this.currentSuperstepNumber, vertexId, 'reg')
+ .done((function(response) {
+ this.onCaptureVertex.done(response);
+ }).bind(this))
+ .fail((function(response) {
+ this.onCaptureVertex.fail(response.responseText);
+ }).bind(this))
+ }).bind(this));
+ // Handle the master capture scenario button the superstep controls.
+ $(this.btnCaptureMasterScenario).click((function(event){
+ Utils.fetchMasterTest(this.debuggerServerRoot, this.currentJobId, this.currentSuperstepNumber)
+ .done((function(response) {
+ this.onCaptureMaster.done(response);
+ }).bind(this))
+ .fail((function(response) {
+ this.onCaptureMaster.fail(response.responseText);
+ }).bind(this))
+ }).bind(this));
+
+ // Handle the toggle view button.
+ $(this.btnToggleView).click((function(event) {
+ this.editor.toggleView();
+ }).bind(this));
+
+ // Handle the generate sample graph button.
+ $(this.btnSampleGraph).click((function(event) {
+ var numVertices = $(this.sampleGraphsInput).val();
+ var graphTypeKey = $(this.selectSampleGraphs).val();
+ this.editor.buildGraphFromSimpleAdjList(Utils.sampleGraphs[graphTypeKey](numVertices));
+
+ Utils.fetchTestGraph(this.debuggerServerRoot, Utils.getAdjListStrForTestGraph(this.editor.getAdjList()))
+ .done((function(response) {
+ this.onGenerateTestGraph.done(response);
+ }).bind(this))
+ .fail((function(response) {
+ this.onGenerateTestGraph.fail(response.responseText);
+ }).bind(this));
+ }).bind(this));
+}
+
+/*
+ * Fetches the data for this superstep, updates the superstep label, graph editor
+ * and disables/enables the prev/next buttons.
+ * @param {int} superstepNumber : Superstep to fetch the data for.
+ */
+GiraphDebugger.prototype.changeSuperstep = function(jobId, superstepNumber) {
+ console.log("Changing Superstep to : " + superstepNumber);
+ $(this.superstepLabel).html(superstepNumber);
+ // Update data of the valpanel
+ this.valpanel.setData(jobId, superstepNumber);
+
+ // Fetch the max number of supersteps again. (Online case)
+ $.ajax({
+ url : this.debuggerServerRoot + "/supersteps",
+ data : {'jobId' : this.currentJobId}
+ })
+
+ .done((function(response) {
+ this.maxSuperstepNumber = Math.max.apply(Math, response);
+ }).bind(this))
+ .fail(function(response) {
+ });
+
+ // If scenario is already cached, don't fetch again.
+ if (superstepNumber in this.stateCache) {
+ this.modifyEditorOnScenario(this.stateCache[superstepNumber]);
+ } else {
+ // Show preloader while AJAX request is in progress.
+ this.editor.showPreloader();
+ // Fetch from the debugger server.
+ $.ajax({
+ url : this.debuggerServerRoot + '/scenario',
+ dataType : 'json',
+ data: { 'jobId' : jobId, 'superstepId' : superstepNumber }
+ })
+ .retry({
+ times : 5,
+ timeout : 2000,
+ retryCallback : function(remainingTimes) {
+ // Failed intermediately. Will be retried.
+ noty({text : 'Failed to fetch job. Retrying ' + remainingTimes + ' more times...', type : 'warning', timeout : 1000});
+ }
+ })
+ .done((function(data) {
+ console.log(data);
+ // Add data to the state cache.
+ // This method will only be called if this superstepNumber was not
+ // in the cache already. This method just overwrites without check.
+ // If this is the first time the graph is being generated, (count = 1)
+ // start from scratch - build from adjList.
+ if (Utils.count(this.stateCache) === 1) {
+ this.stateCache[superstepNumber] = $.extend({}, data);
+ this.editor.buildGraphFromAdjList(data);
+ } else {
+ // Merge this data onto superstepNumber - 1's data
+ this.stateCache[superstepNumber] = this.mergeStates(this.stateCache[superstepNumber - 1], data);
+ this.modifyEditorOnScenario(this.stateCache[superstepNumber]);
+ }
+ }).bind(this))
+ .fail(function(error) {
+ noty({text : 'Failed to fetch job. Please check your network and debugger server.', type : 'error'});
+ })
+ .always((function() {
+ // Hide Editor's preloader.
+ this.editor.hidePreloader();
+ }).bind(this));
+ }
+ // Superstep changed. Enable/Disable the prev/next buttons.
+ $(this.btnNextStep).attr('disabled', superstepNumber === this.maxSuperstepNumber);
+ $(this.btnPrevStep).attr('disabled', superstepNumber === this.minSuperstepNumber);
+}
+
+
+/*
+ * Modifies the editor for a given scenario.
+ */
+GiraphDebugger.prototype.modifyEditorOnScenario = function(scenario) {
+ console.log(scenario);
+ // Add new nodes/links received in this scenario to graph.
+ this.editor.addToGraph(scenario);
+ // Disable the nodes that were not traced as part of this scenario.
+ for (var i = 0; i < this.editor.nodes.length; i++) {
+ var nodeId = this.editor.nodes[i].id;
+ if ((nodeId in scenario) && scenario[nodeId].debugged != false) {
+ this.editor.nodes[i].enabled = true;
+ } else {
+ this.editor.nodes[i].enabled = false;
+ }
+ }
+ // Update graph data with this scenario.
+ this.editor.updateGraphData(scenario);
+}
+
+/*
+ * Creates the document elements, like Node Attributes modal.
+ */
+GiraphDebugger.prototype.initElements = function() {
+ // Div for the node attribute modal.
+ this.nodeAttrModal = $('<div />')
+ .attr('class', 'modal')
+ .attr('id', this.ids._nodeAttrModal)
+ .hide()
+
+ // Div for edge values modal.
+ this.edgeValModal = $('<div />')
+ .attr('class', 'modal')
+ .attr('id', this.ids._edgeValModal)
+ .hide()
+
+ this.edgeValForm = $('<form />')
+ .addClass('form-horizontal')
+ .appendTo(this.edgeValModal);
+
+ // Create a form and append to nodeAttr
+ var nodeAttrForm = $('<form />')
+ .addClass('form-horizontal')
+ .appendTo(this.nodeAttrModal);
+
+ this.initInputElements(nodeAttrForm);
+ this.initMessageElements(nodeAttrForm);
+ this.initSuperstepControls(this.superstepControlsContainer);
+
+ // Initialize the node attr modal dialong.
+ $(this.nodeAttrModal).dialog({
+ modal : true,
+ autoOpen : false,
+ width : 300,
+ resizable : false,
+ closeOnEscape : true,
+ hide : {effect : 'fade', duration : 100},
+ close : (function() {
+ this.selectedNodeId = null;
+ }).bind(this)
+ });
+
+ // Initialize the edge values modal dialog.
+ $(this.edgeValModal).dialog({
+ modal : true,
+ autoOpen : false,
+ width : 250,
+ resizable : false,
+ title : 'Edge',
+ closeOnEscape : true,
+ hide : {effect : 'fade', duration : 100},
+ close : (function() {
+ this.selectedLink = null;
+ }).bind(this)
+ });
+
+ // Attach events.
+ // Click event of the Sent/Received tab buttons
+ $('.nav-msg').click((function(event) {
+ // Render the table
+ var clickedId = event.target.id;
+ this.toggleMessageTabs(clickedId);
+ if (clickedId === 'node-attr-sent') {
+ var messageData = this.editor.getMessagesSentByNode(this.selectedNodeId);
+ this.showMessages(messageData);
+ } else if(clickedId === 'node-attr-received') {
+ var messageData = this.editor.getMessagesReceivedByNode(this.selectedNodeId);
+ this.showMessages(messageData);
+ } else {
+ this.showEdgeValues(this.selectedNodeId, this.selectedEdgeValues);
+ }
+ }).bind(this));
+ // Attach mouseenter event for valpanel - Preview (Expand to the right)
+ $(this.valpanel.container).mouseenter((function(event) {
+ if (this.valpanel.state === ValidationPanel.StateEnum.COMPACT) {
+ this.valpanel.preview();
+ }
+ }).bind(this));
+ // Attach mouseleave event for valpanel - Compact (Compact to the left)
+ $(this.valpanel.container).mouseleave((function(event) {
+ // The user must click the close button to compact from the expanded mode.
+ if (this.valpanel.state != ValidationPanel.StateEnum.EXPAND) {
+ this.valpanel.compact();
+ }
+ }).bind(this));
+}
+
+/*
+ * Handler for opening edge values.
+ * Opens the edge value modal to allow editing/viewing edge values.
+ */
+GiraphDebugger.prototype.openEdgeVals = function(data) {
+ // Set the currently opened link.
+ this.selectedLink = data.link;
+ $(this.edgeValModal).dialog('option', 'position', [data.event.clientX, data.event.clientY]);
+ $(this.edgeValForm).empty();
+ // Data for the form.
+ var table = $('<table />').addClass('table').appendTo(this.edgeValForm);
+ var edges = this.editor.getEdges(data.link);
+ // Convert edges array to a map to be able to cache onChange results.
+ edgeValuesCache = {};
+ $.each(edges, function(i, edge) {
+ edgeValuesCache[edge.source.id] = edge;
+ });
+
+ $.each(edgeValuesCache, (function(sourceId, edge) {
+ var tr = document.createElement('tr');
+ var edgeElement = edge.edgeValue ? edge.edgeValue : 'undefined';
+ if (!this.editor.readonly) {
+ edgeElement = $('<input type="text" />')
+ .attr('value', edge.edgeValue)
+ .css('width', '100%')
+ .attr('placeholder', edge.edgeValue)
+ .change(function(event) {
+ // Save the temporarily edited values to show them as such
+ // when this tab is opened again.
+ edgeValuesCache[sourceId].edgeValue = event.target.value;
+ });
+ }
+ $(tr).append($('<td />').html("{0}->{1}".format(edge.source.id, edge.target.id)));
+ $(tr).append($('<td />').append(edgeElement));
+ table.append(tr);
+ }).bind(this));
+
+ Utils.getBtnSubmitSm()
+ .attr('type', 'submit')
+ .appendTo(this.edgeValForm)
+ .click((function(event) {
+ event.preventDefault();
+ // Save the temporary cache back to the editor object.
+ $.each(edgeValuesCache, (function(sourceId, edge) {
+ this.editor.addEdge(sourceId, edge.target.id, edge.edgeValue);
+ }).bind(this));
+ $(this.edgeValModal).dialog('close');
+ this.editor.restart();
+ }).bind(this));
+
+ Utils.getBtnCancelSm()
+ .appendTo(this.edgeValForm)
+ .click((function() {
+ $(this.edgeValModal).dialog('close');
+ }).bind(this));
+ $(this.edgeValModal).dialog('open');
+ // setTimeout is required because of a Chrome bug - jquery.focus doesn't work expectedly.
+ setTimeout((function() { $(this.edgeValModal).find('form input:text').first().focus(); }).bind(this), 1);
+ $('.ui-widget-overlay').click((function() { $(Utils.getSelectorForId(this.ids._edgeValModal)).dialog('close'); }).bind(this));
+}
+
+/*
+ * This is a double-click handler.
+ * Called from the editor when a node is double clicked.
+ * Opens the node attribute modal with NodeId, Attributes, Messages and Edge Values.
+ */
+GiraphDebugger.prototype.openNodeAttrs = function(data) {
+ // Set the currently double clicked node
+ this.selectedNodeId = data.node.id;
+ // Store the current edge values for this node in a temporary map.
+ // This is used by the Edge Values tab.
+ this.selectedEdgeValues = this.editor.getEdgeValuesForNode(this.selectedNodeId);
+
+ $(this.nodeAttrIdInput).attr('value', data.node.id)
+ .attr('placeholder', data.node.id);
+ $(this.nodeAttrAttrsInput).attr('value', data.node.attrs);
+ $(this.nodeAttrGroupError).hide();
+ $(this.nodeAttrModal).dialog('option', 'position', [data.event.clientX, data.event.clientY]);
+ $(this.nodeAttrModal).dialog('option', 'title', 'Node (ID: ' + data.node.id + ')');
+ $(this.nodeAttrModal).dialog('open');
+ // Set the focus on the Attributes input field by default.
+ $(this.nodeAttrModal).find('form input').eq(1).focus();
+ $('.ui-widget-overlay').click((function() { $(Utils.getSelectorForId(this.ids._nodeAttrModal)).dialog('close'); }).bind(this));
+
+ $(this.btnNodeAttrCancel).click((function() {
+ $(this.nodeAttrModal).dialog('close');
+ }).bind(this));
+
+ $(this.btnNodeAttrSubmit).unbind('click');
+ $(this.btnNodeAttrSubmit).click((function(event) {
+ event.preventDefault();
+ var new_id = $(this.nodeAttrIdInput).val();
+ var new_attrs = $(this.nodeAttrAttrsInput).val();
+ // Check if this id is already taken.
+ if (data.editor.getNodeIndex(new_id) >= 0 && new_id != data.node.id) {
+ $(this.nodeAttrGroupError).show();
+ return;
+ }
+ data.node.id = new_id;
+ data.node.attrs = new_attrs;
+ // Save the stored edge values. If not edited by the user, overwritten by the original values).
+ $.each(this.selectedEdgeValues, (function(targetId, edge) {
+ // This method is safe - If an edge exists, only overwrites the edge value.
+ data.editor.addEdge(this.selectedNodeId, targetId, edge.edgeValue);
+ }).bind(this));
+ data.editor.restart();
+ $(this.nodeAttrModal).dialog('close');
+ }).bind(this));
+
+ // Set the 'Received' tab as the active tab and show messages.
+ this.toggleMessageTabs('node-attr-received');
+ this.showMessages(data.editor.getMessagesReceivedByNode(this.selectedNodeId));
+}
+
+/*
+ * Makes the clicked message tab active and the other inactive,
+ * by setting/removing the 'active' classes on the corresponding elements.
+ * @param - Suffix of the clicked element (one of 'sent'/'received')
+ */
+GiraphDebugger.prototype.toggleMessageTabs = function(clickedId) {
+ if (this.currentlyActiveTab) {
+ $(this.currentlyActiveTab).parent().removeClass('active');
+ }
+ this.currentlyActiveTab = $('#' + clickedId);
+ $(this.currentlyActiveTab).parent().addClass('active');
+}
+
+/*
+ * Populates the messages table on the node attr modal with the message data
+ * @param messageData - The data of the sent/received messages from/to this node.
+ */
+GiraphDebugger.prototype.showMessages = function(messageData) {
+ this.flowTable.html('');
+ for (var nodeId in messageData) {
+ var tr = document.createElement('tr');
+ $(tr).html('<td>' + nodeId + '</td><td>' +
+ messageData[nodeId] + '</td>');
+ this.flowTable.append(tr);
+ }
+}
+
+/*
+ * Populates the edge value table on the node attr modal with the edge vaue data.
+ * Uses this.selectedEdgeValues and this.selectedNodeId - must be populated before calling this method.
+ * Format this.selectedEdgeValues : { targetNodeId : edgeValue }
+ */
+GiraphDebugger.prototype.showEdgeValues = function() {
+ this.flowTable.html('');
+ $.each(this.selectedEdgeValues, (function(nodeId, edge) {
+ var tr = document.createElement('tr');
+ var edgeElement = edge.edgeValue;
+ if (!this.editor.readonly) {
+ edgeElement = $('<input type="text" />')
+ .attr('value', edge.edgeValue)
+ .attr('placeholder', edge.edgeValue)
+ .change((function(event) {
+ // Save the temporarily edited values to show them as such
+ // when this tab is opened again.
+ this.selectedEdgeValues[nodeId].edgeValue = event.target.value;
+ }).bind(this));
+ }
+ $(tr).append($('<td />').html(nodeId));
+ $(tr).append($('<td />').append(edgeElement));
+ this.flowTable.append(tr);
+ }).bind(this));
+}
+
+/*
+ * Merges deltaState on baseState. Merge implies ->
+ * Keep all the values of baseState but overwrite if deltaState
+ * has them too. If deltaState has some vertices not in baseState, add them.
+ */
+GiraphDebugger.prototype.mergeStates = function(baseState, deltaState) {
+ var newState = $.extend(true, {}, baseState);
+ // Start with marking all nodes in baseState as not debugged.
+ // Only nodes debugged in deltaState will be marked as debugged.
+ for (nodeId in baseState) {
+ newState[nodeId].debugged = false;
+ }
+ for (nodeId in deltaState) {
+ // Add this node's properties from deltaState
+ newState[nodeId] = $.extend({}, deltaState[nodeId]);
+ // If nodeId was in deltaState, mark as debugged.
+ newState[nodeId].debugged = true;
+ }
+ return newState;
+}
+
+/*
+ * Toggles between the debug and edit modes.
+ */
+GiraphDebugger.prototype.toggleMode = function() {
+ if (this.mode === GiraphDebugger.ModeEnum.DEBUG) {
+ this.mode = GiraphDebugger.ModeEnum.EDIT;
+ if (this.editor.view != Editor.ViewEnum.GRAPH) {
+ this.editor.toggleView();
+ }
+ // Start with a sample graph as usual.
+ this.editor.setReadonly(false);
+ this.editor.buildSample();
+ // Show Fetch Job and hide controls
+ $(this.debugModeGroup).hide();
+ $(this.editModeGroup).show();
+ // Reset vars
+ this.resetVars();
+ } else {
+ this.mode = GiraphDebugger.ModeEnum.DEBUG;
+ // Set the editor in readonly mode.
+ this.editor.setReadonly(true);
+ // Show Form controls and hide fetch job.
+ $(this.debugModeGroup).show();
+ $(this.editModeGroup).hide();
+ }
+}