Timestamp accurato nella registrazione Python
Domanda
Di recente ho creato un'app per la registrazione degli errori e cercavo in modo accurato il timestamp dei dati in arrivo. Quando dico con precisione intendo che ogni timestamp deve essere accurato l'uno rispetto all'altro (non è necessario sincronizzarsi con un orologio atomico o qualcosa del genere).
Ho usato datetime.now () come primo colpo, ma questo non è perfetto:
>>> for i in range(0,1000):
... datetime.datetime.now()
...
datetime.datetime(2008, 10, 1, 13, 17, 27, 562000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 562000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 562000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 562000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 578000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 578000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 578000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 578000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 578000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 609000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 609000)
datetime.datetime(2008, 10, 1, 13, 17, 27, 609000)
etc.
Le modifiche tra gli orologi per il primo secondo dei campioni sono simili alle seguenti:
uSecs difference
562000
578000 16000
609000 31000
625000 16000
640000 15000
656000 16000
687000 31000
703000 16000
718000 15000
750000 32000
765000 15000
781000 16000
796000 15000
828000 32000
843000 15000
859000 16000
890000 31000
906000 16000
921000 15000
937000 16000
968000 31000
984000 16000
Quindi sembra che i dati del timer vengano aggiornati solo ogni ~ 15-32ms sulla mia macchina. Il problema si presenta quando arriviamo ad analizzare i dati perché l'ordinamento per qualcosa di diverso dal timestamp e quindi l'ordinamento di nuovo in base al timestamp può lasciare i dati nell'ordine sbagliato (cronologicamente). Sarebbe bello avere i timestamp accurati al punto che qualsiasi chiamata al generatore di timestamp dà un timestamp unico.
Avevo preso in considerazione alcuni metodi che prevedevano l'uso di una chiamata time.clock () aggiunta a un datetime iniziale, ma apprezzerei una soluzione che funzionasse con precisione tra i thread sulla stessa macchina. Eventuali suggerimenti saranno ricevuti con gratitudine.
Soluzione
È improbabile che tu ottenga un controllo sufficientemente preciso da poter eliminare completamente la possibilità di timestamp duplicati: avrai bisogno di una risoluzione inferiore al tempo necessario per generare un oggetto datetime. Ci sono un paio di altri approcci che potresti adottare per affrontarlo:
-
Affrontalo. Lascia i tuoi timestamp non unici come sono, ma fai affidamento sul fatto che il tipo di Python sia stabile per gestire i problemi di riordino. Ordinando il timestamp prima , poi qualcos'altro manterrà l'ordinamento del timestamp - devi solo fare attenzione a iniziare sempre dall'elenco di timestamp ordinato ogni volta, anziché fare più ordinamenti nello stesso elenco.
-
Aggiungi il tuo valore per rafforzare l'unicità. Per esempio. includere un valore intero incrementale come parte della chiave o aggiungere tale valore solo se i timestamp sono diversi. Eg.
Quanto segue garantirà valori univoci di data / ora:
class TimeStamper(object):
def __init__(self):
self.lock = threading.Lock()
self.prev = None
self.count = 0
def getTimestamp(self):
with self.lock:
ts = str(datetime.now())
if ts == self.prev:
ts +='.%04d' % self.count
self.count += 1
else:
self.prev = ts
self.count = 1
return ts
Per più processi (piuttosto che thread), però diventa un po 'più complicato.
Altri suggerimenti
time.clock () misura solo il tempo di wallclock su Windows. Su altri sistemi, time.clock () misura effettivamente il tempo della CPU. Su questi sistemi time.time () è più adatto al tempo di wallclock e ha una risoluzione alta quanto Python è in grado di gestire - che è alta quanto il sistema operativo può gestire; di solito usando gettimeofday (3) (risoluzione microsecondi) o ftime (3) (risoluzione millisecondi). Altre restrizioni del sistema operativo rendono la risoluzione reale molto più elevata di quella. datetime.datetime.now () utilizza time.time (), quindi time.time () direttamente non sarà migliore.
Per la cronaca, se uso datetime.datetime.now () in un ciclo, vedo una risoluzione di 1/10000 di secondo. Guardando i tuoi dati, hai una risoluzione molto, molto più grossolana di quella. Non sono sicuro che ci sia qualcosa che Python possa fare, anche se potresti essere in grado di convincere il sistema operativo a fare meglio con altri mezzi.
Mi sembra di ricordare che su Windows, time.clock () è in realtà (leggermente) più preciso di time.time (), ma misura wallclock dalla prima chiamata a time.clock (), quindi devi ricordare per "inizializzarlo" per primo.
Grazie a tutti per i vostri contributi: sono stati tutti molto utili. La risposta di Brian sembra la più vicina a ciò che alla fine sono andato (cioè affrontarlo ma usare una sorta di identificatore univoco - vedi sotto) quindi ho accettato la sua risposta. Sono riuscito a consolidare tutti i vari ricevitori di dati in un singolo thread che è dove ora viene eseguito il timestamp utilizzando la mia nuova classe AccurrateTimeStamp . Quello che ho fatto funziona fintanto che il timestamp è la prima cosa da usare l'orologio.
Come prevede S.Lott, senza un sistema operativo in tempo reale, non saranno mai assolutamente perfetti. Volevo davvero solo qualcosa che mi permettesse di vedere in relazione a ciascun blocco di dati in entrata, quando le cose venivano ricevute, quindi quello che ho sotto funzionerà bene.
Grazie ancora a tutti!
import time
class AccurateTimeStamp():
"""
A simple class to provide a very accurate means of time stamping some data
"""
# Do the class-wide initial time stamp to synchronise calls to
# time.clock() to a single time stamp
initialTimeStamp = time.time()+ time.clock()
def __init__(self):
"""
Constructor for the AccurateTimeStamp class.
This makes a stamp based on the current time which should be more
accurate than anything you can get out of time.time().
NOTE: This time stamp will only work if nothing has called clock() in
this instance of the Python interpreter.
"""
# Get the time since the first of call to time.clock()
offset = time.clock()
# Get the current (accurate) time
currentTime = AccurateTimeStamp.initialTimeStamp+offset
# Split the time into whole seconds and the portion after the fraction
self.accurateSeconds = int(currentTime)
self.accuratePastSecond = currentTime - self.accurateSeconds
def GetAccurateTimeStampString(timestamp):
"""
Function to produce a timestamp of the form "13:48:01.87123" representing
the time stamp 'timestamp'
"""
# Get a struct_time representing the number of whole seconds since the
# epoch that we can use to format the time stamp
wholeSecondsInTimeStamp = time.localtime(timestamp.accurateSeconds)
# Convert the whole seconds and whatever fraction of a second comes after
# into a couple of strings
wholeSecondsString = time.strftime("%H:%M:%S", wholeSecondsInTimeStamp)
fractionAfterSecondString = str(int(timestamp.accuratePastSecond*1000000))
# Return our shiny new accurate time stamp
return wholeSecondsString+"."+fractionAfterSecondString
if __name__ == '__main__':
for i in range(0,500):
timestamp = AccurateTimeStamp()
print GetAccurateTimeStampString(timestamp)
" il timestamp deve essere accurato l'uno rispetto all'altro "
Perché il tempo? Perché non un numero progressivo? Se è un client dell'applicazione client-server, la latenza della rete rende i timestamp un po 'casuali.
Stai abbinando qualche fonte di informazione esterna? Dì un registro su un'altra applicazione? Ancora una volta, se c'è una rete, quei tempi non saranno troppo vicini.
Se devi abbinare elementi tra app separate, considera di passare i GUID in modo che entrambe le app registrino il valore GUID. Quindi potresti essere assolutamente sicuro che corrispondano, indipendentemente dalle differenze di tempismo.
Se vuoi che l'ordine relativo sia esattamente corretto, forse è sufficiente che il tuo logger assegni un numero progressivo a ciascun messaggio nell'ordine in cui sono stati ricevuti.
Ecco un thread sull'accuratezza dei tempi di Python:
Python - time.clock () vs. time.time () - accuratezza?
Pochi anni dopo la domanda e la risposta, e questo è stato risolto, almeno per CPython su Windows. Utilizzando lo script seguente sia su Win7 64 bit che su Windows Server 2008 R2, ho ottenuto gli stessi risultati:
-
datetime.now ()
fornisce una risoluzione di 1 ms e un jitter inferiore a 1 ms -
time.clock ()
fornisce una risoluzione migliore di 1us e un jitter molto più piccolo di 1ms
Lo script:
import time
import datetime
t1_0 = time.clock()
t2_0 = datetime.datetime.now()
with open('output.csv', 'w') as f:
for i in xrange(100000):
t1 = time.clock()
t2 = datetime.datetime.now()
td1 = t1-t1_0
td2 = (t2-t2_0).total_seconds()
f.write('%.6f,%.6f\n' % (td1, td2))
I risultati visualizzati:
Volevo ringraziare J. Cage per questo ultimo post.
Per il mio lavoro, "ragionevole" il tempismo degli eventi tra processi e piattaforme è essenziale. Ovviamente ci sono molti posti in cui le cose possono andare storte (deriva dell'orologio, cambio di contesto, ecc.), Tuttavia questa soluzione di temporizzazione accurata, credo, aiuterà a garantire che i timestamp registrati siano sufficientemente accurati per vedere le altre fonti di errore .
Detto questo, ci sono un paio di dettagli di cui mi chiedo che sono spiegati nel When MicroSeconds Matter . Ad esempio, penso che time.clock () finirà per finire. Penso che affinché funzioni per un lungo processo, potresti doverlo gestire.
Se vuoi i timestamp di microsecondo- risoluzione (NON precisione) in Python, in Windows puoi usare il timer QPC di Windows, come dimostrato nella mia risposta qui: Come ottenere i timestamp con risoluzione in millisecondi e microsecondi in Python . Non sono ancora sicuro di come farlo in Linux, quindi se qualcuno lo sa, per favore commenta o rispondi nel link sopra.