From 197b083fbc6f31f6452ea438314de5072b323bf9 Mon Sep 17 00:00:00 2001
From: Bob Lantz <rlantz@cs.stanford.edu>
Date: Sun, 8 Apr 2012 17:22:30 -0700
Subject: [PATCH] Add static cpu (and memory) assignment.

---
 bin/mn          |  7 ++++++-
 mininet/net.py  | 16 ++++++++++++----
 mininet/node.py | 43 +++++++++++++++++++++++++++++++++++++------
 3 files changed, 55 insertions(+), 11 deletions(-)

diff --git a/bin/mn b/bin/mn
index 29f938c7..16fdc944 100755
--- a/bin/mn
+++ b/bin/mn
@@ -221,6 +221,9 @@ class MininetRunner( object ):
         opts.add_option( '--prefixlen', type='int', default=8,
                         help='prefix length (e.g. /8) for automatic '
                         'network configuration' )
+        opts.add_option( '--pin', action='store_true',
+                         default=False, help="pin hosts to CPU cores "
+                         "(requires --host cfs or --host rt)" )
 
         self.options, self.args = opts.parse_args()
 
@@ -259,6 +262,7 @@ class MininetRunner( object ):
         xterms = self.options.xterms
         mac = self.options.mac
         arp = self.options.arp
+        pin = self.options.pin
         listenPort = None
         if not self.options.nolistenport:
             listenPort = self.options.listenport
@@ -268,7 +272,8 @@ class MininetRunner( object ):
                   ipBase=ipBase,
                   inNamespace=inNamespace,
                   xterms=xterms, autoSetMacs=mac,
-                  autoStaticArp=arp, listenPort=listenPort )
+                  autoStaticArp=arp, autoPinCpus=pin,
+                  listenPort=listenPort )
 
         if self.options.pre:
             CLI( mn, script=self.options.pre )
diff --git a/mininet/net.py b/mininet/net.py
index d1dafab6..82ec2b0b 100755
--- a/mininet/net.py
+++ b/mininet/net.py
@@ -96,7 +96,7 @@
 from mininet.log import info, error, debug, output
 from mininet.node import Host, OVSKernelSwitch, Controller
 from mininet.link import Link, Intf
-from mininet.util import quietRun, fixLimits
+from mininet.util import quietRun, fixLimits, numCores
 from mininet.util import macColonHex, ipStr, ipParse, netParse, ipAdd
 from mininet.term import cleanUpScreens, makeTerms
 
@@ -107,7 +107,8 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
                  controller=Controller, link=Link, intf=Intf,
                  build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
                  inNamespace=False,
-                 autoSetMacs=False, autoStaticArp=False, listenPort=None ):
+                 autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
+                 listenPort=None ):
         """Create Mininet object.
            topo: Topo (topology) object or None
            switch: default Switch class
@@ -120,8 +121,9 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
            xterms: if build now, spawn xterms?
            cleanup: if build now, cleanup before creating?
            inNamespace: spawn switches and controller in net namespaces?
-           autoSetMacs: set MAC addrs from topo dpid?
+           autoSetMacs: set MAC addrs automatically like IP addresses?
            autoStaticArp: set all-pairs static MAC addrs?
+           autoPinCpus: pin hosts to (real) cores (requires CPULimitedHost)?
            listenPort: base listening port to open; will be incremented for
                each additional switch in the net if inNamespace=False"""
         self.topo = topo
@@ -138,6 +140,9 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
         self.cleanup = cleanup
         self.autoSetMacs = autoSetMacs
         self.autoStaticArp = autoStaticArp
+        self.autoPinCpus = autoPinCpus
+        self.numCores = numCores()
+        self.nextCore = 0  # next core for pinning hosts to CPUs
         self.listenPort = listenPort
 
         self.hosts = []
@@ -166,6 +171,9 @@ def addHost( self, name, cls=None, **params ):
                                   prefixLen=self.prefixLen ) }
         if self.autoSetMacs:
             defaults[ 'mac'] = macColonHex( self.nextIP )
