Move tensorflow graph visualizer utility functions to a util.ts file under a tf.graph.util namespace.
Change: 121883015
This commit is contained in:
parent
6e02bf0299
commit
d4f83e1c64
@ -13,228 +13,102 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==============================================================================*/
|
||||
|
||||
/**
|
||||
* @fileoverview Common interfaces for the tensorflow graph visualizer.
|
||||
*/
|
||||
|
||||
module tf {
|
||||
/**
|
||||
* Recommended delay (ms) when running an expensive task asynchronously
|
||||
* that gives enough time for the progress bar to update its UI.
|
||||
*/
|
||||
const ASYNC_TASK_DELAY = 20;
|
||||
|
||||
export function time<T>(msg: string, task: () => T) {
|
||||
let start = Date.now();
|
||||
let result = task();
|
||||
/* tslint:disable */
|
||||
console.log(msg, ':', Date.now() - start, 'ms');
|
||||
/* tslint:enable */
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks task progress. Each task being passed a progress tracker needs
|
||||
* to call the below-defined methods to notify the caller about the gradual
|
||||
* progress of the task.
|
||||
*/
|
||||
export interface ProgressTracker {
|
||||
updateProgress(incrementValue: number): void;
|
||||
setMessage(msg: string): void;
|
||||
reportError(msg: string, err: Error): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker that sets the progress property of the
|
||||
* provided polymer component. The provided component must have
|
||||
* a property called 'progress' that is not read-only. The progress
|
||||
* property is an object with a numerical 'value' property and a
|
||||
* string 'msg' property.
|
||||
*/
|
||||
export function getTracker(polymerComponent: any) {
|
||||
return {
|
||||
setMessage: function(msg) {
|
||||
polymerComponent.set(
|
||||
'progress', {value: polymerComponent.progress.value, msg: msg});
|
||||
},
|
||||
updateProgress: function(value) {
|
||||
polymerComponent.set('progress', {
|
||||
value: polymerComponent.progress.value + value,
|
||||
msg: polymerComponent.progress.msg
|
||||
});
|
||||
},
|
||||
reportError: function(msg: string, err) {
|
||||
// Log the stack trace in the console.
|
||||
console.error(err.stack);
|
||||
// And send a user-friendly message to the UI.
|
||||
polymerComponent.set(
|
||||
'progress',
|
||||
{value: polymerComponent.progress.value, msg: msg, error: true});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker for a subtask given the parent tracker, the total progress
|
||||
* of the subtask and the subtask message. The parent task should pass a
|
||||
* subtracker to its subtasks. The subtask reports its own progress which
|
||||
* becames relative to the main task.
|
||||
*/
|
||||
export function getSubtaskTracker(parentTracker: ProgressTracker,
|
||||
impactOnTotalProgress: number, subtaskMsg: string): ProgressTracker {
|
||||
return {
|
||||
setMessage: function(progressMsg) {
|
||||
// The parent should show a concatenation of its message along with
|
||||
// its subtask tracker message.
|
||||
parentTracker.setMessage(subtaskMsg + ': ' + progressMsg);
|
||||
},
|
||||
updateProgress: function(incrementValue) {
|
||||
// Update the parent progress relative to the child progress.
|
||||
// For example, if the sub-task progresses by 30%, and the impact on the
|
||||
// total progress is 50%, then the task progresses by 30% * 50% = 15%.
|
||||
parentTracker
|
||||
.updateProgress(incrementValue * impactOnTotalProgress / 100);
|
||||
},
|
||||
reportError: function(msg: string, err: Error) {
|
||||
// The parent should show a concatenation of its message along with
|
||||
// its subtask error message.
|
||||
parentTracker.reportError(subtaskMsg + ': ' + msg, err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an expensive task and return the result.
|
||||
*/
|
||||
export function runTask<T>(msg: string, incProgressValue: number,
|
||||
task: () => T, tracker: ProgressTracker): T {
|
||||
// Update the progress message to say the current running task.
|
||||
tracker.setMessage(msg);
|
||||
// Run the expensive task with a delay that gives enough time for the
|
||||
// UI to update.
|
||||
try {
|
||||
var result = tf.time(msg, task);
|
||||
// Update the progress value.
|
||||
tracker.updateProgress(incProgressValue);
|
||||
// Return the result to be used by other tasks.
|
||||
return result;
|
||||
} catch (e) {
|
||||
// Errors that happen inside asynchronous tasks are
|
||||
// reported to the tracker using a user-friendly message.
|
||||
tracker.reportError('Failed ' + msg, e);
|
||||
/**
|
||||
* Tracks task progress. Each task being passed a progress tracker needs
|
||||
* to call the below-defined methods to notify the caller about the gradual
|
||||
* progress of the task.
|
||||
*/
|
||||
export interface ProgressTracker {
|
||||
updateProgress(incrementValue: number): void;
|
||||
setMessage(msg: string): void;
|
||||
reportError(msg: string, err: Error): void;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an expensive task asynchronously and returns a promise of the result.
|
||||
*/
|
||||
export function runAsyncTask<T>(msg: string, incProgressValue: number,
|
||||
task: () => T, tracker: ProgressTracker): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Update the progress message to say the current running task.
|
||||
tracker.setMessage(msg);
|
||||
// Run the expensive task with a delay that gives enough time for the
|
||||
// UI to update.
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var result = tf.time(msg, task);
|
||||
// Update the progress value.
|
||||
tracker.updateProgress(incProgressValue);
|
||||
// Return the result to be used by other tasks.
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
// Errors that happen inside asynchronous tasks are
|
||||
// reported to the tracker using a user-friendly message.
|
||||
tracker.reportError('Failed ' + msg, e);
|
||||
}
|
||||
}, ASYNC_TASK_DELAY);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* TensorFlow node definition as defined in the graph proto file.
|
||||
*/
|
||||
export interface TFNode {
|
||||
/** Name of the node */
|
||||
name: string;
|
||||
/** List of nodes that are inputs for this node. */
|
||||
input: string[];
|
||||
/** The name of the device where the computation will run. */
|
||||
device: string;
|
||||
/** The name of the operation associated with this node. */
|
||||
op: string;
|
||||
/** List of attributes that describe/modify the operation. */
|
||||
attr: {key: string, value: Object}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query selector with escaped special characters that are not
|
||||
* allowed in a query selector.
|
||||
*/
|
||||
export function escapeQuerySelector(querySelector: string): string {
|
||||
return querySelector.replace(/([:.\[\],/\\\(\)])/g, '\\$1');
|
||||
}
|
||||
/**
|
||||
* TensorFlow stats file definition as defined in the stats proto file.
|
||||
*/
|
||||
export interface StepStats {
|
||||
dev_stats: {device: string, node_stats: NodeStats[]}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* TensorFlow node definition as defined in the graph proto file.
|
||||
*/
|
||||
export interface TFNode {
|
||||
/** Name of the node */
|
||||
name: string;
|
||||
/** List of nodes that are inputs for this node. */
|
||||
input: string[];
|
||||
/** The name of the device where the computation will run. */
|
||||
device: string;
|
||||
/** The name of the operation associated with this node. */
|
||||
op: string;
|
||||
/** List of attributes that describe/modify the operation. */
|
||||
attr: {key: string, value: Object}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* TensorFlow stats file definition as defined in the stats proto file.
|
||||
*/
|
||||
export interface StepStats {
|
||||
dev_stats: {device: string, node_stats: NodeStats[]}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* TensorFlow stats for a node as defined in the stats proto file.
|
||||
*/
|
||||
export interface NodeStats {
|
||||
node_name: string;
|
||||
// The next 4 properties are currently stored as string in json
|
||||
// and must be parsed.
|
||||
all_start_micros: number;
|
||||
op_start_rel_micros: number;
|
||||
op_end_rel_micros: number;
|
||||
all_end_rel_micros: number;
|
||||
memory: {
|
||||
allocator_name: string;
|
||||
total_bytes: number; // Stored as string in json and should be parsed.
|
||||
peak_bytes: number; // Stored as string in json and should be parsed.
|
||||
}[];
|
||||
/** Output sizes recorded for a single execution of a graph node */
|
||||
output: TFNodeOutput[];
|
||||
timeline_label: string;
|
||||
scheduled_micros: string;
|
||||
thread_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for the output tensor(s) of an operation in the graph.
|
||||
*/
|
||||
export interface TFNodeOutput {
|
||||
slot: number; // Stored as string in json and should be parsed.
|
||||
tensor_description: {
|
||||
/** Data type of tensor elements */
|
||||
dtype: string;
|
||||
/** Shape of the tensor */
|
||||
shape: {
|
||||
/**
|
||||
* Dimensions of the tensor, such as [{name: 'input', size: 30},
|
||||
* {name: 'output', size: 40}] for a 30 x 40 2D tensor. The names
|
||||
* are optional. The order of entries in 'dim' matters: It indicates
|
||||
* the layout of the values in the tensor in-memory representation.
|
||||
*/
|
||||
dim: {
|
||||
/** Size of the tensor in that dimension */
|
||||
size: number, // Stored as string in json and should be parsed.
|
||||
/** Optional name of the tensor dimension */
|
||||
name?: string
|
||||
}[];
|
||||
};
|
||||
/** Information about the size and allocator used for the data */
|
||||
allocation_description: {
|
||||
// The next 2 properties are stored as string in json and
|
||||
// should be parsed.
|
||||
/** Total number of bytes requested */
|
||||
requested_bytes: number;
|
||||
/** Total number of bytes allocated, if known */
|
||||
allocated_bytes?: number;
|
||||
/** Name of the allocator used */
|
||||
/**
|
||||
* TensorFlow stats for a node as defined in the stats proto file.
|
||||
*/
|
||||
export interface NodeStats {
|
||||
node_name: string;
|
||||
// The next 4 properties are currently stored as string in json
|
||||
// and must be parsed.
|
||||
all_start_micros: number;
|
||||
op_start_rel_micros: number;
|
||||
op_end_rel_micros: number;
|
||||
all_end_rel_micros: number;
|
||||
memory: {
|
||||
allocator_name: string;
|
||||
total_bytes: number; // Stored as string in json and should be parsed.
|
||||
peak_bytes: number; // Stored as string in json and should be parsed.
|
||||
}[];
|
||||
/** Output sizes recorded for a single execution of a graph node */
|
||||
output: TFNodeOutput[];
|
||||
timeline_label: string;
|
||||
scheduled_micros: string;
|
||||
thread_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description for the output tensor(s) of an operation in the graph.
|
||||
*/
|
||||
export interface TFNodeOutput {
|
||||
slot: number; // Stored as string in json and should be parsed.
|
||||
tensor_description: {
|
||||
/** Data type of tensor elements */
|
||||
dtype: string;
|
||||
/** Shape of the tensor */
|
||||
shape: {
|
||||
/**
|
||||
* Dimensions of the tensor, such as [{name: 'input', size: 30},
|
||||
* {name: 'output', size: 40}] for a 30 x 40 2D tensor. The names
|
||||
* are optional. The order of entries in 'dim' matters: It indicates
|
||||
* the layout of the values in the tensor in-memory representation.
|
||||
*/
|
||||
dim: {
|
||||
/** Size of the tensor in that dimension */
|
||||
size: number, // Stored as string in json and should be parsed.
|
||||
/** Optional name of the tensor dimension */
|
||||
name?: string
|
||||
}[];
|
||||
};
|
||||
/** Information about the size and allocator used for the data */
|
||||
allocation_description: {
|
||||
// The next 2 properties are stored as string in json and
|
||||
// should be parsed.
|
||||
/** Total number of bytes requested */
|
||||
requested_bytes: number;
|
||||
/** Total number of bytes allocated, if known */
|
||||
allocated_bytes?: number;
|
||||
/** Name of the allocator used */
|
||||
allocator_name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
} // close module tf
|
||||
|
||||
@ -855,100 +855,115 @@ export function build(rawNodes: tf.TFNode[], params: BuildParams,
|
||||
*/
|
||||
let nodeNames = new Array<string>(rawNodes.length);
|
||||
|
||||
return runAsyncTask('Normalizing names', 30, () => {
|
||||
let opNodes = new Array<OpNode>(rawNodes.length);
|
||||
let index = 0;
|
||||
_.each(rawNodes, rawNode => {
|
||||
let opNode = new OpNodeImpl(rawNode);
|
||||
if (isInEmbeddedPred(opNode)) {
|
||||
embeddingNodeNames.push(opNode.name);
|
||||
inEmbedding[opNode.name] = opNode;
|
||||
return;
|
||||
}
|
||||
return tf.graph.util
|
||||
.runAsyncTask(
|
||||
'Normalizing names', 30,
|
||||
() => {
|
||||
let opNodes = new Array<OpNode>(rawNodes.length);
|
||||
let index = 0;
|
||||
_.each(rawNodes, rawNode => {
|
||||
let opNode = new OpNodeImpl(rawNode);
|
||||
if (isInEmbeddedPred(opNode)) {
|
||||
embeddingNodeNames.push(opNode.name);
|
||||
inEmbedding[opNode.name] = opNode;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isOutEmbeddedPred(opNode)) {
|
||||
embeddingNodeNames.push(opNode.name);
|
||||
outEmbedding[opNode.name] = opNode;
|
||||
_.each(opNode.inputs, input => {
|
||||
let inputName = input.name;
|
||||
outEmbeddings[inputName] = outEmbeddings[inputName] || [];
|
||||
outEmbeddings[inputName].push(opNode);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// The node is not an embedding, so add it to the names and nodes
|
||||
// lists.
|
||||
opNodes[index] = opNode;
|
||||
nodeNames[index] = opNode.name;
|
||||
index++;
|
||||
});
|
||||
opNodes.splice(index);
|
||||
nodeNames.splice(index);
|
||||
return opNodes;
|
||||
}, tracker).then((opNodes) => {
|
||||
// Create the graph data structure from the graphlib library.
|
||||
return runAsyncTask('Building the data structure', 70, () => {
|
||||
let normalizedNameDict = mapStrictHierarchy(nodeNames,
|
||||
embeddingNodeNames);
|
||||
let graph = new SlimGraph;
|
||||
if (isOutEmbeddedPred(opNode)) {
|
||||
embeddingNodeNames.push(opNode.name);
|
||||
outEmbedding[opNode.name] = opNode;
|
||||
_.each(opNode.inputs, input => {
|
||||
let inputName = input.name;
|
||||
outEmbeddings[inputName] = outEmbeddings[inputName] || [];
|
||||
outEmbeddings[inputName].push(opNode);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// The node is not an embedding, so add it to the names and nodes
|
||||
// lists.
|
||||
opNodes[index] = opNode;
|
||||
nodeNames[index] = opNode.name;
|
||||
index++;
|
||||
});
|
||||
opNodes.splice(index);
|
||||
nodeNames.splice(index);
|
||||
return opNodes;
|
||||
},
|
||||
tracker)
|
||||
.then((opNodes) => {
|
||||
// Create the graph data structure from the graphlib library.
|
||||
return tf.graph.util.runAsyncTask(
|
||||
'Building the data structure', 70, () => {
|
||||
let normalizedNameDict =
|
||||
mapStrictHierarchy(nodeNames, embeddingNodeNames);
|
||||
let graph = new SlimGraph;
|
||||
|
||||
// Add the nodes to the graph.
|
||||
_.each(opNodes, opNode => {
|
||||
let normalizedName = normalizedNameDict[opNode.name] || opNode.name;
|
||||
graph.nodes[normalizedName] = opNode;
|
||||
// Check if the node has out-embeddings. If yes, add them to the
|
||||
// node.
|
||||
if (opNode.name in outEmbeddings) {
|
||||
opNode.outEmbeddings = outEmbeddings[opNode.name];
|
||||
// Normalize the names of the out-embeddings.
|
||||
_.each(opNode.outEmbeddings, node => {
|
||||
node.name = normalizedNameDict[node.name] || node.name;
|
||||
});
|
||||
}
|
||||
// Update the name of the node.
|
||||
opNode.name = normalizedName;
|
||||
// Add the nodes to the graph.
|
||||
_.each(opNodes, opNode => {
|
||||
let normalizedName =
|
||||
normalizedNameDict[opNode.name] || opNode.name;
|
||||
graph.nodes[normalizedName] = opNode;
|
||||
// Check if the node has out-embeddings. If yes, add them to the
|
||||
// node.
|
||||
if (opNode.name in outEmbeddings) {
|
||||
opNode.outEmbeddings = outEmbeddings[opNode.name];
|
||||
// Normalize the names of the out-embeddings.
|
||||
_.each(opNode.outEmbeddings, node => {
|
||||
node.name = normalizedNameDict[node.name] || node.name;
|
||||
});
|
||||
}
|
||||
// Update the name of the node.
|
||||
opNode.name = normalizedName;
|
||||
});
|
||||
|
||||
// Visit each node's inputs to add the edges to the graph. If the
|
||||
// input
|
||||
// is an in-embedding, then add it to the node's in-embeddings
|
||||
// instead.
|
||||
_.each(opNodes, opNode => {
|
||||
_.each(opNode.inputs, (input, i) => {
|
||||
let inputName = input.name;
|
||||
if (inputName in inEmbedding) {
|
||||
let inEmbedNode = inEmbedding[inputName];
|
||||
opNode.inEmbeddings.push(inEmbedNode);
|
||||
// Move the inputs of the in-embedding node into incoming
|
||||
// edges of
|
||||
// the main node. E.g. the control dependency of a constant
|
||||
// node
|
||||
// should be moved to the op node where the constant is
|
||||
// embedded.
|
||||
for (let embedInput of inEmbedNode.inputs) {
|
||||
addEdgeToGraph(
|
||||
graph, normalizedNameDict[embedInput.name] ||
|
||||
embedInput.name,
|
||||
opNode, embedInput.isControlDependency, params, i);
|
||||
}
|
||||
} else if (inputName in outEmbedding) {
|
||||
// Move the inputs of the out-embedding node into inputs of
|
||||
// the main node where the out-embedding points to.
|
||||
let outEmbedNode = outEmbedding[inputName];
|
||||
for (let embedInput of outEmbedNode.inputs) {
|
||||
addEdgeToGraph(
|
||||
graph, normalizedNameDict[embedInput.name] ||
|
||||
embedInput.name,
|
||||
opNode, input.isControlDependency, params, i);
|
||||
}
|
||||
} else {
|
||||
addEdgeToGraph(
|
||||
graph, normalizedNameDict[inputName] || inputName,
|
||||
opNode, input.isControlDependency, params, i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Normalize the names of in-embeddings.
|
||||
_.each(inEmbedding, (node, name) => {
|
||||
node.name = normalizedNameDict[node.name] || node.name;
|
||||
});
|
||||
|
||||
return graph;
|
||||
}, tracker);
|
||||
});
|
||||
|
||||
// Visit each node's inputs to add the edges to the graph. If the input
|
||||
// is an in-embedding, then add it to the node's in-embeddings instead.
|
||||
_.each(opNodes, opNode => {
|
||||
_.each(opNode.inputs, (input, i) => {
|
||||
let inputName = input.name;
|
||||
if (inputName in inEmbedding) {
|
||||
let inEmbedNode = inEmbedding[inputName];
|
||||
opNode.inEmbeddings.push(inEmbedNode);
|
||||
// Move the inputs of the in-embedding node into incoming edges of
|
||||
// the main node. E.g. the control dependency of a constant node
|
||||
// should be moved to the op node where the constant is embedded.
|
||||
for (let embedInput of inEmbedNode.inputs) {
|
||||
addEdgeToGraph(graph,
|
||||
normalizedNameDict[embedInput.name] || embedInput.name,
|
||||
opNode, embedInput.isControlDependency, params, i);
|
||||
}
|
||||
} else if (inputName in outEmbedding) {
|
||||
// Move the inputs of the out-embedding node into inputs of
|
||||
// the main node where the out-embedding points to.
|
||||
let outEmbedNode = outEmbedding[inputName];
|
||||
for (let embedInput of outEmbedNode.inputs) {
|
||||
addEdgeToGraph(graph,
|
||||
normalizedNameDict[embedInput.name] || embedInput.name,
|
||||
opNode, input.isControlDependency, params, i);
|
||||
}
|
||||
} else {
|
||||
addEdgeToGraph(graph, normalizedNameDict[inputName] || inputName,
|
||||
opNode, input.isControlDependency, params, i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Normalize the names of in-embeddings.
|
||||
_.each(inEmbedding, (node, name) => {
|
||||
node.name = normalizedNameDict[node.name] || node.name;
|
||||
});
|
||||
|
||||
return graph;
|
||||
}, tracker);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -400,22 +400,23 @@ export function build(graph: tf.graph.SlimGraph, params: HierarchyParams,
|
||||
tracker: ProgressTracker): Promise<Hierarchy|void> {
|
||||
let h = new HierarchyImpl();
|
||||
let seriesNames: { [name: string]: string } = {};
|
||||
return runAsyncTask(
|
||||
'Adding nodes', 20,
|
||||
() => {
|
||||
// Get all the possible device names.
|
||||
let deviceNames = {};
|
||||
_.each(graph.nodes, (node, nodeName) => {
|
||||
if (node.device != null) {
|
||||
deviceNames[node.device] = true;
|
||||
}
|
||||
});
|
||||
h.devices = _.keys(deviceNames);
|
||||
addNodes(h, graph);
|
||||
},
|
||||
tracker)
|
||||
return tf.graph.util
|
||||
.runAsyncTask(
|
||||
'Adding nodes', 20,
|
||||
() => {
|
||||
// Get all the possible device names.
|
||||
let deviceNames = {};
|
||||
_.each(graph.nodes, (node, nodeName) => {
|
||||
if (node.device != null) {
|
||||
deviceNames[node.device] = true;
|
||||
}
|
||||
});
|
||||
h.devices = _.keys(deviceNames);
|
||||
addNodes(h, graph);
|
||||
},
|
||||
tracker)
|
||||
.then(() => {
|
||||
return runAsyncTask('Detect series', 20, () => {
|
||||
return tf.graph.util.runAsyncTask('Detect series', 20, () => {
|
||||
if (params.seriesNodeMinSize > 0) {
|
||||
groupSeries(
|
||||
h.root, h, seriesNames, params.seriesNodeMinSize,
|
||||
@ -424,14 +425,15 @@ export function build(graph: tf.graph.SlimGraph, params: HierarchyParams,
|
||||
}, tracker);
|
||||
})
|
||||
.then(() => {
|
||||
return runAsyncTask('Adding edges', 30, () => {
|
||||
return tf.graph.util.runAsyncTask('Adding edges', 30, () => {
|
||||
addEdges(h, graph, seriesNames);
|
||||
}, tracker);
|
||||
})
|
||||
.then(() => {
|
||||
return runAsyncTask('Finding similar subgraphs', 30, () => {
|
||||
h.templates = template.detect(h, params.verifyTemplate);
|
||||
}, tracker);
|
||||
return tf.graph.util.runAsyncTask(
|
||||
'Finding similar subgraphs', 30, () => {
|
||||
h.templates = template.detect(h, params.verifyTemplate);
|
||||
}, tracker);
|
||||
})
|
||||
.then(() => { return h; });
|
||||
};
|
||||
|
||||
@ -53,16 +53,21 @@ export function fetchPbTxt(filepath: string): Promise<string> {
|
||||
* Fetches the metadata file, parses it and returns a promise of the result.
|
||||
*/
|
||||
export function fetchAndParseMetadata(path: string, tracker: ProgressTracker) {
|
||||
return runTask('Reading metadata pbtxt', 40, () => {
|
||||
if (path == null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return fetchPbTxt(path).then(text => new Blob([text]));
|
||||
}, tracker).then((blob: Blob) => {
|
||||
return runTask('Parsing metadata.pbtxt', 60, () => {
|
||||
return blob != null ? parseStatsPbTxt(blob) : null;
|
||||
}, tracker);
|
||||
});
|
||||
return tf.graph.util
|
||||
.runTask(
|
||||
'Reading metadata pbtxt', 40,
|
||||
() => {
|
||||
if (path == null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return fetchPbTxt(path).then(text => new Blob([text]));
|
||||
},
|
||||
tracker)
|
||||
.then((blob: Blob) => {
|
||||
return tf.graph.util.runTask('Parsing metadata.pbtxt', 60, () => {
|
||||
return blob != null ? parseStatsPbTxt(blob) : null;
|
||||
}, tracker);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,14 +75,19 @@ export function fetchAndParseMetadata(path: string, tracker: ProgressTracker) {
|
||||
*/
|
||||
export function fetchAndParseGraphData(path: string, pbTxtFile: Blob,
|
||||
tracker: ProgressTracker) {
|
||||
return runTask('Reading graph pbtxt', 40, () => {
|
||||
return pbTxtFile ? Promise.resolve(pbTxtFile) :
|
||||
fetchPbTxt(path).then(text => new Blob([text]));
|
||||
}, tracker).then(blob => {
|
||||
return runTask('Parsing graph.pbtxt', 60, () => {
|
||||
return parseGraphPbTxt(blob);
|
||||
}, tracker);
|
||||
});
|
||||
return tf.graph.util
|
||||
.runTask(
|
||||
'Reading graph pbtxt', 40,
|
||||
() => {
|
||||
return pbTxtFile ? Promise.resolve(pbTxtFile) :
|
||||
fetchPbTxt(path).then(text => new Blob([text]));
|
||||
},
|
||||
tracker)
|
||||
.then(blob => {
|
||||
return tf.graph.util.runTask('Parsing graph.pbtxt', 60, () => {
|
||||
return parseGraphPbTxt(blob);
|
||||
}, tracker);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -544,7 +544,7 @@ export function getFillForNode(templateIndex, colorBy,
|
||||
return colorParams.UNKNOWN;
|
||||
}
|
||||
let id = renderInfo.node.name;
|
||||
let escapedId = tf.escapeQuerySelector(id);
|
||||
let escapedId = tf.graph.util.escapeQuerySelector(id);
|
||||
let gradientDefs = d3.select('svg#svg defs #linearGradients');
|
||||
let linearGradient = gradientDefs.select('linearGradient#' + escapedId);
|
||||
// If the linear gradient is not there yet, create it.
|
||||
|
||||
154
tensorflow/tensorboard/components/tf-graph-common/lib/util.ts
Normal file
154
tensorflow/tensorboard/components/tf-graph-common/lib/util.ts
Normal file
@ -0,0 +1,154 @@
|
||||
/* Copyright 2015 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed 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.
|
||||
==============================================================================*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utility functions for the tensorflow graph visualizer.
|
||||
*/
|
||||
|
||||
module tf.graph.util {
|
||||
/**
|
||||
* Recommended delay (ms) when running an expensive task asynchronously
|
||||
* that gives enough time for the progress bar to update its UI.
|
||||
*/
|
||||
const ASYNC_TASK_DELAY = 20;
|
||||
|
||||
export function time<T>(msg: string, task: () => T) {
|
||||
let start = Date.now();
|
||||
let result = task();
|
||||
/* tslint:disable */
|
||||
console.log(msg, ':', Date.now() - start, 'ms');
|
||||
/* tslint:enable */
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker that sets the progress property of the
|
||||
* provided polymer component. The provided component must have
|
||||
* a property called 'progress' that is not read-only. The progress
|
||||
* property is an object with a numerical 'value' property and a
|
||||
* string 'msg' property.
|
||||
*/
|
||||
export function getTracker(polymerComponent: any) {
|
||||
return {
|
||||
setMessage: function(msg) {
|
||||
polymerComponent.set(
|
||||
'progress', {value: polymerComponent.progress.value, msg: msg});
|
||||
},
|
||||
updateProgress: function(value) {
|
||||
polymerComponent.set('progress', {
|
||||
value: polymerComponent.progress.value + value,
|
||||
msg: polymerComponent.progress.msg
|
||||
});
|
||||
},
|
||||
reportError: function(msg: string, err) {
|
||||
// Log the stack trace in the console.
|
||||
console.error(err.stack);
|
||||
// And send a user-friendly message to the UI.
|
||||
polymerComponent.set(
|
||||
'progress',
|
||||
{value: polymerComponent.progress.value, msg: msg, error: true});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker for a subtask given the parent tracker, the total
|
||||
* progress
|
||||
* of the subtask and the subtask message. The parent task should pass a
|
||||
* subtracker to its subtasks. The subtask reports its own progress which
|
||||
* becames relative to the main task.
|
||||
*/
|
||||
export function getSubtaskTracker(
|
||||
parentTracker: ProgressTracker, impactOnTotalProgress: number,
|
||||
subtaskMsg: string): ProgressTracker {
|
||||
return {
|
||||
setMessage: function(progressMsg) {
|
||||
// The parent should show a concatenation of its message along with
|
||||
// its subtask tracker message.
|
||||
parentTracker.setMessage(subtaskMsg + ': ' + progressMsg);
|
||||
},
|
||||
updateProgress: function(incrementValue) {
|
||||
// Update the parent progress relative to the child progress.
|
||||
// For example, if the sub-task progresses by 30%, and the impact on the
|
||||
// total progress is 50%, then the task progresses by 30% * 50% = 15%.
|
||||
parentTracker.updateProgress(
|
||||
incrementValue * impactOnTotalProgress / 100);
|
||||
},
|
||||
reportError: function(msg: string, err: Error) {
|
||||
// The parent should show a concatenation of its message along with
|
||||
// its subtask error message.
|
||||
parentTracker.reportError(subtaskMsg + ': ' + msg, err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an expensive task and return the result.
|
||||
*/
|
||||
export function runTask<T>(
|
||||
msg: string, incProgressValue: number, task: () => T,
|
||||
tracker: ProgressTracker): T {
|
||||
// Update the progress message to say the current running task.
|
||||
tracker.setMessage(msg);
|
||||
// Run the expensive task with a delay that gives enough time for the
|
||||
// UI to update.
|
||||
try {
|
||||
let result = tf.graph.util.time(msg, task);
|
||||
// Update the progress value.
|
||||
tracker.updateProgress(incProgressValue);
|
||||
// Return the result to be used by other tasks.
|
||||
return result;
|
||||
} catch (e) {
|
||||
// Errors that happen inside asynchronous tasks are
|
||||
// reported to the tracker using a user-friendly message.
|
||||
tracker.reportError('Failed ' + msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an expensive task asynchronously and returns a promise of the result.
|
||||
*/
|
||||
export function runAsyncTask<T>(
|
||||
msg: string, incProgressValue: number, task: () => T,
|
||||
tracker: ProgressTracker): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Update the progress message to say the current running task.
|
||||
tracker.setMessage(msg);
|
||||
// Run the expensive task with a delay that gives enough time for the
|
||||
// UI to update.
|
||||
setTimeout(function() {
|
||||
try {
|
||||
let result = tf.graph.util.time(msg, task);
|
||||
// Update the progress value.
|
||||
tracker.updateProgress(incProgressValue);
|
||||
// Return the result to be used by other tasks.
|
||||
resolve(result);
|
||||
} catch (e) {
|
||||
// Errors that happen inside asynchronous tasks are
|
||||
// reported to the tracker using a user-friendly message.
|
||||
tracker.reportError('Failed ' + msg, e);
|
||||
}
|
||||
}, ASYNC_TASK_DELAY);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query selector with escaped special characters that are not
|
||||
* allowed in a query selector.
|
||||
*/
|
||||
export function escapeQuerySelector(querySelector: string): string {
|
||||
return querySelector.replace(/([:.\[\],/\\\(\)])/g, '\\$1');
|
||||
}
|
||||
}
|
||||
@ -3,17 +3,18 @@
|
||||
<link rel="import" href="../tf-imports/graphlib.html">
|
||||
<link rel="import" href="../tf-imports/lodash.html">
|
||||
|
||||
<script src="lib/colors.js"></script>
|
||||
<script src="lib/common.js"></script>
|
||||
<script src="lib/externs.js"></script>
|
||||
<script src="lib/graph.js"></script>
|
||||
<script src="lib/parser.js"></script>
|
||||
<script src="lib/hierarchy.js"></script>
|
||||
<script src="lib/layout.js"></script>
|
||||
<script src="lib/parser.js"></script>
|
||||
<script src="lib/render.js"></script>
|
||||
<script src="lib/template.js"></script>
|
||||
<script src="lib/scene/scene.js"></script>
|
||||
<script src="lib/scene/annotation.js"></script>
|
||||
<script src="lib/scene/contextmenu.js"></script>
|
||||
<script src="lib/scene/edge.js"></script>
|
||||
<script src="lib/scene/node.js"></script>
|
||||
<script src="lib/scene/contextmenu.js"></script>
|
||||
<script src="lib/layout.js"></script>
|
||||
<script src="lib/colors.js"></script>
|
||||
<script src="lib/scene/scene.js"></script>
|
||||
<script src="lib/template.js"></script>
|
||||
<script src="lib/util.js"></script>
|
||||
|
||||
@ -66,7 +66,7 @@ Polymer({
|
||||
value: 0,
|
||||
msg: ''
|
||||
});
|
||||
var tracker = tf.getTracker(this);
|
||||
var tracker = tf.graph.util.getTracker(this);
|
||||
tf.graph.parser.fetchAndParseMetadata(path, tracker)
|
||||
.then(function(stats) {
|
||||
this._setOutStats(stats);
|
||||
@ -78,7 +78,7 @@ Polymer({
|
||||
value: 0,
|
||||
msg: ''
|
||||
});
|
||||
var tracker = tf.getTracker(this);
|
||||
var tracker = tf.graph.util.getTracker(this);
|
||||
var hierarchyParams = {
|
||||
verifyTemplate: true,
|
||||
// If a set of numbered op nodes has at least this number of nodes
|
||||
@ -91,7 +91,7 @@ Polymer({
|
||||
seriesMap: {},
|
||||
};
|
||||
this._setOutHierarchyParams(hierarchyParams);
|
||||
var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data');
|
||||
var dataTracker = tf.graph.util.getSubtaskTracker(tracker, 30, 'Data');
|
||||
tf.graph.parser.fetchAndParseGraphData(path, pbTxtFile, dataTracker)
|
||||
.then(function(graph) {
|
||||
// Build the flat graph (consists only of Op nodes).
|
||||
@ -119,12 +119,12 @@ Polymer({
|
||||
outEmbeddingTypes: ['^[a-zA-Z]+Summary$'],
|
||||
refEdges: refEdges
|
||||
};
|
||||
var graphTracker = tf.getSubtaskTracker(tracker, 20, 'Graph');
|
||||
var graphTracker = tf.graph.util.getSubtaskTracker(tracker, 20, 'Graph');
|
||||
return tf.graph.build(graph, buildParams, graphTracker);
|
||||
})
|
||||
.then(function(graph) {
|
||||
this._setOutGraph(graph);
|
||||
var hierarchyTracker = tf.getSubtaskTracker(tracker, 50,
|
||||
var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 50,
|
||||
'Namespace hierarchy');
|
||||
return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker);
|
||||
}.bind(this))
|
||||
|
||||
@ -569,12 +569,12 @@ Polymer({
|
||||
/** Main method for building the scene */
|
||||
_build: function(renderHierarchy) {
|
||||
this.templateIndex = renderHierarchy.hierarchy.getTemplateIndex();
|
||||
tf.time('tf-graph-scene (layout):', function() {
|
||||
tf.graph.util.time('tf-graph-scene (layout):', function() {
|
||||
// layout the scene for this meta / series node
|
||||
tf.graph.layout.layoutScene(renderHierarchy.root, this);
|
||||
}.bind(this));
|
||||
|
||||
tf.time('tf-graph-scene (build scene):', function() {
|
||||
tf.graph.util.time('tf-graph-scene (build scene):', function() {
|
||||
tf.graph.scene.buildGroup(d3.select(this.$.root), renderHierarchy.root, this);
|
||||
tf.graph.scene.addGraphClickListener(this.$.svg, this);
|
||||
}.bind(this));
|
||||
|
||||
@ -113,7 +113,7 @@ Polymer({
|
||||
}
|
||||
},
|
||||
_buildRenderHierarchy: function(graphHierarchy) {
|
||||
tf.time('new tf.graph.render.Hierarchy', function() {
|
||||
tf.graph.util.time('new tf.graph.render.Hierarchy', function() {
|
||||
if (graphHierarchy.root.type !== tf.graph.NodeType.META) {
|
||||
// root must be metanode but sometimes Polymer's dom-if has not
|
||||
// remove tf-graph element yet in <tf-node-info>
|
||||
@ -265,8 +265,8 @@ Polymer({
|
||||
value: 0,
|
||||
msg: ''
|
||||
});
|
||||
var tracker = tf.getTracker(this);
|
||||
var hierarchyTracker = tf.getSubtaskTracker(tracker, 100,
|
||||
var tracker = tf.graph.util.getTracker(this);
|
||||
var hierarchyTracker = tf.graph.util.getSubtaskTracker(tracker, 100,
|
||||
'Namespace hierarchy');
|
||||
tf.graph.hierarchy.build(this.basicGraph, this.hierarchyParams, hierarchyTracker)
|
||||
.then(function(graphHierarchy) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user