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(); + } +}