+        if self.autoPinCpus:
+            defaults[ 'cores' ] = self.nextCore
+            self.nextCore = ( self.nextCore + 1 ) % self.numCores
         self.nextIP += 1
         defaults.update( params )
         if not cls:
@@ -230,7 +238,7 @@ def configHosts( self ):
         "Configure a set of hosts."
         for host in self.hosts:
             info( host.name + ' ' )
-            host.configDefault( defaultRoute=host.defaultIntf )
+            host.configDefault( defaultRoute=host.defaultIntf() )
             # You're low priority, dude!
             # BL: do we want to do this here or not?
             # May not make sense if we have CPU lmiting...
diff --git a/mininet/node.py b/mininet/node.py
index cee6b675..e8c38061 100644
--- a/mininet/node.py
+++ b/mininet/node.py
@@ -51,8 +51,8 @@
 from subprocess import Popen, PIPE, STDOUT
 
 from mininet.log import info, error, warn, debug
-from mininet.util import quietRun, errRun, errFail, moveIntf, isShellBuiltin
-from mininet.util import numCores, retry
+from mininet.util import ( quietRun, errRun, errFail, moveIntf, isShellBuiltin,
+                          numCores, retry, mountCgroups )
 from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
 from mininet.link import Link, Intf, TCIntf
 
@@ -514,10 +514,15 @@ class CPULimitedHost( Host ):
 
     def __init__( self, name, sched='cfs', **kwargs ):
         Host.__init__( self, name, **kwargs )
+        # Initialize class if necessary
+        if not CPULimitedHost.inited:
+            CPULimitedHost.init()
         # Create a cgroup and move shell into it
-        self.cgroup = 'cpu,cpuacct:/' + self.name
+        self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
         errFail( 'cgcreate -g ' + self.cgroup )
-        errFail( 'cgclassify -g %s %s' % ( self.cgroup, self.pid ) )
+        # We don't add ourselves to a cpuset because you must
+        # specify the cpu and memory placement first
+        errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
         # BL: Setting the correct period/quota is tricky, particularly
         # for RT. RT allows very small quotas, but the overhead
         # seems to be high. CFS has a mininimum quota of 1 ms, but
@@ -540,7 +545,7 @@ def cgroupGet( self, param, resource='cpu' ):
         "Return value of cgroup parameter"
         cmd = 'cgget -r %s.%s /%s' % (
             resource, param, self.name )
-        return quietRun( cmd ).split()[ -1 ]
+        return int( quietRun( cmd ).split()[ -1 ] )
 
     def cgroupDel( self ):
         "Clean up our cgroup"
@@ -616,15 +621,41 @@ def setCPUFrac( self, f=-1, sched=None):
             self.chrt( prio=20 )
         info( '(%s %d/%dus) ' % ( sched, quota, period ) )
 
-    def config( self, cpu=None, **params ):
+    def setCPUs( self, cores ):
+        "Specify (real) cores that our cgroup can run on"
+        if type( cores ) is list:
+            cores = ','.join( [ str( c ) for c in cores ] )
+        self.cgroupSet( resource='cpuset', param='cpus',
+                        value= cores )
+        # Memory placement is probably not relevant, but we
+        # must specify it anyway
+        self.cgroupSet( resource='cpuset', param='mems',
+                        value= cores )
+        # We have to do this here after we've specified
+        # cpus and mems
+        errFail( 'cgclassify -g cpuset:/%s %s' % (
+                self.name, self.pid ) )
+
+    def config( self, cpu=None, cores=None, **params ):
         """cpu: desired overall system CPU fraction
+           cores: (real) core(s) this host can run on
            params: parameters for Node.config()"""
         r = Node.config( self, **params )
         # Was considering cpu={'cpu': cpu , 'sched': sched}, but
         # that seems redundant
         self.setParam( r, 'setCPUFrac', cpu=cpu )
+        self.setParam( r, 'setCPUs', cores=cores )
         return r
 
+    inited = False
+
+    @classmethod
+    def init( cls ):
+        "Initialization for CPULimitedHost class"
+        mountCgroups()
+        cls.inited = True
+
+
 # Some important things to note:
 #
 # The "IP" address which setIP() assigns to the switch is not
-- 
GitLab