From b160ae7999a2eab64cb91c82b2ca6d7c222c6449 Mon Sep 17 00:00:00 2001 From: arvidn Date: Wed, 5 Aug 2015 00:45:58 -0400 Subject: [PATCH] support visualizing simulated network with dot --- include/simulator/simulator.hpp | 39 ++++++- src/default_config.cpp | 6 +- src/io_service.cpp | 6 ++ src/queue.cpp | 30 +++++- src/simulation.cpp | 23 +++++ src/simulator.cpp | 178 ++++++++++++++++++++++++++++++++ test/acceptor.cpp | 2 + test/null_buffers.cpp | 2 + 8 files changed, 281 insertions(+), 5 deletions(-) diff --git a/include/simulator/simulator.hpp b/include/simulator/simulator.hpp index f51b93e..5f866fa 100644 --- a/include/simulator/simulator.hpp +++ b/include/simulator/simulator.hpp @@ -31,6 +31,7 @@ All rights reserved. #include #include #include +#include #include #include #include @@ -49,6 +50,11 @@ namespace sim struct sink { virtual void incoming_packet(aux::packet p) = 0; + + // used for visualization + virtual std::string label() const = 0; + + virtual std::string attributes() const { return "shape=box"; } }; // this represents a network route (a series of sinks to pass a packet @@ -83,6 +89,8 @@ namespace sim { hops.insert(hops.end(), r.hops.begin(), r.hops.end()); } void append(std::shared_ptr s) { hops.push_back(s); } bool empty() const { return hops.empty(); } + std::shared_ptr last() const + { return hops.back(); } private: @@ -503,6 +511,8 @@ namespace sim // implements sink virtual void incoming_packet(aux::packet p) override final; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } void async_receive_from_impl(std::vector const& bufs , udp::endpoint* sender @@ -709,6 +719,8 @@ namespace sim // implements sink virtual void incoming_packet(aux::packet p) override; + virtual std::string label() const override final + { return m_bound_to.address().to_string(); } void internal_connect(tcp::endpoint const& bind_ip , std::shared_ptr const& c @@ -901,6 +913,14 @@ namespace sim io_service(sim::simulation& sim, ip::address const& ip); io_service(); + ~io_service(); + + // not copyable and non movable (it's not movable because we currently + // keep pointers to the io_service instances in the simulator object) + io_service(io_service const&) = delete; + io_service(io_service&&) = delete; + io_service& operator=(io_service const&) = delete; + io_service& operator=(io_service&&) = delete; std::size_t run(boost::system::error_code& ec); std::size_t run(); @@ -947,6 +967,7 @@ namespace sim { return m_incoming_route; } int get_path_mtu(asio::ip::address ip) const; + ip::address get_ip() const { return m_ip; } private: @@ -1064,6 +1085,10 @@ namespace sim configuration& config() const { return m_config; } + void add_io_service(asio::io_service* ios); + void remove_io_service(asio::io_service* ios); + std::vector get_all_io_services() const; + private: struct timer_compare { @@ -1074,6 +1099,9 @@ namespace sim configuration& m_config; + // these are the io services that represent nodes on the network + std::unordered_set m_nodes; + // all non-expired timers typedef std::multiset timer_queue_t; timer_queue_t m_timer_queue; @@ -1160,6 +1188,9 @@ namespace sim m_dst->incoming_packet(std::move(p)); } + virtual std::string label() const override final + { return m_dst ? m_dst->label() : ""; } + void clear() { m_dst = nullptr; } private: @@ -1202,10 +1233,12 @@ namespace sim { queue(asio::io_service& ios, int bandwidth , chrono::high_resolution_clock::duration propagation_delay - , int max_queue_size); + , int max_queue_size, std::string name = "queue"); virtual void incoming_packet(aux::packet p) override final; + virtual std::string label() const override final; + private: void begin_send_next_packet(); @@ -1226,12 +1259,16 @@ namespace sim // the number of bytes currently in the packet queue int m_queue_size; + std::string m_node_name; + // this is the queue of packets and the time each packet was enqueued std::vector> m_queue; asio::high_resolution_timer m_forward_timer; chrono::high_resolution_clock::time_point m_last_forward; }; + + void dump_network_graph(simulation const& s, std::string filename); } #endif // SIMULATOR_HPP_INCLUDED diff --git a/src/default_config.cpp b/src/default_config.cpp index 2ebb652..36504af 100644 --- a/src/default_config.cpp +++ b/src/default_config.cpp @@ -31,7 +31,7 @@ namespace sim { // 0 bandwidth and 0 queue means infinite. The network itself only adds // 50 ms latency m_network = std::make_shared(std::ref(sim.get_io_service()) - , 0, duration_cast(milliseconds(30)), 0); + , 0, duration_cast(milliseconds(30)), 0, "network"); m_sim = ∼ } @@ -49,7 +49,7 @@ namespace sim { if (it != m_incoming.end()) return route(it->second); it = m_incoming.insert(it, std::make_pair(ip, std::make_shared( std::ref(m_sim->get_io_service()), 800 * 1000 - , duration_cast(milliseconds(1)), 200 * 1000))); + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem in"))); return route(it->second); } @@ -68,7 +68,7 @@ namespace sim { if (it != m_outgoing.end()) return route(it->second); it = m_outgoing.insert(it, std::make_pair(ip, std::make_shared( std::ref(m_sim->get_io_service()), 200 * 1000 - , duration_cast(milliseconds(1)), 200 * 1000 ))); + , duration_cast(milliseconds(1)), 200 * 1000, "DSL modem out"))); return route(it->second); } diff --git a/src/io_service.cpp b/src/io_service.cpp index 976a13d..0d0d407 100644 --- a/src/io_service.cpp +++ b/src/io_service.cpp @@ -30,6 +30,12 @@ namespace sim { namespace asio { { m_outgoing_route = m_sim.config().outgoing_route(ip); m_incoming_route = m_sim.config().incoming_route(ip); + m_sim.add_io_service(this); + } + + io_service::~io_service() + { + m_sim.remove_io_service(this); } io_service::io_service() diff --git a/src/queue.cpp b/src/queue.cpp index b18a774..bf27242 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -27,15 +27,43 @@ namespace sim queue::queue(asio::io_service& ios , int bandwidth , chrono::high_resolution_clock::duration propagation_delay - , int max_queue_size) + , int max_queue_size + , std::string name) : m_max_queue_size(max_queue_size) , m_forwarding_latency(propagation_delay) , m_bandwidth(bandwidth) , m_queue_size(0) + , m_node_name(name) , m_forward_timer(ios) , m_last_forward(chrono::high_resolution_clock::now()) {} + std::string queue::label() const + { + char ret[400]; + int p = snprintf(ret, sizeof(ret), "%s\n", m_node_name.c_str()); + + if (m_bandwidth != 0) + { + p += snprintf(ret + p, sizeof(ret) - p, "rate: %d kB/s\n" + , m_bandwidth / 1000); + } + + if (m_queue_size != 0) + { + p += snprintf(ret + p, sizeof(ret) - p, "queue: %d kB\n" + , m_queue_size / 1000); + } + + if (m_forwarding_latency.count() != 0) + { + p += snprintf(ret + p, sizeof(ret) - p, "latency: %d ms\n" + , int(chrono::duration_cast(m_forwarding_latency).count())); + } + + return ret; + } + void queue::incoming_packet(aux::packet p) { const int packet_size = p.buffer.size() + p.overhead; diff --git a/src/simulation.cpp b/src/simulation.cpp index 27912e4..271a7d0 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -255,5 +255,28 @@ namespace sim return network_route; } + void simulation::add_io_service(asio::io_service* ios) + { + bool added = m_nodes.insert(ios).second; + assert(added); + } + + void simulation::remove_io_service(asio::io_service* ios) + { + auto it = m_nodes.find(ios); + assert(it != m_nodes.end()); + m_nodes.erase(it); + } + + std::vector simulation::get_all_io_services() const + { + std::vector ret; + ret.reserve(m_nodes.size()); + std::remove_copy_if( + m_nodes.begin(), m_nodes.end(), std::back_inserter(ret) + , [](io_service* ios) { return ios->get_ip() == asio::ip::address(); }); + return ret; + } + } diff --git a/src/simulator.cpp b/src/simulator.cpp index 80ed4f1..82164e4 100644 --- a/src/simulator.cpp +++ b/src/simulator.cpp @@ -19,6 +19,8 @@ All rights reserved. #include "simulator/simulator.hpp" #include +#include +#include #include typedef sim::chrono::high_resolution_clock::time_point time_point; @@ -49,6 +51,182 @@ void forward_packet(aux::packet p) next_hop->incoming_packet(std::move(p)); } +namespace +{ + // this is a dummy sink for endpoints, wrapping an io_service + struct endpoint : sink + { + endpoint(asio::io_service& ios) + : m_ios(ios) + {} + + virtual void incoming_packet(aux::packet p) override final { assert(false); } + + virtual std::string label() const override final + { + return m_ios.get_ip().to_string(); + } + + virtual std::string attributes() const override final + { + return "shape=ellipse"; + } + + private: + asio::io_service& m_ios; + }; +} + +namespace +{ + std::string escape_label(std::string n) + { + std::string ret; + for (auto c : n) + { + if (c == '\n') + { + ret += "\\n"; + continue; + } + if (c == '\"') + { + ret += "\\\""; + continue; + } + ret += c; + } + return ret; + } +} + +void dump_network_graph(simulation const& s, std::string filename) +{ + // all edges (directed). + std::set, std::shared_ptr>> edges; + + // all network nodes + std::unordered_set> nodes; + + // local nodes (subgrapgs) + std::vector>> local_nodes; + + const std::vector io_services = s.get_all_io_services(); + + for (auto ios : io_services) + { + std::shared_ptr ep = std::make_shared(*ios); + local_nodes.push_back({}); + local_nodes.back().insert(ep); + + route in = ios->get_incoming_route(); + route out = ios->get_outgoing_route(); + + // this is the outgoing node for this endpoint. This is + // how it connects to the network. + const std::shared_ptr egress = out.empty() ? ep : out.last(); + + // first add both the incoming and outgoing chains + std::shared_ptr prev; + while (!in.empty()) + { + auto node = in.pop_front(); + local_nodes.back().insert(node); + if (prev) edges.insert({prev, node}); + prev = node; + } + if (prev) edges.insert({prev, ep}); + + prev = ep; + while (!out.empty()) + { + auto node = out.pop_front(); + local_nodes.back().insert(node); + edges.insert({prev, node}); + prev = node; + } + + // then connect the endpoint of those chains to the rest of the network. + // Since the network may be arbitrarily complex, we actually have to + // completely iterate over all other endpoints + + for (auto ios2 : io_services) + { + route network = s.config().channel_route( + ios->get_ip(), ios2->get_ip()); + + std::shared_ptr last = ios2->get_incoming_route().next_hop(); + + prev = egress; + while (!network.empty()) + { + auto node = network.pop_front(); + nodes.insert(node); + edges.insert({prev, node}); + prev = node; + } + edges.insert({prev, last}); + } + } + + // by now, the nodes and edges should represent the complete graph. Render it + // into dot. + + FILE* f = fopen(filename.c_str(), "w+"); + + fprintf(f, "digraph network {\n" + "concentrate=true;\n" + "overlap=scale;\n" + "splines=true;\n"); + + fprintf(f, "\n// nodes\n\n"); + + for (auto n : nodes) + { + std::string attributes = n->attributes(); + fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"red\"%s%s];\n" + , n.get() + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + fprintf(f, "\n// local networks\n\n"); + + int idx = 0; + for (auto ln : local_nodes) + { + fprintf(f, "subgraph cluster_%d {\n", idx++); + + for (auto n : ln) + { + std::string attributes = n->attributes(); + fprintf(f, " \"%p\" [label=\"%s\",style=\"filled\",color=\"green\"%s%s];\n" + , n.get() + , escape_label(n->label()).c_str() + , attributes.empty() ? "" : ", " + , attributes.c_str()); + } + + fprintf(f, "}\n"); + } + + fprintf(f, "\n// edges\n\n"); + + while (!edges.empty()) + { + auto edge = *edges.begin(); + edges.erase(edges.begin()); + + fprintf(f, "\"%p\" -> \"%p\"\n" + , edge.first.get() + , edge.second.get()); + } + + fprintf(f, "}\n"); + fclose(f); +} + namespace aux { int channel::remote_idx(asio::ip::tcp::endpoint self) const diff --git a/test/acceptor.cpp b/test/acceptor.cpp index 2370857..1634cb7 100644 --- a/test/acceptor.cpp +++ b/test/acceptor.cpp @@ -152,6 +152,8 @@ int main() , std::bind(&incoming_connection, _1, std::ref(incoming) , std::cref(remote_endpoint))); + dump_network_graph(sim, "accept.dot"); + printf("[%4d] connecting\n", millis); ip::tcp::socket outgoing(outgoing_ios); outgoing.open(ip::tcp::v4(), ec); diff --git a/test/null_buffers.cpp b/test/null_buffers.cpp index 24c823d..a7d8222 100644 --- a/test/null_buffers.cpp +++ b/test/null_buffers.cpp @@ -165,6 +165,8 @@ int main() listener.listen(10, ec); if (ec) printf("[%4d] listen failed: %s\n", millis, ec.message().c_str()); + dump_network_graph(sim, "null_buffers.dot"); + ip::tcp::socket incoming(incoming_ios); ip::tcp::endpoint remote_endpoint; listener.async_accept(incoming, remote_endpoint