Skip to content

Commit c04ee3a

Browse files
authored
[Debugger Plugin]: Add clickable links to Session.runs table (#800)
Clicking a node or tensor in the table focuses on the corresponding node in the graph visualizer, if it exists.
1 parent adbb994 commit c04ee3a

File tree

3 files changed

+110
-37
lines changed

3 files changed

+110
-37
lines changed

tensorboard/plugins/debugger/tf_debugger_dashboard/tf-debugger-dashboard.html

+54-22
Original file line numberDiff line numberDiff line change
@@ -76,25 +76,25 @@ <h2>Continue...</h2>
7676
force-expansion-node-name="[[_forceExpansionNodeName]]">
7777
</tf-op-selector>
7878
</div>
79-
<div class="sidebar-section">
79+
<div>
8080
<tf-session-runs-view
8181
id="sessionRunsView"
8282
latest-session-run="[[_latestSessionRun]]"
8383
session-run-key-to-device-names="[[_sessionRunKey2DeviceNames]]"
84-
sole-active="[[_sessionRunSoleActive]]">
84+
sole-active="[[_sessionRunSoleActive]]"
85+
node-or-tensor-clicked="[[_createFeedFetchTargetClickedHandler()]]">
8586
</tf-session-runs-view>
86-
<br/>
87-
<div>
88-
<paper-button raised class="continue-button" on-click="_step">
89-
<span>[[_stepButtonText]]</span>
90-
</paper-button>
91-
<paper-button
92-
raised
93-
class="continue-button"
94-
on-click="_openContinueDialog">
95-
<span>[[_continueButtonText]]</span>
96-
</paper-button>
97-
</div>
87+
</div>
88+
<div>
89+
<paper-button raised class="continue-button" on-click="_step">
90+
<span>[[_stepButtonText]]</span>
91+
</paper-button>
92+
<paper-button
93+
raised
94+
class="continue-button"
95+
on-click="_openContinueDialog">
96+
<span>[[_continueButtonText]]</span>
97+
</paper-button>
9898
</div>
9999
<div class="container">
100100
<tf-graph-loader id="loader"
@@ -151,7 +151,7 @@ <h2>Continue...</h2>
151151
</div>
152152

153153
<div id="tensor-data" class="tensor-data">
154-
<div class="debugger-section-title">Tensor Value Summary</div>
154+
<div class="debugger-section-title">Tensor Value Overview</div>
155155
<tf-tensor-data-summary
156156
latest-tensor-data="[[_latestTensorData]]"
157157
expand-handler="[[_createTensorDataExpandHandler()]]">
@@ -221,6 +221,10 @@ <h2>Continue...</h2>
221221
max-width: 540px;
222222
margin: 80px auto 0 auto;
223223
}
224+
.sidebar {
225+
position: relative;
226+
height: 100%;
227+
}
224228
.center {
225229
position: relative;
226230
height: 100%;
@@ -627,7 +631,8 @@ <h2>Continue...</h2>
627631
const gatedGrpcTensors = response['gated_grpc_tensors'][deviceName];
628632
for (let i = 0; i < gatedGrpcTensors.length; ++i) {
629633
debugWatches.push(
630-
{'node_name': deviceName + '/' + gatedGrpcTensors[i][0],
634+
{'device_name': deviceName,
635+
'node_name': gatedGrpcTensors[i][0],
631636
'op_type': gatedGrpcTensors[i][1],
632637
'output_slot': gatedGrpcTensors[i][2],
633638
'debug_op': gatedGrpcTensors[i][3]});
@@ -658,18 +663,45 @@ <h2>Continue...</h2>
658663
const state = checked ? 'break' : 'disable';
659664
// TODO(cais): Investigate why this is fired twice sometimes.
660665
this._requestBreakpointStateChange(
661-
tf_debugger_dashboard.getCleanNodeName(debugObject.node_name),
666+
tf_debugger_dashboard.getCleanNodeName(
667+
debugObject.device_name + '/' + debugObject.node_name),
662668
debugObject.output_slot, debugObject.debug_op, state);
663669
});
664670
},
671+
_focusOnGraphNode(deviceName, nodeName) {
672+
if (deviceName != null &&
673+
this._activeRuntimeGraphDeviceName !== deviceName) {
674+
this.set('_activeRuntimeGraphDeviceName', deviceName);
675+
}
676+
this._setTopRightRuntimeGraphsToActive();
677+
this.$$('#graph').set('selectedNode', nodeName);
678+
},
665679
_createListNodeClickedHandler() {
666680
return((deviceName, nodeName) => {
667-
if (deviceName != null &&
668-
this._activeRuntimeGraphDeviceName !== deviceName) {
669-
this.set('_activeRuntimeGraphDeviceName', deviceName);
681+
this._focusOnGraphNode(deviceName, nodeName);
682+
});
683+
},
684+
_createFeedFetchTargetClickedHandler() {
685+
return((graphElementName) => {
686+
let nodeName = graphElementName;
687+
// If it is a tensor name, get the node name.
688+
if (nodeName.indexOf(':') !== -1) {
689+
nodeName = nodeName.slice(0, nodeName.indexOf(':'));
690+
}
691+
// Find the debug watch that matches the clicked node.
692+
const matchingDebugWatch = _.find(this._debugWatches, debugWatch => {
693+
// Take into account the possibility of base-expanded node names.
694+
return (debugWatch.node_name === nodeName ||
695+
debugWatch.node_name.indexOf(nodeName) === 0 &&
696+
debugWatch.node_name[nodeName.length] === '(');
697+
});
698+
if (matchingDebugWatch == null) {
699+
this._showToast(
700+
'Node \'' + nodeName + '\' is not in the runtime graph of the ' +
701+
'current Session.run or does not have a debug op attached.');
702+
} else {
703+
this._focusOnGraphNode(matchingDebugWatch.device_name, nodeName);
670704
}
671-
this._setTopRightRuntimeGraphsToActive();
672-
this.$$('#graph').set('selectedNode', nodeName);
673705
});
674706
},
675707

tensorboard/plugins/debugger/tf_debugger_dashboard/tf-op-selector.html

+22-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<template>
3232
<div>
3333
<paper-dropdown-menu
34-
id="f-mode"
34+
id="filter-mode"
3535
no-label-float="true"
3636
label="Filter Mode"
3737
selected-item-label="{{_filterMode}}"
@@ -82,6 +82,10 @@
8282
cursor: pointer;
8383
}
8484

