обрезать большой файл журнала - PullRequest
7 голосов
/ 11 марта 2012

Я выполняю тесты производительности для нескольких приложений Java. Во время теста приложения создают очень большие файлы журналов (это может быть 7-10 ГБ). Мне нужно обрезать эти файлы журнала между конкретными датами и временем. В настоящее время я использую скрипт Python, который анализирует временные метки журнала в объекте Python datetime и печатает только совпадающие строки. Но это решение очень медленное. 5 Гб лог разбирается около 25 минут Очевидно, записи в лог-файл последовательно, и мне не нужно читать все файлы построчно. Я думал о чтении файла с начала и с конца, до тех пор, пока не будет выполнено условие, и печатаю файлы между совпадающим числом строк. Но я не знаю, как я могу прочитать файл назад, не загружая его в память.

Подскажите, пожалуйста, какое-нибудь подходящее решение для этого случая.

Вот часть скрипта 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,

Ответы [ 4 ]

5 голосов
/ 11 марта 2012

Поскольку данные являются последовательными, если начало и конец интересующей области находятся рядом с началом файла, то чтение с конца файла (чтобы найти подходящую конечную точку) все еще является плохим решением!

Я написал некоторый код, который будет быстро находить начальную и конечную точки, как вам требуется, этот подход называется бинарный поиск и похож на классическую детскую игру в догадки "выше или ниже"!

Скрипт читает пробную строку посередине между lower_bounds и upper_bounds (первоначально SOF и EOF) и проверяет критерии соответствия.Если искомая строка более ранняя, то она снова угадывает, читая строку на полпути между lower_bound и предыдущим испытанием чтения (если оно выше, то оно разделяется между его предположением и верхней границей).Таким образом, вы продолжаете перебирать верхнюю и нижнюю границы - это дает самое быстрое из возможных решений «в среднем».

Это должно быть очень быстрое решение (войдите в основание 2 числа строк !!).Например, в наихудшем из возможных случаев (поиск строки 999 из 1000 строк), использование бинарного поиска займет всего 9 строк!(из миллиарда строк потребуется всего 30 ...)

Допущения для приведенного ниже кода:

  • Каждая строка начинается с информации о времени.
  • Времена уникальны - если нет, то при обнаружении совпадения вам придется проверять вперед или назад, чтобы включить или исключить все записи с совпадающим временем, в зависимости от ситуации (если требуется).
  • Забавно, но это рекурсивная функция, поэтому количество строк в вашем файле ограничено 2 ** 1000 (к счастью, это позволяет довольно большой файл ...)

Далее:

  • Это может быть адаптировано для чтения в произвольных блоках, а не в виде строки, если это предпочтительно.Как предложил Дж. Ф. Себастьян.
  • В своем первоначальном ответе я предложил этот подход, но с использованием linecache.getline , хотя это возможно, его неуместно для больших файлов, так как он читает весь файл в память (таким образом,file.seek() лучше), спасибо TerryE и JF Sebastian за указание на это.

import datetime

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()
2 голосов
/ 11 марта 2012

5 ГБ журнал анализируется около 25 минут

Это ~ 3 МБ / с. Даже последовательное сканирование O(n) в Python может работать намного лучше (~ 500 МБ / с для wc-l.py) , т.е. производительность должна быть ограничена только вводом / выводом.

Чтобы выполнить бинарный поиск по файлу, вы можете адаптировать FileSearcher , который использует фиксированные записи для использования строк вместо этого, используя подход, аналогичный tail -n реализация в Python (это O(n) для поиска '\n').

Чтобы избежать O(n) (если диапазон дат выбирает только небольшую часть журнала), вы можете использовать приблизительный поиск, который использует большие фиксированные порции и позволяет пропустить некоторые записи, поскольку они лежат на границе порций, например: используйте неизмененный FileSearcher с record_size=1MB и пользовательский 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)]

Чтобы принять во внимание, что диапазон дат может охватывать несколько фрагментов, вы можете изменить FileSearcher.__getitem__, чтобы вернуть (filepos, chunk), и дважды выполнить поиск (bisect_left(), bisect_right()), чтобы найти приблизительные filepos_mindate, filepos_maxdate. После этого вы можете выполнить линейный поиск (например, используя tail -n подход) по заданным позициям файла, чтобы найти точные первую и последнюю записи журнала.

1 голос
/ 11 марта 2012

7–10 ГБ - большой объем данных. Если бы мне пришлось анализировать данные такого рода, я бы либо делал журнал приложений в базе данных, либо загружал файлы журнала в базу данных. Затем есть множество анализов, которые вы можете эффективно выполнять в базе данных. Если вы используете стандартный инструмент ведения журналов, такой как Log4J, регистрация в базе данных должна быть довольно простой. Просто предлагаю альтернативное решение.

Подробнее о регистрации в базе данных вы можете прочитать в этом посте:

Хороший апплет журнала базы данных для Java?

0 голосов
/ 18 октября 2012

Если у вас есть доступ к среде Windows, вы можете использовать MS LogParser для чтения файлов и сбора любой информации, которая может вам понадобиться. Он использует синтаксис SQL, что делает использование этого инструмента радостью. Он также поддерживает большое количество типов ввода.

В качестве дополнительного бонуса он также поддерживает переключатель iCheckPoint, который вызывает создание файла контрольных точек при работе с последовательными файлами журнала. Для получения дополнительной информации см. Справку по анализу журнала в разделе «Расширенные функции -> Анализ входных данных постепенно»

Смотри также:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...