def updateGui(self): if self._LastUpdate + self._Interval < Time.GetCurrMS(): self._LastUpdate = Time.GetCurrMS() sm = Statistics.GetStatistics() self.lblTotalPacketsDownstream.configure( text=str(sm._TotalPacketsDownstream)) self.lblPacketDownstream.configure( text=str(sm._UniquePacketsDownstream)) self.lblBytesTransmittedDownstream.configure( text=str(sm._totalTxBytesDownstream)) self.lblBytesReceivedFromDownstream.configure( text=str(sm._totalRxBytesDownstream)) self.lblTotalPacketsUpstream.configure( text=str(sm._TotalPacketsUpstream)) self.lblPacketUpstream.configure( text=str(sm._UniquePacketsUpstream)) self.lblBytesTransmittedUpstream.configure( text=str(sm._totalTxBytesUpstream)) self.lblBytesReceivedFromUpstream.configure( text=str(sm._totalRxBytesUpstream)) self.lblTotalDroppedPackets.configure( text=str(sm._TotalPacketsDropped)) self.lblTotalMalformedPackets.configure( text=str(sm._TotalMalformedPacketsReceived)) self.lblTotalChainedPackets.configure( text=str(sm._TotalChainedDownstreamPackets)) self.lblTotalOscarTasks.configure( text=str(sm._TotalOscarTasksReceived)) self.lblTotalMinionTasks.configure( text=str(sm._TotalMinionTasksReceived)) self.lblTotalShuntedPackets.configure( text=str(sm._TotalShuntedPackets))
def alternateCollectionProc(self): if self.IsOnDemand(): Log.getLogger().error( "On Demand Collector called with alternateCollectionProc") if self.NeedsCollecting(): startCollectionTime = Time.GetCurrMS() buffer = self.PerformCollection() TimeToCollect = Time.GetCurrMS() - startCollectionTime #print(TimeToCollect) if None != buffer: buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + buffer if not self._NamespaceObject.SendPacket(buffer): return 0 #self._NamespaceObject.CheckMTU(len(buffer),self._MinionID) if TimeToCollect > self._PollingInterval: # do a sanity check to see if collection time is longer than collector frequency Log.getLogger().warning( "Collector: " + self.GetID() + " took longer to perform the actual collection than the specified frequency for it (" + str(self._PollingInterval) + ". It is suggested you change something to fix this.") return len(buffer) return 0
def WorkerProc(self, fnKillSignalled, userData): lastUpdate = 0 interval = Configuration.get().GetConnectionUpdateInterval() buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" buffer = buffer + "<Oscar Type=\"ConnectionInformation\">" buffer = buffer + "<Version>1.0</Version>" buffer = buffer + "<OscarVersion>" + VersionMgr.ReadVer( ) + "</OscarVersion>" buffer = buffer + "<ID>" + Configuration.get().GetID() + "</ID>" buffer = buffer + "<Port>" + str(Configuration.get( ).GetDownstreamConnection().getPort()) + "</Port>" buffer = buffer + "</Oscar>" #<?xml version="1.0" encoding="utf-8"?> #<Oscar Type="ConnectionInformation"> # <Version>1.0</Version> # <ID>Foo</Foo> # <Port>Port</Port> #</Oscar> while not fnKillSignalled( ): # run until signalled to end - call passed function to check for the signal if lastUpdate < Time.GetCurrMS() - interval: TargetManager.GetTargetManager().BroadcastDownstream( buffer, True, None ) # send Connection Data to all downstream things (Oscars & Marvins) lastUpdate = Time.GetCurrMS() Configuration.get().RescanTargets() else: Sleep.Sleep(0.25) TargetManager.GetTargetManager( ).CheckForRemovalOfDynamicMarvins()
def _Flush(self): self._RecordedData = [] self._Bytes = 0 self._StartTime = Time.GetCurrMS() self._StopTime = Time.GetCurrMS() self._Stopped = True self._Saved = True
def alternateWorker(self): ## Maybe have one thread that goes and self.m_objLockDict.acquire() # thread safety if len( self.m_SendList ) == 0: # rest if nothing to send, or sent a bunch of packets without a sleep dataToProcess = False else: buffer = self.m_SendList[0] del self.m_SendList[0] dataToProcess = True self.m_objLockDict.release() if dataToProcess: if None == self.m_IP_InUse: Log.getLogger().info("Getting IP address for host: " + self.ConfigurationDefinedTarget) try: self.m_LastDNSResolution = Time.GetCurrMS() self.m_IP_InUse = socket.gethostbyname( self.ConfigurationDefinedTarget ) #use this for looking at heartbeats except Exception as _: self.m_IP_InUse = self.ConfigurationDefinedTarget try: self.m_socket.sendto(bytes(buffer, 'utf-8'), (self.m_IP_InUse, self.getPort())) self.m_PacketsSent += 1 self.m_BytestSent += len(buffer) except Exception as ex: Log.getLogger().info("It appears that the target [" + self.ConfigurationDefinedTarget + "] has went away.") self.m_objLockDict.acquire() Statistics.GetStatistics().OnPacketDropped( len(self.m_SendList) + 1) self.m_SendList.clear() self.m_objLockDict.release() if self.m_LastDNSResolution + self.m_DNSResolutionPeriod < Time.GetCurrMS( ) and self.m_hasTimedOut == True: self.m_IP_InUse = None # Force a DNS resolution, may help when move laptop and gets new address - # eventually # if self.m_hasTimedOut: # Log.getLogger().error("Timed out" + str(ConnectionType.DynamicMarvin)) if self.m_hasTimedOut and (self.Type == ConnectionType.DynamicMarvin or self.Type == ConnectionType.DynamicOscar): self.MarkedForRemoval = True self.Type += 1 return dataToProcess
def __init__(self): if None != Recorder._instance: return Recorder._instance = self self._RecordedData = [] self._Bytes = 0 self._StartTime = Time.GetCurrMS() self._StopTime = Time.GetCurrMS() self._Stopped = True self._Saved = True
def StartupWorkerProc(fnKillSignalled, userData): downstreamServer = userData[0] upstreamServer = userData[1] Sleep.SleepMs(500) downstreamServer.Start() upstreamServer.DropPackets(True) upstreamServer.Start() Watchdog.ConnectionUpdateTimer() Watchdog.WatchdogTimer() conf = Configuration.get() if None != conf.GetAutorunFilename(): GuiMgr.OnStopLiveData() #GuiMgr.OnStopPlayback() #GuiMgr.OnStopRecording(True) #drop all recorded packets GuiMgr.OnSetPlaybackSpeed(Configuration.get().GetPlaybackSpeed()) ss = Configuration.get().GetAutorunLocations() #GuiMgr.OnEnablePlayback() GuiMgr.ReadFromFile(Configuration.get().GetAutorunFilename()) GuiMgr.OnStopPlayback() Sleep.SleepMs( 100) # let gui worker threads catch up, so gui updates properly GuiMgr.OnStartPlayback() GuiMgr.OnSetRepeatMode(Configuration.get().GetAutoRunMode(), ss[0], ss[1]) else: upstreamServer.DropPackets(False) if None != conf.GetAutorunTime() and conf.GetAutorunTime( ) > 0: # specified a --time, so let's hang out for that long endTime = Time.GetCurrMS() + conf.GetAutorunTime() * 60 * 1000 Log.getLogger().info("Waiting for " + str(conf.GetAutorunTime()) + " minutes before auto shutdown") if conf.GetRecordFilename(): GuiMgr.OnStartRecording() while not fnKillSignalled() and endTime > Time.GetCurrMS(): Sleep.SleepMs(250) Log.getLogger().info("Shutting down after time period") if conf.GetRecordFilename( ): # was a recording session, so quit after that time GuiMgr.OnStopRecording() GuiMgr.WriteToFile(conf.GetRecordFilename()) Log.getLogger().info("Saving Recorded data to file: " + conf.GetRecordFilename()) GuiMgr.Quit()
def Stop(self, flush): self._StopTime = Time.GetCurrMS() self._Stopped = True if True == flush: self._Flush() if self._Bytes > 0: Playback.get().SetData(self._RecordedData)
def __sendConnectionInfoProc(self,fnKillSignalled,userData): buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" buffer = buffer + "<Minion Type=\"ConnectionInformation\">" buffer = buffer + "<Version>1.0</Version>" buffer = buffer + "<MinionVersion>" + VersionMgr.ReadVer()+ "</MinionVersion>" buffer += "<Namespace>" + str(self) + "</Namespace>" buffer += "<Port>" + str(self._Server.getPort()) + "</Port>" buffer = buffer + "</Minion>" lastUpdate = 0 while not fnKillSignalled(): if lastUpdate + Namespace.ConnectionInfoUpdateInterval < Time.GetCurrMS(): if self.SendPacket(buffer): Log.getLogger().debug("Sent announcement to Oscar") lastUpdate = Time.GetCurrMS() Sleep.SleepMs(Namespace.ConnectionUpdateThreadSleepinterval) # Don't want to sleep for Namespace.ConnectionInfoUpdateInterval in case
def AddData(self, objData): if True == self._Stopped: return if 0 == len( self._RecordedData): # only start timing when get 1st packet self._StartTime = Time.GetCurrMS() self._RecordedData.append(objData) self._Bytes += sys.getsizeof(objData) self._Saved = False
def PerformInsertBookmark(self,Params): #<?xml version="1.0" encoding="utf-8"?> #<Marvin Type="OscarTask"> # <Version>1.0</Version> # <OscarID>DemoOscar</OscarID> # <UniqueID>1233456</UniqueID> # <Task>InsertBookmark</Task> # <Param>Namespace=foo</Param> # <Param>ID=TestMarker</Param> # <Param>Data=StartTest</Param> # <Param>Propagate=True</Param> -- if True, the data is sent back upstream towards Marvin #</Marvin> if len(Params) >= 3: fPropagate = False for param in Params: parts = param.split("=") if len(parts)==2: if parts[0].lower()=='namespace': namespace = Alias.Alias(parts[1]) elif parts[0].lower()=='id': id = Alias.Alias(parts[1]) elif parts[0].lower()=='data': data = Alias.Alias(parts[1]) elif parts[0].lower()=='propagate': propagate = Alias.Alias(parts[1]) if propagate.lower() == "true": fPropagate = True else: Log.getLogger().error("Received invalid InsertBookmark task parameter: " + str(param)) return else: Log.getLogger().error("Received invalid InsertBookmark task. Insufficient Parameters.") return if None == namespace: Log.getLogger().error("Received invalid InsertBookmark task. Namespace not specified.") return if None == id: Log.getLogger().error("Received invalid InsertBookmark task. ID not specified.") return if None == data: Log.getLogger().error("Received invalid InsertBookmark task. Data not specified.") return eTime = Time.GetCurrMS() objData = MarvinData.MarvinData(namespace,id,data,eTime,1.0) if None != objData: Recorder.get().AddData(objData) GuiMgr.OnDataPacketSentDownstream(objData,"Minion") if fPropagate: TargetManager.GetTargetManager().BroadcastDownstream(objData.ToXML(),False,None)
def WatchdogProc(self, fnKillSignalled, userData): lastUpdate = 0 interval = Configuration.get().GetTimeoutPeriod( ) * 0.25 # send a watchdog at 4x rate of timeout buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" buffer = buffer + "<Oscar Type=\"WatchdogTimer\">" buffer = buffer + "<Version>1.0</Version>" buffer = buffer + "<Port>" + str( Configuration.get().GetUpstreamConnection().getPort()) + "</Port>" buffer = buffer + "</Oscar>" while not fnKillSignalled( ): # run until signalled to end - call passed function to check for the signal if lastUpdate < Time.GetCurrMS() - interval: TargetManager.GetTargetManager().BroadcastUpstreamToType( buffer, ConnectionType.UpstreamOscar ) # send heartbeat to all upstream Oscars lastUpdate = Time.GetCurrMS() Sleep.Sleep(0.25) #snooze for 250 ms
def NeedsCollecting(self): refresh = self._RefreshRequested retVal = self._LastCollectionTime + self._PollingInterval < Time.GetCurrMS() or True == refresh #a = self.GetID() #b = self._RunOnce if self._RunOnce and self._LastCollectionTime > 0: retVal = False if self.IsDynamicallyCreated and self.DynamicValueCollected: retVal = False # Don't collect if it is a dynamic collector that hasn't been updated since last collect return retVal
def SetCollectorValueFromPlugin(self,collectorID,Value,elapsedTime=None): objCollector = self._NamespaceObject.GetCollector(self.__PrefixStr + collectorID + self.__SuffixStr) if None == objCollector: Log.getLogger().error("User defined DynamicCollector tried to Set a value to a collector that does not exist, with ID: " + collectorID) return False if None == elapsedTime: elapsedTime = Time.GetCurrMS() - objCollector._LastCollectionTime objCollector.SetDynamicData(Value,elapsedTime) return True
def __init__(self, ip=None, Port=None, ConnType=ConnectionType.Unknown, canTimeout=True): super(Target, self).__init__(ip, Port, ConnType) self.ConfigurationDefinedTarget = ip self.m_IP_InUse = None #m_ip could be DNS name self.m_socket = None self.m_lastHeartbeat = Time.GetCurrMS() self.m_PacketsSent = 0 self.m_BytestSent = 0 self.m_InitialRefreshSent = False self.m_objLockDict = threading.Lock() self.m_SendList = [] self.m_hasTimedOut = False self.m_LastDNSResolution = Time.GetCurrMS() self.m_DNSResolutionPeriod = 30000 #30 seconds self.m_CanTimeout = canTimeout self.threadName = None self.lastRefreshRequestID = 0 self.MarkedForRemoval = False try: self.m_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.m_socket.setblocking(True) self.m_socket.settimeout(0.001) except Exception as _: Log.getLogger().error("Error setting up Target Socket -->" + str(super.m_Connection)) self.threadName = "Target:" + self.getIP() + "[" + str( self.getPort()) + "]" ThreadManager.GetThreadManager().CreateThread(self.threadName, self.WorkerProc) ThreadManager.GetThreadManager().StartThread(self.threadName)
def Collect(self): if None == self._Interval: collectors=self.GetCollectors() self._Collector = collectors[0] try: self._Interval = int(collectors[1].GetLastValue()) * 1000 #make it seconds except: return "Operator Running Average must have time in seconds as 2nd <Input>" oldestTime = Time.GetCurrMS() - self._Interval validRangeStart=0 for index, dataPoint in enumerate(self._dataPointList): val,time = dataPoint if time < oldestTime: validRangeStart = index +1 if validRangeStart > 0: self._dataPointList = self._dataPointList[validRangeStart:] try: val = float(self._Collector.GetLastValue()) except: return "Operator Running Average cannot average value {}".format(self._Collector.GetLastValue()) time = Time.GetCurrMS() - (self._Collector.GetElapsedTimeSinceLast() * 1000) self._dataPointList.append((val,time)) tVal = 0.0 for val,_ in self._dataPointList: tVal += float(val) if len(self._dataPointList) > 0: retVal = tVal/len(self._dataPointList) else: retVal = 0.0 return retVal
def __init__(self,Namespace,ID,Value,ElapsedTime,FormatVersion,isLive=True): from Helpers import Configuration self.FormatVersion=FormatVersion self.Value = Value if True == isLive: self.ArrivalTime = Time.GetCurrMS() - MarvinData.__FirstTime # just a delta from start is fine, no need for a 10 digit string for time else: self.ArrivalTime = ElapsedTime self.Namespace = Configuration.get().HandleBITWNamespace(Namespace) # if Bump in the Wire, change NS self.ID = ID self.Live = isLive
def __init__(self, ID, Value, ElapsedTime, Namespace, ReceivedTime="NotFromFile"): self.Value = Value self.ArrivalTime = ElapsedTime self.Namespace = Namespace self.ID = ID # self.PacketNumber = PacketNumber # self.Normalized = False if ReceivedTime == "NotFromFile": #self.ArrivalTime = TimeUtils.GetTimeDeltaMS() self.ArrivalTime = Time.GetCurrMS() else: self.ArrivalTime = ReceivedTime
def __CheckForTimeout(self): from Helpers import Configuration if not self.m_CanTimeout: return False if self.m_hasTimedOut: return True toPeriod = Configuration.get().GetTimeoutPeriod() if toPeriod < Time.GetCurrMS( ) - self.m_lastHeartbeat: # a timeout has ocurred self.m_hasTimedOut = True Log.getLogger().info("Target [" + str(self) + "] has timed out.") self.lastRefreshRequestID = 0 #self.ReArmRefreshRequest(123456789) return self.m_hasTimedOut
def __init__(self, Namespace, ID, Value, ElapsedTime, FormatVersion, isLive=True): #from Helpers import Configuration self.FormatVersion = FormatVersion self.Value = Value if True == isLive: self.ArrivalTime = Time.GetCurrMS() else: self.ArrivalTime = ElapsedTime self.Namespace = Namespace #self.Namespace = Configuration.get().HandleBITWNamespace(Namespace) # if Bump in the Wire, change NS self.ID = ID self.Live = isLive
def StrokeWatchdogTimer(self): if True == self.m_CanTimeout: self.m_lastHeartbeat = Time.GetCurrMS() self.m_hasTimedOut = False if False == self.m_InitialRefreshSent: self.m_InitialRefreshSent = True buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" buffer = buffer + "<Oscar Type=\"Refresh\">" buffer = buffer + "<Version>1.0</Version>" uID = str(random.randint(0, 500000)) buffer = buffer + "<UniqueID>" + uID + "</UniqueID>" buffer = buffer + "</Oscar>" from Helpers import TargetManager if TargetManager.GetTargetManager().BroadcastUpstream(buffer): Log.getLogger().info("Sending Refresh Request to Minions [" + uID + ']') TargetManager.GetTargetManager().BroadcastUpstream( buffer ) # is UDP, so send a couple more times, dups will be filtered on Minion TargetManager.GetTargetManager().BroadcastUpstream(buffer)
class MarvinData(object): __FirstTime = Time.GetCurrMS() def __init__(self,Namespace,ID,Value,ElapsedTime,FormatVersion,isLive=True): from Helpers import Configuration self.FormatVersion=FormatVersion self.Value = Value if True == isLive: self.ArrivalTime = Time.GetCurrMS() - MarvinData.__FirstTime # just a delta from start is fine, no need for a 10 digit string for time else: self.ArrivalTime = ElapsedTime self.Namespace = Configuration.get().HandleBITWNamespace(Namespace) # if Bump in the Wire, change NS self.ID = ID self.Live = isLive def ToXML(self,destIsFile=False): startCDATA="<![CDATA[" endCDATA="]]>" if False == destIsFile: buffer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" else: buffer = "" buffer = buffer + "<Oscar Type=\"Data\">" buffer = buffer + "<Version>1</Version>" buffer = buffer + "<Namespace>"+self.Namespace+"</Namespace>" buffer = buffer + "<ID>"+self.ID+"</ID>" if False == destIsFile: buffer = buffer + "<Value LiveData=\""+str(self.Live)+"\">"+startCDATA+self.Value+endCDATA+"</Value>" else: buffer = buffer + "<Value>"+self.Value+"</Value>" buffer = buffer + "</Oscar>" return buffer
def PerformCollection(self): #Get collected time after collection, can't be sure each collection take same amount of time currMS = Time.GetCurrMS() elapsedTime = currMS - self._LastCollectionTime self._LastCollectionTime = currMS retValue = None collectedGroupData = "" for collector in self._CollectorList: id = collector.GetID() if self._ForceCollectionEvenIfNoUpdate or collector.NeedsCollecting( ): # by default a group ALWAYS collects, but can override that with AlwaysCollect="False" as group attribute collectorData = collector.PerformCollection() if None != collectorData: collectedGroupData += collectorData if len(collectedGroupData) > 1: retValue = "<MinionGroup>" + collectedGroupData + "</MinionGroup>" if self._RefreshRequested: self._RefreshRequested = False return retValue
def __CollectSingleRange(self,fnKillSignalled,processThreadID): from Helpers import Configuration count = 0 currTotal = 0 maxTx = Configuration.GetMaxTransmitBufferBeforeRest() startTime = Time.GetCurrMS() collectorList = self.__GetCollectorListForThreadGroup(processThreadID) for collector in collectorList: if fnKillSignalled(): # get out of possible long loop if we are to exit return if not collector.IsInGroup() and not collector.IsOnDemand(): SizeOfSentData = collector.alternateCollectionProc() if SizeOfSentData > 0: self.IncrementSentBytes(SizeOfSentData) count+=1 currTotal += SizeOfSentData if currTotal > maxTx: # don't want to overload Oscar Sleep.SleepMs(50) currTotal = 0 #timeTaken = Time.GetCurrMS() - startTime #print(processThreadID +": " + str(timeTaken)) #if Namespace._LogTimePerProcessLoop and timeTaken > 0: # Log.getLogger().debug("Process Thread: " + collectorList[0].GetProcessThreadID() + " took " + str(timeTaken) + "ms to process one loop") #if timeTaken > Namespace._LoopTimePeriodWarningThreshold and not Namespace._LogTimePerProcessLoop : # Log.getLogger().warning("Process Thread: " + collectorList[0].GetProcessThreadID() + " took " + str(timeTaken) + "ms to process one loop - you may want to investigate.") #if "Default" != processThreadID: # print(processThreadID + " Collected " + str(count) +"/" + str(len(collectorList))) return count
def GetRecordingTime(self): if True == self._Stopped: return int((self._StopTime - self._StartTime) / 1000) return int((Time.GetCurrMS() - self._StartTime) / 1000)
def GetLastElapsedTimePeriod(self): return Time.GetCurrMS()
def ResetStats(self): self.m_lastHeartbeat = Time.GetCurrMS() self.m_PacketsSent = 0 self.m_BytestSent = 0 self.m_hasTimedOut = False self.LastPacket = None
def WriteRawCSVFile(self,filename): namespaceDict = {} entries = self.PlaybackData firstPktTime = Time.GetCurrMS() for entry in entries: self.AddRawEntryToDict(namespaceDict,entry) if entry.ArrivalTime < firstPktTime: firstPktTime = entry.ArrivalTime sortedNamespaceDictionary = collections.OrderedDict(sorted(namespaceDict.items())) writeData=[] writeData.append( "BIFF CSV File. Created {0}. Time(ms) is the # of ms data value received after the 1st value (so a relative time).".format(datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y"))) writeData.append( "Namespace,,") ROW_NS=1 writeData.append("ID,,") ROW_ID=2 writeData.append(",,") #Samples,Average Header Line ROW_AVG_HDR = 3 writeData.append(",,") #Samples,Average Line ROW_AVG = 4 writeData.append(",,") #Values, Time Header Line ROW_DATA_HDR=5 startRow = len(writeData) line = "" maxDp = 0 #go through each NS, making 1st two rows, keep track of longest ID set, and append that many lines for namespace in sortedNamespaceDictionary: nsDict = sortedNamespaceDictionary[namespace] for id in nsDict: if len(nsDict[id]) > maxDp: maxDp = len(nsDict[id]) for i in range(0,maxDp+1): writeData.append(",,") rows = len(writeData) for namespace in sortedNamespaceDictionary: nsDict = sortedNamespaceDictionary[namespace] sortedIDDictionary = collections.OrderedDict(sorted(nsDict.items())) for ID in sortedIDDictionary: DataSet = sortedIDDictionary[ID] writeData[ROW_NS] += namespace + ",,," writeData[ROW_ID] += ID + ",,," writeData[ROW_AVG_HDR] += "Average,#Samples,," writeData[ROW_DATA_HDR] += "Values,Time(ms),," row = startRow tValue=0.0 nonNumeric=False for entry in DataSet: rowData = writeData[row] if "," in entry.Value: writeData[row] += str(entry.Value.replace(",",";") +",") else: writeData[row] += str(entry.Value) +"," writeData[row] += str(entry.ArrivalTime - firstPktTime) +",," row += 1 if not nonNumeric: try: tValue += float(entry.Value) except Exception: nonNumeric = True if nonNumeric: writeData[ROW_AVG] += "NA," else: writeData[ROW_AVG] += "{0},".format(tValue/len(DataSet)) writeData[ROW_AVG] += str(len(DataSet)) +",," if len(DataSet) < maxDp: for row in range(startRow+len(DataSet),startRow + maxDp): writeData[row] += ",,," with open(filename,'w+t') as fp: for line in writeData: fp.write(line + "\n")
def Start(self): self._StartTime = Time.GetCurrMS() self._Stopped = False self._Saved = False
def GetTimeMS(self): if None == self._SyncFile: return Time.GetCurrMS() return Time.GetFileTimestampMS(self._SyncFile)