Horodatage précis dans la journalisation Python
Question
Je construis depuis peu une application de consignation des erreurs et je cherchais un moyen d’horodatage précis des données entrantes. Lorsque je dis avec précision, je veux dire que chaque horodatage doit être précis l’un par rapport à l’autre (nul besoin de synchroniser une horloge atomique ni quoi que ce soit de ce genre).
J'utilise datetime.now () comme premier coup de poignard, mais ce n'est pas parfait:
>>> 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.
Les changements d'horloge pour la première seconde d'échantillons ressemblent à ceci:
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
Il semble donc que les données de la minuterie ne sont mises à jour que toutes les 15 à 32 ms sur ma machine. Le problème survient lorsque nous analysons les données, car le tri par autre chose que l'horodatage, puis à nouveau par horodatage peut laisser les données dans le mauvais ordre (chronologiquement). Il serait bien que les horodatages soient précis au point que tout appel au générateur d’horodatage donne un horodatage unique.
J'avais envisagé certaines méthodes impliquant l'utilisation d'un appel time.clock () ajouté à une date / heure de début, mais j'apprécierais une solution qui fonctionnerait avec précision sur plusieurs threads sur la même machine. Toute suggestion serait très chaleureusement reçue.
La solution
Il est peu probable que vous obteniez un contrôle suffisamment fin pour pouvoir éliminer complètement la possibilité des horodatages en double - il vous faudrait une résolution inférieure au temps nécessaire pour générer un objet datetime. Il existe deux autres solutions que vous pourriez adopter pour y faire face:
-
Traitez-le. Laissez vos horodatages non-uniques tels qu'ils sont, mais reposez-vous sur le caractère stable de python pour traiter les problèmes de réorganisation. Triez d'abord d'abord sur l'horodatage, puis quelque chose d'autre conservera l'ordre de l'horodatage - vous devez simplement faire attention à toujours démarrer à partir de la liste ordonnée d'horodatage à chaque fois, plutôt que de faire plusieurs tris sur la même liste.
-
Ajoutez votre propre valeur pour renforcer l'unicité. Par exemple. incluez une valeur entière incrémentante dans la clé ou ajoutez-la uniquement si les horodatages sont différents. Par exemple,
Les éléments suivants garantissent des valeurs d’horodatage uniques:
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
Pour plusieurs processus (plutôt que des threads), cela devient un peu plus compliqué.
Autres conseils
time.clock () ne mesure que l'heure de l'horloge murale sous Windows. Sur d'autres systèmes, time.clock () mesure en réalité le temps CPU. Sur ces systèmes, time.time () est plus approprié pour l'heure wallclock et sa résolution est aussi élevée que celle que Python peut gérer - ce qui est aussi élevé que le système d'exploitation ne peut la gérer. utilisez habituellement gettimeofday (3) (résolution en microsecondes) ou ftime (3) (résolution en millisecondes.) D'autres restrictions du système d'exploitation rendent la résolution réelle beaucoup plus élevée que cela. datetime.datetime.now () utilise time.time (), donc time.time () ne sera pas meilleur directement.
Pour mémoire, si j'utilise datetime.datetime.now () dans une boucle, je vois une résolution d'environ 1/10000 seconde. En regardant vos données, vous avez une résolution beaucoup, beaucoup plus grossière que cela. Je ne sais pas si Python en tant que tel peut faire quoi que ce soit, même si vous pouvez convaincre le système d'exploitation de faire mieux par d'autres moyens.
Il me semble que, sous Windows, time.clock () est en réalité (légèrement) plus précis que time.time (), mais qu'il mesure wallclock depuis le premier appel à time.clock (), vous devez donc vous en souvenir. pour "l'initialiser" en premier.
Merci à tous pour vos contributions - elles ont toutes été très utiles. La réponse de Brian semble se rapprocher de ce que j’ai finalement choisi (c’est-à-dire l’utiliser mais utiliser une sorte d’identificateur unique - voir ci-dessous) et j’ai donc accepté sa réponse. J'ai réussi à regrouper tous les récepteurs de données dans un seul thread. C'est ici que l'horodatage est maintenant effectué à l'aide de ma nouvelle classe AccurrateTimeStamp . Ce que j'ai fait fonctionne tant que l'horodatage est la première chose à utiliser l'horloge.
Comme le stipule S.Lott, sans système d’exploitation temps réel, ils ne seront jamais absolument parfaits. Je ne voulais vraiment que quelque chose qui me laisse voir par rapport à chaque bloc de données entrant, lorsque les éléments étaient reçus, donc ce que j'ai ci-dessous fonctionnera bien.
Merci encore à tous!
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)
"l'horodatage doit être précis l'un par rapport à l'autre"
Pourquoi le temps? Pourquoi pas un numéro de séquence? S'il s'agit d'un client d'application serveur-serveur, la latence du réseau rend les horodatages aléatoires.
Faites-vous correspondre une source d’information externe? Dites un journal sur une autre application? Encore une fois, s'il y a un réseau, ces temps ne seront pas trop proches.
Si vous devez faire correspondre des éléments entre des applications distinctes, envisagez de passer des GUID afin que les deux applications enregistrent la valeur du GUID. Ensuite, vous pouvez être absolument sûr qu'ils correspondent, quelles que soient les différences de timing.
Si vous souhaitez que l'ordre relatif soit exactement correct, il suffit peut-être que votre enregistreur attribue un numéro de séquence à chaque message dans l'ordre de réception.
Voici un fil de discussion sur la précision de la synchronisation Python:
Python-time.clock () contre time.time (): exactitude?
Il y a quelques années, la question a été posée et une réponse a été apportée, du moins pour CPython sous Windows. En utilisant le script ci-dessous sous Win7 64 bits et Windows Server 2008 R2, j'ai obtenu les mêmes résultats:
-
datetime.now ()
donne une résolution de 1 ms et une gigue inférieure à 1 ms -
time.clock ()
donne une résolution meilleure que 1us et une gigue beaucoup plus petite que 1ms
Le 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))
Les résultats visualisés:
Je voulais remercier J. Cage pour ce dernier post.
Pour mon travail, "raisonnable" La synchronisation des événements entre processus et plates-formes est essentielle. Il y a évidemment beaucoup d'endroits où les choses peuvent aller de travers (dérive de l'horloge, changement de contexte, etc.), cependant, cette solution de minutage précis contribuera, je pense, à garantir que les horodatages enregistrés sont suffisamment précis pour identifier les autres sources d'erreur. .
Cela dit, je m'interroge sur quelques détails qui sont expliqués dans le titre Lorsque les micro-secondes sont importantes . Par exemple, je pense que time.clock () finira par s’emballer. Je pense que pour que cela fonctionne pendant un long processus, vous devrez peut-être gérer cela.
Si vous souhaitez des horodatages en microsecondes résolution (PAS de précision) en Python, dans Windows, , vous pouvez utiliser le temporisateur QPC de Windows, comme indiqué dans ma réponse ici: . Je ne sais pas encore comment faire cela sous Linux. Si quelqu'un le sait, merci de commenter ou de répondre via le lien ci-dessus.