Skip to content

Commit

Permalink
Move the creation of Runtime dot graph to a separate submodule (#1025)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbehjati authored May 26, 2020
1 parent dd18be8 commit e075a5a
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 263 deletions.
2 changes: 1 addition & 1 deletion oak/server/rust/oak_runtime/src/runtime/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.
//

use crate::{message::Message, runtime::DotIdentifier};
use crate::{message::Message, runtime::graph::DotIdentifier};
use log::debug;
use oak_abi::OakStatus;
use std::{
Expand Down
286 changes: 286 additions & 0 deletions oak/server/rust/oak_runtime/src/runtime/graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
//
// Copyright 2020 The Project Oak Authors
//
// 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.
//

/// This module defines a trait and an implementation for representing the [`Runtime`] as
/// a Graphviz dot graph. This is used in the introspection service.
use crate::runtime::{ChannelHalfDirection, NodeId, Runtime};
use itertools::Itertools;
use std::{fmt::Write, string::String};

/// Trait that gives an identifier for a data structure that is suitable for
/// use with Graphviz/Dot.
pub trait DotIdentifier {
fn dot_id(&self) -> String;
}

/// Trait that returns the path at which the debug introspection server will
/// show a page for a data structure.
pub trait HtmlPath {
fn html_path(&self) -> String;
}

impl DotIdentifier for NodeId {
fn dot_id(&self) -> String {
format!("node{}", self.0)
}
}

impl HtmlPath for NodeId {
fn html_path(&self) -> String {
format!("/node/{}", self.0)
}
}

impl DotIdentifier for (NodeId, oak_abi::Handle) {
fn dot_id(&self) -> String {
format!("{}_{}", self.0.dot_id(), self.1)
}
}

impl HtmlPath for (NodeId, oak_abi::Handle) {
fn html_path(&self) -> String {
format!("{}/{}", self.0.html_path(), self.1)
}
}

pub trait DotGraph {
fn graph(&self) -> String;
fn html(&self) -> String;
fn html_for_node(&self, id: u64) -> Option<String>;
fn html_for_handle(&self, node_id: u64, handle: oak_abi::Handle) -> Option<String>;
}

impl DotGraph for Runtime {
/// Generate a Graphviz dot graph that shows the current shape of the Nodes and Channels in
/// the runtime.
fn graph(&self) -> String {
let mut s = String::new();
writeln!(&mut s, "digraph Runtime {{").unwrap();
// Graph nodes for Oak Nodes and ABI handles.
{
writeln!(&mut s, " {{").unwrap();
writeln!(
&mut s,
" node [shape=box style=filled fillcolor=red fontsize=24]"
)
.unwrap();
let node_infos = self.node_infos.read().unwrap();
for node_id in node_infos.keys().sorted() {
let node_info = node_infos.get(node_id).unwrap();
write!(
&mut s,
r###" {} [label="{}" URL="{}"]"###,
node_id.dot_id(),
node_info.name,
node_id.html_path(),
)
.unwrap();
if node_info.node_stopper.is_none() {
write!(&mut s, " [style=dashed]").unwrap();
}
writeln!(&mut s).unwrap();
}
writeln!(&mut s, " }}").unwrap();
}
{
writeln!(&mut s, " {{").unwrap();
writeln!(
&mut s,
" node [shape=hexagon style=filled fillcolor=orange]"
)
.unwrap();
let node_infos = self.node_infos.read().unwrap();
for node_id in node_infos.keys().sorted() {
let node_info = node_infos.get(node_id).unwrap();
for handle in node_info.abi_handles.keys() {
writeln!(
&mut s,
r###" {} [label="{}:{}" URL="{}"]"###,
(*node_id, *handle).dot_id(),
node_id.0,
handle,
(*node_id, *handle).html_path(),
)
.unwrap();
}
}
writeln!(&mut s, " }}").unwrap();
}
{
writeln!(&mut s, " {{").unwrap();
writeln!(
&mut s,
" node [shape=ellipse style=filled fillcolor=green]"
)
.unwrap();
let node_infos = self.node_infos.read().unwrap();
for node_id in node_infos.keys().sorted() {
let node_info = node_infos.get(node_id).unwrap();
for half in node_info.abi_handles.values() {
writeln!(&mut s, " {}", half.dot_id(),).unwrap();
}
}
writeln!(&mut s, " }}").unwrap();
}

// Edges for connections between Nodes and channels and messages.
let mut msg_counter = 0;
{
let node_infos = self.node_infos.read().unwrap();
for node_id in node_infos.keys().sorted() {
let node_info = node_infos.get(node_id).unwrap();
for (handle, half) in &node_info.abi_handles {
match half.direction {
ChannelHalfDirection::Write => {
writeln!(
&mut s,
" {} -> {} -> {}",
node_id.dot_id(),
(*node_id, *handle).dot_id(),
half.dot_id()
)
.unwrap();
}
ChannelHalfDirection::Read => {
writeln!(
&mut s,
" {} -> {} -> {}",
half.dot_id(),
(*node_id, *handle).dot_id(),
node_id.dot_id()
)
.unwrap();
}
}
// Include messages in the channel.
let messages = half.get_messages();
if messages.len() > 0 {
writeln!(&mut s, " {{").unwrap();
writeln!(
&mut s,
r###" node [shape=rect fontsize=10 label="msg"]"###
)
.unwrap();
// Messages have no identifier, so just use a count (and don't make it
// visible to the user).
let mut prev_graph_node = half.dot_id();
for msg in messages.iter() {
msg_counter += 1;
let graph_node = format!("msg{}", msg_counter);
writeln!(
&mut s,
" {} -> {} [style=dashed arrowhead=none]",
graph_node, prev_graph_node
)
.unwrap();
for half in &msg.channels {
match half.direction {
ChannelHalfDirection::Write => {
writeln!(&mut s, " {} -> {}", graph_node, half.dot_id())
.unwrap();
}
ChannelHalfDirection::Read => {
writeln!(&mut s, " {} -> {}", half.dot_id(), graph_node)
.unwrap();
}
}
}
prev_graph_node = graph_node;
}
writeln!(&mut s, " }}").unwrap();
}
}
}
}

writeln!(&mut s, "}}").unwrap();
s
}

/// Generate an HTML page that describes the internal state of the [`Runtime`].
fn html(&self) -> String {
let mut s = String::new();
writeln!(&mut s, "<h2>Nodes</h2>").unwrap();
writeln!(&mut s, r###"<p><a href="/graph">Show as graph</a><ul>"###).unwrap();
{
let node_infos = self.node_infos.read().unwrap();
for node_id in node_infos.keys().sorted() {
let node_info = node_infos.get(node_id).unwrap();
write!(
&mut s,
r###"<li><a href="{}">{:?}</a> => <tt>{:?}</tt>"###,
node_id.html_path(),
node_id,
node_info
)
.unwrap();
}
}
writeln!(&mut s, "</ul>").unwrap();
s
}

/// Generate an HTML page that describes the internal state of a specific Node.
fn html_for_node(&self, id: u64) -> Option<String> {
let node_id = NodeId(id);
let node_infos = self.node_infos.read().unwrap();
let node_info = node_infos.get(&node_id)?;
let mut s = String::new();
write!(&mut s, "<h2>{}</h2>", node_info.name).unwrap();
if let Some(node_stopper) = &node_info.node_stopper {
write!(
&mut s,
"<p>Node thread is currently running, join_handle={:?}",
node_stopper.join_handle
)
.unwrap();
} else {
write!(&mut s, "<p>No current thread for Node.").unwrap();
}
write!(&mut s, "<p>Label={:?}<p>Handles:<ul>", node_info.label).unwrap();
for (handle, half) in &node_info.abi_handles {
write!(
&mut s,
r###"<li>Handle <a href="{}">{}</a> => {:?}"###,
(node_id, *handle).html_path(),
handle,
half
)
.unwrap();
}
Some(s)
}

/// Generate an HTML page that describes the channel accessible via an ABI handle for the
/// specified Node.
fn html_for_handle(&self, node_id: u64, handle: oak_abi::Handle) -> Option<String> {
let node_id = NodeId(node_id);
let node_infos = self.node_infos.read().unwrap();
let node_info = node_infos.get(&node_id)?;
let half = node_info.abi_handles.get(&handle)?;
let mut s = String::new();
write!(
&mut s,
r###"<h2><a href="{}">Node {}</a> Handle {}</h2>"###,
node_id.html_path(),
node_info.name,
handle,
)
.unwrap();
write!(&mut s, r###"<p>Maps to {:?}"###, half).unwrap();
Some(s)
}
}
2 changes: 1 addition & 1 deletion oak/server/rust/oak_runtime/src/runtime/introspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.
//

use crate::runtime::Runtime;
use crate::runtime::{graph::DotGraph, Runtime};
use hyper::{
service::{make_service_fn, service_fn},
Body, Error, Method, Request, Response, Server, StatusCode,
Expand Down
Loading

0 comments on commit e075a5a

Please sign in to comment.