syntax error. fixed.
[nodemanager.git] / bwmon.py
1 #!/usr/bin/python
2 #
3 # Average bandwidth monitoring script. Run periodically via NM db.sync to
4 # enforce a soft limit on daily bandwidth usage for each slice. If a
5 # slice is found to have transmitted 80% of its daily byte limit usage,
6 # its instantaneous rate will be capped at the bytes remaning in the limit
7 # over the time remaining in the recording period.
8 #
9 # Two separate limits are enforced, one for destinations exempt from
10 # the node bandwidth cap (i.e. Internet2), and the other for all other destinations.
11 #
12 # Mark Huang <mlhuang@cs.princeton.edu>
13 # Andy Bavier <acb@cs.princeton.edu>
14 # Faiyaz Ahmed <faiyaza@cs.princeton.edu>
15 # Copyright (C) 2004-2008 The Trustees of Princeton University
16 #
17 # $Id$
18 #
19
20 import os
21 import sys
22 import time
23 import pickle
24 import socket
25 import logger
26 import copy
27 import threading
28 import tools
29
30 import bwlimit
31 import database
32
33 from sets import Set
34
35 # Defaults
36 debug = False
37 verbose = False
38 datafile = "/var/lib/misc/bwmon.dat"
39
40 try:
41     sys.path.append("/etc/planetlab")
42     from plc_config import *
43 except:
44     logger.log("bwmon:  Warning: Configuration file /etc/planetlab/plc_config.py not found", 2)
45     logger.log("bwmon:  Running in DEBUG mode.  Logging to file and not emailing.", 1)
46
47 # Constants
48 seconds_per_day = 24 * 60 * 60
49 bits_per_byte = 8
50
51 # Burst to line rate (or node cap).  Set by NM. in KBit/s
52 default_MaxRate = int(bwlimit.get_bwcap() / 1000)
53 default_Maxi2Rate = int(bwlimit.bwmax / 1000)
54 # 5.4 Gbyte per day. 5.4 * 1024 k * 1024M * 1024G 
55 # 5.4 Gbyte per day max allowed transfered per recording period
56 default_MaxKByte = 5662310
57 # 16.4 Gbyte per day max allowed transfered per recording period to I2
58 default_Maxi2KByte = 17196646
59 # Default share quanta
60 default_Share = 1
61
62 # Average over 1 day
63 period = 1 * seconds_per_day
64
65 # Message template
66 template = \
67 """
68 The slice %(slice)s has transmitted more than %(bytes)s from
69 %(hostname)s to %(class)s destinations
70 since %(since)s.
71
72 Its maximum %(class)s burst rate will be capped at %(new_maxrate)s/s
73 until %(until)s.
74
75 Please reduce the average %(class)s transmission rate
76 of the slice to %(limit)s per %(period)s.
77
78 """.lstrip()
79
80 footer = \
81 """
82 %(date)s %(hostname)s bwcap %(slice)s
83 """.lstrip()
84
85 def format_bytes(bytes, si = True):
86     """
87     Formats bytes into a string
88     """
89     if si:
90         kilo = 1000.
91     else:
92         # Officially, a kibibyte
93         kilo = 1024.
94
95     if bytes >= (kilo * kilo * kilo):
96         return "%.1f GB" % (bytes / (kilo * kilo * kilo))
97     elif bytes >= 1000000:
98         return "%.1f MB" % (bytes / (kilo * kilo))
99     elif bytes >= 1000:
100         return "%.1f KB" % (bytes / kilo)
101     else:
102         return "%.0f bytes" % bytes
103
104 def format_period(seconds):
105     """
106     Formats a period in seconds into a string
107     """
108
109     if seconds == (24 * 60 * 60):
110         return "day"
111     elif seconds == (60 * 60):
112         return "hour"
113     elif seconds > (24 * 60 * 60):
114         return "%.1f days" % (seconds / 24. / 60. / 60.)
115     elif seconds > (60 * 60):
116         return "%.1f hours" % (seconds / 60. / 60.)
117     elif seconds > (60):
118         return "%.1f minutes" % (seconds / 60.)
119     else:
120         return "%.0f seconds" % seconds
121
122 def slicemail(slice, subject, body):
123     '''
124     Front end to sendmail.  Sends email to slice alias with given subject and body.
125     '''
126
127     sendmail = os.popen("/usr/sbin/sendmail -N never -t -f%s" % PLC_MAIL_SUPPORT_ADDRESS, "w")
128
129     # Parsed from MyPLC config
130     to = [PLC_MAIL_MOM_LIST_ADDRESS]
131
132     if slice is not None and slice != "root":
133         to.append(PLC_MAIL_SLICE_ADDRESS.replace("SLICE", slice))
134
135     header = {'from': "%s Support <%s>" % (PLC_NAME, PLC_MAIL_SUPPORT_ADDRESS),
136               'to': ", ".join(to),
137               'version': sys.version.split(" ")[0],
138               'subject': subject}
139
140     # Write headers
141     sendmail.write(
142 """
143 Content-type: text/plain
144 From: %(from)s
145 Reply-To: %(from)s
146 To: %(to)s
147 X-Mailer: Python/%(version)s
148 Subject: %(subject)s
149
150 """.lstrip() % header)
151
152     # Write body
153     sendmail.write(body)
154     # Done
155     sendmail.close()
156
157
158 class Slice:
159     """
160     Stores the last recorded bandwidth parameters of a slice.
161
162     xid - slice context/VServer ID
163     name - slice name
164     time - beginning of recording period in UNIX seconds
165     bytes - low bandwidth bytes transmitted at the beginning of the recording period
166     i2bytes - high bandwidth bytes transmitted at the beginning of the recording period (for I2 -F)
167     MaxKByte - total volume of data allowed
168     ThreshKbyte - After thresh, cap node to (maxkbyte - bytes)/(time left in period)
169     Maxi2KByte - same as MaxKByte, but for i2 
170     Threshi2Kbyte - same as Threshi2KByte, but for i2 
171     MaxRate - max_rate slice attribute. 
172     Maxi2Rate - max_exempt_rate slice attribute.
173     Share - Used by Sirius to loan min rates
174     Sharei2 - Used by Sirius to loan min rates for i2
175     self.emailed - did slice recv email during this recording period
176
177     """
178
179     def __init__(self, xid, name, rspec):
180         self.xid = xid
181         self.name = name
182         self.time = 0
183         self.bytes = 0
184         self.i2bytes = 0
185         self.MaxRate = default_MaxRate
186         self.MinRate = bwlimit.bwmin / 1000
187         self.Maxi2Rate = default_Maxi2Rate
188         self.Mini2Rate = bwlimit.bwmin / 1000
189         self.MaxKByte = default_MaxKByte
190         self.ThreshKByte = int(.8 * self.MaxKByte)
191         self.Maxi2KByte = default_Maxi2KByte
192         self.Threshi2KByte = int(.8 * self.Maxi2KByte)
193         self.Share = default_Share
194         self.Sharei2 = default_Share
195         self.emailed = False
196         self.capped = False
197
198         self.updateSliceAttributes(rspec)
199         bwlimit.set(xid = self.xid, 
200                 minrate = self.MinRate * 1000, 
201                 maxrate = self.MaxRate * 1000, 
202                 maxexemptrate = self.Maxi2Rate * 1000,
203                 minexemptrate = self.Mini2Rate * 1000,
204                 share = self.Share)
205
206     def __repr__(self):
207         return self.name
208
209     def updateSliceAttributes(self, rspec):
210         '''
211         Use respects from GetSlivers to PLC to populate slice object.  Also
212         do some sanity checking.
213         '''
214
215         # Sanity check plus policy decision for MinRate:
216         # Minrate cant be greater than 25% of MaxRate or NodeCap.
217         MinRate = int(rspec.get("net_min_rate", bwlimit.bwmin / 1000))
218         if MinRate > int(.25 * default_MaxRate):
219             MinRate = int(.25 * default_MaxRate)
220         if MinRate != self.MinRate:
221             self.MinRate = MinRate
222             logger.log("bwmon:  Updating %s: Min Rate = %s" %(self.name, self.MinRate))
223
224         MaxRate = int(rspec.get('net_max_rate', bwlimit.get_bwcap() / 1000))
225         if MaxRate != self.MaxRate:
226             self.MaxRate = MaxRate
227             logger.log("bwmon:  Updating %s: Max Rate = %s" %(self.name, self.MaxRate))
228
229         Mini2Rate = int(rspec.get('net_i2_min_rate', bwlimit.bwmin / 1000))
230         if Mini2Rate != self.Mini2Rate:
231             self.Mini2Rate = Mini2Rate 
232             logger.log("bwmon:  Updating %s: Min i2 Rate = %s" %(self.name, self.Mini2Rate))
233
234         Maxi2Rate = int(rspec.get('net_i2_max_rate', bwlimit.bwmax / 1000))
235         if Maxi2Rate != self.Maxi2Rate:
236             self.Maxi2Rate = Maxi2Rate
237             logger.log("bwmon:  Updating %s: Max i2 Rate = %s" %(self.name, self.Maxi2Rate))
238                           
239         MaxKByte = int(rspec.get('net_max_kbyte', default_MaxKByte))
240         if MaxKByte != self.MaxKByte:
241             self.MaxKByte = MaxKByte
242             logger.log("bwmon:  Updating %s: Max KByte lim = %s" %(self.name, self.MaxKByte))
243                           
244         Maxi2KByte = int(rspec.get('net_i2_max_kbyte', default_Maxi2KByte))
245         if Maxi2KByte != self.Maxi2KByte:
246             self.Maxi2KByte = Maxi2KByte
247             logger.log("bwmon:  Updating %s: Max i2 KByte = %s" %(self.name, self.Maxi2KByte))
248                           
249         ThreshKByte = int(rspec.get('net_thresh_kbyte', (MaxKByte * .8)))
250         if ThreshKByte != self.ThreshKByte:
251             self.ThreshKByte = ThreshKByte
252             logger.log("bwmon:  Updating %s: Thresh KByte = %s" %(self.name, self.ThreshKByte))
253                           
254         Threshi2KByte = int(rspec.get('net_i2_thresh_kbyte', (Maxi2KByte * .8)))
255         if Threshi2KByte != self.Threshi2KByte:    
256             self.Threshi2KByte = Threshi2KByte
257             logger.log("bwmon:  Updating %s: i2 Thresh KByte = %s" %(self.name, self.Threshi2KByte))
258  
259         Share = int(rspec.get('net_share', default_Share))
260         if Share != self.Share:
261             self.Share = Share
262             logger.log("bwmon:  Updating %s: Net Share = %s" %(self.name, self.Share))
263
264         Sharei2 = int(rspec.get('net_i2_share', default_Share))
265         if Sharei2 != self.Sharei2:
266             self.Sharei2 = Sharei2 
267             logger.log("bwmon:  Updating %s: Net i2 Share = %s" %(self.name, self.i2Share))
268
269
270     def reset(self, runningmaxrate, runningmaxi2rate, usedbytes, usedi2bytes, rspec):
271         """
272         Begin a new recording period. Remove caps by restoring limits
273         to their default values.
274         """
275         
276         # Query Node Manager for max rate overrides
277         self.updateSliceAttributes(rspec)    
278
279         # Reset baseline time
280         self.time = time.time()
281
282         # Reset baseline byte coutns
283         self.bytes = usedbytes
284         self.i2bytes = usedi2bytes
285
286         # Reset email 
287         self.emailed = False
288         # Reset flag
289         self.capped = False
290         # Reset rates.
291         maxrate = self.MaxRate * 1000 
292         maxi2rate = self.Maxi2Rate * 1000
293         if (self.MaxRate != runningmaxrate) or (self.Maxi2Rate != runningmaxi2rate):
294             logger.log("bwmon:  %s reset to %s/%s" % \
295                   (self.name,
296                    bwlimit.format_tc_rate(maxrate),
297                    bwlimit.format_tc_rate(maxi2rate)), 1)
298             bwlimit.set(xid = self.xid, 
299                 minrate = self.MinRate * 1000, 
300                 maxrate = self.MaxRate * 1000, 
301                 maxexemptrate = self.Maxi2Rate * 1000,
302                 minexemptrate = self.Mini2Rate * 1000,
303                 share = self.Share)
304
305     def notify(self, new_maxrate, new_maxexemptrate, usedbytes, usedi2bytes):
306         """
307         Notify the slice it's being capped.
308         """
309          # Prepare message parameters from the template
310         message = ""
311         params = {'slice': self.name, 'hostname': socket.gethostname(),
312                   'since': time.asctime(time.gmtime(self.time)) + " GMT",
313                   'until': time.asctime(time.gmtime(self.time + period)) + " GMT",
314                   'date': time.asctime(time.gmtime()) + " GMT",
315                   'period': format_period(period)}
316
317         if new_maxrate != self.MaxRate:
318             # Format template parameters for low bandwidth message
319             params['class'] = "low bandwidth"
320             params['bytes'] = format_bytes(usedbytes - self.bytes)
321             params['limit'] = format_bytes(self.MaxKByte * 1024)
322             params['new_maxrate'] = bwlimit.format_tc_rate(new_maxrate)
323
324             # Cap low bandwidth burst rate
325             message += template % params
326             logger.log("bwmon:   ** %(slice)s %(class)s capped at %(new_maxrate)s/s " % params)
327
328         if new_maxexemptrate != self.Maxi2Rate:
329             # Format template parameters for high bandwidth message
330             params['class'] = "high bandwidth"
331             params['bytes'] = format_bytes(usedi2bytes - self.i2bytes)
332             params['limit'] = format_bytes(self.Maxi2KByte * 1024)
333             params['new_maxexemptrate'] = bwlimit.format_tc_rate(new_maxexemptrate)
334  
335             message += template % params
336             logger.log("bwmon:   ** %(slice)s %(class)s capped at %(new_maxrate)s/s " % params)
337        
338         # Notify slice
339         if message and self.emailed == False:
340             subject = "pl_mom capped bandwidth of slice %(slice)s on %(hostname)s" % params
341             if debug:
342                 logger.log("bwmon:  "+ subject)
343                 logger.log("bwmon:  "+ message + (footer % params))
344             else:
345                 self.emailed = True
346                 slicemail(self.name, subject, message + (footer % params))
347
348
349     def update(self, runningmaxrate, runningmaxi2rate, usedbytes, usedi2bytes, runningshare, rspec):
350         """
351         Update byte counts and check if byte thresholds have been
352         exceeded. If exceeded, cap to remaining bytes in limit over remaining time in period.  
353         Recalculate every time module runs.
354         """
355     
356         # Query Node Manager for max rate overrides
357         self.updateSliceAttributes(rspec)    
358
359         # Check limits.
360         if usedbytes >= (self.bytes + (self.ThreshKByte * 1024)):
361             sum = self.bytes + (self.ThreshKByte * 1024)
362             maxbyte = self.MaxKByte * 1024
363             bytesused = usedbytes - self.bytes
364             timeused = int(time.time() - self.time)
365             # Calcuate new rate.
366             new_maxrate = int(((maxbyte - bytesused) * 8)/(period - timeused))
367             # Never go under MinRate
368             if new_maxrate < (self.MinRate * 1000):
369                 new_maxrate = self.MinRate * 1000
370             # State information.  I'm capped.
371             self.capped = True
372         else:
373             # Sanity Check
374             new_maxrate = self.MaxRate * 1000
375             self.capped = False
376  
377         if usedi2bytes >= (self.i2bytes + (self.Threshi2KByte * 1024)):
378             maxi2byte = self.Maxi2KByte * 1024
379             i2bytesused = usedi2bytes - self.i2bytes
380             timeused = int(time.time() - self.time)
381             # Calcuate New Rate.
382             new_maxi2rate = int(((maxi2byte - i2bytesused) * 8)/(period - timeused))
383             # Never go under MinRate
384             if new_maxi2rate < (self.Mini2Rate * 1000):
385                 new_maxi2rate = self.Mini2Rate * 1000
386             # State information.  I'm capped.
387             self.capped = True
388         else:
389             # Sanity
390             new_maxi2rate = self.Maxi2Rate * 1000
391             self.capped = False
392
393         # Apply parameters
394         bwlimit.set(xid = self.xid, 
395                 minrate = self.MinRate * 1000, 
396                 maxrate = new_maxrate,
397                 minexemptrate = self.Mini2Rate * 1000,
398                 maxexemptrate = new_maxi2rate,
399                 share = self.Share)
400
401         # Notify slice
402         if self.capped == True and self.emailed == False:
403             self.notify(new_maxrate, new_maxi2rate, usedbytes, usedi2bytes)
404
405
406 def gethtbs(root_xid, default_xid):
407     """
408     Return dict {xid: {*rates}} of running htbs as reported by tc that have names.
409     Turn off HTBs without names.
410     """
411     livehtbs = {}
412     for params in bwlimit.get():
413         (xid, share,
414          minrate, maxrate,
415          minexemptrate, maxexemptrate,
416          usedbytes, usedi2bytes) = params
417         
418         name = bwlimit.get_slice(xid)
419
420         if (name is None) \
421         and (xid != root_xid) \
422         and (xid != default_xid):
423             # Orphaned (not associated with a slice) class
424             name = "%d?" % xid
425             logger.log("bwmon:  Found orphaned HTB %s. Removing." %name, 1)
426             bwlimit.off(xid)
427
428         livehtbs[xid] = {'share': share,
429             'minrate': minrate,
430             'maxrate': maxrate,
431             'maxexemptrate': maxexemptrate,
432             'minexemptrate': minexemptrate,
433             'usedbytes': usedbytes,
434             'name': name, 
435             'usedi2bytes': usedi2bytes}
436
437     return livehtbs
438
439 def sync(nmdbcopy):
440     """
441     Syncs tc, db, and bwmon.dat.  Then, starts new slices, kills old ones, and updates byte accounts for each running slice.  Sends emails and caps those that went over their limit.
442     """
443     # Defaults
444     global datafile, \
445         period, \
446         default_MaxRate, \
447         default_Maxi2Rate, \
448         default_MaxKByte,\
449         default_Maxi2KByte,\
450         default_Share,\
451         verbose
452
453     # All slices
454     names = []
455     # Incase the limits have changed. 
456     default_MaxRate = int(bwlimit.get_bwcap() / 1000)
457     default_Maxi2Rate = int(bwlimit.bwmax / 1000)
458
459     # Incase default isn't set yet.
460     if default_MaxRate == -1:
461         default_MaxRate = 1000000
462
463     try:
464         f = open(datafile, "r+")
465         logger.log("bwmon:  Loading %s" % datafile, 2)
466         (version, slices, deaddb) = pickle.load(f)
467         f.close()
468         # Check version of data file
469         if version != "$Id$":
470             logger.log("bwmon:  Not using old version '%s' data file %s" % (version, datafile))
471             raise Exception
472     except Exception:
473         version = "$Id$"
474         slices = {}
475         deaddb = {}
476
477     # Get/set special slice IDs
478     root_xid = bwlimit.get_xid("root")
479     default_xid = bwlimit.get_xid("default")
480
481     # Since root is required for sanity, its not in the API/plc database, so pass {} 
482     # to use defaults.
483     if root_xid not in slices.keys():
484         slices[root_xid] = Slice(root_xid, "root", {})
485         slices[root_xid].reset(0, 0, 0, 0, {})
486     
487     # Used by bwlimit.  pass {} since there is no rspec (like above).
488     if default_xid not in slices.keys():
489         slices[default_xid] = Slice(default_xid, "default", {})
490         slices[default_xid].reset(0, 0, 0, 0, {})
491
492     live = {}
493     # Get running slivers that should be on this node (from plc). {xid: name}
494     # db keys on name, bwmon keys on xid.  db doesnt have xid either.
495     for plcSliver in nmdbcopy.keys():
496         live[bwlimit.get_xid(plcSliver)] = nmdbcopy[plcSliver]
497
498     logger.log("bwmon:  Found %s instantiated slices" % live.keys().__len__(), 2)
499     logger.log("bwmon:  Found %s slices in dat file" % slices.values().__len__(), 2)
500
501     # Get actual running values from tc.
502     # Update slice totals and bandwidth. {xid: {values}}
503     kernelhtbs = gethtbs(root_xid, default_xid)
504     logger.log("bwmon:  Found %s running HTBs" % kernelhtbs.keys().__len__(), 2)
505
506     # The dat file has HTBs for slices, but the HTBs aren't running
507     nohtbslices =  Set(slices.keys()) - Set(kernelhtbs.keys())
508     logger.log( "bwmon:  Found %s slices in dat but not running." % nohtbslices.__len__(), 2)
509     # Reset tc counts.
510     for nohtbslice in nohtbslices:
511         if live.has_key(nohtbslice): 
512             slices[nohtbslice].reset( 0, 0, 0, 0, live[nohtbslice]['_rspec'] )
513         else:
514             logger.log("bwmon:  Removing abondoned slice %s from dat." % nohtbslice)
515             del slices[nohtbslice]
516
517     # The dat file doesnt have HTB for the slice but kern has HTB
518     slicesnodat = Set(kernelhtbs.keys()) - Set(slices.keys())
519     logger.log( "bwmon:  Found %s slices with HTBs but not in dat" % slicesnodat.__len__(), 2)
520     for slicenodat in slicesnodat:
521         # But slice is running 
522         if live.has_key(slicenodat): 
523             # init the slice.  which means start accounting over since kernel
524             # htb was already there.
525             slices[slicenodat] = Slice(slicenodat, 
526                 live[slicenodat]['name'], 
527                 live[slicenodat]['_rspec'])
528
529     # Get new slices.
530     # Slices in GetSlivers but not running HTBs
531     newslicesxids = Set(live.keys()) - Set(kernelhtbs.keys())
532     logger.log("bwmon:  Found %s new slices" % newslicesxids.__len__(), 2)
533        
534     # Setup new slices
535     for newslice in newslicesxids:
536         # Delegated slices dont have xids (which are uids) since they haven't been
537         # instantiated yet.
538         if newslice != None and live[newslice].has_key('_rspec') == True:
539             # Check to see if we recently deleted this slice.
540             if live[newslice]['name'] not in deaddb.keys():
541                 logger.log( "bwmon: New Slice %s" % live[newslice]['name'] )
542                 # _rspec is the computed rspec:  NM retrieved data from PLC, computed loans
543                 # and made a dict of computed values.
544                 slices[newslice] = Slice(newslice, live[newslice]['name'], live[newslice]['_rspec'])
545                 slices[newslice].reset( 0, 0, 0, 0, live[newslice]['_rspec'] )
546             # Double check time for dead slice in deaddb is within 24hr recording period.
547             elif (time.time() <= (deaddb[live[newslice]['name']]['slice'].time + period)):
548                 deadslice = deaddb[live[newslice]['name']]
549                 logger.log("bwmon: Reinstantiating deleted slice %s" % live[newslice]['name'])
550                 slices[newslice] = deadslice['slice']
551                 slices[newslice].xid = newslice
552                 # Start the HTB
553                 slices[newslice].reset(deadslice['slice'].MaxRate,
554                                     deadslice['slice'].Maxi2Rate,
555                                     deadslice['htb']['usedbytes'],
556                                     deadslice['htb']['usedi2bytes'],
557                                     live[newslice]['_rspec'])
558                 # Bring up to date
559                 slices[newslice].update(deadslice['slice'].MaxRate, 
560                                     deadslice['slice'].Maxi2Rate, 
561                                     deadslice['htb']['usedbytes'], 
562                                     deadslice['htb']['usedi2bytes'], 
563                                     deadslice['htb']['share'], 
564                                     live[newslice]['_rspec'])
565                 # Since the slice has been reinitialed, remove from dead database.
566                 del deaddb[deadslice['slice'].name]
567         else:
568             logger.log("bwmon:  Slice %s doesn't have xid.  Skipping." % live[newslice]['name'])
569
570     # Move dead slices that exist in the pickle file, but
571     # aren't instantiated by PLC into the dead dict until
572     # recording period is over.  This is to avoid the case where a slice is dynamically created
573     # and destroyed then recreated to get around byte limits.
574     deadxids = Set(slices.keys()) - Set(live.keys())
575     logger.log("bwmon:  Found %s dead slices" % (deadxids.__len__() - 2), 2)
576     for deadxid in deadxids:
577         if deadxid == root_xid or deadxid == default_xid:
578             continue
579         logger.log("bwmon:  removing dead slice %s " % deadxid)
580         if slices.has_key(deadxid) and kernelhtbs.has_key(deadxid):
581             # add slice (by name) to deaddb
582             logger.log("bwmon:  Saving bandwidth totals for %s." % slices[deadxid].name)
583             deaddb[slices[deadxid].name] = {'slice': slices[deadxid], 'htb': kernelhtbs[deadxid]}
584             del slices[deadxid]
585         if kernelhtbs.has_key(deadxid): 
586             logger.log("bwmon:  Removing HTB for %s." % deadxid, 2)
587             bwlimit.off(deadxid)
588     
589     # Clean up deaddb
590     for deadslice in deaddb.keys():
591         if (time.time() >= (deaddb[deadslice]['slice'].time + period)):
592             logger.log("bwmon:  Removing dead slice %s from dat." \
593                         % deaddb[deadslice]['slice'].name)
594             del deaddb[deadslice]
595
596     # Get actual running values from tc since we've added and removed buckets.
597     # Update slice totals and bandwidth. {xid: {values}}
598     kernelhtbs = gethtbs(root_xid, default_xid)
599     logger.log("bwmon:  now %s running HTBs" % kernelhtbs.keys().__len__(), 2)
600
601     for (xid, slice) in slices.iteritems():
602         # Monitor only the specified slices
603         if xid == root_xid or xid == default_xid: continue
604         if names and name not in names:
605             continue
606  
607         if (time.time() >= (slice.time + period)) or \
608             (kernelhtbs[xid]['usedbytes'] < slice.bytes) or \
609             (kernelhtbs[xid]['usedi2bytes'] < slice.i2bytes):
610             # Reset to defaults every 24 hours or if it appears
611             # that the byte counters have overflowed (or, more
612             # likely, the node was restarted or the HTB buckets
613             # were re-initialized).
614             slice.reset(kernelhtbs[xid]['maxrate'], \
615                 kernelhtbs[xid]['maxexemptrate'], \
616                 kernelhtbs[xid]['usedbytes'], \
617                 kernelhtbs[xid]['usedi2bytes'], \
618                 live[xid]['_rspec'])
619         else:
620             logger.log("bwmon:  Updating slice %s" % slice.name, 2)
621             # Update byte counts
622             slice.update(kernelhtbs[xid]['maxrate'], \
623                 kernelhtbs[xid]['maxexemptrate'], \
624                 kernelhtbs[xid]['usedbytes'], \
625                 kernelhtbs[xid]['usedi2bytes'], \
626                 kernelhtbs[xid]['share'],
627                 live[xid]['_rspec'])
628
629     logger.log("bwmon:  Saving %s slices in %s" % (slices.keys().__len__(),datafile), 2)
630     f = open(datafile, "w")
631     pickle.dump((version, slices, deaddb), f)
632     f.close()
633
634 lock = threading.Event()
635 def run():
636     """When run as a thread, wait for event, lock db, deep copy it, release it, run bwmon.GetSlivers(), then go back to waiting."""
637     logger.log("bwmon:  Thread started", 2)
638     while True:
639         lock.wait()
640         logger.log("bwmon:  Event received.  Running.", 2)
641         database.db_lock.acquire()
642         nmdbcopy = copy.deepcopy(database.db)
643         database.db_lock.release()
644         try:  sync(nmdbcopy)
645         except: logger.log_exc()
646         lock.clear()
647
648 def start(*args):
649     tools.as_daemon_thread(run)
650
651 def GetSlivers(*args):
652     pass