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.
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.
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
36 sys.path.append("/etc/planetlab")
37 from plc_config import *
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"
46 seconds_per_day = 24 * 60 * 60
52 datafile = "/var/lib/misc/bwmon.dat"
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)
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 # 16.4 Gbyte per day max allowed transfered per recording period to I2
65 default_Maxi2KByte = 17196646
66 # Default share quanta
70 period = 1 * seconds_per_day
75 The slice %(slice)s has transmitted more than %(bytes)s from
76 %(hostname)s to %(class)s destinations
79 Its maximum %(class)s burst rate will be capped at %(new_maxrate)s/s
82 Please reduce the average %(class)s transmission rate
83 of the slice to %(limit)s per %(period)s.
89 %(date)s %(hostname)s bwcap %(slice)s
92 def format_bytes(bytes, si = True):
94 Formats bytes into a string
99 # Officially, a kibibyte
102 if bytes >= (kilo * kilo * kilo):
103 return "%.1f GB" % (bytes / (kilo * kilo * kilo))
104 elif bytes >= 1000000:
105 return "%.1f MB" % (bytes / (kilo * kilo))
107 return "%.1f KB" % (bytes / kilo)
109 return "%.0f bytes" % bytes
111 def format_period(seconds):
113 Formats a period in seconds into a string
116 if seconds == (24 * 60 * 60):
118 elif seconds == (60 * 60):
120 elif seconds > (24 * 60 * 60):
121 return "%.1f days" % (seconds / 24. / 60. / 60.)
122 elif seconds > (60 * 60):
123 return "%.1f hours" % (seconds / 60. / 60.)
125 return "%.1f minutes" % (seconds / 60.)
127 return "%.0f seconds" % seconds
129 def slicemail(slice, subject, body):
131 Front end to sendmail. Sends email to slice alias with given subject and body.
134 sendmail = os.popen("/usr/sbin/sendmail -N never -t -f%s" % PLC_MAIL_SUPPORT_ADDRESS, "w")
136 # PLC and PLE has a separate list for pl_mom messages
137 if PLC_MAIL_SUPPORT_ADDRESS == "support@planet-lab.org":
138 to = ["pl-mom@planet-lab.org"]
139 elif PLC_MAIL_SUPPORT_ADDRESS == "support@planet-lab.eu":
140 to = ["pl-mom@planet-lab.eu"]
142 to = [PLC_MAIL_SUPPORT_ADDRESS]
144 if slice is not None and slice != "root":
145 to.append(PLC_MAIL_SLICE_ADDRESS.replace("SLICE", slice))
147 header = {'from': "%s Support <%s>" % (PLC_NAME, PLC_MAIL_SUPPORT_ADDRESS),
149 'version': sys.version.split(" ")[0],
155 Content-type: text/plain
159 X-Mailer: Python/%(version)s
162 """.lstrip() % header)
172 Stores the last recorded bandwidth parameters of a slice.
174 xid - slice context/VServer ID
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
191 def __init__(self, xid, name, rspec):
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 = (.8 * self.MaxKByte)
203 self.Maxi2KByte = default_Maxi2KByte
204 self.Threshi2KByte = (.8 * self.Maxi2KByte)
205 self.Share = default_Share
206 self.Sharei2 = default_Share
210 self.updateSliceAttributes(rspec)
211 bwlimit.set(xid = self.xid,
212 minrate = self.MinRate * 1000,
213 maxrate = self.MaxRate * 1000,
214 maxexemptrate = self.Maxi2Rate * 1000,
215 minexemptrate = self.Mini2Rate * 1000,
221 def updateSliceAttributes(self, rspec):
223 Use respects from GetSlivers to PLC to populate slice object. Also
224 do some sanity checking.
227 # Sanity check plus policy decision for MinRate:
228 # Minrate cant be greater than 25% of MaxRate or NodeCap.
229 MinRate = int(rspec.get("net_min_rate", default_MinRate))
230 if MinRate > int(.25 * default_MaxRate):
231 MinRate = int(.25 * default_MaxRate)
232 if MinRate != self.MinRate:
233 self.MinRate = MinRate
234 logger.log("bwmon: Updating %s: Min Rate = %s" %(self.name, self.MinRate))
236 MaxRate = int(rspec.get('net_max_rate', bwlimit.get_bwcap() / 1000))
237 if MaxRate != self.MaxRate:
238 self.MaxRate = MaxRate
239 logger.log("bwmon: Updating %s: Max Rate = %s" %(self.name, self.MaxRate))
241 Mini2Rate = int(rspec.get('net_i2_min_rate', default_Mini2Rate))
242 if Mini2Rate != self.Mini2Rate:
243 self.Mini2Rate = Mini2Rate
244 logger.log("bwmon: Updating %s: Min i2 Rate = %s" %(self.name, self.Mini2Rate))
246 Maxi2Rate = int(rspec.get('net_i2_max_rate', bwlimit.bwmax / 1000))
247 if Maxi2Rate != self.Maxi2Rate:
248 self.Maxi2Rate = Maxi2Rate
249 logger.log("bwmon: Updating %s: Max i2 Rate = %s" %(self.name, self.Maxi2Rate))
251 MaxKByte = int(rspec.get('net_max_kbyte', default_MaxKByte))
252 if MaxKByte != self.MaxKByte:
253 self.MaxKByte = MaxKByte
254 logger.log("bwmon: Updating %s: Max KByte lim = %s" %(self.name, self.MaxKByte))
256 Maxi2KByte = int(rspec.get('net_i2_max_kbyte', default_Maxi2KByte))
257 if Maxi2KByte != self.Maxi2KByte:
258 self.Maxi2KByte = Maxi2KByte
259 logger.log("bwmon: Updating %s: Max i2 KByte = %s" %(self.name, self.Maxi2KByte))
261 ThreshKByte = int(rspec.get('net_thresh_kbyte', (MaxKByte * .8)))
262 if ThreshKByte != self.ThreshKByte:
263 self.ThreshKByte = ThreshKByte
264 logger.log("bwmon: Updating %s: Thresh KByte = %s" %(self.name, self.ThreshKByte))
266 Threshi2KByte = int(rspec.get('net_i2_thresh_kbyte', (Maxi2KByte * .8)))
267 if Threshi2KByte != self.Threshi2KByte:
268 self.Threshi2KByte = Threshi2KByte
269 logger.log("bwmon: Updating %s: i2 Thresh KByte = %s" %(self.name, self.Threshi2KByte))
271 Share = int(rspec.get('net_share', default_Share))
272 if Share != self.Share:
274 logger.log("bwmon: Updating %s: Net Share = %s" %(self.name, self.Share))
276 Sharei2 = int(rspec.get('net_i2_share', default_Share))
277 if Sharei2 != self.Sharei2:
278 self.Sharei2 = Sharei2
279 logger.log("bwmon: Updating %s: Net i2 Share = %s" %(self.name, self.i2Share))
282 def reset(self, runningmaxrate, runningmaxi2rate, usedbytes, usedi2bytes, rspec):
284 Begin a new recording period. Remove caps by restoring limits
285 to their default values.
288 # Query Node Manager for max rate overrides
289 self.updateSliceAttributes(rspec)
291 # Reset baseline time
292 self.time = time.time()
294 # Reset baseline byte coutns
295 self.bytes = usedbytes
296 self.i2bytes = usedi2bytes
303 maxrate = self.MaxRate * 1000
304 maxi2rate = self.Maxi2Rate * 1000
305 if (self.MaxRate != runningmaxrate) or (self.Maxi2Rate != runningmaxi2rate):
306 logger.log("bwmon: %s reset to %s/%s" % \
308 bwlimit.format_tc_rate(maxrate),
309 bwlimit.format_tc_rate(maxi2rate)))
310 bwlimit.set(xid = self.xid,
311 minrate = self.MinRate * 1000,
312 maxrate = self.MaxRate * 1000,
313 maxexemptrate = self.Maxi2Rate * 1000,
314 minexemptrate = self.Mini2Rate * 1000,
317 def notify(self, new_maxrate, new_maxexemptrate, usedbytes, usedi2bytes):
319 Notify the slice it's being capped.
321 # Prepare message parameters from the template
323 params = {'slice': self.name, 'hostname': socket.gethostname(),
324 'since': time.asctime(time.gmtime(self.time)) + " GMT",
325 'until': time.asctime(time.gmtime(self.time + period)) + " GMT",
326 'date': time.asctime(time.gmtime()) + " GMT",
327 'period': format_period(period)}
329 if new_maxrate != self.MaxRate:
330 # Format template parameters for low bandwidth message
331 params['class'] = "low bandwidth"
332 params['bytes'] = format_bytes(usedbytes - self.bytes)
333 params['limit'] = format_bytes(self.MaxKByte * 1024)
334 params['new_maxrate'] = bwlimit.format_tc_rate(new_maxrate)
336 # Cap low bandwidth burst rate
337 message += template % params
338 logger.log("bwmon: ** %(slice)s %(class)s capped at %(new_maxrate)s/s " % params)
340 if new_maxexemptrate != self.Maxi2Rate:
341 # Format template parameters for high bandwidth message
342 params['class'] = "high bandwidth"
343 params['bytes'] = format_bytes(usedi2bytes - self.i2bytes)
344 params['limit'] = format_bytes(self.Maxi2KByte * 1024)
345 params['new_maxexemptrate'] = bwlimit.format_tc_rate(new_maxi2rate)
347 message += template % params
348 logger.log("bwmon: ** %(slice)s %(class)s capped at %(new_maxrate)s/s " % params)
351 if message and self.emailed == False:
352 subject = "pl_mom capped bandwidth of slice %(slice)s on %(hostname)s" % params
354 logger.log("bwmon: "+ subject)
355 logger.log("bwmon: "+ message + (footer % params))
358 slicemail(self.name, subject, message + (footer % params))
361 def update(self, runningmaxrate, runningmaxi2rate, usedbytes, usedi2bytes, runningshare, rspec):
363 Update byte counts and check if byte thresholds have been
364 exceeded. If exceeded, cap to remaining bytes in limit over remaining in period.
365 Recalculate every time module runs.
368 # Query Node Manager for max rate overrides
369 self.updateSliceAttributes(rspec)
371 # Check shares for Sirius loans.
372 if runningshare != self.share:
373 logger.log("bwmon: Updating share to %s" % self.share)
374 bwlimit.set(xid = self.xid,
375 minrate = self.MinRate * 1000,
376 maxrate = self.MaxRate * 1000,
377 maxexemptrate = self.Maxi2Rate * 1000,
378 minexemptrate = self.Mini2Rate * 1000,
381 # Prepare message parameters from the template
383 #params = {'slice': self.name, 'hostname': socket.gethostname(),
384 # 'since': time.asctime(time.gmtime(self.time)) + " GMT",
385 # 'until': time.asctime(time.gmtime(self.time + period)) + " GMT",
386 # 'date': time.asctime(time.gmtime()) + " GMT",
387 # 'period': format_period(period)}
390 if usedbytes >= (self.bytes + (self.ThreshKByte * 1024)):
391 sum = self.bytes + (self.ThreshKByte * 1024)
392 maxbyte = self.MaxKByte * 1024
393 bytesused = usedbytes - self.bytes
394 timeused = int(time.time() - self.time)
396 new_maxrate = int(((maxbyte - bytesused) * 8)/(period - timeused))
397 # Never go under MinRate
398 if new_maxrate < (self.MinRate * 1000):
399 new_maxrate = self.MinRate * 1000
400 # State information. I'm capped.
404 new_maxrate = self.MaxRate * 1000
407 ## Format template parameters for low bandwidth message
408 #params['class'] = "low bandwidth"
409 #params['bytes'] = format_bytes(usedbytes - self.bytes)
410 #params['limit'] = format_bytes(self.MaxKByte * 1024)
411 #params['thresh'] = format_bytes(self.ThreshKByte * 1024)
412 #params['new_maxrate'] = bwlimit.format_tc_rate(new_maxrate)
414 # Cap low bandwidth burst rate
415 #if new_maxrate != runningmaxrate:
416 # message += template % params
417 # logger.log("bwmon: ** %(slice)s %(class)s capped at %(new_maxrate)s/s " % params)
419 if usedi2bytes >= (self.i2bytes + (self.Threshi2KByte * 1024)):
420 maxi2byte = self.Maxi2KByte * 1024
421 i2bytesused = usedi2bytes - self.i2bytes
422 timeused = int(time.time() - self.time)
424 new_maxi2rate = int(((maxi2byte - i2bytesused) * 8)/(period - timeused))
425 # Never go under MinRate
426 if new_maxi2rate < (self.Mini2Rate * 1000):
427 new_maxi2rate = self.Mini2Rate * 1000
428 # State information. I'm capped.
432 new_maxi2rate = self.Maxi2Rate * 1000
435 # Format template parameters for high bandwidth message
436 #params['class'] = "high bandwidth"
437 #params['bytes'] = format_bytes(usedi2bytes - self.i2bytes)
438 #params['limit'] = format_bytes(self.Maxi2KByte * 1024)
439 #params['new_maxexemptrate'] = bwlimit.format_tc_rate(new_maxi2rate)
441 # Cap high bandwidth burst rate
442 #if new_maxi2rate != runningmaxi2rate:
443 # message += template % params
444 # logger.log("bwmon: %(slice)s %(class)s capped at %(new_maxexemptrate)s/s" % params)
447 if new_maxrate != runningmaxrate or new_maxi2rate != runningmaxi2rate:
448 bwlimit.set(xid = self.xid, maxrate = new_maxrate, maxexemptrate = new_maxi2rate)
451 if self.capped == True and self.emailed == False:
452 self.notify(newmaxrate, newmaxexemptrate, usedbytes, usedi2bytes)
453 # subject = "pl_mom capped bandwidth of slice %(slice)s on %(hostname)s" % params
455 # logger.log("bwmon: "+ subject)
456 # logger.log("bwmon: "+ message + (footer % params))
458 # self.emailed = True
459 # slicemail(self.name, subject, message + (footer % params))
461 def gethtbs(root_xid, default_xid):
463 Return dict {xid: {*rates}} of running htbs as reported by tc that have names.
464 Turn off HTBs without names.
467 for params in bwlimit.get():
470 minexemptrate, maxexemptrate,
471 usedbytes, usedi2bytes) = params
473 name = bwlimit.get_slice(xid)
476 and (xid != root_xid) \
477 and (xid != default_xid):
478 # Orphaned (not associated with a slice) class
480 logger.log("bwmon: Found orphaned HTB %s. Removing." %name)
483 livehtbs[xid] = {'share': share,
486 'maxexemptrate': maxexemptrate,
487 'minexemptrate': minexemptrate,
488 'usedbytes': usedbytes,
490 'usedi2bytes': usedi2bytes}
496 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.
505 default_ThreshKByte,\
507 default_Threshi2KByte,\
513 # Incase the limits have changed.
514 default_MaxRate = int(bwlimit.get_bwcap() / 1000)
515 default_Maxi2Rate = int(bwlimit.bwmax / 1000)
517 # Incase default isn't set yet.
518 if default_MaxRate == -1:
519 default_MaxRate = 1000000
522 f = open(datafile, "r+")
523 logger.log("bwmon: Loading %s" % datafile)
524 (version, slices, deaddb) = pickle.load(f)
526 # Check version of data file
527 if version != "$Id$":
528 logger.log("bwmon: Not using old version '%s' data file %s" % (version, datafile))
535 # Get/set special slice IDs
536 root_xid = bwlimit.get_xid("root")
537 default_xid = bwlimit.get_xid("default")
539 # Since root is required for sanity, its not in the API/plc database, so pass {}
541 if root_xid not in slices.keys():
542 slices[root_xid] = Slice(root_xid, "root", {})
543 slices[root_xid].reset(0, 0, 0, 0, {})
545 # Used by bwlimit. pass {} since there is no rspec (like above).
546 if default_xid not in slices.keys():
547 slices[default_xid] = Slice(default_xid, "default", {})
548 slices[default_xid].reset(0, 0, 0, 0, {})
551 # Get running slivers that should be on this node (from plc). {xid: name}
552 # db keys on name, bwmon keys on xid. db doesnt have xid either.
553 for plcSliver in nmdbcopy.keys():
554 live[bwlimit.get_xid(plcSliver)] = nmdbcopy[plcSliver]
556 logger.log("bwmon: Found %s instantiated slices" % live.keys().__len__())
557 logger.log("bwmon: Found %s slices in dat file" % slices.values().__len__())
559 # Get actual running values from tc.
560 # Update slice totals and bandwidth. {xid: {values}}
561 kernelhtbs = gethtbs(root_xid, default_xid)
562 logger.log("bwmon: Found %s running HTBs" % kernelhtbs.keys().__len__())
564 # The dat file has HTBs for slices, but the HTBs aren't running
565 nohtbslices = Set(slices.keys()) - Set(kernelhtbs.keys())
566 logger.log( "bwmon: Found %s slices in dat but not running." % nohtbslices.__len__() )
568 for nohtbslice in nohtbslices:
569 if live.has_key(nohtbslice):
570 slices[nohtbslice].reset( 0, 0, 0, 0, live[nohtbslice]['_rspec'] )
572 # The dat file doesnt have HTB for the slice but kern has HTB
573 slicesnodat = Set(kernelhtbs.keys()) - Set(slices.keys())
574 logger.log( "bwmon: Found %s slices with HTBs but not in dat" % slicesnodat.__len__() )
575 for slicenodat in slicesnodat:
576 # But slice is running
577 if live.has_key(slicenodat):
578 # init the slice. which means start accounting over since kernel
579 # htb was already there.
580 slices[slicenodat] = Slice(slicenodat,
581 live[slicenodat]['name'],
582 live[slicenodat]['_rspec'])
583 else: bwlimit.off(slicenodat) # Abandoned. it doesnt exist at PLC or the dat
586 # Slices in GetSlivers but not running HTBs
587 newslicesxids = Set(live.keys()) - Set(kernelhtbs.keys())
588 logger.log("bwmon: Found %s new slices" % newslicesxids.__len__())
591 for newslice in newslicesxids:
592 # Delegated slices dont have xids (which are uids) since they haven't been
594 if newslice != None and live[newslice].has_key('_rspec') == True:
595 # Check to see if we recently deleted this slice.
596 if live[newslice]['name'] not in deaddb.keys():
597 logger.log( "bwmon: New Slice %s" % live[newslice]['name'] )
598 # _rspec is the computed rspec: NM retrieved data from PLC, computed loans
599 # and made a dict of computed values.
600 slices[newslice] = Slice(newslice, live[newslice]['name'], live[newslice]['_rspec'])
601 slices[newslice].reset( 0, 0, 0, 0, live[newslice]['_rspec'] )
602 # Double check time for dead slice in deaddb is within 24hr recording period.
603 elif (time.time() <= (deaddb[live[newslice]['name']]['slice'].time + period)):
604 deadslice = deaddb[live[newslice]['name']]
605 logger.log("bwmon: Reinstantiating deleted slice %s" % live[newslice]['name'])
606 slices[newslice] = deadslice['slice']
607 slices[newslice].xid = newslice
609 slices[newslice].reset(deadslice['slice'].MaxRate,
610 deadslice['slice'].Maxi2Rate,
611 deadslice['htb']['usedbytes'],
612 deadslice['htb']['usedi2bytes'],
613 live[newslice]['_rspec'])
615 slices[newslice].update(deadslice['slice'].MaxRate,
616 deadslice['slice'].Maxi2Rate,
617 deadslice['htb']['usedbytes'],
618 deadslice['htb']['usedi2bytes'],
619 deadslice['htb']['share'],
620 live[newslice]['_rspec'])
621 # Since the slice has been reinitialed, remove from dead database.
622 del deaddb[deadslice]
624 logger.log("bwmon Slice %s doesn't have xid. Must be delegated."\
625 "Skipping." % live[newslice]['name'])
627 # Move dead slices that exist in the pickle file, but
628 # aren't instantiated by PLC into the dead dict until
629 # recording period is over. This is to avoid the case where a slice is dynamically created
630 # and destroyed then recreated to get around byte limits.
631 deadxids = Set(slices.keys()) - Set(live.keys())
632 logger.log("bwmon: Found %s dead slices" % (deadxids.__len__() - 2))
633 for deadxid in deadxids:
634 if deadxid == root_xid or deadxid == default_xid:
636 logger.log("bwmon: removing dead slice %s " % deadxid)
637 if slices.has_key(deadxid):
638 # add slice (by name) to deaddb
639 deaddb[slices[deadxid].name] = {'slice': slices[deadxid], 'htb': kernelhtbs[deadxid]}
641 if kernelhtbs.has_key(deadxid):
645 for (deadslice, deadhtb) in deaddb.iteritems():
646 if (time.time() >= (deadslice.time() + period)):
647 logger.log("bwmon: Removing dead slice %s from dat." % deadslice.name)
648 del deaddb[deadslice.name]
650 # Get actual running values from tc since we've added and removed buckets.
651 # Update slice totals and bandwidth. {xid: {values}}
652 kernelhtbs = gethtbs(root_xid, default_xid)
653 logger.log("bwmon: now %s running HTBs" % kernelhtbs.keys().__len__())
655 for (xid, slice) in slices.iteritems():
656 # Monitor only the specified slices
657 if xid == root_xid or xid == default_xid: continue
658 if names and name not in names:
661 if (time.time() >= (slice.time + period)) or \
662 (kernelhtbs[xid]['usedbytes'] < slice.bytes) or \
663 (kernelhtbs[xid]['usedi2bytes'] < slice.i2bytes):
664 # Reset to defaults every 24 hours or if it appears
665 # that the byte counters have overflowed (or, more
666 # likely, the node was restarted or the HTB buckets
667 # were re-initialized).
668 slice.reset(kernelhtbs[xid]['maxrate'], \
669 kernelhtbs[xid]['maxexemptrate'], \
670 kernelhtbs[xid]['usedbytes'], \
671 kernelhtbs[xid]['usedi2bytes'], \
674 if debug: logger.log("bwmon: Updating slice %s" % slice.name)
676 slice.update(kernelhtbs[xid]['maxrate'], \
677 kernelhtbs[xid]['maxexemptrate'], \
678 kernelhtbs[xid]['usedbytes'], \
679 kernelhtbs[xid]['usedi2bytes'], \
680 kernelhtbs[xid]['share'],
683 logger.log("bwmon: Saving %s slices in %s" % (slices.keys().__len__(),datafile))
684 f = open(datafile, "w")
685 pickle.dump((version, slices, deaddb), f)
688 lock = threading.Event()
690 """When run as a thread, wait for event, lock db, deep copy it, release it, run bwmon.GetSlivers(), then go back to waiting."""
691 if debug: logger.log("bwmon: Thread started")
694 if debug: logger.log("bwmon: Event received. Running.")
695 database.db_lock.acquire()
696 nmdbcopy = copy.deepcopy(database.db)
697 database.db_lock.release()
699 except: logger.log_exc()
703 tools.as_daemon_thread(run)
705 def GetSlivers(*args):