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