From de0af97b3a7ca17d751af75e3fb35105daa57a33 Mon Sep 17 00:00:00 2001 From: "Gustavo J. A. M. Carneiro" Date: Mon, 6 Dec 2010 17:26:06 +0000 Subject: [PATCH] Codereview Issue 2692041: Add pyviz module --- doc/doxygen.conf | 4 +- src/tools/visualizer/doc/readme.txt | 9 + src/tools/visualizer/examples/readme.txt | 3 + src/tools/visualizer/model/pyviz.cc | 1406 ++++++++++++++++ src/tools/visualizer/model/pyviz.h | 225 +++ .../visualizer/model/visual-simulator-impl.cc | 228 +++ .../visualizer/model/visual-simulator-impl.h | 82 + .../visualizer/model/visualizer-ideas.txt | 16 + src/tools/visualizer/visualizer/__init__.py | 3 + src/tools/visualizer/visualizer/base.py | 107 ++ src/tools/visualizer/visualizer/core.py | 1470 +++++++++++++++++ .../visualizer/visualizer/higcontainer.py | 97 ++ src/tools/visualizer/visualizer/hud.py | 144 ++ .../visualizer/visualizer/ipython_view.py | 296 ++++ .../plugins/interface_statistics.py | 167 ++ .../visualizer/plugins/ipv4_routing_table.py | 95 ++ .../visualizer/visualizer/plugins/olsr.py | 102 ++ .../visualizer/plugins/show_last_packets.py | 231 +++ .../plugins/wifi_intrastructure_link.py | 117 ++ .../visualizer/resource/Basurero_Palm_Z22.svg | 311 ++++ .../adriankierman_cell_phone_tower.svg | 38 + .../resource/bobocal_Yellow_Bus.svg | 39 + .../resource/thilakarathna_Bus_Halt.svg | 90 + src/tools/visualizer/visualizer/svgitem.py | 156 ++ src/tools/visualizer/wscript | 22 + src/wscript | 1 + wscript | 9 +- wutils.py | 13 +- 28 files changed, 5474 insertions(+), 7 deletions(-) create mode 100644 src/tools/visualizer/doc/readme.txt create mode 100644 src/tools/visualizer/examples/readme.txt create mode 100644 src/tools/visualizer/model/pyviz.cc create mode 100644 src/tools/visualizer/model/pyviz.h create mode 100644 src/tools/visualizer/model/visual-simulator-impl.cc create mode 100644 src/tools/visualizer/model/visual-simulator-impl.h create mode 100644 src/tools/visualizer/model/visualizer-ideas.txt create mode 100644 src/tools/visualizer/visualizer/__init__.py create mode 100644 src/tools/visualizer/visualizer/base.py create mode 100644 src/tools/visualizer/visualizer/core.py create mode 100644 src/tools/visualizer/visualizer/higcontainer.py create mode 100644 src/tools/visualizer/visualizer/hud.py create mode 100644 src/tools/visualizer/visualizer/ipython_view.py create mode 100644 src/tools/visualizer/visualizer/plugins/interface_statistics.py create mode 100644 src/tools/visualizer/visualizer/plugins/ipv4_routing_table.py create mode 100644 src/tools/visualizer/visualizer/plugins/olsr.py create mode 100644 src/tools/visualizer/visualizer/plugins/show_last_packets.py create mode 100644 src/tools/visualizer/visualizer/plugins/wifi_intrastructure_link.py create mode 100644 src/tools/visualizer/visualizer/resource/Basurero_Palm_Z22.svg create mode 100644 src/tools/visualizer/visualizer/resource/adriankierman_cell_phone_tower.svg create mode 100644 src/tools/visualizer/visualizer/resource/bobocal_Yellow_Bus.svg create mode 100644 src/tools/visualizer/visualizer/resource/thilakarathna_Bus_Halt.svg create mode 100644 src/tools/visualizer/visualizer/svgitem.py create mode 100644 src/tools/visualizer/wscript diff --git a/doc/doxygen.conf b/doc/doxygen.conf index 465b1db1e..cc5912638 100644 --- a/doc/doxygen.conf +++ b/doc/doxygen.conf @@ -607,7 +607,9 @@ EXCLUDE = src/routing/olsr/olsr-state.h \ src/routing/olsr/olsr-repositories.h \ src/simulator/high-precision.h \ src/simulator/high-precision-128.h \ - src/simulator/high-precision-double.h + src/simulator/high-precision-double.h \ + src/tools/visualizer/model/visual-simulator-impl.h \ + src/tools/visualizer/model/pyviz.h # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded diff --git a/src/tools/visualizer/doc/readme.txt b/src/tools/visualizer/doc/readme.txt new file mode 100644 index 000000000..125f4f3eb --- /dev/null +++ b/src/tools/visualizer/doc/readme.txt @@ -0,0 +1,9 @@ +NS-3 PyViz is a live simulation visualizer, meaning that it uses no +trace files. It can be most useful for debugging purposes, i.e. to +figure out if mobility models are what you expect, where packets are +being dropped, etc. There's also a builtin interactive python console +that can be used to debug the state of the running objects. Although +it is mostly written in Python, it works both with Python and pure C++ +simulations. + +For more information, see http://www.nsnam.org/wiki/index.php/PyViz diff --git a/src/tools/visualizer/examples/readme.txt b/src/tools/visualizer/examples/readme.txt new file mode 100644 index 000000000..47c55f7f0 --- /dev/null +++ b/src/tools/visualizer/examples/readme.txt @@ -0,0 +1,3 @@ +For activating the visualizer, with any example, just pass the option +--SimulatorImplementationType=ns3::VisualSimulatorImpl to it, assuming +the script uses ns-3's command line parser (class CommandLine). diff --git a/src/tools/visualizer/model/pyviz.cc b/src/tools/visualizer/model/pyviz.cc new file mode 100644 index 000000000..2e9a4cc81 --- /dev/null +++ b/src/tools/visualizer/model/pyviz.cc @@ -0,0 +1,1406 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2008 INESC Porto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Gustavo Carneiro + */ + +#include +#include "pyviz.h" +#include "ns3/simulator.h" +#include "ns3/config.h" +#include "ns3/node-list.h" +#include "ns3/wifi-net-device.h" +#include "ns3/ppp-header.h" +#include "ns3/wifi-mac-header.h" +#include "ns3/ethernet-header.h" +#include "ns3/log.h" +#include "ns3/abort.h" + +#include "visual-simulator-impl.h" + +#include + +NS_LOG_COMPONENT_DEFINE ("PyViz"); +#define NUM_LAST_PACKETS 10 + +static +std::vector +PathSplit (std::string str) +{ + std::vector results; + size_t cutAt; + while ((cutAt = str.find_first_of('/')) != str.npos) + { + if(cutAt > 0) + { + results.push_back(str.substr(0,cutAt)); + } + str = str.substr(cutAt+1); + } + if (str.length() > 0) + { + results.push_back(str); + } + return results; +} + + +namespace ns3 { + +static PyViz* g_visualizer = NULL; + + + +struct PyVizPacketTag : public Tag +{ + static TypeId GetTypeId (void); + virtual TypeId GetInstanceTypeId (void) const; + virtual uint32_t GetSerializedSize (void) const; + virtual void Serialize (TagBuffer buf) const; + virtual void Deserialize (TagBuffer buf); + virtual void Print (std::ostream &os) const; + PyVizPacketTag (); + + uint32_t m_packetId; +}; + + +TypeId +PyVizPacketTag::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::PyVizPacketTag") + .SetParent () + .AddConstructor () + ; + return tid; +} +TypeId +PyVizPacketTag::GetInstanceTypeId (void) const +{ + return GetTypeId (); +} +uint32_t +PyVizPacketTag::GetSerializedSize (void) const +{ + return 4; +} +void +PyVizPacketTag::Serialize (TagBuffer buf) const +{ + buf.WriteU32 (m_packetId); +} +void +PyVizPacketTag::Deserialize (TagBuffer buf) +{ + m_packetId = buf.ReadU32 (); +} +void +PyVizPacketTag::Print (std::ostream &os) const +{ + os << "PacketId=" << m_packetId; +} +PyVizPacketTag::PyVizPacketTag () + : Tag () +{} + + + +PyViz::PyViz () +{ + NS_LOG_FUNCTION_NOARGS (); + NS_ASSERT (g_visualizer == NULL); + g_visualizer = this; + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacTx", + MakeCallback (&PyViz::TraceNetDevTxWifi, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacRx", + MakeCallback (&PyViz::TraceNetDevRxWifi, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacTx", + MakeCallback (&PyViz::TraceNetDevTxCsma, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacRx", + MakeCallback (&PyViz::TraceNetDevRxCsma, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacPromiscRx", + MakeCallback (&PyViz::TraceNetDevPromiscRxCsma, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/TxQueue/Drop", + MakeCallback (&PyViz::TraceDevQueueDrop, this)); + + Config::Connect ("/NodeList/*/$ns3::Ipv4L3Protocol/Drop", + MakeCallback (&PyViz::TraceIpv4Drop, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/MacTx", + MakeCallback (&PyViz::TraceNetDevTxPointToPoint, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/MacRx", + MakeCallback (&PyViz::TraceNetDevRxPointToPoint, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::WimaxNetDevice/Tx", + MakeCallback (&PyViz::TraceNetDevTxWimax, this)); + + Config::Connect ("/NodeList/*/DeviceList/*/$ns3::WimaxNetDevice/Rx", + MakeCallback (&PyViz::TraceNetDevRxWimax, this)); + +} + +void +PyViz::RegisterCsmaLikeDevice (std::string const &deviceTypeName) +{ + TypeId::LookupByName (deviceTypeName); // this will assert if the type name is invalid + + std::ostringstream sstream; + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/MacTx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevTxCsma, this)); + + sstream.str (""); + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevRxCsma, this)); + + sstream.str (""); + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/PromiscRx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevPromiscRxCsma, this)); +} + +void +PyViz::RegisterWifiLikeDevice (std::string const &deviceTypeName) +{ + TypeId::LookupByName (deviceTypeName); // this will assert if the type name is invalid + + std::ostringstream sstream; + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Tx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevTxWifi, this)); + + sstream.str (""); + sstream <<"/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevRxWifi, this)); +} + +void +PyViz::RegisterPointToPointLikeDevice (std::string const &deviceTypeName) +{ + TypeId::LookupByName (deviceTypeName); // this will assert if the type name is invalid + + std::ostringstream sstream; + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/TxQueue/Dequeue"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevTxPointToPoint, this)); + + sstream.str (""); + sstream << "/NodeList/*/DeviceList/*/$" << deviceTypeName << "/Rx"; + Config::Connect (sstream.str (), MakeCallback (&PyViz::TraceNetDevRxPointToPoint, this)); +} + +void +PyViz::SetPacketCaptureOptions (uint32_t nodeId, PacketCaptureOptions options) +{ + NS_LOG_DEBUG (" SetPacketCaptureOptions " << nodeId + << " PacketCaptureOptions (headers size = " << options.headers.size () + << " mode = " << options.mode << " numLastPackets = " << options.numLastPackets + << ")"); + m_packetCaptureOptions[nodeId] = options; +} + +void +PyViz::RegisterDropTracePath (std::string const &tracePath) +{ + Config::Connect (tracePath, MakeCallback (&PyViz::TraceDevQueueDrop, this)); +} + +PyViz::~PyViz () +{ + NS_LOG_FUNCTION_NOARGS (); + + NS_ASSERT (g_visualizer == this); + g_visualizer = NULL; +} + +void PyViz::DoPause (std::string const &message) +{ + m_pauseMessages.push_back (message); + m_stop = true; + NS_LOG_LOGIC (Simulator::Now ().GetSeconds () << ": Have " + << g_visualizer->m_pauseMessages.size () << " pause messages"); +} + +void PyViz::Pause (std::string const &message) +{ + NS_ASSERT (g_visualizer); + g_visualizer->DoPause (message); +} + +std::vector +PyViz::GetPauseMessages () const +{ + NS_LOG_LOGIC (Simulator::Now ().GetSeconds () << ": GetPauseMessages: have " + << g_visualizer->m_pauseMessages.size () << " pause messages"); + return m_pauseMessages; +} + + +void +PyViz::CallbackStopSimulation () +{ + NS_LOG_FUNCTION_NOARGS (); + Simulator::Stop (Seconds (0)); // Stop right now + m_stop = true; +} + +void +PyViz::SimulatorRunUntil (Time time) +{ + NS_LOG_LOGIC ("SimulatorRunUntil " << time << " (now is " << Simulator::Now () << ")"); + + m_pauseMessages.clear (); + m_transmissionSamples.clear (); + m_packetDrops.clear (); + + Time expirationTime = Simulator::Now () - Seconds (10); + + // Clear very old transmission records + for (std::map::iterator iter = m_txRecords.begin (); + iter != m_txRecords.end ();) + { + if (iter->second.time < expirationTime) + { + m_txRecords.erase (iter++); + } + else + { + iter++; + } + } + + // Clear very old packets of interest + for (std::map::iterator iter = m_packetsOfInterest.begin (); + iter != m_packetsOfInterest.end ();) + { + if (iter->second < expirationTime) + { + m_packetsOfInterest.erase (iter++); + } + else + { + iter++; + } + } + + if (Simulator::Now () >= time) + { + return; + } + // Schedule a dummy callback function for the target time, to make + // sure we stop at the right time. Otherwise, simulations with few + // events just appear to "jump" big chunks of time. + NS_LOG_LOGIC ("Schedule dummy callback to be called in " << (time - Simulator::Now ())); + m_stop = false; + Simulator::Cancel (m_stopCallbackEvent); + m_stopCallbackEvent = Simulator::Schedule (time - Simulator::Now (), &PyViz::CallbackStopSimulation, this); + + Ptr impl = Simulator::GetImplementation (); + Ptr visualImpl = DynamicCast (impl); + if (visualImpl) + { + visualImpl->RunRealSimulator (); + } + else + { + impl->Run (); + } +} + +bool PyViz::TransmissionSampleKey::operator < (PyViz::TransmissionSampleKey const &other) const +{ + if (this->transmitter < other.transmitter) + { + return true; + } + if (this->transmitter != other.transmitter) + { + return false; + } + if (this->receiver < other.receiver) + { + return true;} + if (this->receiver != other.receiver) + { + return false; + } + if (this->channel < other.channel) + { + return true; + } + else + { + return false; + } +} + +bool PyViz::TransmissionSampleKey::operator == (PyViz::TransmissionSampleKey const &other) const +{ + bool retval = (transmitter == other.transmitter) && + (receiver == other.receiver) && + (channel == other.channel); + return retval; +} + + +PyViz::NetDeviceStatistics & +PyViz::FindNetDeviceStatistics (int node, int interface) +{ + std::map >::iterator nodeStatsIter = m_nodesStatistics.find (node); + std::vector *stats; + if (nodeStatsIter == m_nodesStatistics.end ()) + { + stats = &m_nodesStatistics[node]; + stats->resize (NodeList::GetNode (node)->GetNDevices ()); + } + else + { + stats = &(nodeStatsIter->second); + } + NetDeviceStatistics &devStats = (*stats)[interface]; + return devStats; +} + +bool PyViz::GetPacketCaptureOptions (uint32_t nodeId, const PacketCaptureOptions **outOptions) const +{ + std::map::const_iterator iter = m_packetCaptureOptions.find (nodeId); + if (iter == m_packetCaptureOptions.end ()) + { + return false; + } + else + { + *outOptions = &iter->second; + return true; + } +} + +bool PyViz::FilterPacket (Ptr packet, const PacketCaptureOptions &options) +{ + switch (options.mode) + { + case PACKET_CAPTURE_DISABLED: + return false; + + case PACKET_CAPTURE_FILTER_HEADERS_OR: + { + PacketMetadata::ItemIterator metadataIterator = packet->BeginItem (); + while (metadataIterator.HasNext ()) + { + PacketMetadata::Item item = metadataIterator.Next (); + if (options.headers.find (item.tid) != options.headers.end ()) + { + return true; + } + } + return false; + } + + case PACKET_CAPTURE_FILTER_HEADERS_AND: + { + std::set missingHeaders (options.headers); + PacketMetadata::ItemIterator metadataIterator = packet->BeginItem (); + while (metadataIterator.HasNext ()) + { + PacketMetadata::Item item = metadataIterator.Next (); + std::set::iterator missingIter = missingHeaders.find (item.tid); + if (missingIter != missingHeaders.end ()) + { + missingHeaders.erase (missingIter); + } + } + if (missingHeaders.size () == 0) + { + return true; + } + else + { + return false; + } + } + + default: + NS_FATAL_ERROR ("should not be reached"); + return false; + } +} + +void +PyViz::TraceDevQueueDrop (std::string context, Ptr packet) +{ + NS_LOG_FUNCTION (context << packet->GetUid ()); + std::vector splitPath = PathSplit (context); + int nodeIndex = atoi (splitPath[1].c_str ()); + Ptr node = NodeList::GetNode (nodeIndex); + + if (m_nodesOfInterest.find (nodeIndex) == m_nodesOfInterest.end ()) + { + // if the transmitting node is not "of interest", we still + // record the transmission if it is a packet of interest. + if (m_packetsOfInterest.find (packet->GetUid ()) == m_packetsOfInterest.end ()) + { + NS_LOG_DEBUG ("Packet " << packet->GetUid () << " is not of interest"); + return; + } + } + + // ---- "last packets" + const PacketCaptureOptions *captureOptions; + if (GetPacketCaptureOptions (nodeIndex, &captureOptions) && FilterPacket (packet, *captureOptions)) + { + LastPacketsSample &last = m_lastPackets[nodeIndex]; + PacketSample lastPacket; + lastPacket.time = Simulator::Now (); + lastPacket.packet = packet->Copy (); + lastPacket.device = NULL; + last.lastDroppedPackets.push_back (lastPacket); + while (last.lastDroppedPackets.size () > captureOptions->numLastPackets) + { + last.lastDroppedPackets.erase (last.lastDroppedPackets.begin ()); + } + } + + std::map, uint32_t>::iterator iter = m_packetDrops.find (node); + if (iter == m_packetDrops.end ()) + { + m_packetDrops[node] = packet->GetSize (); + } + else + { + iter->second += packet->GetSize (); + } +} + +void +PyViz::TraceIpv4Drop (std::string context, ns3::Ipv4Header const &hdr, Ptr packet, + ns3::Ipv4L3Protocol::DropReason reason, Ptr dummy_ipv4, uint32_t interface) +{ + Ptr packetCopy = packet->Copy (); + packetCopy->AddHeader (hdr); + TraceDevQueueDrop (context, packetCopy); +} + + + // --------- TX device tracing ------------------- + +void +PyViz::TraceNetDevTxCommon (std::string const &context, Ptr packet, + Mac48Address const &destinationAddress) +{ + NS_LOG_FUNCTION (context << packet->GetUid () << *packet); + + std::vector splitPath = PathSplit (context); + int nodeIndex = atoi (splitPath[1].c_str ()); + int devIndex = atoi (splitPath[3].c_str ()); + Ptr node = NodeList::GetNode (nodeIndex); + Ptr device = node->GetDevice (devIndex); + + // ---- statistics + NetDeviceStatistics &stats = FindNetDeviceStatistics (nodeIndex, devIndex); + ++stats.transmittedPackets; + stats.transmittedBytes += packet->GetSize (); + + // ---- "last packets" + const PacketCaptureOptions *captureOptions; + if (GetPacketCaptureOptions (nodeIndex, &captureOptions) && FilterPacket (packet, *captureOptions)) + { + LastPacketsSample &last = m_lastPackets[nodeIndex]; + TxPacketSample lastPacket; + lastPacket.time = Simulator::Now (); + lastPacket.packet = packet->Copy (); + lastPacket.device = device; + lastPacket.to = destinationAddress; + last.lastTransmittedPackets.push_back (lastPacket); + while (last.lastTransmittedPackets.size () > captureOptions->numLastPackets) + { + last.lastTransmittedPackets.erase (last.lastTransmittedPackets.begin ()); + } + } + + // ---- transmissions records + + if (m_nodesOfInterest.find (nodeIndex) == m_nodesOfInterest.end ()) + { + // if the transmitting node is not "of interest", we still + // record the transmission if it is a packet of interest. + if (m_packetsOfInterest.find (packet->GetUid ()) == m_packetsOfInterest.end ()) + { + NS_LOG_DEBUG ("Packet " << packet->GetUid () << " is not of interest"); + return; + } + } + else + { + // We will follow this packet throughout the network. + m_packetsOfInterest[packet->GetUid ()] = Simulator::Now (); + } + + TxRecordValue record = { Simulator::Now (), node, false }; + if (destinationAddress == device->GetBroadcast ()) + { + record.isBroadcast = true; + } + + m_txRecords[TxRecordKey (device->GetChannel (), packet->GetUid ())] = record; + + PyVizPacketTag tag; + //packet->RemovePacketTag (tag); + tag.m_packetId = packet->GetUid (); + packet->AddByteTag (tag); +} + +void +PyViz::TraceNetDevTxWifi (std::string context, Ptr packet) +{ + NS_LOG_FUNCTION (context << packet->GetUid () << *packet); + + /* + * To DS From DS Address 1 Address 2 Address 3 Address 4 + *---------------------------------------------------------------------- + * 0 0 Destination Source BSSID N/A + * 0 1 Destination BSSID Source N/A + * 1 0 BSSID Source Destination N/A + * 1 1 Receiver Transmitter Destination Source + */ + WifiMacHeader hdr; + NS_ABORT_IF (packet->PeekHeader (hdr) == 0); + Mac48Address destinationAddress; + if (hdr.IsToDs () && !hdr.IsFromDs ()) + { + destinationAddress = hdr.GetAddr3 (); + } + else if (!hdr.IsToDs () && hdr.IsFromDs ()) + { + destinationAddress = hdr.GetAddr1 (); + } + else if (!hdr.IsToDs () && !hdr.IsFromDs ()) + { + destinationAddress = hdr.GetAddr1 (); + } + else + { + destinationAddress = hdr.GetAddr3 (); + } + TraceNetDevTxCommon (context, packet, destinationAddress); +} + + +void +PyViz::TraceNetDevTxCsma (std::string context, Ptr packet) +{ + EthernetHeader ethernetHeader; + NS_ABORT_IF (packet->PeekHeader (ethernetHeader) == 0); + TraceNetDevTxCommon (context, packet, ethernetHeader.GetDestination ()); +} + +void +PyViz::TraceNetDevTxPointToPoint (std::string context, Ptr packet) +{ + TraceNetDevTxCommon (context, packet, Mac48Address ()); +} + + + + + // --------- RX device tracing ------------------- + +void +PyViz::TraceNetDevRxCommon (std::string const &context, Ptr packet, Mac48Address const &from) +{ + uint32_t uid; + PyVizPacketTag tag; + if (packet->FindFirstMatchingByteTag (tag)) + { + uid = tag.m_packetId; + } + else + { + //NS_ASSERT (0); + NS_LOG_WARN ("Packet has no byte tag; wimax link?"); + uid = packet->GetUid (); + } + + NS_LOG_FUNCTION (context << uid); + std::vector splitPath = PathSplit (context); + int nodeIndex = atoi (splitPath[1].c_str ()); + int devIndex = atoi (splitPath[3].c_str ()); + + // ---- statistics + NetDeviceStatistics &stats = FindNetDeviceStatistics (nodeIndex, devIndex); + ++stats.receivedPackets; + stats.receivedBytes += packet->GetSize (); + + Ptr node = NodeList::GetNode (nodeIndex); + Ptr device = node->GetDevice (devIndex); + + // ---- "last packets" + const PacketCaptureOptions *captureOptions; + if (GetPacketCaptureOptions (nodeIndex, &captureOptions) && FilterPacket (packet, *captureOptions)) + { + LastPacketsSample &last = m_lastPackets[nodeIndex]; + RxPacketSample lastPacket; + lastPacket.time = Simulator::Now (); + lastPacket.packet = packet->Copy (); + lastPacket.device = device; + lastPacket.from = from; + last.lastReceivedPackets.push_back (lastPacket); + while (last.lastReceivedPackets.size () > captureOptions->numLastPackets) + { + last.lastReceivedPackets.erase (last.lastReceivedPackets.begin ()); + } + } + + // ---- transmissions + if (m_packetsOfInterest.find (uid) == m_packetsOfInterest.end ()) + { + NS_LOG_DEBUG ("RX Packet " << uid << " is not of interest"); + return; + } + + Ptr channel = device->GetChannel (); + + std::map::iterator recordIter = + m_txRecords.find (TxRecordKey (channel, uid)); + + if (recordIter == m_txRecords.end ()) + { + NS_LOG_DEBUG ("RX Packet " << uid << " was not transmitted?!"); + return; + } + + TxRecordValue &record = recordIter->second; + + if (record.srcNode == node) + { + NS_LOG_WARN ("Node " << node->GetId () << " receiving back the same packet (UID=" << uid + << ") it had previously transmitted, on the same channel!"); + return; + } + + TransmissionSampleKey key = { record.srcNode, node, channel }; + +#ifdef NS3_LOG_ENABLE + NS_LOG_DEBUG("m_transmissionSamples begin:"); + if (g_log.IsEnabled (ns3::LOG_DEBUG)) + { + for (std::map::const_iterator iter + = m_transmissionSamples.begin (); iter != m_transmissionSamples.end (); iter++) + { + NS_LOG_DEBUG(iter->first.transmitter<<"/"<first.transmitter->GetId () << ", " + << iter->first.receiver<<"/"<first.receiver->GetId () + << ", " << iter->first.channel << " => " << iter->second.bytes << " (@ " << &iter->second << ")"); + } + } + NS_LOG_DEBUG("m_transmissionSamples end."); +#endif + + std::map::iterator + iter = m_transmissionSamples.find (key); + + if (iter == m_transmissionSamples.end ()) + { + TransmissionSampleValue sample = { packet->GetSize () }; + NS_LOG_DEBUG ("RX: from " << key.transmitter<<"/"<GetId() << " to " + << key.receiver<<"/"<GetId() + << " channel " << channel << ": " << packet->GetSize () + << " bytes more. => new sample with " << packet->GetSize () << " bytes."); + m_transmissionSamples[key] = sample; + } + else + { + TransmissionSampleValue &sample = iter->second; + NS_LOG_DEBUG ("RX: from " << key.transmitter<<"/"<GetId() << " to " + << key.receiver<<"/"<GetId() + << " channel " << channel << ": " << packet->GetSize () + << " bytes more. => sample " << &sample << " with bytes " << sample.bytes); + + sample.bytes += packet->GetSize (); + } +} + +void +PyViz::TraceNetDevRxWifi (std::string context, Ptr packet) +{ + NS_LOG_FUNCTION (context << packet->GetUid ()); + + + /* + * To DS From DS Address 1 Address 2 Address 3 Address 4 + *---------------------------------------------------------------------- + * 0 0 Destination Source BSSID N/A + * 0 1 Destination BSSID Source N/A + * 1 0 BSSID Source Destination N/A + * 1 1 Receiver Transmitter Destination Source + */ + WifiMacHeader hdr; + NS_ABORT_IF (packet->PeekHeader (hdr) == 0); + Mac48Address sourceAddress; + if (hdr.IsToDs () && !hdr.IsFromDs ()) + { + sourceAddress = hdr.GetAddr2 (); + } + else if (!hdr.IsToDs () && hdr.IsFromDs ()) + { + sourceAddress = hdr.GetAddr3 (); + } + else if (!hdr.IsToDs () && !hdr.IsFromDs ()) + { + sourceAddress = hdr.GetAddr2 (); + } + else + { + sourceAddress = hdr.GetAddr4 (); + } + + TraceNetDevRxCommon (context, packet, sourceAddress); +} + + + +void +PyViz::TraceNetDevRxCsma (std::string context, Ptr packet) +{ + EthernetHeader ethernetHeader; + NS_ABORT_IF (packet->PeekHeader (ethernetHeader) == 0); + TraceNetDevRxCommon (context, packet, ethernetHeader.GetSource ()); +} + +void +PyViz::TraceNetDevRxPointToPoint (std::string context, Ptr packet) +{ + TraceNetDevRxCommon (context, packet, Mac48Address ()); +} + +void +PyViz::TraceNetDevPromiscRxCsma (std::string context, Ptr packet) +{ + EthernetHeader ethernetHeader; + NS_ABORT_IF (packet->PeekHeader (ethernetHeader) == 0); + + NetDevice::PacketType packetType = NetDevice::PACKET_OTHERHOST; // FIXME + + // Other packet types are already being received by + // TraceNetDevRxCsma; we don't want to receive them twice. + if (packetType == NetDevice::PACKET_OTHERHOST) + { + TraceNetDevRxCommon (context, packet, ethernetHeader.GetDestination ()); + } +} + +void +PyViz::TraceNetDevTxWimax (std::string context, Ptr packet, Mac48Address const &destination) +{ + NS_LOG_FUNCTION (context); + TraceNetDevTxCommon (context, packet, destination); +} + +void +PyViz::TraceNetDevRxWimax (std::string context, Ptr packet, Mac48Address const &source) +{ + NS_LOG_FUNCTION (context); + TraceNetDevRxCommon (context, packet, source); +} + + + // --------------------- + +PyViz::TransmissionSampleList +PyViz::GetTransmissionSamples () const +{ + NS_LOG_DEBUG ("GetTransmissionSamples BEGIN"); + TransmissionSampleList list; + for (std::map::const_iterator + iter = m_transmissionSamples.begin (); + iter != m_transmissionSamples.end (); + iter++) + { + TransmissionSample sample; + sample.transmitter = iter->first.transmitter; + sample.receiver = iter->first.receiver; + sample.channel = iter->first.channel; + sample.bytes = iter->second.bytes; + NS_LOG_DEBUG ("from " << sample.transmitter->GetId() << " to " << sample.receiver->GetId() + << ": " << sample.bytes << " bytes."); + list.push_back (sample); + } + NS_LOG_DEBUG ("GetTransmissionSamples END"); + return list; +} + +PyViz::PacketDropSampleList +PyViz::GetPacketDropSamples () const +{ + NS_LOG_DEBUG ("GetPacketDropSamples BEGIN"); + PacketDropSampleList list; + for (std::map, uint32_t>::const_iterator + iter = m_packetDrops.begin (); + iter != m_packetDrops.end (); + iter++) + { + PacketDropSample sample; + sample.transmitter = iter->first; + sample.bytes = iter->second; + NS_LOG_DEBUG ("in " << sample.transmitter->GetId() + << ": " << sample.bytes << " bytes dropped."); + list.push_back (sample); + } + NS_LOG_DEBUG ("GetPacketDropSamples END"); + return list; +} + +void +PyViz::SetNodesOfInterest (std::set nodes) +{ + m_nodesOfInterest = nodes; +} + +std::vector +PyViz::GetNodesStatistics () const +{ + std::vector retval; + for (std::map >::const_iterator iter = m_nodesStatistics.begin (); + iter != m_nodesStatistics.end (); iter++) + { + NodeStatistics stats = { iter->first, iter->second }; + retval.push_back (stats); + } + return retval; +} + + +PyViz::LastPacketsSample +PyViz::GetLastPackets (uint32_t nodeId) const +{ + NS_LOG_DEBUG ("GetLastPackets: " << nodeId); + + std::map::const_iterator + iter = m_lastPackets.find(nodeId); + if (iter != m_lastPackets.end ()) + { + return iter->second; + } + else + { + return LastPacketsSample (); + } +} + + + + + + +namespace +{ + // Adapted from http://en.wikipedia.org/w/index.php?title=Line_clipping&oldid=248609574 + class FastClipping + { + public: + struct Vector2 + { + double x; + double y; + }; + + Vector2 m_clipMin, m_clipMax; + + struct Line + { + Vector2 start, end; + double dx, dy; + }; + + private: + + void ClipStartTop (Line &line) + { + line.start.x += line.dx * (m_clipMin.y - line.start.y) / line.dy; + line.start.y = m_clipMin.y; + } + + void ClipStartBottom (Line &line) + { + line.start.x += line.dx * (m_clipMax.y - line.start.y) / line.dy; + line.start.y = m_clipMax.y; + } + + void ClipStartRight (Line &line) + { + line.start.y += line.dy * (m_clipMax.x - line.start.x) / line.dx; + line.start.x = m_clipMax.x; + } + + void ClipStartLeft (Line &line) + { + line.start.y += line.dy * (m_clipMin.x - line.start.x) / line.dx; + line.start.x = m_clipMin.x; + } + + void ClipEndTop (Line &line) + { + line.end.x += line.dx * (m_clipMin.y - line.end.y) / line.dy; + line.end.y = m_clipMin.y; + } + + void ClipEndBottom (Line &line) { + line.end.x += line.dx * (m_clipMax.y - line.end.y) / line.dy; + line.end.y = m_clipMax.y; + } + + void ClipEndRight (Line &line) + { + line.end.y += line.dy * (m_clipMax.x - line.end.x) / line.dx; + line.end.x = m_clipMax.x; + } + + void ClipEndLeft (Line &line) + { + line.end.y += line.dy * (m_clipMin.x - line.end.x) / line.dx; + line.end.x = m_clipMin.x; + } + + public: + FastClipping (Vector2 clipMin, Vector2 clipMax) + : m_clipMin (clipMin), m_clipMax (clipMax) + { + } + + + bool ClipLine (Line &line) + { + uint8_t lineCode = 0; + + if (line.end.y < m_clipMin.y) + lineCode |= 8; + else if (line.end.y > m_clipMax.y) + lineCode |= 4; + + if (line.end.x > m_clipMax.x) + lineCode |= 2; + else if (line.end.x < m_clipMin.x) + lineCode |= 1; + + if (line.start.y < m_clipMin.y) + lineCode |= 128; + else if (line.start.y > m_clipMax.y) + lineCode |= 64; + + if (line.start.x > m_clipMax.x) + lineCode |= 32; + else if (line.start.x < m_clipMin.x) + lineCode |= 16; + + // 9 - 8 - A + // | | | + // 1 - 0 - 2 + // | | | + // 5 - 4 - 6 + switch (lineCode) + { + // center + case 0x00: + return true; + + case 0x01: + ClipEndLeft (line); + return true; + + case 0x02: + ClipEndRight (line); + return true; + + case 0x04: + ClipEndBottom (line); + return true; + + case 0x05: + ClipEndLeft (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + case 0x06: + ClipEndRight (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + case 0x08: + ClipEndTop (line); + return true; + + case 0x09: + ClipEndLeft (line); + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + return true; + + case 0x0A: + ClipEndRight (line); + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + return true; + + // left + case 0x10: + ClipStartLeft (line); + return true; + + case 0x12: + ClipStartLeft (line); + ClipEndRight (line); + return true; + + case 0x14: + ClipStartLeft (line); + if (line.start.y > m_clipMax.y) + return false; + ClipEndBottom (line); + return true; + + case 0x16: + ClipStartLeft (line); + if (line.start.y > m_clipMax.y) + return false; + ClipEndBottom (line); + if (line.end.x > m_clipMax.x) + ClipEndRight (line); + return true; + + case 0x18: + ClipStartLeft (line); + if (line.start.y < m_clipMin.y) + return false; + ClipEndTop (line); + return true; + + case 0x1A: + ClipStartLeft (line); + if (line.start.y < m_clipMin.y) + return false; + ClipEndTop (line); + if (line.end.x > m_clipMax.x) + ClipEndRight (line); + return true; + + // right + case 0x20: + ClipStartRight (line); + return true; + + case 0x21: + ClipStartRight (line); + ClipEndLeft (line); + return true; + + case 0x24: + ClipStartRight (line); + if (line.start.y > m_clipMax.y) + return false; + ClipEndBottom (line); + return true; + + case 0x25: + ClipStartRight (line); + if (line.start.y > m_clipMax.y) + return false; + ClipEndBottom (line); + if (line.end.x < m_clipMin.x) + ClipEndLeft (line); + return true; + + case 0x28: + ClipStartRight (line); + if (line.start.y < m_clipMin.y) + return false; + ClipEndTop (line); + return true; + + case 0x29: + ClipStartRight (line); + if (line.start.y < m_clipMin.y) + return false; + ClipEndTop (line); + if (line.end.x < m_clipMin.x) + ClipEndLeft (line); + return true; + + // bottom + case 0x40: + ClipStartBottom (line); + return true; + + case 0x41: + ClipStartBottom (line); + if (line.start.x < m_clipMin.x) + return false; + ClipEndLeft (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + case 0x42: + ClipStartBottom (line); + if (line.start.x > m_clipMax.x) + return false; + ClipEndRight (line); + return true; + + case 0x48: + ClipStartBottom (line); + ClipEndTop (line); + return true; + + case 0x49: + ClipStartBottom (line); + if (line.start.x < m_clipMin.x) + return false; + ClipEndLeft (line); + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + return true; + + case 0x4A: + ClipStartBottom (line); + if (line.start.x > m_clipMax.x) + return false; + ClipEndRight (line); + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + return true; + + // bottom-left + case 0x50: + ClipStartLeft (line); + if (line.start.y > m_clipMax.y) + ClipStartBottom (line); + return true; + + case 0x52: + ClipEndRight (line); + if (line.end.y > m_clipMax.y) + return false; + ClipStartBottom (line); + if (line.start.x < m_clipMin.x) + ClipStartLeft (line); + return true; + + case 0x58: + ClipEndTop (line); + if (line.end.x < m_clipMin.x) + return false; + ClipStartBottom (line); + if (line.start.x < m_clipMin.x) + ClipStartLeft (line); + return true; + + case 0x5A: + ClipStartLeft (line); + if (line.start.y < m_clipMin.y) + return false; + ClipEndRight (line); + if (line.end.y > m_clipMax.y) + return false; + if (line.start.y > m_clipMax.y) + ClipStartBottom (line); + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + return true; + + // bottom-right + case 0x60: + ClipStartRight (line); + if (line.start.y > m_clipMax.y) + ClipStartBottom (line); + return true; + + case 0x61: + ClipEndLeft (line); + if (line.end.y > m_clipMax.y) + return false; + ClipStartBottom (line); + if (line.start.x > m_clipMax.x) + ClipStartRight (line); + return true; + + case 0x68: + ClipEndTop (line); + if (line.end.x > m_clipMax.x) + return false; + ClipStartRight (line); + if (line.start.y > m_clipMax.y) + ClipStartBottom (line); + return true; + + case 0x69: + ClipEndLeft (line); + if (line.end.y > m_clipMax.y) + return false; + ClipStartRight (line); + if (line.start.y < m_clipMin.y) + return false; + if (line.end.y < m_clipMin.y) + ClipEndTop (line); + if (line.start.y > m_clipMax.y) + ClipStartBottom (line); + return true; + + // top + case 0x80: + ClipStartTop (line); + return true; + + case 0x81: + ClipStartTop (line); + if (line.start.x < m_clipMin.x) + return false; + ClipEndLeft (line); + return true; + + case 0x82: + ClipStartTop (line); + if (line.start.x > m_clipMax.x) + return false; + ClipEndRight (line); + return true; + + case 0x84: + ClipStartTop (line); + ClipEndBottom (line); + return true; + + case 0x85: + ClipStartTop (line); + if (line.start.x < m_clipMin.x) + return false; + ClipEndLeft (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + case 0x86: + ClipStartTop (line); + if (line.start.x > m_clipMax.x) + return false; + ClipEndRight (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + // top-left + case 0x90: + ClipStartLeft (line); + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + return true; + + case 0x92: + ClipEndRight (line); + if (line.end.y < m_clipMin.y) + return false; + ClipStartTop (line); + if (line.start.x < m_clipMin.x) + ClipStartLeft (line); + return true; + + case 0x94: + ClipEndBottom (line); + if (line.end.x < m_clipMin.x) + return false; + ClipStartLeft (line); + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + return true; + + case 0x96: + ClipStartLeft (line); + if (line.start.y > m_clipMax.y) + return false; + ClipEndRight (line); + if (line.end.y < m_clipMin.y) + return false; + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + return true; + + // top-right + case 0xA0: + ClipStartRight (line); + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + return true; + + case 0xA1: + ClipEndLeft (line); + if (line.end.y < m_clipMin.y) + return false; + ClipStartTop (line); + if (line.start.x > m_clipMax.x) + ClipStartRight (line); + return true; + + case 0xA4: + ClipEndBottom (line); + if (line.end.x > m_clipMax.x) + return false; + ClipStartRight (line); + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + return true; + + case 0xA5: + ClipEndLeft (line); + if (line.end.y < m_clipMin.y) + return false; + ClipStartRight (line); + if (line.start.y > m_clipMax.y) + return false; + if (line.end.y > m_clipMax.y) + ClipEndBottom (line); + if (line.start.y < m_clipMin.y) + ClipStartTop (line); + return true; + } + + return false; + } + }; +} + +void +PyViz::LineClipping (double boundsX1, double boundsY1, double boundsX2, double boundsY2, + double &lineX1, double &lineY1, double &lineX2, double &lineY2) +{ + FastClipping::Vector2 clipMin = {boundsX1, boundsY1}, clipMax = {boundsX2, boundsY2}; + FastClipping::Line line = { { lineX1, lineY1 }, { lineX2, lineY2 }, (lineX2-lineX1), (lineY2-lineY1) }; + + FastClipping clipper (clipMin, clipMax); + clipper.ClipLine (line); + lineX1 = line.start.x; + lineX2 = line.end.x; + lineY1 = line.start.y; + lineY2 = line.end.y; +} + + +} + diff --git a/src/tools/visualizer/model/pyviz.h b/src/tools/visualizer/model/pyviz.h new file mode 100644 index 000000000..5b7bd8f4a --- /dev/null +++ b/src/tools/visualizer/model/pyviz.h @@ -0,0 +1,225 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2008 INESC Porto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * C++ helper functions for use by the python visualizer (for things + * Python is too slow at). + * + * Author: Gustavo Carneiro + */ +#ifndef NS3_PYVIZ_H +#define NS3_PYVIZ_H + +#include "ns3/nstime.h" +#include "ns3/event-id.h" +#include "ns3/node.h" +#include "ns3/channel.h" +#include "ns3/packet.h" +#include "ns3/mac48-address.h" +#include "ns3/ipv4-header.h" +#include "ns3/ipv4-l3-protocol.h" + +#include +#include + +namespace ns3 { + +/** + * \brief helper class to be used by the visualizer + * \internal + * + * This class is not meant to be used by simulations. It is only + * meant to be used by the visualizer tool (PyViz). The only reason + * it is public is because Python bindings for it are needed, + * otherwise it should be considered private. + **/ +class PyViz +{ +public: + PyViz (); + ~PyViz (); + + void RegisterDropTracePath (std::string const &tracePath); + + void RegisterCsmaLikeDevice (std::string const &deviceTypeName); + void RegisterWifiLikeDevice (std::string const &deviceTypeName); + void RegisterPointToPointLikeDevice (std::string const &deviceTypeName); + + // Run simulation until a given (simulated, absolute) time is reached + void SimulatorRunUntil (Time time); + + static void Pause (std::string const &message); + std::vector GetPauseMessages () const; + + struct TransmissionSample + { + Ptr transmitter; + Ptr receiver; // NULL if broadcast + Ptr channel; + uint32_t bytes; + }; + typedef std::vector TransmissionSampleList; + TransmissionSampleList GetTransmissionSamples () const; + + struct PacketDropSample + { + Ptr transmitter; + uint32_t bytes; + }; + typedef std::vector PacketDropSampleList; + PacketDropSampleList GetPacketDropSamples () const; + + + struct PacketSample + { + Time time; + Ptr packet; + Ptr device; + }; + struct TxPacketSample : PacketSample + { + Mac48Address to; + }; + struct RxPacketSample : PacketSample + { + Mac48Address from; + }; + + struct LastPacketsSample + { + std::vector lastReceivedPackets; + std::vector lastTransmittedPackets; + std::vector lastDroppedPackets; + }; + LastPacketsSample GetLastPackets (uint32_t nodeId) const; + + + void SetNodesOfInterest (std::set nodes); + + struct NetDeviceStatistics + { + NetDeviceStatistics () : transmittedBytes (0), receivedBytes (0), + transmittedPackets (0), receivedPackets (0) {} + uint64_t transmittedBytes; + uint64_t receivedBytes; + uint32_t transmittedPackets; + uint32_t receivedPackets; + }; + + struct NodeStatistics + { + uint32_t nodeId; + std::vector statistics; + }; + + std::vector GetNodesStatistics () const; + + enum PacketCaptureMode { + PACKET_CAPTURE_DISABLED=1, // packet capture is disabled + PACKET_CAPTURE_FILTER_HEADERS_OR, // packet capture if any of the indicated headers is present + PACKET_CAPTURE_FILTER_HEADERS_AND, // packet capture if all of the indicated headers are present + }; + + struct PacketCaptureOptions + { + std::set headers; + uint32_t numLastPackets; + PacketCaptureMode mode; + }; + + void SetPacketCaptureOptions (uint32_t nodeId, PacketCaptureOptions options); + + + // Yes, I know, this is just a utility function, not really related to the class in any way. + + // -#- @lineX1(direction=inout); @lineY1(direction=inout); @lineX2(direction=inout); @lineY2(direction=inout) -#- + static void LineClipping (double boundsX1, double boundsY1, double boundsX2, double boundsY2, double &lineX1, double &lineY1, double &lineX2, double &lineY2); // don't break this line or pybindgen will not be able to pick up the above annotation :( + + +private: + + bool GetPacketCaptureOptions (uint32_t nodeId, const PacketCaptureOptions **outOptions) const; + static bool FilterPacket (Ptr packet, const PacketCaptureOptions &options); + + + typedef std::pair, uint32_t> TxRecordKey; + + struct TxRecordValue + { + Time time; + Ptr srcNode; + bool isBroadcast; + }; + + struct TransmissionSampleKey + { + bool operator < (TransmissionSampleKey const &other) const; + bool operator == (TransmissionSampleKey const &other) const; + Ptr transmitter; + Ptr receiver; // NULL if broadcast + Ptr channel; + }; + + struct TransmissionSampleValue + { + uint32_t bytes; + }; + + // data + std::map m_packetCaptureOptions; + std::vector m_pauseMessages; + std::map m_txRecords; + std::map m_transmissionSamples; + std::map, uint32_t> m_packetDrops; + std::set m_nodesOfInterest; // list of node IDs whose transmissions will be monitored + std::map m_packetsOfInterest; // list of packet UIDs that will be monitored + std::map m_lastPackets; + std::map > m_nodesStatistics; + + // Trace callbacks + void TraceNetDevTxCommon (std::string const &context, Ptr packet, Mac48Address const &destination); + void TraceNetDevRxCommon (std::string const &context, Ptr packet, Mac48Address const &source); + + void TraceNetDevTxWifi (std::string context, Ptr packet); + void TraceNetDevRxWifi (std::string context, Ptr packet); + + void TraceDevQueueDrop (std::string context, Ptr packet); + void TraceIpv4Drop (std::string context, ns3::Ipv4Header const &hdr, Ptr packet, + ns3::Ipv4L3Protocol::DropReason reason, Ptr dummy_ipv4, uint32_t interface); + + void TraceNetDevTxCsma (std::string context, Ptr packet); + void TraceNetDevRxCsma (std::string context, Ptr packet); + void TraceNetDevPromiscRxCsma (std::string context, Ptr packet); + + void TraceNetDevTxPointToPoint (std::string context, Ptr packet); + void TraceNetDevRxPointToPoint (std::string context, Ptr packet); + + void TraceNetDevTxWimax (std::string context, Ptr packet, Mac48Address const &destination); + void TraceNetDevRxWimax (std::string context, Ptr packet, Mac48Address const &source); + + inline NetDeviceStatistics & FindNetDeviceStatistics (int node, int interface); + + void DoPause (std::string const &message); + + bool m_stop; + EventId m_stopCallbackEvent; + void CallbackStopSimulation (); +}; + + +} + +#endif /* NS3_PYVIZ_H */ diff --git a/src/tools/visualizer/model/visual-simulator-impl.cc b/src/tools/visualizer/model/visual-simulator-impl.cc new file mode 100644 index 000000000..282bd2659 --- /dev/null +++ b/src/tools/visualizer/model/visual-simulator-impl.cc @@ -0,0 +1,228 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2010 Gustavo Carneiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Gustavo Carneiro + */ +#include +#include "visual-simulator-impl.h" +#include "ns3/default-simulator-impl.h" +#include "ns3/log.h" + +NS_LOG_COMPONENT_DEFINE ("VisualSimulatorImpl"); + +namespace ns3 { + + + +NS_OBJECT_ENSURE_REGISTERED (VisualSimulatorImpl); + +namespace +{ + ObjectFactory + GetDefaultSimulatorImplFactory () + { + ObjectFactory factory; + factory.SetTypeId (DefaultSimulatorImpl::GetTypeId ()); + return factory; + } +} + + +TypeId +VisualSimulatorImpl::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::VisualSimulatorImpl") + .SetParent () + .AddConstructor () + .AddAttribute ("SimulatorImplFactory", + "Factory for the underlying simulator implementation used by the visualizer.", + ObjectFactoryValue (GetDefaultSimulatorImplFactory ()), + MakeObjectFactoryAccessor (&VisualSimulatorImpl::m_simulatorImplFactory), + MakeObjectFactoryChecker ()) + ; + return tid; +} + + +VisualSimulatorImpl::VisualSimulatorImpl () +{ +} + +VisualSimulatorImpl::~VisualSimulatorImpl () +{} + +void +VisualSimulatorImpl::DoDispose (void) +{ + if (m_simulator) + { + m_simulator->Dispose (); + m_simulator = NULL; + } + SimulatorImpl::DoDispose (); +} + +void +VisualSimulatorImpl::NotifyConstructionCompleted () +{ + m_simulator = m_simulatorImplFactory.Create (); +} + + +void +VisualSimulatorImpl::Destroy () +{ + m_simulator->Destroy (); +} + +void +VisualSimulatorImpl::SetScheduler (ObjectFactory schedulerFactory) +{ + m_simulator->SetScheduler (schedulerFactory); +} + +// System ID for non-distributed simulation is always zero +uint32_t +VisualSimulatorImpl::GetSystemId (void) const +{ + return m_simulator->GetSystemId (); +} + +bool +VisualSimulatorImpl::IsFinished (void) const +{ + return m_simulator->IsFinished (); +} + +Time +VisualSimulatorImpl::Next (void) const +{ + return m_simulator->Next (); +} + +void +VisualSimulatorImpl::Run (void) +{ + if (!Py_IsInitialized ()) + { + const char *argv[] = {"python", NULL}; + Py_Initialize(); + PySys_SetArgv(1, (char**) argv); + } + PyRun_SimpleString( + "import visualizer\n" + "visualizer.start();\n" + ); +} + +void +VisualSimulatorImpl::RunOneEvent (void) +{ + m_simulator->RunOneEvent (); +} + +void +VisualSimulatorImpl::Stop (void) +{ + m_simulator->Stop (); +} + +void +VisualSimulatorImpl::Stop (Time const &time) +{ + m_simulator->Stop (time); +} + +// +// Schedule an event for a _relative_ time in the future. +// +EventId +VisualSimulatorImpl::Schedule (Time const &time, EventImpl *event) +{ + return m_simulator->Schedule (time, event); +} + +void +VisualSimulatorImpl::ScheduleWithContext (uint32_t context, Time const &time, EventImpl *event) +{ + m_simulator->ScheduleWithContext (context, time, event); +} + +EventId +VisualSimulatorImpl::ScheduleNow (EventImpl *event) +{ + return m_simulator->ScheduleNow (event); +} + +EventId +VisualSimulatorImpl::ScheduleDestroy (EventImpl *event) +{ + return m_simulator->ScheduleDestroy (event); +} + +Time +VisualSimulatorImpl::Now (void) const +{ + return m_simulator->Now (); +} + +Time +VisualSimulatorImpl::GetDelayLeft (const EventId &id) const +{ + return m_simulator->GetDelayLeft (id); +} + +void +VisualSimulatorImpl::Remove (const EventId &id) +{ + m_simulator->Remove (id); +} + +void +VisualSimulatorImpl::Cancel (const EventId &id) +{ + m_simulator->Cancel (id); +} + +bool +VisualSimulatorImpl::IsExpired (const EventId &ev) const +{ + return m_simulator->IsExpired (ev); +} + +Time +VisualSimulatorImpl::GetMaximumSimulationTime (void) const +{ + return m_simulator->GetMaximumSimulationTime (); +} + +uint32_t +VisualSimulatorImpl::GetContext (void) const +{ + return m_simulator->GetContext (); +} + +void +VisualSimulatorImpl::RunRealSimulator (void) +{ + m_simulator->Run (); +} + + +} // namespace ns3 + + diff --git a/src/tools/visualizer/model/visual-simulator-impl.h b/src/tools/visualizer/model/visual-simulator-impl.h new file mode 100644 index 000000000..b8d27c3cb --- /dev/null +++ b/src/tools/visualizer/model/visual-simulator-impl.h @@ -0,0 +1,82 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2010 Gustavo Carneiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Gustavo Carneiro + */ + +#ifndef VISUAL_SIMULATOR_IMPL_H +#define VISUAL_SIMULATOR_IMPL_H + +#include "ns3/simulator-impl.h" + +namespace ns3 { + + +/** + * \brief A replacement simulator that starts the visualizer + * \internal + * + * To use this class, run any ns-3 simulation with the command-line + * argument --SimulatorImplementationType=ns3::VisualSimulatorImpl. + * This causes the visualizer (PyViz) to start automatically. + **/ +class VisualSimulatorImpl : public SimulatorImpl +{ +public: + static TypeId GetTypeId (void); + + VisualSimulatorImpl (); + ~VisualSimulatorImpl (); + + virtual void Destroy (); + virtual bool IsFinished (void) const; + virtual Time Next (void) const; + virtual void Stop (void); + virtual void Stop (Time const &time); + virtual EventId Schedule (Time const &time, EventImpl *event); + virtual void ScheduleWithContext (uint32_t context, Time const &time, EventImpl *event); + virtual EventId ScheduleNow (EventImpl *event); + virtual EventId ScheduleDestroy (EventImpl *event); + virtual void Remove (const EventId &ev); + virtual void Cancel (const EventId &ev); + virtual bool IsExpired (const EventId &ev) const; + virtual void Run (void); + virtual void RunOneEvent (void); + virtual Time Now (void) const; + virtual Time GetDelayLeft (const EventId &id) const; + virtual Time GetMaximumSimulationTime (void) const; + virtual void SetScheduler (ObjectFactory schedulerFactory); + virtual uint32_t GetSystemId (void) const; + virtual uint32_t GetContext (void) const; + + /// calls Run() in the wrapped simulator + void RunRealSimulator (void); + +protected: + void DoDispose (); + void NotifyConstructionCompleted (void); + +private: + Ptr GetSim (); + Ptr m_simulator; + ObjectFactory m_simulatorImplFactory; + +}; + +} // namespace ns3 + +#endif /* DEFAULT_SIMULATOR_IMPL_H */ diff --git a/src/tools/visualizer/model/visualizer-ideas.txt b/src/tools/visualizer/model/visualizer-ideas.txt new file mode 100644 index 000000000..d995d83a5 --- /dev/null +++ b/src/tools/visualizer/model/visualizer-ideas.txt @@ -0,0 +1,16 @@ +- Add an Attribute browser plugin, simililar to Mathieu's GtkConfigStore; + - Right click on a node -> Show Attributes; + - Allow editing attributes too; +- List of all nodes, navigator; +- Represent individual NetDevices in Nodes; +- Colorize flows; possible approaches: + - Apply color based on hash function of ethertype, IP packet type, L4 destination port; + - Programmatically marked flows; + - Packet tags? + - Present a GUI to show applications and set color for each one; + - Problems: + > How about multiple flows? How to represent them simultaneously? +- Track down a Gtk+ bug preventing tooltips from working correctly with large zoom levels; +- Possibly look for embedding an ipython shell as a widget inside the + main window: http://ipython.scipy.org/moin/Cookbook/EmbeddingInGTK + diff --git a/src/tools/visualizer/visualizer/__init__.py b/src/tools/visualizer/visualizer/__init__.py new file mode 100644 index 000000000..10a4dbd41 --- /dev/null +++ b/src/tools/visualizer/visualizer/__init__.py @@ -0,0 +1,3 @@ + +from core import start, register_plugin, set_bounds, add_initialization_hook + diff --git a/src/tools/visualizer/visualizer/base.py b/src/tools/visualizer/visualizer/base.py new file mode 100644 index 000000000..acbe3e28b --- /dev/null +++ b/src/tools/visualizer/visualizer/base.py @@ -0,0 +1,107 @@ +import ns3 +import gobject +import os.path +import sys + +PIXELS_PER_METER = 3.0 # pixels-per-meter, at 100% zoom level + +class PyVizObject(gobject.GObject): + __gtype_name__ = "PyVizObject" + + def tooltip_query(self, tooltip): + tooltip.set_text("TODO: tooltip for %r" % self) + +class Link(PyVizObject): + pass + + +class InformationWindow(object): + def update(self): + raise NotImplementedError + +class NetDeviceTraits(object): + def __init__(self, is_wireless=None, is_virtual=False): + assert is_virtual or is_wireless is not None + self.is_wireless = is_wireless + self.is_virtual = is_virtual + +netdevice_traits = { + ns3.PointToPointNetDevice: NetDeviceTraits(is_wireless=False), + ns3.CsmaNetDevice: NetDeviceTraits(is_wireless=False), + ns3.WifiNetDevice: NetDeviceTraits(is_wireless=True), + ns3.BridgeNetDevice: NetDeviceTraits(is_virtual=True), + ns3.LoopbackNetDevice: NetDeviceTraits(is_virtual=True, is_wireless=False), + ns3.MeshPointDevice: NetDeviceTraits(is_virtual=True), + ns3.SubscriberStationNetDevice: NetDeviceTraits(is_wireless=True), + ns3.BaseStationNetDevice: NetDeviceTraits(is_wireless=True), +} + +def lookup_netdevice_traits(class_type): + try: + return netdevice_traits[class_type] + except KeyError: + sys.stderr.write("WARNING: no NetDeviceTraits registered for device type %r; " + "I will assume this is a non-virtual wireless device, " + "but you should edit %r, variable 'netdevice_traits'," + " to make sure.\n" % (class_type.__name__, __file__)) + t = NetDeviceTraits(is_virtual=False, is_wireless=True) + netdevice_traits[class_type] = t + return t + +def transform_distance_simulation_to_canvas(d): + return d*PIXELS_PER_METER + +def transform_point_simulation_to_canvas(x, y): + return x*PIXELS_PER_METER, y*PIXELS_PER_METER + +def transform_distance_canvas_to_simulation(d): + return d/PIXELS_PER_METER + +def transform_point_canvas_to_simulation(x, y): + return x/PIXELS_PER_METER, y/PIXELS_PER_METER + + + + +plugins = [] +plugin_modules = {} + +def register_plugin(plugin_init_func, plugin_name=None, plugin_module=None): + """ + Register a plugin. + + @param plugin: a callable object that will be invoked whenever a + Visualizer object is created, like this: plugin(visualizer) + """ + assert callable(plugin_init_func) + plugins.append(plugin_init_func) + if plugin_module is not None: + plugin_modules[plugin_name] = plugin_module + +plugins_loaded = False +def load_plugins(): + global plugins_loaded + if plugins_loaded: + return + plugins_loaded = True + plugins_dir = os.path.join(os.path.dirname(__file__), 'plugins') + old_path = list(sys.path) + sys.path.insert(0, plugins_dir) + for filename in os.listdir(plugins_dir): + name, ext = os.path.splitext(filename) + if ext != '.py': + continue + try: + plugin_module = __import__(name) + except ImportError, ex: + print >> sys.stderr, "Could not load plugin %r: %s" % (filename, str(ex)) + continue + try: + plugin_func = plugin_module.register + except AttributeError: + print >> sys.stderr, "Plugin %r has no 'register' function" % name + else: + #print >> sys.stderr, "Plugin %r registered" % name + register_plugin(plugin_func, name, plugin_module) + sys.path = old_path + diff --git a/src/tools/visualizer/visualizer/core.py b/src/tools/visualizer/visualizer/core.py new file mode 100644 index 000000000..c505cb079 --- /dev/null +++ b/src/tools/visualizer/visualizer/core.py @@ -0,0 +1,1470 @@ +# -*- Mode: python; coding: utf-8 -*- +from __future__ import division +#from __future__ import with_statement + +LAYOUT_ALGORITHM = 'neato' # ['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop'] +REPRESENT_CHANNELS_AS_NODES = 1 +DEFAULT_NODE_SIZE = 3.0 # default node size in meters +DEFAULT_TRANSMISSIONS_MEMORY = 5 # default number of of past intervals whose transmissions are remembered +BITRATE_FONT_SIZE = 10 + +# internal constants, normally not meant to be changed +SAMPLE_PERIOD = 0.1 +PRIORITY_UPDATE_MODEL = -100 +PRIORITY_UPDATE_VIEW = 200 + +import platform +if platform.system() == "Windows": + SHELL_FONT = "Lucida Console 9" +else: + SHELL_FONT = "Luxi Mono 10" + + +import ns3 +import math +import os +import sys +import gobject +import time + +try: + import pygraphviz + import gtk + import pango + import goocanvas + import cairo + import threading + import hud + #import time + import cairo + from higcontainer import HIGContainer + gobject.threads_init() + try: + import svgitem + except ImportError: + svgitem = None +except ImportError, _import_error: + import dummy_threading as threading +else: + _import_error = None + +try: + import ipython_view +except ImportError: + ipython_view = None + +from base import InformationWindow, PyVizObject, Link, lookup_netdevice_traits, PIXELS_PER_METER +from base import transform_distance_simulation_to_canvas, transform_point_simulation_to_canvas +from base import transform_distance_canvas_to_simulation, transform_point_canvas_to_simulation +from base import load_plugins, register_plugin, plugins + +PI_OVER_2 = math.pi/2 +PI_TIMES_2 = math.pi*2 + +class Node(PyVizObject): + + __gsignals__ = { + + # signal emitted whenever a tooltip is about to be shown for the node + # the first signal parameter is a python list of strings, to which information can be appended + 'query-extra-tooltip-info': (gobject.SIGNAL_RUN_LAST, None, (object,)), + + } + + def __init__(self, visualizer, node_index): + super(Node, self).__init__() + + self.visualizer = visualizer + self.node_index = node_index + self.canvas_item = goocanvas.Ellipse() + self.canvas_item.set_data("pyviz-object", self) + self.links = [] + self._has_mobility = None + self._selected = False + self._highlighted = False + self._color = 0x808080ff + self._size = DEFAULT_NODE_SIZE + self.canvas_item.connect("enter-notify-event", self.on_enter_notify_event) + self.canvas_item.connect("leave-notify-event", self.on_leave_notify_event) + self.menu = None + self.svg_item = None + self.svg_align_x = None + self.svg_align_y = None + self._label = None + self._label_canvas_item = None + + self._update_appearance() # call this last + + def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5): + """ + Set a background SVG icon for the node. + + @param file_base_name: base file name, including .svg + extension, of the svg file. Place the file in the folder + src/contrib/visualizer/resource. + + @param width: scale to the specified width, in meters + @param width: scale to the specified height, in meters + + @param align_x: horizontal alignment of the icon relative to + the node position, from 0 (icon fully to the left of the node) + to 1.0 (icon fully to the right of the node) + + @param align_y: vertical alignment of the icon relative to the + node position, from 0 (icon fully to the top of the node) to + 1.0 (icon fully to the bottom of the node) + + """ + if width is None and height is None: + raise ValueError("either width or height must be given") + rsvg_handle = svgitem.rsvg_handle_factory(file_base_name) + x = self.canvas_item.props.center_x + y = self.canvas_item.props.center_y + self.svg_item = svgitem.SvgItem(x, y, rsvg_handle) + self.svg_item.props.parent = self.visualizer.canvas.get_root_item() + self.svg_item.props.pointer_events = 0 + self.svg_item.lower(None) + self.svg_item.props.visibility = goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD + if width is not None: + self.svg_item.props.width = transform_distance_simulation_to_canvas(width) + if height is not None: + self.svg_item.props.height = transform_distance_simulation_to_canvas(height) + + #threshold1 = 10.0/self.svg_item.props.height + #threshold2 = 10.0/self.svg_item.props.width + #self.svg_item.props.visibility_threshold = min(threshold1, threshold2) + + self.svg_align_x = align_x + self.svg_align_y = align_y + self._update_svg_position(x, y) + self._update_appearance() + + def set_label(self, label): + assert isinstance(label, basestring) + self._label = label + self._update_appearance() + + def _update_svg_position(self, x, y): + w = self.svg_item.width + h = self.svg_item.height + self.svg_item.set_properties(x=(x - (1-self.svg_align_x)*w), + y=(y - (1-self.svg_align_y)*h)) + + + def tooltip_query(self, tooltip): + self.visualizer.simulation.lock.acquire() + try: + ns3_node = ns3.NodeList.GetNode(self.node_index) + ipv4 = ns3_node.GetObject(ns3.Ipv4.GetTypeId()) + lines = ['Node %i' % self.node_index] + lines.append('') + + self.emit("query-extra-tooltip-info", lines) + + mob = ns3_node.GetObject(ns3.MobilityModel.GetTypeId()) + if mob is not None: + lines.append(' Mobility Model: %s' % mob.GetInstanceTypeId().GetName()) + + for devI in range(ns3_node.GetNDevices()): + lines.append('') + lines.append(' NetDevice %i:' % devI) + dev = ns3_node.GetDevice(devI) + name = ns3.Names.FindName(dev) + if name: + lines.append(' Name: %s' % name) + devname = dev.GetInstanceTypeId().GetName() + lines.append(' Type: %s' % devname) + if ipv4 is not None: + ipv4_idx = ipv4.GetInterfaceForDevice(dev) + if ipv4_idx != -1: + addresses = [ + '%s/%s' % (ipv4.GetAddress(ipv4_idx, i).GetLocal(), + ipv4.GetAddress(ipv4_idx, i).GetMask()) + for i in range(ipv4.GetNAddresses(ipv4_idx))] + lines.append(' IPv4 Addresses: %s' % '; '.join(addresses)) + + lines.append(' MAC Address: %s' % (dev.GetAddress(),)) + + tooltip.set_markup('\n'.join(lines)) + finally: + self.visualizer.simulation.lock.release() + + def on_enter_notify_event(self, view, target, event): + self.highlighted = True + def on_leave_notify_event(self, view, target, event): + self.highlighted = False + + def _set_selected(self, value): + self._selected = value + self._update_appearance() + def _get_selected(self): + return self._selected + selected = property(_get_selected, _set_selected) + + def _set_highlighted(self, value): + self._highlighted = value + self._update_appearance() + def _get_highlighted(self): + return self._highlighted + highlighted = property(_get_highlighted, _set_highlighted) + + def set_size(self, size): + self._size = size + self._update_appearance() + + def _update_appearance(self): + """Update the node aspect to reflect the selected/highlighted state""" + + size = transform_distance_simulation_to_canvas(self._size) + if self.svg_item is not None: + alpha = 0x80 + else: + alpha = 0xff + fill_color_rgba = (self._color & 0xffffff00) | alpha + self.canvas_item.set_properties(radius_x=size, radius_y=size, + fill_color_rgba=fill_color_rgba) + if self._selected: + line_width = size*.3 + else: + line_width = size*.15 + if self.highlighted: + stroke_color = 'yellow' + else: + stroke_color = 'black' + self.canvas_item.set_properties(line_width=line_width, stroke_color=stroke_color) + + if self._label is not None: + if self._label_canvas_item is None: + self._label_canvas_item = goocanvas.Text(visibility_threshold=0.5, + font="Sans Serif 10", + fill_color_rgba=0x808080ff, + alignment=pango.ALIGN_CENTER, + anchor=gtk.ANCHOR_N, + parent=self.visualizer.canvas.get_root_item(), + pointer_events=0) + self._label_canvas_item.lower(None) + + self._label_canvas_item.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD, + text=self._label) + self._update_position() + + def set_position(self, x, y): + self.canvas_item.set_property("center_x", x) + self.canvas_item.set_property("center_y", y) + if self.svg_item is not None: + self._update_svg_position(x, y) + + for link in self.links: + link.update_points() + + if self._label_canvas_item is not None: + self._label_canvas_item.set_properties(x=x, y=(y+self._size*3)) + + def get_position(self): + return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y")) + + def _update_position(self): + x, y = self.get_position() + self.set_position(x, y) + + def set_color(self, color): + if isinstance(color, str): + color = gtk.gdk.color_parse(color) + color = ((color.red>>8) << 24) | ((color.green>>8) << 16) | ((color.blue>>8) << 8) | 0xff + self._color = color + self._update_appearance() + + def add_link(self, link): + assert isinstance(link, Link) + self.links.append(link) + + def remove_link(self, link): + assert isinstance(link, Link) + self.links.remove(link) + + @property + def has_mobility(self): + if self._has_mobility is None: + node = ns3.NodeList.GetNode(self.node_index) + mobility = node.GetObject(ns3.MobilityModel.GetTypeId()) + self._has_mobility = (mobility is not None) + return self._has_mobility + + +class Channel(PyVizObject): + def __init__(self, channel): + self.channel = channel + self.canvas_item = goocanvas.Ellipse(radius_x=30, radius_y=30, + fill_color="white", + stroke_color="grey", line_width=2.0, + line_dash=goocanvas.LineDash([10.0, 10.0 ]), + visibility=goocanvas.ITEM_VISIBLE) + self.canvas_item.set_data("pyviz-object", self) + self.links = [] + + def set_position(self, x, y): + self.canvas_item.set_property("center_x", x) + self.canvas_item.set_property("center_y", y) + + for link in self.links: + link.update_points() + + def get_position(self): + return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y")) + + +class WiredLink(Link): + def __init__(self, node1, node2): + assert isinstance(node1, Node) + assert isinstance(node2, (Node, Channel)) + self.node1 = node1 + self.node2 = node2 + self.canvas_item = goocanvas.Path(line_width=1.0, stroke_color="black") + self.canvas_item.set_data("pyviz-object", self) + self.node1.links.append(self) + self.node2.links.append(self) + + def update_points(self): + pos1_x, pos1_y = self.node1.get_position() + pos2_x, pos2_y = self.node2.get_position() + self.canvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y)) + + + +class SimulationThread(threading.Thread): + def __init__(self, viz): + super(SimulationThread, self).__init__() + assert isinstance(viz, Visualizer) + self.viz = viz # Visualizer object + self.lock = threading.Lock() + self.go = threading.Event() + self.go.clear() + self.target_time = 0 # in seconds + self.quit = False + self.sim_helper = ns3.PyViz() + self.pause_messages = [] + + def set_nodes_of_interest(self, nodes): + self.lock.acquire() + try: + self.sim_helper.SetNodesOfInterest(nodes) + finally: + self.lock.release() + + def run(self): + while not self.quit: + #print "sim: Wait for go" + self.go.wait() # wait until the main (view) thread gives us the go signal + self.go.clear() + if self.quit: + break + #self.go.clear() + #print "sim: Acquire lock" + self.lock.acquire() + try: + if 0: + if ns3.Simulator.IsFinished(): + self.viz.play_button.set_sensitive(False) + break + #print "sim: Current time is %f; Run until: %f" % (ns3.Simulator.Now ().GetSeconds (), self.target_time) + #if ns3.Simulator.Now ().GetSeconds () > self.target_time: + # print "skipping, model is ahead of view!" + self.sim_helper.SimulatorRunUntil(ns3.Seconds(self.target_time)) + #print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds () + self.pause_messages.extend(self.sim_helper.GetPauseMessages()) + gobject.idle_add(self.viz.update_model, priority=PRIORITY_UPDATE_MODEL) + #print "sim: Run until: ", self.target_time, ": finished." + finally: + self.lock.release() + #print "sim: Release lock, loop." + +# enumeration +class ShowTransmissionsMode(object): + __slots__ = [] +ShowTransmissionsMode.ALL = ShowTransmissionsMode() +ShowTransmissionsMode.NONE = ShowTransmissionsMode() +ShowTransmissionsMode.SELECTED = ShowTransmissionsMode() + +class Visualizer(gobject.GObject): + INSTANCE = None + + if _import_error is None: + __gsignals__ = { + + # signal emitted whenever a right-click-on-node popup menu is being constructed + 'populate-node-menu': (gobject.SIGNAL_RUN_LAST, None, (object, gtk.Menu,)), + + # signal emitted after every simulation period (SAMPLE_PERIOD seconds of simulated time) + # the simulation lock is acquired while the signal is emitted + 'simulation-periodic-update': (gobject.SIGNAL_RUN_LAST, None, ()), + + # signal emitted right after the topology is scanned + 'topology-scanned': (gobject.SIGNAL_RUN_LAST, None, ()), + + # signal emitted when it's time to update the view objects + 'update-view': (gobject.SIGNAL_RUN_LAST, None, ()), + + } + + def __init__(self): + assert Visualizer.INSTANCE is None + Visualizer.INSTANCE = self + super(Visualizer, self).__init__() + self.nodes = {} # node index -> Node + self.channels = {} # id(ns3.Channel) -> Channel + self.window = None # toplevel window + self.canvas = None # goocanvas.Canvas + self.time_label = None # gtk.Label + self.play_button = None # gtk.ToggleButton + self.zoom = None # gtk.Adjustment + self._scrolled_window = None # gtk.ScrolledWindow + + self.links_group = goocanvas.Group() + self.channels_group = goocanvas.Group() + self.nodes_group = goocanvas.Group() + + self._update_timeout_id = None + self.simulation = SimulationThread(self) + self.selected_node = None # node currently selected + self.speed = 1.0 + self.information_windows = [] + self._transmission_arrows = [] + self._last_transmissions = [] + self._drop_arrows = [] + self._last_drops = [] + self._show_transmissions_mode = None + self.set_show_transmissions_mode(ShowTransmissionsMode.ALL) + self._panning_state = None + self.node_size_adjustment = None + self.transmissions_smoothing_adjustment = None + self.sample_period = SAMPLE_PERIOD + self.node_drag_state = None + self.follow_node = None + self.shell_window = None + + self.create_gui() + + for plugin in plugins: + plugin(self) + + def set_show_transmissions_mode(self, mode): + assert isinstance(mode, ShowTransmissionsMode) + self._show_transmissions_mode = mode + if self._show_transmissions_mode == ShowTransmissionsMode.ALL: + self.simulation.set_nodes_of_interest(range(ns3.NodeList.GetNNodes())) + elif self._show_transmissions_mode == ShowTransmissionsMode.NONE: + self.simulation.set_nodes_of_interest([]) + elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED: + if self.selected_node is None: + self.simulation.set_nodes_of_interest([]) + else: + self.simulation.set_nodes_of_interest([self.selected_node.node_index]) + + def _create_advanced_controls(self): + expander = gtk.Expander("Advanced") + expander.show() + + main_vbox = gobject.new(gtk.VBox, border_width=8, visible=True) + expander.add(main_vbox) + + main_hbox1 = gobject.new(gtk.HBox, border_width=8, visible=True) + main_vbox.pack_start(main_hbox1) + + show_transmissions_group = HIGContainer("Show transmissions") + show_transmissions_group.show() + main_hbox1.pack_start(show_transmissions_group, False, False, 8) + + vbox = gtk.VBox(True, 4) + vbox.show() + show_transmissions_group.add(vbox) + + all_nodes = gtk.RadioButton(None) + all_nodes.set_label("All nodes") + all_nodes.set_active(True) + all_nodes.show() + vbox.add(all_nodes) + + selected_node = gtk.RadioButton(all_nodes) + selected_node.show() + selected_node.set_label("Selected node") + selected_node.set_active(False) + vbox.add(selected_node) + + no_node = gtk.RadioButton(all_nodes) + no_node.show() + no_node.set_label("Disabled") + no_node.set_active(False) + vbox.add(no_node) + + def toggled(radio): + if radio.get_active(): + self.set_show_transmissions_mode(ShowTransmissionsMode.ALL) + all_nodes.connect("toggled", toggled) + + def toggled(radio): + if radio.get_active(): + self.set_show_transmissions_mode(ShowTransmissionsMode.NONE) + no_node.connect("toggled", toggled) + + def toggled(radio): + if radio.get_active(): + self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED) + selected_node.connect("toggled", toggled) + + + # -- misc settings + misc_settings_group = HIGContainer("Misc Settings") + misc_settings_group.show() + main_hbox1.pack_start(misc_settings_group, False, False, 8) + settings_hbox = gobject.new(gtk.HBox, border_width=8, visible=True) + misc_settings_group.add(settings_hbox) + + # --> node size + vbox = gobject.new(gtk.VBox, border_width=0, visible=True) + scale = gobject.new(gtk.HScale, visible=True, digits=2) + vbox.pack_start(scale, True, True, 0) + vbox.pack_start(gobject.new(gtk.Label, label="Node Size", visible=True), True, True, 0) + settings_hbox.pack_start(vbox, False, False, 6) + self.node_size_adjustment = scale.get_adjustment() + def node_size_changed(adj): + for node in self.nodes.itervalues(): + node.set_size(adj.value) + self.node_size_adjustment.connect("value-changed", node_size_changed) + self.node_size_adjustment.set_all(DEFAULT_NODE_SIZE, 0.01, 20, 0.1) + + # --> transmissions smooth factor + vbox = gobject.new(gtk.VBox, border_width=0, visible=True) + scale = gobject.new(gtk.HScale, visible=True, digits=1) + vbox.pack_start(scale, True, True, 0) + vbox.pack_start(gobject.new(gtk.Label, label="Tx. Smooth Factor (s)", visible=True), True, True, 0) + settings_hbox.pack_start(vbox, False, False, 6) + self.transmissions_smoothing_adjustment = scale.get_adjustment() + self.transmissions_smoothing_adjustment.set_all(DEFAULT_TRANSMISSIONS_MEMORY*0.1, 0.1, 10, 0.1) + + return expander + + class _PanningState(object): + __slots__ = ['initial_mouse_pos', 'initial_canvas_pos', 'motion_signal'] + + def _begin_panning(self, widget, event): + self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR)) + self._panning_state = self._PanningState() + x, y, dummy = widget.window.get_pointer() + self._panning_state.initial_mouse_pos = (x, y) + x = self._scrolled_window.get_hadjustment().value + y = self._scrolled_window.get_vadjustment().value + self._panning_state.initial_canvas_pos = (x, y) + self._panning_state.motion_signal = self.canvas.connect("motion-notify-event", self._panning_motion) + + def _end_panning(self, event): + if self._panning_state is None: + return + self.canvas.window.set_cursor(None) + self.canvas.disconnect(self._panning_state.motion_signal) + self._panning_state = None + + def _panning_motion(self, widget, event): + assert self._panning_state is not None + if event.is_hint: + x, y, dummy = widget.window.get_pointer() + else: + x, y = event.x, event.y + + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + mx0, my0 = self._panning_state.initial_mouse_pos + cx0, cy0 = self._panning_state.initial_canvas_pos + + dx = x - mx0 + dy = y - my0 + hadj.value = cx0 - dx + vadj.value = cy0 - dy + return True + + def _canvas_button_press(self, widget, event): + if event.button == 2: + self._begin_panning(widget, event) + return True + return False + + def _canvas_button_release(self, dummy_widget, event): + if event.button == 2: + self._end_panning(event) + return True + return False + + def _canvas_scroll_event(self, dummy_widget, event): + if event.direction == gtk.gdk.SCROLL_UP: + self.zoom.value *= 1.25 + return True + elif event.direction == gtk.gdk.SCROLL_DOWN: + self.zoom.value /= 1.25 + return True + return False + + def get_hadjustment(self): + return self._scrolled_window.get_hadjustment() + def get_vadjustment(self): + return self._scrolled_window.get_vadjustment() + + def create_gui(self): + self.window = gtk.Window() + vbox = gtk.VBox(); vbox.show() + self.window.add(vbox) + + # canvas + self.canvas = goocanvas.Canvas() + self.canvas.connect_after("button-press-event", self._canvas_button_press) + self.canvas.connect_after("button-release-event", self._canvas_button_release) + self.canvas.connect("scroll-event", self._canvas_scroll_event) + self.canvas.props.has_tooltip = True + self.canvas.connect("query-tooltip", self._canvas_tooltip_cb) + self.canvas.show() + sw = gtk.ScrolledWindow(); sw.show() + self._scrolled_window = sw + sw.add(self.canvas) + vbox.pack_start(sw, True, True, 4) + self.canvas.set_size_request(600, 450) + self.canvas.set_bounds(-10000, -10000, 10000, 10000) + self.canvas.scroll_to(0, 0) + + + self.canvas.get_root_item().add_child(self.links_group) + self.links_group.set_property("visibility", goocanvas.ITEM_VISIBLE) + + self.canvas.get_root_item().add_child(self.channels_group) + self.channels_group.set_property("visibility", goocanvas.ITEM_VISIBLE) + self.channels_group.raise_(self.links_group) + + self.canvas.get_root_item().add_child(self.nodes_group) + self.nodes_group.set_property("visibility", goocanvas.ITEM_VISIBLE) + self.nodes_group.raise_(self.channels_group) + + self.hud = hud.Axes(self) + + hbox = gtk.HBox(); hbox.show() + vbox.pack_start(hbox, False, False, 4) + + # zoom + zoom_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0) + self.zoom = zoom_adj + def _zoom_changed(adj): + self.canvas.set_scale(adj.value) + zoom_adj.connect("value-changed", _zoom_changed) + zoom = gtk.SpinButton(zoom_adj) + zoom.set_digits(3) + zoom.show() + hbox.pack_start(gobject.new(gtk.Label, label=" Zoom:", visible=True), False, False, 4) + hbox.pack_start(zoom, False, False, 4) + _zoom_changed(zoom_adj) + + # speed + speed_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0) + def _speed_changed(adj): + self.speed = adj.value + self.sample_period = SAMPLE_PERIOD*adj.value + self._start_update_timer() + speed_adj.connect("value-changed", _speed_changed) + speed = gtk.SpinButton(speed_adj) + speed.set_digits(3) + speed.show() + hbox.pack_start(gobject.new(gtk.Label, label=" Speed:", visible=True), False, False, 4) + hbox.pack_start(speed, False, False, 4) + _speed_changed(speed_adj) + + # Current time + self.time_label = gobject.new(gtk.Label, label=" Speed:", visible=True) + self.time_label.set_width_chars(20) + hbox.pack_start(self.time_label, False, False, 4) + + # Screenshot button + screenshot_button = gobject.new(gtk.Button, + label="Snapshot", + relief=gtk.RELIEF_NONE, focus_on_click=False, + visible=True) + hbox.pack_start(screenshot_button, False, False, 4) + + def load_button_icon(button, icon_name): + try: + import gnomedesktop + except ImportError: + sys.stderr.write("Could not load icon %s due to missing gnomedesktop Python module\n" % icon_name) + else: + icon = gnomedesktop.find_icon(gtk.icon_theme_get_default(), icon_name, 16, 0) + if icon is not None: + button.props.image = gobject.new(gtk.Image, file=icon, visible=True) + + load_button_icon(screenshot_button, "applets-screenshooter") + screenshot_button.connect("clicked", self._take_screenshot) + + # Shell button + if ipython_view is not None: + shell_button = gobject.new(gtk.Button, + label="Shell", + relief=gtk.RELIEF_NONE, focus_on_click=False, + visible=True) + hbox.pack_start(shell_button, False, False, 4) + load_button_icon(shell_button, "gnome-terminal") + shell_button.connect("clicked", self._start_shell) + + # Play button + self.play_button = gobject.new(gtk.ToggleButton, + image=gobject.new(gtk.Image, stock=gtk.STOCK_MEDIA_PLAY, visible=True), + label="Simulate (F3)", + relief=gtk.RELIEF_NONE, focus_on_click=False, + use_stock=True, visible=True) + accel_group = gtk.AccelGroup() + self.window.add_accel_group(accel_group) + self.play_button.add_accelerator("clicked", accel_group, + gtk.keysyms.F3, 0, gtk.ACCEL_VISIBLE) + self.play_button.connect("toggled", self._on_play_button_toggled) + hbox.pack_start(self.play_button, False, False, 4) + + self.canvas.get_root_item().connect("button-press-event", self.on_root_button_press_event) + + vbox.pack_start(self._create_advanced_controls(), False, False, 4) + + self.window.show() + + def scan_topology(self): + print "scanning topology: %i nodes..." % (ns3.NodeList.GetNNodes(),) + graph = pygraphviz.AGraph() + seen_nodes = 0 + for nodeI in range(ns3.NodeList.GetNNodes()): + seen_nodes += 1 + if seen_nodes == 100: + print "scan topology... %i nodes visited (%.1f%%)" % (nodeI, 100*nodeI/ns3.NodeList.GetNNodes()) + seen_nodes = 0 + node = ns3.NodeList.GetNode(nodeI) + node_name = "Node %i" % nodeI + node_view = self.get_node(nodeI) + + mobility = node.GetObject(ns3.MobilityModel.GetTypeId()) + if mobility is not None: + node_view.set_color("red") + pos = mobility.GetPosition() + node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y)) + #print "node has mobility position -> ", "%f,%f" % (pos.x, pos.y) + else: + graph.add_node(node_name) + + for devI in range(node.GetNDevices()): + device = node.GetDevice(devI) + device_traits = lookup_netdevice_traits(type(device)) + if device_traits.is_wireless: + continue + if device_traits.is_virtual: + continue + channel = device.GetChannel() + if channel.GetNDevices() > 2: + if REPRESENT_CHANNELS_AS_NODES: + # represent channels as white nodes + if mobility is None: + channel_name = "Channel %s" % id(channel) + graph.add_edge(node_name, channel_name) + self.get_channel(channel) + self.create_link(self.get_node(nodeI), self.get_channel(channel)) + else: + # don't represent channels, just add links between nodes in the same channel + for otherDevI in range(channel.GetNDevices()): + otherDev = channel.GetDevice(otherDevI) + otherNode = otherDev.GetNode() + otherNodeView = self.get_node(otherNode.GetId()) + if otherNode is not node: + if mobility is None and not otherNodeView.has_mobility: + other_node_name = "Node %i" % otherNode.GetId() + graph.add_edge(node_name, other_node_name) + self.create_link(self.get_node(nodeI), otherNodeView) + else: + for otherDevI in range(channel.GetNDevices()): + otherDev = channel.GetDevice(otherDevI) + otherNode = otherDev.GetNode() + otherNodeView = self.get_node(otherNode.GetId()) + if otherNode is not node: + if mobility is None and not otherNodeView.has_mobility: + other_node_name = "Node %i" % otherNode.GetId() + graph.add_edge(node_name, other_node_name) + self.create_link(self.get_node(nodeI), otherNodeView) + + print "scanning topology: calling graphviz layout" + graph.layout(LAYOUT_ALGORITHM) + for node in graph.iternodes(): + #print node, "=>", node.attr['pos'] + node_type, node_id = node.split(' ') + pos_x, pos_y = [float(s) for s in node.attr['pos'].split(',')] + if node_type == 'Node': + obj = self.nodes[int(node_id)] + elif node_type == 'Channel': + obj = self.channels[int(node_id)] + obj.set_position(pos_x, pos_y) + + print "scanning topology: all done." + self.emit("topology-scanned") + + def get_node(self, index): + try: + return self.nodes[index] + except KeyError: + node = Node(self, index) + self.nodes[index] = node + self.nodes_group.add_child(node.canvas_item) + node.canvas_item.connect("button-press-event", self.on_node_button_press_event, node) + node.canvas_item.connect("button-release-event", self.on_node_button_release_event, node) + return node + + def get_channel(self, ns3_channel): + try: + return self.channels[id(ns3_channel)] + except KeyError: + channel = Channel(ns3_channel) + self.channels[id(ns3_channel)] = channel + self.channels_group.add_child(channel.canvas_item) + return channel + + def create_link(self, node, node_or_channel): + link = WiredLink(node, node_or_channel) + self.links_group.add_child(link.canvas_item) + link.canvas_item.lower(None) + + def update_view(self): + #print "update_view" + + self.time_label.set_text("Time: %f s" % ns3.Simulator.Now().GetSeconds()) + + self._update_node_positions() + + # Update information + for info_win in self.information_windows: + info_win.update() + + self._update_transmissions_view() + self._update_drops_view() + + self.emit("update-view") + + def _update_node_positions(self): + for node in self.nodes.itervalues(): + if node.has_mobility: + ns3_node = ns3.NodeList.GetNode(node.node_index) + mobility = ns3_node.GetObject(ns3.MobilityModel.GetTypeId()) + if mobility is not None: + pos = mobility.GetPosition() + x, y = transform_point_simulation_to_canvas(pos.x, pos.y) + node.set_position(x, y) + if node is self.follow_node: + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + px, py = self.canvas.convert_to_pixels(x, y) + hadj.value = px - hadj.page_size/2 + vadj.value = py - vadj.page_size/2 + + def center_on_node(self, node): + if isinstance(node, ns3.Node): + node = self.nodes[node.GetId()] + elif isinstance(node, (int, long)): + node = self.nodes[node] + elif isinstance(node, Node): + pass + else: + raise TypeError("expected int, viz.Node or ns3.Node, not %r" % node) + + x, y = node.get_position() + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + px, py = self.canvas.convert_to_pixels(x, y) + hadj.value = px - hadj.page_size/2 + vadj.value = py - vadj.page_size/2 + + + def update_model(self): + self.simulation.lock.acquire() + try: + self.emit("simulation-periodic-update") + finally: + self.simulation.lock.release() + + def do_simulation_periodic_update(self): + smooth_factor = int(self.transmissions_smoothing_adjustment.value*10) + + transmissions = self.simulation.sim_helper.GetTransmissionSamples() + self._last_transmissions.append(transmissions) + while len(self._last_transmissions) > smooth_factor: + self._last_transmissions.pop(0) + + drops = self.simulation.sim_helper.GetPacketDropSamples() + self._last_drops.append(drops) + while len(self._last_drops) > smooth_factor: + self._last_drops.pop(0) + + def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y): + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value) + bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(hadj.value + hadj.page_size, + vadj.value + vadj.page_size) + pos1_x, pos1_y, pos2_x, pos2_y = ns3.PyViz.LineClipping(bounds_x1, bounds_y1, + bounds_x2, bounds_y2, + pos1_x, pos1_y, + pos2_x, pos2_y) + return (pos1_x + pos2_x)/2, (pos1_y + pos2_y)/2 + + def _update_transmissions_view(self): + transmissions_average = {} + for transmission_set in self._last_transmissions: + for transmission in transmission_set: + key = (transmission.transmitter.GetId(), transmission.receiver.GetId()) + rx_bytes, count = transmissions_average.get(key, (0, 0)) + rx_bytes += transmission.bytes + count += 1 + transmissions_average[key] = rx_bytes, count + + old_arrows = self._transmission_arrows + for arrow, label in old_arrows: + arrow.set_property("visibility", goocanvas.ITEM_HIDDEN) + label.set_property("visibility", goocanvas.ITEM_HIDDEN) + new_arrows = [] + + k = self.node_size_adjustment.value/5 + + for (transmitter_id, receiver_id), (rx_bytes, rx_count) in transmissions_average.iteritems(): + transmitter = self.get_node(transmitter_id) + receiver = self.get_node(receiver_id) + try: + arrow, label = old_arrows.pop() + except IndexError: + arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0x00C000C0, close_path=False, end_arrow=True) + arrow.set_property("parent", self.canvas.get_root_item()) + arrow.props.pointer_events = 0 + arrow.raise_(None) + + label = goocanvas.Text(parent=self.canvas.get_root_item(), pointer_events=0) + label.raise_(None) + + arrow.set_property("visibility", goocanvas.ITEM_VISIBLE) + line_width = max(0.1, math.log(float(rx_bytes)/rx_count/self.sample_period)*k) + arrow.set_property("line-width", line_width) + + pos1_x, pos1_y = transmitter.get_position() + pos2_x, pos2_y = receiver.get_position() + points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)]) + arrow.set_property("points", points) + + kbps = float(rx_bytes*8)/1e3/rx_count/self.sample_period + label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD, + visibility_threshold=0.5, + font=("Sans Serif %f" % int(1+BITRATE_FONT_SIZE*k))) + angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x)) + if -PI_OVER_2 <= angle <= PI_OVER_2: + label.set_properties(text=("%.2f kbit/s →" % (kbps,)), + alignment=pango.ALIGN_CENTER, + anchor=gtk.ANCHOR_S, + x=0, y=-line_width/2) + M = cairo.Matrix() + M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y)) + M.rotate(angle) + label.set_transform(M) + else: + label.set_properties(text=("← %.2f kbit/s" % (kbps,)), + alignment=pango.ALIGN_CENTER, + anchor=gtk.ANCHOR_N, + x=0, y=line_width/2) + M = cairo.Matrix() + M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y)) + M.rotate(angle) + M.scale(-1, -1) + label.set_transform(M) + + new_arrows.append((arrow, label)) + + self._transmission_arrows = new_arrows + old_arrows + + + def _update_drops_view(self): + drops_average = {} + for drop_set in self._last_drops: + for drop in drop_set: + key = drop.transmitter.GetId() + drop_bytes, count = drops_average.get(key, (0, 0)) + drop_bytes += drop.bytes + count += 1 + drops_average[key] = drop_bytes, count + + old_arrows = self._drop_arrows + for arrow, label in old_arrows: + arrow.set_property("visibility", goocanvas.ITEM_HIDDEN) + label.set_property("visibility", goocanvas.ITEM_HIDDEN) + new_arrows = [] + + # get the coordinates for the edge of screen + vadjustment = self._scrolled_window.get_vadjustment() + bottom_y = vadjustment.value + vadjustment.page_size + dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y) + + k = self.node_size_adjustment.value/5 + + for transmitter_id, (drop_bytes, drop_count) in drops_average.iteritems(): + transmitter = self.get_node(transmitter_id) + try: + arrow, label = old_arrows.pop() + except IndexError: + arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0xC00000C0, close_path=False, end_arrow=True) + arrow.props.pointer_events = 0 + arrow.set_property("parent", self.canvas.get_root_item()) + arrow.raise_(None) + + label = goocanvas.Text()#, fill_color_rgba=0x00C000C0) + label.props.pointer_events = 0 + label.set_property("parent", self.canvas.get_root_item()) + label.raise_(None) + + arrow.set_property("visibility", goocanvas.ITEM_VISIBLE) + arrow.set_property("line-width", max(0.1, math.log(float(drop_bytes)/drop_count/self.sample_period)*k)) + pos1_x, pos1_y = transmitter.get_position() + pos2_x, pos2_y = pos1_x, edge_y + points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)]) + arrow.set_property("points", points) + + label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD, + visibility_threshold=0.5, + font=("Sans Serif %i" % int(1+BITRATE_FONT_SIZE*k)), + text=("%.2f kbit/s" % (float(drop_bytes*8)/1e3/drop_count/self.sample_period,)), + alignment=pango.ALIGN_CENTER, + x=(pos1_x + pos2_x)/2, + y=(pos1_y + pos2_y)/2) + + new_arrows.append((arrow, label)) + + self._drop_arrows = new_arrows + old_arrows + + + def update_view_timeout(self): + #print "view: update_view_timeout called at real time ", time.time() + + # while the simulator is busy, run the gtk event loop + while not self.simulation.lock.acquire(False): + while gtk.events_pending(): + gtk.main_iteration() + pause_messages = self.simulation.pause_messages + self.simulation.pause_messages = [] + try: + self.update_view() + self.simulation.target_time = ns3.Simulator.Now ().GetSeconds () + self.sample_period + #print "view: target time set to %f" % self.simulation.target_time + finally: + self.simulation.lock.release() + + if pause_messages: + #print pause_messages + dialog = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK, + message_format='\n'.join(pause_messages)) + dialog.connect("response", lambda d, r: d.destroy()) + dialog.show() + self.play_button.set_active(False) + + # if we're paused, stop the update timer + if not self.play_button.get_active(): + self._update_timeout_id = None + return False + + #print "view: self.simulation.go.set()" + self.simulation.go.set() + #print "view: done." + return True + + def _start_update_timer(self): + if self._update_timeout_id is not None: + gobject.source_remove(self._update_timeout_id) + #print "start_update_timer" + self._update_timeout_id = gobject.timeout_add(int(SAMPLE_PERIOD/min(self.speed, 1)*1e3), + self.update_view_timeout, + priority=PRIORITY_UPDATE_VIEW) + + def _on_play_button_toggled(self, button): + if button.get_active(): + self._start_update_timer() + else: + if self._update_timeout_id is not None: + gobject.source_remove(self._update_timeout_id) + + def _quit(self, *dummy_args): + if self._update_timeout_id is not None: + gobject.source_remove(self._update_timeout_id) + self._update_timeout_id = None + self.simulation.quit = True + self.simulation.go.set() + self.simulation.join() + gtk.main_quit() + + def _monkey_patch_ipython(self): + # The user may want to access the NS 3 simulation state, but + # NS 3 is not thread safe, so it could cause serious problems. + # To work around this, monkey-patch IPython to automatically + # acquire and release the simulation lock around each code + # that is executed. + + original_runcode = __IPYTHON__.runcode + def runcode(ip, *args): + #print "lock" + self.simulation.lock.acquire() + try: + return original_runcode(*args) + finally: + #print "unlock" + self.simulation.lock.release() + import types + __IPYTHON__.runcode = types.MethodType(runcode, __IPYTHON__) + + def autoscale_view(self): + self._update_node_positions() + positions = [node.get_position() for node in self.nodes.itervalues()] + min_x, min_y = min(x for (x,y) in positions), min(y for (x,y) in positions) + max_x, max_y = max(x for (x,y) in positions), max(y for (x,y) in positions) + min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y) + max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y) + dx = max_x - min_x + dy = max_y - min_y + dx_px = max_x_px - min_x_px + dy_px = max_y_px - min_y_px + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + new_dx, new_dy = 1.5*dx_px, 1.5*dy_px + + if new_dx == 0 or new_dy == 0: + return + + self.zoom.value = min(hadj.page_size/new_dx, vadj.page_size/new_dy) + + x1, y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value) + x2, y2 = self.canvas.convert_from_pixels(hadj.value+hadj.page_size, vadj.value+vadj.page_size) + width = x2 - x1 + height = y2 - y1 + center_x = (min_x + max_x) / 2 + center_y = (min_y + max_y) / 2 + + self.canvas.scroll_to(center_x - width/2, center_y - height/2) + + return False + + def start(self): + self.scan_topology() + self.window.connect("delete-event", self._quit) + #self._start_update_timer() + gobject.timeout_add(200, self.autoscale_view) + self.simulation.start() + + try: + __IPYTHON__ + except NameError: + pass + else: + self._monkey_patch_ipython() + + gtk.main() + + + def on_root_button_press_event(self, view, target, event): + if event.button == 1: + self.select_node(None) + return True + + def on_node_button_press_event(self, view, target, event, node): + if event.button == 1: + self.select_node(node) + return True + elif event.button == 3: + self.popup_node_menu(node, event) + return True + elif event.button == 2: + self.begin_node_drag(node) + return True + return False + + def on_node_button_release_event(self, view, target, event, node): + if event.button == 2: + self.end_node_drag(node) + return True + return False + + class NodeDragState(object): + def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0): + self.canvas_x0 = canvas_x0 + self.canvas_y0 = canvas_y0 + self.sim_x0 = sim_x0 + self.sim_y0 = sim_y0 + self.motion_signal = None + + def begin_node_drag(self, node): + self.simulation.lock.acquire() + try: + ns3_node = ns3.NodeList.GetNode(node.node_index) + mob = ns3_node.GetObject(ns3.MobilityModel.GetTypeId()) + if mob is None: + return + if self.node_drag_state is not None: + return + pos = mob.GetPosition() + finally: + self.simulation.lock.release() + x, y, dummy = self.canvas.window.get_pointer() + x0, y0 = self.canvas.convert_from_pixels(x, y) + self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y) + self.node_drag_state.motion_signal = node.canvas_item.connect("motion-notify-event", self.node_drag_motion, node) + + def node_drag_motion(self, item, targe_item, event, node): + self.simulation.lock.acquire() + try: + ns3_node = ns3.NodeList.GetNode(node.node_index) + mob = ns3_node.GetObject(ns3.MobilityModel.GetTypeId()) + if mob is None: + return False + if self.node_drag_state is None: + return False + x, y, dummy = self.canvas.window.get_pointer() + canvas_x, canvas_y = self.canvas.convert_from_pixels(x, y) + dx = (canvas_x - self.node_drag_state.canvas_x0) + dy = (canvas_y - self.node_drag_state.canvas_y0) + pos = mob.GetPosition() + pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx) + pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy) + #print "SetPosition(%G, %G)" % (pos.x, pos.y) + mob.SetPosition(pos) + node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y)) + finally: + self.simulation.lock.release() + return True + + def end_node_drag(self, node): + if self.node_drag_state is None: + return + node.canvas_item.disconnect(self.node_drag_state.motion_signal) + self.node_drag_state = None + + def popup_node_menu(self, node, event): + menu = gtk.Menu() + self.emit("populate-node-menu", node, menu) + menu.popup(None, None, None, event.button, event.time) + + def _update_ipython_selected_node(self): + # If we are running under ipython -gthread, make this new + # selected node available as a global 'selected_node' + # variable. + try: + __IPYTHON__ + except NameError: + pass + else: + if self.selected_node is None: + ns3_node = None + else: + self.simulation.lock.acquire() + try: + ns3_node = ns3.NodeList.GetNode(self.selected_node.node_index) + finally: + self.simulation.lock.release() + __IPYTHON__.user_ns['selected_node'] = ns3_node + + + def select_node(self, node): + if isinstance(node, ns3.Node): + node = self.nodes[node.GetId()] + elif isinstance(node, (int, long)): + node = self.nodes[node] + elif isinstance(node, Node): + pass + elif node is None: + pass + else: + raise TypeError("expected None, int, viz.Node or ns3.Node, not %r" % node) + + if node is self.selected_node: + return + + if self.selected_node is not None: + self.selected_node.selected = False + self.selected_node = node + if self.selected_node is not None: + self.selected_node.selected = True + + if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED: + if self.selected_node is None: + self.simulation.set_nodes_of_interest([]) + else: + self.simulation.set_nodes_of_interest([self.selected_node.node_index]) + + self._update_ipython_selected_node() + + + def add_information_window(self, info_win): + self.information_windows.append(info_win) + self.simulation.lock.acquire() + try: + info_win.update() + finally: + self.simulation.lock.release() + + def remove_information_window(self, info_win): + self.information_windows.remove(info_win) + + def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip): + #print "tooltip query: ", x, y + hadj = self._scrolled_window.get_hadjustment() + vadj = self._scrolled_window.get_vadjustment() + x, y = self.canvas.convert_from_pixels(hadj.value + x, vadj.value + y) + item = self.canvas.get_item_at(x, y, True) + #print "items at (%f, %f): %r | keyboard_mode=%r" % (x, y, item, keyboard_mode) + if not item: + return False + while item is not None: + obj = item.get_data("pyviz-object") + if obj is not None: + obj.tooltip_query(tooltip) + return True + item = item.props.parent + return False + + def _get_export_file_name(self): + sel = gtk.FileChooserDialog("Save...", self.canvas.get_toplevel(), + gtk.FILE_CHOOSER_ACTION_SAVE, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, gtk.RESPONSE_OK)) + sel.set_default_response(gtk.RESPONSE_OK) + sel.set_local_only(True) + sel.set_do_overwrite_confirmation(True) + sel.set_current_name("Unnamed.pdf") + + filter = gtk.FileFilter() + filter.set_name("Embedded PostScript") + filter.add_mime_type("image/x-eps") + sel.add_filter(filter) + + filter = gtk.FileFilter() + filter.set_name("Portable Document Graphics") + filter.add_mime_type("application/pdf") + sel.add_filter(filter) + + filter = gtk.FileFilter() + filter.set_name("Scalable Vector Graphics") + filter.add_mime_type("image/svg+xml") + sel.add_filter(filter) + + resp = sel.run() + if resp != gtk.RESPONSE_OK: + sel.destroy() + return None + + file_name = sel.get_filename() + sel.destroy() + return file_name + + def _take_screenshot(self, dummy_button): + #print "Cheese!" + file_name = self._get_export_file_name() + if file_name is None: + return + + # figure out the correct bounding box for what is visible on screen + x1 = self._scrolled_window.get_hadjustment().value + y1 = self._scrolled_window.get_vadjustment().value + x2 = x1 + self._scrolled_window.get_hadjustment().page_size + y2 = y1 + self._scrolled_window.get_vadjustment().page_size + bounds = goocanvas.Bounds() + bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1) + bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2) + dest_width = bounds.x2 - bounds.x1 + dest_height = bounds.y2 - bounds.y1 + #print bounds.x1, bounds.y1, " -> ", bounds.x2, bounds.y2 + + dummy, extension = os.path.splitext(file_name) + extension = extension.lower() + if extension == '.eps': + surface = cairo.PSSurface(file_name, dest_width, dest_height) + elif extension == '.pdf': + surface = cairo.PDFSurface(file_name, dest_width, dest_height) + elif extension == '.svg': + surface = cairo.SVGSurface(file_name, dest_width, dest_height) + else: + dialog = gtk.MessageDialog(parent = self.canvas.get_toplevel(), + flags = gtk.DIALOG_DESTROY_WITH_PARENT, + type = gtk.MESSAGE_ERROR, + buttons = gtk.BUTTONS_OK, + message_format = "Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')" + % (extension,)) + dialog.run() + dialog.destroy() + return + + # draw the canvas to a printing context + cr = cairo.Context(surface) + cr.translate(-bounds.x1, -bounds.y1) + self.canvas.render(cr, bounds, self.zoom.value) + cr.show_page() + surface.finish() + + def set_follow_node(self, node): + if isinstance(node, ns3.Node): + node = self.nodes[node.GetId()] + self.follow_node = node + + def _start_shell(self, dummy_button): + if self.shell_window is not None: + self.shell_window.present() + return + + self.shell_window = gtk.Window() + self.shell_window.set_size_request(750,550) + self.shell_window.set_resizable(True) + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) + ipython = ipython_view.IPythonView() + ipython.modify_font(pango.FontDescription(SHELL_FONT)) + ipython.set_wrap_mode(gtk.WRAP_CHAR) + ipython.show() + scrolled_window.add(ipython) + scrolled_window.show() + self.shell_window.add(scrolled_window) + self.shell_window.show() + self.shell_window.connect('destroy', self._on_shell_window_destroy) + + self._update_ipython_selected_node() + __IPYTHON__.user_ns['viz'] = self + + + def _on_shell_window_destroy(self, window): + self.shell_window = None + + +initialization_hooks = [] + +def add_initialization_hook(hook, *args): + """ + Adds a callback to be called after + the visualizer is initialized, like this:: + initialization_hook(visualizer, *args) + """ + global initialization_hooks + initialization_hooks.append((hook, args)) + + +def set_bounds(x1, y1, x2, y2): + assert x2>x1 + assert y2>y1 + def hook(viz): + cx1, cy1 = transform_point_simulation_to_canvas(x1, y1) + cx2, cy2 = transform_point_simulation_to_canvas(x2, y2) + viz.canvas.set_bounds(cx1, cy1, cx2, cy2) + add_initialization_hook(hook) + + +def start(): + assert Visualizer.INSTANCE is None + if _import_error is not None: + import sys + print >> sys.stderr, "No visualization support (%s)." % (str(_import_error),) + ns3.Simulator.Run() + return + load_plugins() + viz = Visualizer() + for hook, args in initialization_hooks: + gobject.idle_add(hook, viz, *args) + ns3.Packet.EnablePrinting() + viz.start() diff --git a/src/tools/visualizer/visualizer/higcontainer.py b/src/tools/visualizer/visualizer/higcontainer.py new file mode 100644 index 000000000..81a925563 --- /dev/null +++ b/src/tools/visualizer/visualizer/higcontainer.py @@ -0,0 +1,97 @@ +import gtk +import gobject +try: + from gazpacho.widgets.base.base import SimpleContainerAdaptor +except ImportError: + pass + +#root_library = 'hig' + + +class HIGContainer(gtk.Bin): + __gtype_name__ = 'HIGContainer' + __gproperties__ = { + 'title': (str, 'Group Title', 'the group title', + '', gobject.PARAM_READWRITE|gobject.PARAM_CONSTRUCT), + } + + def __init__(self, title=None): + self.__title_text = None + gtk.widget_push_composite_child() + self.__title = gobject.new(gtk.Label, visible=True, xalign=0, yalign=0.5) + self.__indent = gobject.new(gtk.Label, visible=True, label=' ') + gtk.widget_pop_composite_child() + gtk.Bin.__init__(self) + self.__title.set_parent(self) + self.__indent.set_parent(self) + if title is not None: + self.props.title = title + + def do_size_request(self, requisition): + title_req = gtk.gdk.Rectangle(0, 0, *self.__title.size_request()) + indent_req = gtk.gdk.Rectangle(0, 0, *self.__indent.size_request()) + if self.child is None: + child_req = gtk.gdk.Rectangle() + else: + child_req = gtk.gdk.Rectangle(0, 0, *self.child.size_request()) + requisition.height = (title_req.height + 6 + + max(child_req.height, indent_req.height)) + requisition.width = max(title_req.width, indent_req.width + child_req.width) + + def do_size_allocate(self, allocation): + self.allocation = allocation + + ## title + title_req = gtk.gdk.Rectangle(0, 0, *self.__title.get_child_requisition()) + title_alloc = gtk.gdk.Rectangle() + title_alloc.x = allocation.x + title_alloc.y = allocation.y + title_alloc.width = min(title_req.width, allocation.width) + title_alloc.height = min(title_req.height, allocation.height) + self.__title.size_allocate(title_alloc) + + ## child + if self.child is None: + return + indent_req = gtk.gdk.Rectangle(0, 0, *self.__indent.get_child_requisition()) + child_req = gtk.gdk.Rectangle(0, 0, *self.child.get_child_requisition()) + child_alloc = gtk.gdk.Rectangle() + child_alloc.x = allocation.x + indent_req.width + child_alloc.y = allocation.y + title_alloc.height + 6 + child_alloc.width = allocation.width - indent_req.width + child_alloc.height = allocation.height - 6 - title_alloc.height + self.child.size_allocate(child_alloc) + + def do_forall(self, internal, callback, data): + if internal: + callback(self.__title, data) + callback(self.__indent, data) + if self.child is not None: + callback(self.child, data) + + def do_set_property(self, pspec, value): + if pspec.name == 'title': + self.__title.set_markup('%s' % + gobject.markup_escape_text(value)) + self.__title_text = value + else: + raise AttributeError, 'unknown property %s' % pspec.name + + def do_get_property(self, pspec): + if pspec.name == 'title': + return self.__title_text + else: + raise AttributeError, 'unknown property %s' % pspec.name + +if __name__ == '__main__': + frame = gtk.Frame() + group = gobject.new(HIGContainer, title="Hello") + frame.add(group) + check = gtk.CheckButton("foobar") + group.add(check) + w = gtk.Window() + w.add(frame) + w.show_all() + w.connect("destroy", lambda w: gtk.main_quit()) + gtk.main() + diff --git a/src/tools/visualizer/visualizer/hud.py b/src/tools/visualizer/visualizer/hud.py new file mode 100644 index 000000000..6040823da --- /dev/null +++ b/src/tools/visualizer/visualizer/hud.py @@ -0,0 +1,144 @@ +import goocanvas +import core +import math +import pango +import gtk + + +class Axes(object): + def __init__(self, viz): + self.viz = viz + self.color = 0x8080C0FF + self.hlines = goocanvas.Path(parent=viz.canvas.get_root_item(), stroke_color_rgba=self.color) + self.hlines.lower(None) + self.vlines = goocanvas.Path(parent=viz.canvas.get_root_item(), stroke_color_rgba=self.color) + self.vlines.lower(None) + self.labels = [] + hadj = self.viz.get_hadjustment() + vadj = self.viz.get_vadjustment() + def update(adj): + if self.visible: + self.update_view() + hadj.connect("value-changed", update) + vadj.connect("value-changed", update) + hadj.connect("changed", update) + vadj.connect("changed", update) + self.visible = True + self.update_view() + + def set_visible(self, visible): + self.visible = visible + if self.visible: + self.hlines.props.visibility = goocanvas.ITEM_VISIBLE + self.vlines.props.visibility = goocanvas.ITEM_VISIBLE + else: + self.hlines.props.visibility = goocanvas.ITEM_HIDDEN + self.vlines.props.visibility = goocanvas.ITEM_HIDDEN + for label in self.labels: + label.props.visibility = goocanvas.ITEM_HIDDEN + + def _compute_divisions(self, xi, xf): + assert xf > xi + dx = xf - xi + size = dx + ndiv = 5 + text_width = dx/ndiv/2 + + def rint(x): + return math.floor(x+0.5) + + dx_over_ndiv = dx / ndiv + for n in range(5): # iterate 5 times to find optimum division size + #/* div: length of each division */ + tbe = math.log10(dx_over_ndiv)#; /* looking for approx. 'ndiv' divisions in a length 'dx' */ + div = pow(10, rint(tbe))#; /* div: power of 10 closest to dx/ndiv */ + if math.fabs(div/2 - dx_over_ndiv) < math.fabs(div - dx_over_ndiv): #/* test if div/2 is closer to dx/ndiv */ + div /= 2 + elif math.fabs(div*2 - dx_over_ndiv) < math.fabs(div - dx_over_ndiv): + div *= 2 # /* test if div*2 is closer to dx/ndiv */ + x0 = div*math.ceil(xi / div) - div + if n > 1: + ndiv = rint(size / text_width) + return x0, div + + + def update_view(self): + if self.viz.zoom is None: + return + + unused_labels = self.labels + self.labels = [] + for label in unused_labels: + label.set_property("visibility", goocanvas.ITEM_HIDDEN) + def get_label(): + try: + label = unused_labels.pop(0) + except IndexError: + label = goocanvas.Text(parent=self.viz.canvas.get_root_item(), stroke_color_rgba=self.color) + else: + label.set_property("visibility", goocanvas.ITEM_VISIBLE) + label.lower(None) + self.labels.append(label) + return label + + hadj = self.viz.get_hadjustment() + vadj = self.viz.get_vadjustment() + zoom = self.viz.zoom.value + offset = 10/zoom + + x1, y1 = self.viz.canvas.convert_from_pixels(hadj.value, vadj.value) + x2, y2 = self.viz.canvas.convert_from_pixels(hadj.value + hadj.page_size, vadj.value + vadj.page_size) + line_width = 5.0/self.viz.zoom.value + + # draw the horizontal axis + self.hlines.set_property("line-width", line_width) + yc = y2 - line_width/2 + + sim_x1 = x1/core.PIXELS_PER_METER + sim_x2 = x2/core.PIXELS_PER_METER + x0, xdiv = self._compute_divisions(sim_x1, sim_x2) + path = ["M %r %r L %r %r" % (x1, yc, x2, yc)] + x = x0 + while x < sim_x2: + path.append("M %r %r L %r %r" % (core.PIXELS_PER_METER*x, yc - offset, core.PIXELS_PER_METER*x, yc)) + label = get_label() + label.set_properties(font=("Sans Serif %f" % int(12/zoom)), + text=("%G" % x), + fill_color_rgba=self.color, + alignment=pango.ALIGN_CENTER, + anchor=gtk.ANCHOR_S, + x=core.PIXELS_PER_METER*x, + y=(yc - offset)) + x += xdiv + del x + + self.hlines.set_property("data", " ".join(path)) + + # draw the vertical axis + self.vlines.set_property("line-width", line_width) + xc = x1 + line_width/2 + + sim_y1 = y1/core.PIXELS_PER_METER + sim_y2 = y2/core.PIXELS_PER_METER + + + y0, ydiv = self._compute_divisions(sim_y1, sim_y2) + path = ["M %r %r L %r %r" % (xc, y1, xc, y2)] + y = y0 + while y < sim_y2: + path.append("M %r %r L %r %r" % (xc, core.PIXELS_PER_METER*y, xc + offset, core.PIXELS_PER_METER*y)) + label = get_label() + label.set_properties(font=("Sans Serif %f" % int(12/zoom)), + text=("%G" % y), + fill_color_rgba=self.color, + alignment=pango.ALIGN_LEFT, + anchor=gtk.ANCHOR_W, + x=xc + offset, + y=core.PIXELS_PER_METER*y) + y += ydiv + + self.vlines.set_property("data", " ".join(path)) + + + + self.labels.extend(unused_labels) diff --git a/src/tools/visualizer/visualizer/ipython_view.py b/src/tools/visualizer/visualizer/ipython_view.py new file mode 100644 index 000000000..c6135790b --- /dev/null +++ b/src/tools/visualizer/visualizer/ipython_view.py @@ -0,0 +1,296 @@ +""" +Backend to the console plugin. + +@author: Eitan Isaacson +@organization: IBM Corporation +@copyright: Copyright (c) 2007 IBM Corporation +@license: BSD + +All rights reserved. This program and the accompanying materials are made +available under the terms of the BSD which accompanies this distribution, and +is available at U{http://www.opensource.org/licenses/bsd-license.php} +""" +# this file is a modified version of source code from the Accerciser project +# http://live.gnome.org/accerciser + +import gtk +import re +import sys +import os +import pango +from StringIO import StringIO +import IPython + +ansi_colors = {'0;30': 'Black', + '0;31': 'Red', + '0;32': 'Green', + '0;33': 'Brown', + '0;34': 'Blue', + '0;35': 'Purple', + '0;36': 'Cyan', + '0;37': 'LightGray', + '1;30': 'DarkGray', + '1;31': 'DarkRed', + '1;32': 'SeaGreen', + '1;33': 'Yellow', + '1;34': 'LightBlue', + '1;35': 'MediumPurple', + '1;36': 'LightCyan', + '1;37': 'White'} + +class IterableIPShell: + def __init__(self,argv=None,user_ns=None,user_global_ns=None, + cin=None, cout=None,cerr=None, input_func=None): + if input_func: + IPython.iplib.raw_input_original = input_func + if cin: + IPython.Shell.Term.cin = cin + if cout: + IPython.Shell.Term.cout = cout + if cerr: + IPython.Shell.Term.cerr = cerr + + if argv is None: + argv=[] + + # This is to get rid of the blockage that occurs during + # IPython.Shell.InteractiveShell.user_setup() + IPython.iplib.raw_input = lambda x: None + + self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) + os.environ['TERM'] = 'dumb' + excepthook = sys.excepthook + self.IP = IPython.Shell.make_IPython(argv,user_ns=user_ns, + user_global_ns=user_global_ns, + embedded=True, + shell_class=IPython.Shell.InteractiveShell) + self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), + header='IPython system call: ', + verbose=self.IP.rc.system_verbose) + sys.excepthook = excepthook + self.iter_more = 0 + self.history_level = 0 + self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') + + def execute(self): + self.history_level = 0 + orig_stdout = sys.stdout + sys.stdout = IPython.Shell.Term.cout + try: + line = self.IP.raw_input(None, self.iter_more) + if self.IP.autoindent: + self.IP.readline_startup_hook(None) + except KeyboardInterrupt: + self.IP.write('\nKeyboardInterrupt\n') + self.IP.resetbuffer() + # keep cache in sync with the prompt counter: + self.IP.outputcache.prompt_count -= 1 + + if self.IP.autoindent: + self.IP.indent_current_nsp = 0 + self.iter_more = 0 + except: + self.IP.showtraceback() + else: + self.iter_more = self.IP.push(line) + if (self.IP.SyntaxTB.last_syntax_error and + self.IP.rc.autoedit_syntax): + self.IP.edit_syntax_error() + if self.iter_more: + self.prompt = str(self.IP.outputcache.prompt2).strip() + if self.IP.autoindent: + self.IP.readline_startup_hook(self.IP.pre_readline) + else: + self.prompt = str(self.IP.outputcache.prompt1).strip() + sys.stdout = orig_stdout + + def historyBack(self): + self.history_level -= 1 + return self._getHistory() + + def historyForward(self): + self.history_level += 1 + return self._getHistory() + + def _getHistory(self): + try: + rv = self.IP.user_ns['In'][self.history_level].strip('\n') + except IndexError: + self.history_level = 0 + rv = '' + return rv + + def updateNamespace(self, ns_dict): + self.IP.user_ns.update(ns_dict) + + def complete(self, line): + split_line = self.complete_sep.split(line) + possibilities = self.IP.complete(split_line[-1]) + if possibilities: + common_prefix = reduce(self._commonPrefix, possibilities) + completed = line[:-len(split_line[-1])]+common_prefix + else: + completed = line + return completed, possibilities + + def _commonPrefix(self, str1, str2): + for i in range(len(str1)): + if not str2.startswith(str1[:i+1]): + return str1[:i] + return str1 + + def shell(self, cmd,verbose=0,debug=0,header=''): + stat = 0 + if verbose or debug: print header+cmd + # flush stdout so we don't mangle python's buffering + if not debug: + input, output = os.popen4(cmd) + print output.read() + output.close() + input.close() + +class ConsoleView(gtk.TextView): + def __init__(self): + gtk.TextView.__init__(self) + self.modify_font(pango.FontDescription('Mono')) + self.set_cursor_visible(True) + self.text_buffer = self.get_buffer() + self.mark = self.text_buffer.create_mark('scroll_mark', + self.text_buffer.get_end_iter(), + False) + for code in ansi_colors: + self.text_buffer.create_tag(code, + foreground=ansi_colors[code], + weight=700) + self.text_buffer.create_tag('0') + self.text_buffer.create_tag('notouch', editable=False) + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.line_start = \ + self.text_buffer.create_mark('line_start', + self.text_buffer.get_end_iter(), True + ) + self.connect('key-press-event', self._onKeypress) + self.last_cursor_pos = 0 + + def write(self, text, editable=False): + segments = self.color_pat.split(text) + segment = segments.pop(0) + start_mark = self.text_buffer.create_mark(None, + self.text_buffer.get_end_iter(), + True) + self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + for tag in ansi_tags: + i = segments.index(tag) + self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), + segments[i+1], tag) + segments.pop(i) + if not editable: + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(start_mark), + self.text_buffer.get_end_iter()) + self.text_buffer.delete_mark(start_mark) + self.scroll_mark_onscreen(self.mark) + + def showPrompt(self, prompt): + self.write(prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + + def changeLine(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter) + self.write(text, True) + + def getCurrentLine(self): + rv = self.text_buffer.get_slice(self.text_buffer.get_iter_at_mark(self.line_start), + self.text_buffer.get_end_iter(), False) + return rv + + def showReturned(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(self.line_start), + iter) + self.write('\n'+text) + if text: + self.write('\n') + self.showPrompt(self.prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) + + def _onKeypress(self, obj, event): + if not event.string: + return + insert_mark = self.text_buffer.get_insert() + insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) + selection_mark = self.text_buffer.get_selection_bound() + selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) + start_iter = self.text_buffer.get_iter_at_mark(self.line_start) + if start_iter.compare(insert_iter) <= 0 and \ + start_iter.compare(selection_iter) <= 0: + return + elif start_iter.compare(insert_iter) > 0 and \ + start_iter.compare(selection_iter) > 0: + self.text_buffer.place_cursor(start_iter) + elif insert_iter.compare(selection_iter) < 0: + self.text_buffer.move_mark(insert_mark, start_iter) + elif insert_iter.compare(selection_iter) > 0: + self.text_buffer.move_mark(selection_mark, start_iter) + + +class IPythonView(ConsoleView, IterableIPShell): + def __init__(self): + ConsoleView.__init__(self) + self.cout = StringIO() + IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, + input_func=self.raw_input) + self.connect('key_press_event', self.keyPress) + self.execute() + self.cout.truncate(0) + self.showPrompt(self.prompt) + self.interrupt = False + + def raw_input(self, prompt=''): + if self.interrupt: + self.interrupt = False + raise KeyboardInterrupt + return self.getCurrentLine() + + def keyPress(self, widget, event): + if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: + self.interrupt = True + self._processLine() + return True + elif event.keyval == gtk.keysyms.Return: + self._processLine() + return True + elif event.keyval == gtk.keysyms.Up: + self.changeLine(self.historyBack()) + return True + elif event.keyval == gtk.keysyms.Down: + self.changeLine(self.historyForward()) + return True + elif event.keyval == gtk.keysyms.Tab: + if not self.getCurrentLine().strip(): + return False + completed, possibilities = self.complete(self.getCurrentLine()) + if len(possibilities) > 1: + slice = self.getCurrentLine() + self.write('\n') + for symbol in possibilities: + self.write(symbol+'\n') + self.showPrompt(self.prompt) + self.changeLine(completed or slice) + return True + + def _processLine(self): + self.history_pos = 0 + self.execute() + rv = self.cout.getvalue() + if rv: rv = rv.strip('\n') + self.showReturned(rv) + self.cout.truncate(0) diff --git a/src/tools/visualizer/visualizer/plugins/interface_statistics.py b/src/tools/visualizer/visualizer/plugins/interface_statistics.py new file mode 100644 index 000000000..0124ab8c3 --- /dev/null +++ b/src/tools/visualizer/visualizer/plugins/interface_statistics.py @@ -0,0 +1,167 @@ +import gtk +import ns3 +from visualizer.base import InformationWindow + +NODE_STATISTICS_MEMORY = 10 + + +class StatisticsCollector(object): + """ + Collects interface statistics for all nodes. + """ + + class NetDevStats(object): + __slots__ = ['rxPackets', 'rxBytes', 'txPackets', 'txBytes', + 'rxPacketRate', 'rxBitRate', 'txPacketRate', 'txBitRate'] + + def __init__(self, visualizer): + self.node_statistics = {} # nodeid -> list(raw statistics) + self.visualizer = visualizer + + def simulation_periodic_update(self, viz): + nodes_statistics = viz.simulation.sim_helper.GetNodesStatistics() + for stats in nodes_statistics: + try: + raw_stats_list = self.node_statistics[stats.nodeId] + except KeyError: + raw_stats_list = [] + self.node_statistics[stats.nodeId] = raw_stats_list + raw_stats_list.append(stats.statistics) + while len(raw_stats_list) > NODE_STATISTICS_MEMORY: + raw_stats_list.pop(0) + + def get_interface_statistics(self, nodeId): + try: + raw_stats_list = self.node_statistics[nodeId] + except KeyError: + return [] + + if len(raw_stats_list) < NODE_STATISTICS_MEMORY: + return [] + assert len(raw_stats_list) == NODE_STATISTICS_MEMORY + tx_packets1 = [] # transmitted packets, one value per interface + rx_packets1 = [] + tx_bytes1 = [] + rx_bytes1 = [] + for iface, stats in enumerate(raw_stats_list[0]): + tx_packets1.append(stats.transmittedPackets) + tx_bytes1.append(stats.transmittedBytes) + rx_packets1.append(stats.receivedPackets) + rx_bytes1.append(stats.receivedBytes) + + retval = [] + + k = self.visualizer.sample_period*(NODE_STATISTICS_MEMORY-1) + for iface, stats in enumerate(raw_stats_list[-1]): + outStat = self.NetDevStats() + outStat.txPackets = stats.transmittedPackets + outStat.txBytes = stats.transmittedBytes + outStat.rxPackets = stats.receivedPackets + outStat.rxBytes = stats.receivedBytes + + outStat.txPacketRate = (stats.transmittedPackets - tx_packets1[iface])/k + outStat.rxPacketRate = (stats.receivedPackets - rx_packets1[iface])/k + outStat.txBitRate = (stats.transmittedBytes - tx_bytes1[iface])*8/k + outStat.rxBitRate = (stats.receivedBytes - rx_bytes1[iface])*8/k + retval.append(outStat) + return retval + + +class ShowInterfaceStatistics(InformationWindow): + ( + COLUMN_INTERFACE, + + COLUMN_TX_PACKETS, + COLUMN_TX_BYTES, + COLUMN_TX_PACKET_RATE, + COLUMN_TX_BIT_RATE, + + COLUMN_RX_PACKETS, + COLUMN_RX_BYTES, + COLUMN_RX_PACKET_RATE, + COLUMN_RX_BIT_RATE, + + ) = range(9) + + def __init__(self, visualizer, node_index, statistics_collector): + InformationWindow.__init__(self) + self.win = gtk.Dialog(parent=visualizer.window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_NO_SEPARATOR, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + self.win.connect("response", self._response_cb) + self.win.set_title("Statistics for node %i" % node_index) + self.visualizer = visualizer + self.statistics_collector = statistics_collector + self.node_index = node_index + self.viz_node = visualizer.get_node(node_index) + + self.table_model = gtk.ListStore(*([str]*13)) + + treeview = gtk.TreeView(self.table_model) + treeview.show() + self.win.vbox.add(treeview) + + def add_column(descr, colid): + column = gtk.TreeViewColumn(descr, gtk.CellRendererText(), text=colid) + treeview.append_column(column) + + add_column("Interface", self.COLUMN_INTERFACE) + + add_column("Tx Packets", self.COLUMN_TX_PACKETS) + add_column("Tx Bytes", self.COLUMN_TX_BYTES) + add_column("Tx pkt/1s", self.COLUMN_TX_PACKET_RATE) + add_column("Tx bit/1s", self.COLUMN_TX_BIT_RATE) + + add_column("Rx Packets", self.COLUMN_RX_PACKETS) + add_column("Rx Bytes", self.COLUMN_RX_BYTES) + add_column("Rx pkt/1s", self.COLUMN_RX_PACKET_RATE) + add_column("Rx bit/1s", self.COLUMN_RX_BIT_RATE) + + self.visualizer.add_information_window(self) + self.win.show() + + def _response_cb(self, win, response): + self.win.destroy() + self.visualizer.remove_information_window(self) + + def update(self): + node = ns3.NodeList.GetNode(self.node_index) + stats_list = self.statistics_collector.get_interface_statistics(self.node_index) + self.table_model.clear() + for iface, stats in enumerate(stats_list): + tree_iter = self.table_model.append() + netdevice = node.GetDevice(iface) + interface_name = ns3.Names.FindName(netdevice) + if not interface_name: + interface_name = "(interface %i)" % iface + self.table_model.set(tree_iter, + self.COLUMN_INTERFACE, interface_name, + + self.COLUMN_TX_PACKETS, str(stats.txPackets), + self.COLUMN_TX_BYTES, str(stats.txBytes), + self.COLUMN_TX_PACKET_RATE, str(stats.txPacketRate), + self.COLUMN_TX_BIT_RATE, str(stats.txBitRate), + + self.COLUMN_RX_PACKETS, str(stats.rxPackets), + self.COLUMN_RX_BYTES, str(stats.rxBytes), + self.COLUMN_RX_PACKET_RATE, str(stats.rxPacketRate), + self.COLUMN_RX_BIT_RATE, str(stats.rxBitRate) + ) + + +def populate_node_menu(viz, node, menu, statistics_collector): + + menu_item = gtk.MenuItem("Show Interface Statistics") + menu_item.show() + + def _show_it(dummy_menu_item): + ShowInterfaceStatistics(viz, node.node_index, statistics_collector) + + menu_item.connect("activate", _show_it) + menu.add(menu_item) + + +def register(viz): + statistics_collector = StatisticsCollector(viz) + viz.connect("populate-node-menu", populate_node_menu, statistics_collector) + viz.connect("simulation-periodic-update", statistics_collector.simulation_periodic_update) diff --git a/src/tools/visualizer/visualizer/plugins/ipv4_routing_table.py b/src/tools/visualizer/visualizer/plugins/ipv4_routing_table.py new file mode 100644 index 000000000..82fba98ad --- /dev/null +++ b/src/tools/visualizer/visualizer/plugins/ipv4_routing_table.py @@ -0,0 +1,95 @@ +import gtk +import ns3 +from visualizer.base import InformationWindow + +class ShowIpv4RoutingTable(InformationWindow): + ( + COLUMN_DESTINATION, + COLUMN_NEXT_HOP, + COLUMN_INTERFACE, + ) = range(3) + + def __init__(self, visualizer, node_index): + InformationWindow.__init__(self) + self.win = gtk.Dialog(parent=visualizer.window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_NO_SEPARATOR, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + self.win.connect("response", self._response_cb) + self.win.set_title("IPv4 routing table for node %i" % node_index) + self.visualizer = visualizer + self.node_index = node_index + + self.table_model = gtk.ListStore(str, str, str) + + treeview = gtk.TreeView(self.table_model) + treeview.show() + self.win.vbox.add(treeview) + + # Dest. + column = gtk.TreeViewColumn('Destination', gtk.CellRendererText(), + text=self.COLUMN_DESTINATION) + treeview.append_column(column) + + # Next hop + column = gtk.TreeViewColumn('Next hop', gtk.CellRendererText(), + text=self.COLUMN_NEXT_HOP) + treeview.append_column(column) + + # Interface + column = gtk.TreeViewColumn('Interface', gtk.CellRendererText(), + text=self.COLUMN_INTERFACE) + treeview.append_column(column) + + self.visualizer.add_information_window(self) + self.win.show() + + def _response_cb(self, win, response): + self.win.destroy() + self.visualizer.remove_information_window(self) + + def update(self): + node = ns3.NodeList.GetNode(self.node_index) + ipv4 = node.GetObject(ns3.Ipv4.GetTypeId()) + routing = ipv4.GetRoutingProtocol() + if routing is None: + return + if isinstance(routing, ns3.Ipv4StaticRouting): + ipv4_routing = routing + elif isinstance(routing, ns3.Ipv4ListRouting): + for rI in range(routing.GetNRoutingProtocols()): + ipv4_routing, prio = routing.GetRoutingProtocol(rI) + if isinstance(ipv4_routing, ns3.Ipv4StaticRouting): + break + else: + return + else: + return + self.table_model.clear() + for routeI in range(ipv4_routing.GetNRoutes()): + route = ipv4_routing.GetRoute(routeI) + tree_iter = self.table_model.append() + netdevice = ipv4.GetNetDevice(route.GetInterface()) + if netdevice is None: + interface_name = 'lo' + else: + interface_name = ns3.Names.FindName(netdevice) + if not interface_name: + interface_name = "(interface %i)" % route.GetInterface() + self.table_model.set(tree_iter, + self.COLUMN_DESTINATION, str(route.GetDest()), + self.COLUMN_NEXT_HOP, str(route.GetGateway()), + self.COLUMN_INTERFACE, interface_name) + + +def populate_node_menu(viz, node, menu): + menu_item = gtk.MenuItem("Show IPv4 Routing Table") + menu_item.show() + + def _show_ipv4_routing_table(dummy_menu_item): + ShowIpv4RoutingTable(viz, node.node_index) + + menu_item.connect("activate", _show_ipv4_routing_table) + menu.add(menu_item) + +def register(viz): + viz.connect("populate-node-menu", populate_node_menu) diff --git a/src/tools/visualizer/visualizer/plugins/olsr.py b/src/tools/visualizer/visualizer/plugins/olsr.py new file mode 100644 index 000000000..f74023417 --- /dev/null +++ b/src/tools/visualizer/visualizer/plugins/olsr.py @@ -0,0 +1,102 @@ +import gtk +import ns3 +from visualizer.base import InformationWindow + +class ShowOlsrRoutingTable(InformationWindow): + ( + COLUMN_DESTINATION, + COLUMN_NEXT_HOP, + COLUMN_INTERFACE, + COLUMN_NUM_HOPS, + ) = range(4) + + def __init__(self, visualizer, node_index): + InformationWindow.__init__(self) + self.win = gtk.Dialog(parent=visualizer.window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_NO_SEPARATOR, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + self.win.set_default_size(gtk.gdk.screen_width()/2, gtk.gdk.screen_height()/2) + self.win.connect("response", self._response_cb) + self.win.set_title("OLSR routing table for node %i" % node_index) + self.visualizer = visualizer + self.node_index = node_index + + self.table_model = gtk.ListStore(str, str, str, int) + + treeview = gtk.TreeView(self.table_model) + treeview.show() + sw = gtk.ScrolledWindow() + sw.set_properties(hscrollbar_policy=gtk.POLICY_AUTOMATIC, + vscrollbar_policy=gtk.POLICY_AUTOMATIC) + sw.show() + sw.add(treeview) + self.win.vbox.add(sw) + + # Dest. + column = gtk.TreeViewColumn('Destination', gtk.CellRendererText(), + text=self.COLUMN_DESTINATION) + treeview.append_column(column) + + # Next hop + column = gtk.TreeViewColumn('Next hop', gtk.CellRendererText(), + text=self.COLUMN_NEXT_HOP) + treeview.append_column(column) + + # Interface + column = gtk.TreeViewColumn('Interface', gtk.CellRendererText(), + text=self.COLUMN_INTERFACE) + treeview.append_column(column) + + # Num. Hops + column = gtk.TreeViewColumn('Num. Hops', gtk.CellRendererText(), + text=self.COLUMN_NUM_HOPS) + treeview.append_column(column) + + self.visualizer.add_information_window(self) + self.win.show() + + def _response_cb(self, win, response): + self.win.destroy() + self.visualizer.remove_information_window(self) + + def update(self): + node = ns3.NodeList.GetNode(self.node_index) + olsr = node.GetObject(ns3.olsr.RoutingProtocol.GetTypeId()) + ipv4 = node.GetObject(ns3.Ipv4.GetTypeId()) + if olsr is None: + return + self.table_model.clear() + for route in olsr.GetRoutingTableEntries(): + tree_iter = self.table_model.append() + netdevice = ipv4.GetNetDevice(route.interface) + if netdevice is None: + interface_name = 'lo' + else: + interface_name = ns3.Names.FindName(netdevice) + if not interface_name: + interface_name = "(interface %i)" % route.interface + self.table_model.set(tree_iter, + self.COLUMN_DESTINATION, str(route.destAddr), + self.COLUMN_NEXT_HOP, str(route.nextAddr), + self.COLUMN_INTERFACE, interface_name, + self.COLUMN_NUM_HOPS, route.distance) + + +def populate_node_menu(viz, node, menu): + ns3_node = ns3.NodeList.GetNode(node.node_index) + olsr = ns3_node.GetObject(ns3.olsr.RoutingProtocol.GetTypeId()) + if olsr is None: + print "No OLSR" + return + + menu_item = gtk.MenuItem("Show OLSR Routing Table") + menu_item.show() + + def _show_ipv4_routing_table(dummy_menu_item): + ShowOlsrRoutingTable(viz, node.node_index) + + menu_item.connect("activate", _show_ipv4_routing_table) + menu.add(menu_item) + +def register(viz): + viz.connect("populate-node-menu", populate_node_menu) diff --git a/src/tools/visualizer/visualizer/plugins/show_last_packets.py b/src/tools/visualizer/visualizer/plugins/show_last_packets.py new file mode 100644 index 000000000..78ae288cd --- /dev/null +++ b/src/tools/visualizer/visualizer/plugins/show_last_packets.py @@ -0,0 +1,231 @@ +import gobject +import gtk +import ns3 +from visualizer.base import InformationWindow +from visualizer.higcontainer import HIGContainer +from kiwi.ui.objectlist import ObjectList, Column + +class ShowLastPackets(InformationWindow): + class PacketList(gtk.ScrolledWindow): + ( + COLUMN_TIME, + COLUMN_INTERFACE, + COLUMN_SIZE, + COLUMN_CONTENTS, + ) = range(4) + + def __init__(self): + super(ShowLastPackets.PacketList, self).__init__() + self.set_properties(hscrollbar_policy=gtk.POLICY_AUTOMATIC, + vscrollbar_policy=gtk.POLICY_AUTOMATIC) + self.table_model = gtk.ListStore(*([str]*4)) + treeview = gtk.TreeView(self.table_model) + treeview.show() + self.add(treeview) + + def add_column(descr, colid): + column = gtk.TreeViewColumn(descr, gtk.CellRendererText(), text=colid) + treeview.append_column(column) + + add_column("Time", self.COLUMN_TIME) + add_column("Interface", self.COLUMN_INTERFACE) + add_column("Size", self.COLUMN_SIZE) + add_column("Contents", self.COLUMN_CONTENTS) + + def update(self, node, packet_list): + self.table_model.clear() + for sample in packet_list: + tree_iter = self.table_model.append() + if sample.device is None: + interface_name = "(unknown)" + else: + interface_name = ns3.Names.FindName(sample.device) + if not interface_name: + interface_name = "(interface %i)" % sample.device.GetIfIndex() + self.table_model.set(tree_iter, + self.COLUMN_TIME, str(sample.time.GetSeconds()), + self.COLUMN_INTERFACE, interface_name, + self.COLUMN_SIZE, str(sample.packet.GetSize ()), + self.COLUMN_CONTENTS, str(sample.packet) + ) + + + def __init__(self, visualizer, node_index): + InformationWindow.__init__(self) + self.win = gtk.Dialog(parent=visualizer.window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_NO_SEPARATOR, + buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) + self.win.connect("response", self._response_cb) + self.win.set_title("Last packets for node %i" % node_index) + self.visualizer = visualizer + self.viz_node = visualizer.get_node(node_index) + self.node = ns3.NodeList.GetNode(node_index) + + def smart_expand(expander, vbox): + if expander.get_expanded(): + vbox.set_child_packing(expander, expand=True, fill=True, padding=0, pack_type=gtk.PACK_START) + else: + vbox.set_child_packing(expander, expand=False, fill=False, padding=0, pack_type=gtk.PACK_START) + + main_hbox = gtk.HBox(False, 4) + main_hbox.show() + main_vbox = gtk.VBox(False, 4) + main_vbox.show() + self.win.vbox.add(main_hbox) + main_hbox.add(main_vbox) + + self.tx_list = self.PacketList() + self.tx_list.show() + group = gtk.Expander("Last transmitted packets") + group.show() + group.add(self.tx_list) + main_vbox.pack_start(group, expand=False, fill=False) + group.connect_after("activate", smart_expand, main_vbox) + + self.rx_list = self.PacketList() + self.rx_list.show() + group = gtk.Expander("Last received packets") + group.show() + group.add(self.rx_list) + main_vbox.pack_start(group, expand=False, fill=False) + group.connect_after("activate", smart_expand, main_vbox) + + self.drop_list = self.PacketList() + self.drop_list.show() + group = gtk.Expander("Last dropped packets") + group.show() + group.add(self.drop_list) + main_vbox.pack_start(group, expand=False, fill=False) + group.connect_after("activate", smart_expand, main_vbox) + + + # Packet Filter + + # - options + self.packet_capture_options = ns3.PyViz.PacketCaptureOptions() + self.packet_capture_options.numLastPackets = 100 + + packet_filter_vbox = gtk.VBox(False, 4) + packet_filter_vbox.show() + main_hbox.add(packet_filter_vbox) + + sel_buttons_box = gtk.HButtonBox() + sel_buttons_box.show() + packet_filter_vbox.pack_start(sel_buttons_box, False, False, 4) + select_all_button = gobject.new(gtk.Button, label="Sel. All", visible=True) + select_none_button = gobject.new(gtk.Button, label="Sel. None", visible=True) + sel_buttons_box.add(select_all_button) + sel_buttons_box.add(select_none_button) + + self.packet_filter_widget = ObjectList([ + Column('selected', title="Sel.", data_type=bool, editable=True), + Column('name', title="Header"), + ], sortable=True) + self.packet_filter_widget.show() + packet_filter_vbox.pack_start(self.packet_filter_widget, True, True, 4) + + class TypeIdConfig(object): + __slots__ = ['name', 'selected', 'typeid'] + + self.packet_filter_list = [] # list of TypeIdConfig instances + + Header = ns3.TypeId.LookupByName("ns3::Header") + Trailer = ns3.TypeId.LookupByName("ns3::Trailer") + for typeid_i in range(ns3.TypeId.GetRegisteredN()): + typeid = ns3.TypeId.GetRegistered(typeid_i) + # check if this is a header or trailer subtype + typeid_tmp = typeid + type_is_good = False + while 1: + if typeid_tmp == Header or typeid_tmp == Trailer: + type_is_good = True + break + if typeid_tmp.HasParent(): + typeid_tmp = typeid_tmp.GetParent() + else: + break + if not type_is_good: + continue + if typeid in [Header, Trailer]: + continue + c = TypeIdConfig() + c.selected = True + c.name = typeid.GetName() + c.typeid = typeid + self.packet_filter_list.append(c) + self.packet_filter_widget.add_list(self.packet_filter_list) + + def update_capture_options(): + if self.op_AND_button.props.active: + self.packet_capture_options.mode = ns3.PyViz.PACKET_CAPTURE_FILTER_HEADERS_AND + else: + self.packet_capture_options.mode = ns3.PyViz.PACKET_CAPTURE_FILTER_HEADERS_OR + self.packet_capture_options.numLastPackets = 100 + self.packet_capture_options.headers = [c.typeid for c in self.packet_filter_list if c.selected] + self.visualizer.simulation.lock.acquire() + try: + self.visualizer.simulation.sim_helper.SetPacketCaptureOptions( + self.node.GetId(), self.packet_capture_options) + finally: + self.visualizer.simulation.lock.release() + + def sel_all_cb(bt): + for c in self.packet_filter_list: + c.selected = True + self.packet_filter_widget.refresh() + update_capture_options() + + def sel_none_cb(bt): + for c in self.packet_filter_list: + c.selected = False + self.packet_filter_widget.refresh() + update_capture_options() + + select_all_button.connect("clicked", sel_all_cb) + select_none_button.connect("clicked", sel_none_cb) + + op_buttons_box = gtk.HButtonBox() + op_buttons_box.show() + packet_filter_vbox.pack_start(op_buttons_box, False, False, 4) + self.op_AND_button = gobject.new(gtk.RadioButton, label="AND", visible=True) + self.op_OR_button = gobject.new(gtk.RadioButton, label="OR", visible=True, group=self.op_AND_button) + op_buttons_box.add(self.op_AND_button) + op_buttons_box.add(self.op_OR_button) + self.op_OR_button.props.active = True + + self.op_AND_button.connect("toggled", lambda b: update_capture_options()) + + def cell_edited(l, obj, attribute): + update_capture_options() + self.packet_filter_widget.connect("cell-edited", cell_edited) + + update_capture_options() + + self.visualizer.add_information_window(self) + self.win.set_default_size(600, 300) + self.win.show() + + def _response_cb(self, win, response): + self.win.destroy() + self.visualizer.remove_information_window(self) + + def update(self): + last_packets = self.visualizer.simulation.sim_helper.GetLastPackets(self.node.GetId()) + + self.tx_list.update(self.node, last_packets.lastTransmittedPackets) + self.rx_list.update(self.node, last_packets.lastReceivedPackets) + self.drop_list.update(self.node, last_packets.lastDroppedPackets) + + +def populate_node_menu(viz, node, menu): + menu_item = gtk.MenuItem("Show Last Packets") + menu_item.show() + + def _show_it(dummy_menu_item): + ShowLastPackets(viz, node.node_index) + + menu_item.connect("activate", _show_it) + menu.add(menu_item) + +def register(viz): + viz.connect("populate-node-menu", populate_node_menu) diff --git a/src/tools/visualizer/visualizer/plugins/wifi_intrastructure_link.py b/src/tools/visualizer/visualizer/plugins/wifi_intrastructure_link.py new file mode 100644 index 000000000..acd6fa79d --- /dev/null +++ b/src/tools/visualizer/visualizer/plugins/wifi_intrastructure_link.py @@ -0,0 +1,117 @@ +import math +import ns3 +import goocanvas +from visualizer.base import Link, transform_distance_canvas_to_simulation + +class WifiLink(Link): + def __init__(self, parent_canvas_item, sta, dev): + self.node1 = sta + self.dev = dev + self.node2 = None # ap + self.canvas_item = goocanvas.Group(parent=parent_canvas_item) + self.invisible_line = goocanvas.Polyline(parent=self.canvas_item, + line_width=25.0, + visibility=goocanvas.ITEM_HIDDEN) + self.visible_line = goocanvas.Polyline(parent=self.canvas_item, + line_width=1.0, + stroke_color_rgba=0xC00000FF, + line_dash=goocanvas.LineDash([2.0, 2.0 ])) + self.invisible_line.props.pointer_events = (goocanvas.EVENTS_STROKE_MASK + |goocanvas.EVENTS_FILL_MASK + |goocanvas.EVENTS_PAINTED_MASK) + self.canvas_item.set_data("pyviz-object", self) + self.canvas_item.lower(None) + self.set_ap(None) + + def set_ap(self, ap): + if ap is self.node2: + return + if self.node2 is not None: + self.node2.remove_link(self) + self.node2 = ap + if self.node2 is None: + self.canvas_item.set_property("visibility", goocanvas.ITEM_HIDDEN) + else: + self.node2.add_link(self) + self.canvas_item.set_property("visibility", goocanvas.ITEM_VISIBLE) + self.update_points() + + def update_points(self): + if self.node2 is None: + return + pos1_x, pos1_y = self.node1.get_position() + pos2_x, pos2_y = self.node2.get_position() + points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)]) + self.visible_line.set_property("points", points) + self.invisible_line.set_property("points", points) + + def destroy(self): + self.canvas_item.destroy() + self.node1 = None + self.node2 = None + + def tooltip_query(self, tooltip): + pos1_x, pos1_y = self.node1.get_position() + pos2_x, pos2_y = self.node2.get_position() + dx = pos2_x - pos1_x + dy = pos2_y - pos1_y + d = transform_distance_canvas_to_simulation(math.sqrt(dx*dx + dy*dy)) + mac = self.dev.GetMac() + tooltip.set_text(("WiFi link between STA Node %i and AP Node %i; distance=%.2f m.\n" + "SSID: %s\n" + "BSSID: %s") + % (self.node1.node_index, self.node2.node_index, d, + mac.GetSsid(), mac.GetBssid())) + + +class WifiLinkMonitor(object): + def __init__(self, dummy_viz): + self.access_points = {} # bssid -> node + self.stations = [] # list of (sta_netdevice, viz_node, wifi_link) + + def scan_nodes(self, viz): + for (sta_netdevice, viz_node, wifi_link) in self.stations: + wifi_link.destroy() + + self.access_points = {} + self.stations = [] + + for node in viz.nodes.itervalues(): + ns3_node = ns3.NodeList.GetNode(node.node_index) + for devI in range(ns3_node.GetNDevices()): + dev = ns3_node.GetDevice(devI) + if not isinstance(dev, ns3.WifiNetDevice): + continue + wifi_mac = dev.GetMac() + if isinstance(wifi_mac, ns3.NqstaWifiMac): + wifi_link = WifiLink(viz.links_group, node, dev) + self.stations.append((dev, node, wifi_link)) + elif isinstance(wifi_mac, ns3.NqapWifiMac): + bssid = ns3.Mac48Address.ConvertFrom(dev.GetAddress()) + self.access_points[str(bssid)] = node + #print "APs: ", self.access_points + #print "STAs: ", self.stations + + def simulation_periodic_update(self, viz): + for (sta_netdevice, viz_node, wifi_link) in self.stations: + if not sta_netdevice.IsLinkUp(): + wifi_link.set_ap(None) + continue + bssid = str(sta_netdevice.GetMac().GetBssid()) + if bssid == '00:00:00:00:00:00': + wifi_link.set_ap(None) + continue + ap = self.access_points[bssid] + wifi_link.set_ap(ap) + + def update_view(self, viz): + for (dummy_sta_netdevice, dummy_viz_node, wifi_link) in self.stations: + if wifi_link is not None: + wifi_link.update_points() + + +def register(viz): + link_monitor = WifiLinkMonitor(viz) + viz.connect("simulation-periodic-update", link_monitor.simulation_periodic_update) + viz.connect("update-view", link_monitor.update_view) + viz.connect("topology-scanned", link_monitor.scan_nodes) diff --git a/src/tools/visualizer/visualizer/resource/Basurero_Palm_Z22.svg b/src/tools/visualizer/visualizer/resource/Basurero_Palm_Z22.svg new file mode 100644 index 000000000..6aae3a0fe --- /dev/null +++ b/src/tools/visualizer/visualizer/resource/Basurero_Palm_Z22.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tools/visualizer/visualizer/resource/adriankierman_cell_phone_tower.svg b/src/tools/visualizer/visualizer/resource/adriankierman_cell_phone_tower.svg new file mode 100644 index 000000000..ce439b742 --- /dev/null +++ b/src/tools/visualizer/visualizer/resource/adriankierman_cell_phone_tower.svg @@ -0,0 +1,38 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/visualizer/visualizer/resource/bobocal_Yellow_Bus.svg b/src/tools/visualizer/visualizer/resource/bobocal_Yellow_Bus.svg new file mode 100644 index 000000000..83beef6ff --- /dev/null +++ b/src/tools/visualizer/visualizer/resource/bobocal_Yellow_Bus.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/visualizer/visualizer/resource/thilakarathna_Bus_Halt.svg b/src/tools/visualizer/visualizer/resource/thilakarathna_Bus_Halt.svg new file mode 100644 index 000000000..62c8ae573 --- /dev/null +++ b/src/tools/visualizer/visualizer/resource/thilakarathna_Bus_Halt.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Bus Halt + 07 th June 2008 + + + Kenneth Thilakarathna - UCSC - LK + + + + + + bus-stop + bus-halt + bus stop + bus halt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/visualizer/visualizer/svgitem.py b/src/tools/visualizer/visualizer/svgitem.py new file mode 100644 index 000000000..d3591564d --- /dev/null +++ b/src/tools/visualizer/visualizer/svgitem.py @@ -0,0 +1,156 @@ +import gobject +import rsvg +#import cairo +import goocanvas +import os.path + + +class SvgItem(goocanvas.ItemSimple): + # setup our custom properties + __gproperties__ = { + 'x': (float, # property type + 'X', # property nick name + 'The x coordinate of a SVG image', # property description + -10e6, # property minimum value + 10e6, # property maximum value + 0, # property default value + gobject.PARAM_READWRITE), # property flags + + 'y': (float, + 'Y', + 'The y coordinate of a SVG image', + -10e6, + 10e6, + 0, + gobject.PARAM_READWRITE), + + 'width': (float, + 'Width', + 'The width of the SVG Image', + 0, + 10e6, + 0, + gobject.PARAM_READWRITE), + + 'height': (float, + 'Height', + 'The width of the SVG Image', + 0, + 10e6, + 0, + gobject.PARAM_READWRITE), + } + + def __init__(self, x, y, rsvg_handle, **kwargs): + super(SvgItem, self).__init__(**kwargs) + assert isinstance(rsvg_handle, rsvg.Handle) + self.x = x + self.y = y + self.sx = 1.0 + self.sy = 1.0 + self.handle = rsvg_handle + self.width = self.handle.props.width + self.height = self.handle.props.height + self.custom_width = None + self.custom_height = None + + def do_set_property(self, pspec, value): + if pspec.name == 'x': + self.x = value + + # make sure we update the display + self.changed(True) + + elif pspec.name == 'y': + self.y = value + + # make sure we update the display + self.changed(True) + + elif pspec.name == 'width': + self.custom_width = value + self._size_changed() + + # make sure we update the display + self.changed(True) + + elif pspec.name == 'height': + self.custom_height = value + self._size_changed() + + # make sure we update the display + self.changed(True) + + else: + raise AttributeError, 'unknown property %s' % pspec.name + + def _size_changed(self): + if self.custom_width is None and self.custom_height is None: + self.width = self.handle.props.width + self.height = self.handle.props.height + self.sx = 1.0 + self.sy = 1.0 + elif self.custom_width is not None and self.custom_height is None: + self.width = self.custom_width + self.sx = self.custom_width / self.handle.props.width + self.sy = self.sx + self.height = self.handle.props.height*self.sy + elif self.custom_width is None and self.custom_height is not None: + self.height = self.custom_height + self.sy = self.custom_height / self.handle.props.height + self.sx = self.sy + self.width = self.handle.props.width*self.sx + else: + self.width = self.custom_width + self.height = self.custom_height + self.sx = self.custom_width / self.handle.props.width + self.sy = self.custom_height / self.handle.props.height + + def do_get_property(self, pspec): + if pspec.name == 'x': + return self.x + + elif pspec.name == 'y': + return self.y + + elif pspec.name == 'width': + self.width = self.handle.props.width + self.height = self.handle.props.height + + return self.width + + elif pspec.name == 'height': + return self.height + + else: + raise AttributeError, 'unknown property %s' % pspec.name + + def do_simple_paint(self, cr, bounds): + cr.translate(self.x, self.y) + cr.scale(self.sx, self.sy) + self.handle.render_cairo(cr) + + def do_simple_update(self, cr): + self.bounds_x1 = float(self.x) + self.bounds_y1 = float(self.y) + self.bounds_x2 = float(self.x + self.width) + self.bounds_y2 = float(self.y + self.height) + + def do_simple_is_item_at(self, x, y, cr, is_pointer_event): + if ((x < self.x) or (x > self.x + self.width)) or ((y < self.y) or (y > self.y + self.height)): + return False + else: + return True + + +_rsvg_cache = dict() + +def rsvg_handle_factory(base_file_name): + try: + return _rsvg_cache[base_file_name] + except KeyError: + full_path = os.path.join(os.path.dirname(__file__), 'resource', base_file_name) + rsvg_handle = rsvg.Handle(full_path) + _rsvg_cache[base_file_name] = rsvg_handle + return rsvg_handle + diff --git a/src/tools/visualizer/wscript b/src/tools/visualizer/wscript new file mode 100644 index 000000000..2af1c3467 --- /dev/null +++ b/src/tools/visualizer/wscript @@ -0,0 +1,22 @@ +## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + +def build(bld): + headers = bld.new_task_gen('ns3header') + headers.module = 'visualizer' + headers.source = [ + ] + + module = bld.create_ns3_module('visualizer', ['core']) + module.features.append('pyembed') + module.env.append_value('CXXFLAGS', module.env['shlib_CXXFLAGS']) + module.includes = '.' + + if bld.env['ENABLE_PYTHON_BINDINGS']: + module.source = [ + 'model/pyviz.cc', + 'model/visual-simulator-impl.cc', + ] + headers.source.append('model/pyviz.h') + else: + module.source = [ + ] diff --git a/src/wscript b/src/wscript index 36c08f6b2..685e88c2b 100644 --- a/src/wscript +++ b/src/wscript @@ -62,6 +62,7 @@ all_modules = ( 'mpi', 'contrib/topology-read', 'contrib/energy', + 'tools/visualizer', ) def set_options(opt): diff --git a/wscript b/wscript index 5d8eb60bc..6a2b5ef3c 100644 --- a/wscript +++ b/wscript @@ -124,6 +124,9 @@ def set_options(opt): help=('Run a locally built program; argument can be a program name,' ' or a command starting with the program name.'), type="string", default='', dest='run') + opt.add_option('--visualize', + help=('Modify --run arguments to enable the visualizer'), + action="store_true", default=False, dest='visualize') opt.add_option('--command-template', help=('Template of the command used to run the program given by --run;' ' It should be a shell command string containing %s inside,' @@ -639,11 +642,13 @@ def shutdown(ctx): lcov_report() if Options.options.run: - wutils.run_program(Options.options.run, env, wutils.get_command_template(env)) + wutils.run_program(Options.options.run, env, wutils.get_command_template(env), + visualize=Options.options.visualize) raise SystemExit(0) if Options.options.pyrun: - wutils.run_python_program(Options.options.pyrun, env) + wutils.run_python_program(Options.options.pyrun, env, + visualize=Options.options.visualize) raise SystemExit(0) if Options.options.shell: diff --git a/wutils.py b/wutils.py index bed965761..465fd284b 100644 --- a/wutils.py +++ b/wutils.py @@ -100,10 +100,11 @@ def get_proc_env(os_env=None): proc_env[pathvar] = os.pathsep.join(list(env['NS3_MODULE_PATH'])) pymoddir = bld.path.find_dir('bindings/python').abspath(env) + pyvizdir = bld.path.find_dir('src/tools/visualizer').abspath() if 'PYTHONPATH' in proc_env: - proc_env['PYTHONPATH'] = os.pathsep.join([pymoddir] + [proc_env['PYTHONPATH']]) + proc_env['PYTHONPATH'] = os.pathsep.join([pymoddir, pyvizdir] + [proc_env['PYTHONPATH']]) else: - proc_env['PYTHONPATH'] = pymoddir + proc_env['PYTHONPATH'] = os.pathsep.join([pymoddir, pyvizdir]) if 'PATH' in proc_env: proc_env['PATH'] = os.pathsep.join(list(env['NS3_EXECUTABLE_PATH']) + [proc_env['PATH']]) @@ -201,7 +202,7 @@ def get_run_program(program_string, command_template=None): #print "%r ==shlex.split==> %r" % (command_template % (program_node.abspath(env),), execvec) return program_name, execvec -def run_program(program_string, env, command_template=None, cwd=None): +def run_program(program_string, env, command_template=None, cwd=None, visualize=False): """ if command_template is not None, then program_string == program name and argv is given by command_template with %s replaced by the @@ -214,17 +215,21 @@ def run_program(program_string, env, command_template=None, cwd=None): cwd = Options.options.cwd_launch else: cwd = Options.cwd_launch + if visualize: + execvec.append("--SimulatorImplementationType=ns3::VisualSimulatorImpl") return run_argv(execvec, env, cwd=cwd) -def run_python_program(program_string, env): +def run_python_program(program_string, env, visualize=False): env = bld.env execvec = shlex.split(program_string) if (Options.options.cwd_launch): cwd = Options.options.cwd_launch else: cwd = Options.cwd_launch + if visualize: + execvec.append("--SimulatorImplementationType=ns3::VisualSimulatorImpl") return run_argv([env['PYTHON']] + execvec, env, cwd=cwd)