2012-03-11 12 views
7

realizo pruebas de rendimiento para algunas aplicaciones de Java. Las aplicaciones producen archivos de registro muy grandes (puede ser de 7-10 GB) durante la prueba. Necesito recortar estos archivos de registro entre fechas y horas específicas. Actualmente, utilizo el script python, que analiza las marcas de tiempo del registro en el objeto python datetime e imprime solo cadenas coincidentes. Pero esta solución es muy lenta. El registro de 5 GB se analiza aproximadamente 25 minutos Obviamente, las entradas en el archivo de registro son secuenciales y no es necesario que lea todo el archivo línea por línea. Pensé en leer el archivo desde el principio y desde el final, hasta que coincidan las condiciones e imprimir archivos entre el número de líneas coincidentes. Pero no sé cómo puedo leer el archivo desde atrás, sin descargarlo en la memoria.recortar archivo de registro grande

Por favor, ¿me puede sugerir alguna solución suitibale para este caso.

aquí es parte de secuencia de comandos Python:

 lfmt = '%Y-%m-%d %H:%M:%S' 
     file = open(filename, 'rU') 
     normal_line = '' 
     for line in file: 
     if line[0] == '[': 
      ltimestamp = datetime.strptime(line[1:20], lfmt) 

      if ltimestamp >= str and ltimestamp <= end: 
      normal_line = 'True' 
     else: 
      normal_line = '' 

     if normal_line: 
     print line, 
+3

Se puede publicar sus archivos de registro? – kev

+2

Haciendo caso omiso de la broma de kev, realmente debería mirar su política de rotación de archivos de registro. Es una mala práctica dejar que todos los archivos de registro se vuelvan tan grandes. – TerryE

+1

La utilidad 'logrotate' es el camino a seguir aquí, existe para evitar este tipo de cosas. – Daenyth

Respuesta

5

Como los datos es secuencial si el inicio y el final de la región de interés están cerca del principio del archivo, entonces la lectura desde el final del archivo (para encontrar el punto final coincidente) sigue siendo una mala solución.

He escrito un código que encontrará rápidamente los puntos de inicio y finalización que requiera, este enfoque se llama binary search y es similar al clásico juego de adivinanza "superior o inferior" para niños.

La secuencia de comandos lee una línea de prueba a medio camino entre lower_bounds y upper_bounds (inicialmente SOF y EOF), y comprueba los criterios de coincidencia. Si la línea buscada es anterior, entonces adivina de nuevo leyendo una línea a medio camino entre lower_bound y la prueba de lectura anterior (si es más alta, se divide entre su conjetura y el límite superior). Así que sigues iterando entre los límites superior e inferior: esto produce la solución "promedio" más rápida posible.

Esto debería ser una solución realmente rápida (log a la base 2 de la cantidad de líneas !!). Por ejemplo, en el peor de los casos posibles (encontrar la línea 999 de entre 1000 líneas), usar la búsqueda binaria tomaría solo 9 lecturas de línea. (De mil millones de líneas tomaría sólo 30 ...)

Supuestos para el código de abajo:

  • Cada línea comienza con la información de tiempo.
  • Los tiempos son únicos. De lo contrario, cuando se encuentre una coincidencia, deberá verificar hacia atrás o hacia adelante para incluir o excluir todas las entradas con el tiempo coincidente, según corresponda (si es necesario).
  • Curiosamente, esta es una función recursiva, por lo que el número de líneas de su archivo está limitado a 2 ** 1000 (afortunadamente, esto permite un archivo bastante grande ...)

Además:

  • Este podría ser adaptado para leer en bloques arbitrarias, en vez de por línea, si se prefiere. Como lo sugirió J.F. Sebastian.
  • En mi respuesta original, sugerí este enfoque pero usando linecache.getline, aunque esto es posible, no es apropiado para archivos grandes ya que lee todo el archivo en la memoria (así es file.seek()), gracias a TerryE y J.F. Sebastian por señalarlo.

de fecha y hora de importación

def match(line): 
    lfmt = '%Y-%m-%d %H:%M:%S' 
    if line[0] == '[': 
     return datetime.datetime.strptime(line[1:20], lfmt) 

def retrieve_test_line(position): 
    file.seek(position,0) 
    file.readline() # avoids reading partial line, which will mess up match attempt 
    new_position = file.tell() # gets start of line position 
    return file.readline(), new_position 

def check_lower_bound(position): 
    file.seek(position,0) 
    new_position = file.tell() # gets start of line position 
    return file.readline(), new_position 

def find_line(target, lower_bound, upper_bound): 
    trial = int((lower_bound + upper_bound) /2) 
    inspection_text, position = retrieve_test_line(trial) 
    if position == upper_bound: 
     text, position = check_lower_bound(lower_bound) 
     if match(text) == target: 
      return position 
     return # no match for target within range 
    matched_position = match(inspection_text) 
    if matched_position == target: 
     return position 
    elif matched_position < target: 
     return find_line(target, position, upper_bound) 
    elif matched_position > target: 
     return find_line(target, lower_bound, position) 
    else: 
     return # no match for target within range 