85+
:host .op-title-leaf:hover {
86+
color: blue;
87+
}
88+
8589
:host .partial-checkbox {
8690
background: #f57c00;
8791
}
@@ -109,14 +113,18 @@
109113
}
110114

111115
:host #filter-mode {
112-
width: 100px;
116+
width: 150px;
113117
display: inline-block;
114118
}
115119

116120
:host #filter-input {
117121
width: 250px;
118122
display: inline-block;
119123
}
124+
125+
#selector-hierarchy {
126+
width: 100%;
127+
}
120128
</style>
121129
</template>
122130
<script src="selection-tree-node.js"></script>
@@ -125,8 +133,9 @@
125133
Polymer({
126134
is: "tf-op-selector",
127135
properties: {
128-
// A list of debug watches. A debug watch object has 3 properties:
129-
// node_name (string), output_slot (number), and debug_op (string).
136+
// A list of debug watches. A debug watch object has 4 properties:
137+
// device_name (string), node_name (string), output_slot (number), and
138+
// debug_op (string).
130139
debugWatches: Array,
131140
// A function called when a debug watch is toggled. This function takes
132141
// the click event.
@@ -204,7 +213,8 @@
204213
hierarchy.isRoot = true;
205214

206215
_.forEach(filteredDebugWatches, debugWatch => {
207-
const portions = tf_debugger_dashboard.splitNodeName(debugWatch.node_name);
216+
const nodeNameWithDevice = debugWatch.device_name + '/' + debugWatch.node_name;
217+
const portions = tf_debugger_dashboard.splitNodeName(nodeNameWithDevice);
208218
// Start at the highest level.
209219
let currentNode = hierarchy;
210220
_.forEach(portions, (portion, i) => {
@@ -237,6 +247,10 @@
237247
},
238248

239249
_renderHierarchyWithTimeout(watchHierarchy, debugWatchChange, filterMode, filterInput) {
250+
if (this._isLoading) {
251+
// Wait till the ongoing rendering finishes.
252+
return;
253+
}
240254
this._clearSelectorHierarchy();
241255
this.set('_isLoading', true);
242256
setTimeout(() => {
@@ -457,10 +471,11 @@
457471
},
458472

459473
_handleLeafNodeSelected(debugWatchChangeMethod, debugWatch, isChecked) {
474+
const nodeNameWidthDevice = debugWatch.device_name + '/' + debugWatch.node_name;
460475
if (isChecked) {
461-
this._selectedDebugWatchMapping[debugWatch.node_name] = debugWatch;
476+
this._selectedDebugWatchMapping[nodeNameWidthDevice] = debugWatch;
462477
} else {
463-
delete this._selectedDebugWatchMapping[debugWatch.node_name];
478+
delete this._selectedDebugWatchMapping[nodeNameWidthDevice];
464479
}
465480
debugWatchChangeMethod(debugWatch, isChecked);
466481
},

tensorboard/plugins/debugger/tf_debugger_dashboard/tf-session-runs-view.html

+34-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
-->
2424
<dom-module id="tf-session-runs-view">
2525
<template>
26-
<div id="tensor-data-div" class="tensor-data-div">
26+
<div class="session-runs-div">
2727
<div class="section-title">Session Runs</div>
2828
<table id="session-runs-table" align="left" class="session-runs-table">
2929
<tr align="left">
@@ -43,15 +43,18 @@
4343
:host .indented-level-container .content-container {
4444
margin: 0 0 0 10px;
4545
}
46+
4647
/* TODO(cais): This needs work: the table shouldn't get too wide when
4748
there are many feeds/fetches/targte names. */
4849
.session-runs-table {
4950
align-content: left;
5051
align-items: left;
5152
text-align: left;
53+
font-size: 90%;
5254
border-style: solid 1px black;
53-
width: 400px;
5455
table-layout: fixed;
56+
min-width: 400px;
57+
max-width: 450px;
5558
word-break: break-all;
5659
padding-top: 3px;
5760
padding-left: 3px;
@@ -66,6 +69,15 @@
6669
background-color: rgb(172, 232, 188);
6770
font-weight: bold;
6871
}
72+
73+
.node-or-tensor-element {
74+
text-decoration: underline;
75+
cursor: pointer;
76+
}
77+
78+
.node-or-tensor-element:hover {
79+
color: blue;
80+
}
6981
</style>
7082
</template>
7183
<script>
@@ -87,6 +99,9 @@
8799
// breakpoint being active.
88100
soleActive: Boolean,
89101

102+
// A function called when a node or tensor in the list is clicked.
103+
nodeOrTensorClicked: Function,
104+
90105
// A map from session.run key to count of how many the session.run's
91106
// execution has started.
92107
_runKey2Count: {
@@ -184,12 +199,9 @@
184199
// clickable links, which when clicked, will focus on the corresponding
185200
// node in the Graph View and/or highlight corresponding tensors in the
186201
// Tensor Summary View and the Tensor Value View.
187-
const feedsCell = document.createElement('td');
188-
feedsCell.textContent = JSON.stringify(sessionRun.feeds);
189-
const fetchesCell = document.createElement('td');
190-
fetchesCell.textContent = JSON.stringify(sessionRun.fetches);
191-
const targetsCell = document.createElement('td');
192-
targetsCell.textContent = JSON.stringify(sessionRun.targets);
202+
const feedsCell = this._renderGraphElements(sessionRun.feeds);
203+
const fetchesCell = this._renderGraphElements(sessionRun.fetches);
204+
const targetsCell = this._renderGraphElements(sessionRun.targets);
193205
const numDevicesCell = document.createElement('td');
194206
numDevicesCell.textContent = numDevices;
195207
const countCell = document.createElement('td');
@@ -211,6 +223,20 @@
211223

212224
Polymer.dom(this.$$('#session-runs-table')).appendChild(sessionRunRow);
213225
},
226+
227+
_renderGraphElements(graphElements) {
228+
const container = document.createElement('td');
229+
_.forEach(graphElements, element => {
230+
const elementDiv = document.createElement('div');
231+
elementDiv.textContent = element;
232+
elementDiv.setAttribute('class', 'node-or-tensor-element');
233+
elementDiv.addEventListener('click', () => {
234+
this.nodeOrTensorClicked(element);
235+
});
236+
container.appendChild(elementDiv);
237+
});
238+
return container;
239+
}
214240
});
215241
</script>
216242
</dom-module>

0 commit comments

Comments
 (0)