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