lfmt = '%Y-%m-%d %H:%M:%S' 
# start_target = # first line you are trying to find: 
start_target = datetime.datetime.strptime("2012-02-01 13:10:00", lfmt) 
# end_target = # last line you are trying to find: 
end_target = datetime.datetime.strptime("2012-02-01 13:39:00", lfmt) 
file = open("log_file.txt","r") 
lower_bound = 0 
file.seek(0,2) # find upper bound 
upper_bound = file.tell() 

sequence_start = find_line(start_target, lower_bound, upper_bound) 

if sequence_start or sequence_start == 0: #allow for starting at zero - corner case 
    sequence_end = find_line(end_target, sequence_start, upper_bound) 
    if not sequence_end: 
     print "start_target match: ", sequence_start 
     print "end match is not present in the current file" 
else: 
    print "start match is not present in the current file" 

if (sequence_start or sequence_start == 0) and sequence_end: 
    print "start_target match: ", sequence_start 
    print "end_target match: ", sequence_end 
    print 
    print start_target, 'target' 
    file.seek(sequence_start,0) 
    print file.readline() 
    print end_target, 'target' 
    file.seek(sequence_end,0) 
    print file.readline() 
+1

fraxel es casi correcto. Usted * no puede * utilizar el caché de línea ya que cualquier enfoque que admita desplazamientos de números de línea precisos en un archivo de longitud de línea variable debe realizar un escaneo completo (vago) internamente. Haga una interpolación lineal leyendo bloques de 1024 bytes y luego escanee cada uno para el patrón \ n + DTS para encontrar la hora actual. Como dice fraxel, esto es O (log N) en el tamaño del archivo. – TerryE

+0

@TerryE ¿Podría explicar un poco su sugerencia de "interpolación lineal" o proporcionar un enlace? – urschrei

+1

Sí, es un método iterativo simple que funciona bien * si * las tasas de registro son uniformes. Si no, utiliza una búsqueda binaria. Wikipedia tiene artículos sobre ambos. Está utilizando el hecho de que el archivo está en DTS ascendente para encontrar los bytes de inicio y final correctos mediante sondeo. esto tomará ~ 66 lecturas de bloques para un registro de 8 Gb, digamos ~ 6 segundos. Por supuesto, deberá copiar desde el byte M a N y/o comprimir sobre la marcha, p. 'head -c -1234567890 somelog.log | cabeza -c 999999 | gzip -c3> someextract.log.gz' usa un factor de compresión modesto si comprimes. Realmente solo estás guardando E/S. – TerryE

1

7 10 GB es una gran cantidad de datos. Si tuviera que analizar ese tipo de datos, haría que las aplicaciones se conectaran a una base de datos o subiría los archivos de registro a una base de datos. Luego hay un montón de análisis que puede hacer de manera eficiente en la base de datos. Si está utilizando una herramienta de registro estándar como Log4J, iniciar sesión en una base de datos debería ser bastante simple. Solo sugiriendo una solución alternativa.

Para más información sobre la base de datos de registro se puede hacer referencia a este post:

A good database log appender for Java?

2

5 log GB se analiza unos 25 minutos

Es ~ 3 MB/s. Incluso el escaneo secuencial de O(n) en Python can do much better (~500MB/s for wc-l.py) es decir, el rendimiento debe estar limitado solo por E/S.

para realizar una búsqueda binaria en un archivo que podría adaptarse FileSearcher que usa los registros fijos de usar líneas en su lugar, utilizando el enfoque similar al tail -n implementation in Python (es O(n) para buscar '\n').

Para evitar O(n) (si el intervalo de fechas selecciona sólo una pequeña parte del registro), puede utilizar una búsqueda aproximada que utiliza grandes trozos fijos y permite que algunos registros se puede perder debido a que se encuentran en un límite de cantidad, por ejemplo, el uso sin modificar FileSearcher con record_size=1MB y una clase personalizada Query:

class Query(object): 

    def __init__(self, query): 
     self.query = query # e.g., '2012-01-01' 

    def __lt__(self, chunk): 
     # assume line starts with a date; find the start of line 
     i = chunk.find('\n') 
     # assert '\n' in chunk and len(chunk) > (len(self.query) + i) 
     # e.g., '2012-01-01' < '2012-03-01' 
     return self.query < chunk[i+1:i+1+len(self.query)] 

Tener en cuenta que el rango de fechas puede abarcar varios trozos que podría modificar FileSearcher.__getitem__ para volver (filepos, chunk) y buscar dos veces (bisect_left(), bisect_right()) para encontrar aproximada filepos_mindate, filepos_maxdate. Después de eso, puede realizar una búsqueda lineal (por ejemplo, usando el enfoque tail -n) alrededor de las posiciones de archivo dadas para encontrar el primer y último registro exacto.

0

Si tiene acceso a un entorno de Windows, puede usar MS LogParser para leer los archivos y recopilar la información que pueda necesitar. Utiliza una sintaxis SQL que hace que usar esta herramienta sea una alegría. También es compatible con una gran cantidad de tipos de entrada.

Como una ventaja adicional, también es compatible con el conmutador iCheckPoint que hace que se cree un archivo de punto de control cuando se trabaja con archivos de registro secuenciales. Para obtener más información, busque en el Parse Ayuda iniciar sesión en "Características avanzadas -> Entrada Analizar de forma incremental"

Ver también: