めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

LXC コンテナの CPU 割り当てを変更する GUI

ちょっとしたデモに便利かも、ということで作ってみました。

005 と 006 の 2 つのコンテナで無駄ループ(loop005, loop006)を回しています。どちらも CPU Pinning を 1 にセットしているので、CPU1 だけが 100% で振り切れています。ただし、CPU Shares を 2:1 に配分しているので、loop005 と loop006 の CPU 使用率がちょうど 2:1 に配分されています。

LXC の環境は、こちらを使用しています。

今回は LXC コンテナに適用していますが、KVM の仮想マシンに対しても同じことが実現可能です。

ソースコードはこちら。

#!/usr/bin/python

import sys, os, re, commands
from Tkinter import *
from tkMessageBox import *

class CpuSet( Frame ):
    def __init__( self, parent, container, cgroups, **params ):
        Frame.__init__( self, parent, **params )

        self.cont = container
        self.cgdir = cgroups
        self.cpunums = int(
            commands.getoutput( "grep processor /proc/cpuinfo | wc -l" ) )
        self.statesVar = []
        self.states = []

        cpus = commands.getoutput(
            "cat " + os.path.join( self.cgdir, self.cont, "cpuset.cpus" ) )

        self.label = Label( self, text="CPU Pinning / Current: " + cpus )
        self.label.pack( side=TOP, anchor=W )

        for i in range( self.cpunums ):
            var = IntVar()
            chk = Checkbutton( self, text=str( i ), variable=var )
            chk.pack( side=LEFT )
            self.states.append( 0 )
            self.statesVar.append( var )

        for item in cpus.split( "," ):
            match = re.match( r"^(\d+)-(\d+)", item )
            if match:
                for i in range( int(match.group(1)), int(match.group(2)) + 1 ):
                    self.statesVar[ i ].set( 1 )
                    self.states[ i ] = 1
            else:
                self.statesVar[ int( item ) ].set( 1 )
                self.states[ int( item ) ] = 1

    def apply( self ):
        cpus = []
        stats = [ self.statesVar[ i ].get() for i in range( self.cpunums ) ]
        if sum( stats ) == 0:
            return
        self.states = stats
        for i in range( self.cpunums ):
            if self.states[ i ]: cpus.append( str( i ) )
        commands.getoutput(
            "echo \"" + ",".join( cpus ) + "\" >" +
                os.path.join( self.cgdir, self.cont, "cpuset.cpus" ) )
        cpus = commands.getoutput(
            "cat " + os.path.join( self.cgdir, self.cont, "cpuset.cpus" ) )
        self.label.config( text="CPU Pinning / Current: " + cpus )

    def reset( self ):
        for i in range( self.cpunums ):
            self.statesVar[ i ].set( self.states[ i ] )

class CpuShares( Frame ):
    def __init__( self, parent, container, cgroups, **params ):
        Frame.__init__( self, parent, **params )
        self.cont = container
        self.cgdir = cgroups

        self.shares = int ( commands.getoutput(
            "cat " + os.path.join( cgdir, container,"cpu.shares" ) ) )
        self.sharesVar = IntVar()
        self.sharesVar.set( self.shares )

        self.cpuscale = Scale( self,
            variable=self.sharesVar, from_=100, to=2048, length=300,
            showvalue=YES, orient="horizontal"
        )

        self.label = Label(
            self, text="CPU Shares / Current: " + str( self.shares ) )
        self.label.pack( side=TOP, anchor=W )
        self.cpuscale.pack( side=TOP, fill=BOTH, expand=YES )

    def apply( self ):
        self.shares = self.sharesVar.get()
        commands.getoutput(
            "echo \"" + str( self.shares ) + "\">" +
                os.path.join( self.cgdir, self.cont, "cpu.shares" ) )
        self.shares = int ( commands.getoutput(
            "cat " + os.path.join( self.cgdir, self.cont,"cpu.shares" ) ) )
        self.label.config( text="CPU Shares / Current: " + str( self.shares ) )

    def reset( self ):
        self.sharesVar.set( self.shares )

class ContainerLabel( Frame ):
    def __init__( self, parent, container, cgroups, **params ):
        Frame.__init__( self, parent, **params )

        self.cont = container
        self.cgdir = cgroups
        self.contlabel = Label( self, text="Container: " + self.cont )
        self.contlabel.pack( side=TOP, anchor=W )

class CpuController( Frame ):
    def __init__( self, parent, container, cgroups, **params ):
        Frame.__init__( self, parent, **params )
        self.cont = container
        self.cgdir = cgroups

        contlabel = ContainerLabel( self, self.cont, self.cgdir )
        apply = Button( self, text="Apply", command=self.onApply )
        reset = Button( self, text="Reset", command=self.onReset )

        self.cpuset = CpuSet( self, container, cgroups, bd=1, relief=SUNKEN )
        self.cpushares = CpuShares(
            self, container, cgroups, bd=1, relief=SUNKEN )

        self.cpuset.pack( side=TOP, fill=BOTH, expand=YES )
        self.cpushares.pack( side=TOP, fill=BOTH, expand=YES )
        contlabel.pack( side=LEFT , fill=X, expand=YES )
        apply.pack( side=LEFT )
        reset.pack( side=LEFT )

    def onApply( self ):
        self.cpushares.apply()
        self.cpuset.apply()

    def onReset( self ):
        self.cpushares.reset()
        self.cpuset.reset()

class MainController:
    def __init__( self, parent, **params ):
        self.parent = parent
        self.params = params
        self.controller = {}
        for con in params[ "containers" ]:
            self.controller[ con ] = CpuController(
                    self.parent, con, params[ "cgroups" ], bd=4, relief=RIDGE )
            self.controller[ con ].pack( side=TOP, fill=BOTH, expand=YES )

def getCgroupDir():
    for line in open( "/etc/mtab", "r" ):
        match = re.match( r"^\s*\S+\s+(\S+)\s+cgroup\s+", line )
        if match:
            return match.group(1)
    return None

def getActiveContainers( cgdir ):
    return filter( lambda f: os.path.isdir( os.path.join( cgdir, f ) ),
        os.listdir( cgdir ) )

if __name__ == "__main__":
    cgdir = getCgroupDir()
    if cgdir == None:
        print "No cgroup directory in /etc/mtab."
        exit( 1 )

    conts = getActiveContainers( cgdir )
    conts.sort()

    root = Tk()
    controller = MainController( root, containers=conts, cgroups=cgdir )
    root.mainloop()

# vi:ts=4