LINUX.ORG.RU
ФорумAdmin

Корректный периодический парсинг текстовых файлов

 , ,


0

2

Привет.

Есть необходимость раз в {период времени} пробегать объемный текстовый файл, контент в котором постоянно обновляется.

Интересует та часть файла, которая начинается с {метка} и до конца.

Первым решением стал sed, выделяющий искомую часть контента для последующего использования.

Однако упомянутый прежде объем файла, а также высокая периодичность запуска истошно увеличили нагрузку на CPU.

Какой подход наиболее грамотный в решении подобной задачи, учитывающий то, что размер файла может меняться и предполагающий высокой частоту подобных операций?

Иных инструментов, кроме стандартных Linux нет.

Первая мысль — использовать tail для сокращения объема данных. Этот метод использовался ранее при решении подобных проблем. Однако прирост данных в файле неравномерен, поэтому заранее знать, сколько строк отсекать невозможно.

Перемещено hobbit из general



Последнее исправление: biophydener (всего исправлений: 5)

Файл только дописывается в конец новыми данными?

  1. Сохраняй между запусками обработчика строку, на которой закончил обработку. Дальше с tail обрабатывай начиная с неё.
  2. Запускай обработку только если файл изменился (проверяй размер или запускайся по inotify).
  3. Читай файл с конца до метки (tac, например).
anlar
()

А потом свой велосипед - пародие на ELK ?

sanyo1234
()

попробуй начать с программы на C или Go

kaldeon
()
Последнее исправление: kaldeon (всего исправлений: 1)

Вариантов нормальных тут два: а) хранить где-то позицию последней обработанной строки и б) если есть контроль над источником данных, поменять схему, пусть та программа пишет не в файл, а в stdout (может быть и туда, и туда, с помощью tee), а ваш скрипт уже будет обрабатывать строки по мере поступления.

ugoday ★★★★★
()
Ответ на: комментарий от anlar

Файл только дописывается в конец новыми данными.

Он - лог.

Я тут помыслил: а умеет sed искать не первую строку с начала файла, выводя остаток до конца, а искать первую строку при поиске с конца файла?

То есть при

..data..
..data..
<90% of content>
..
..data..
regex_1
..data..
..data..
regex_1
..data..
regex_1
..data..

шерстить не весь файл с начала, а поднимаясь последовательно с конца до первого вхождения искомого паттерна в файл, выводя затем результат в виде

regex_1
..data..
..data..
regex_1
..data..
regex_1
..data..
biophydener
() автор топика
Последнее исправление: biophydener (всего исправлений: 2)
Ответ на: комментарий от biophydener

В первом же ответе предолжено использовать tail.

С заданной строки почти наверняка может читать awk. Хотя давно не пользовался ни тем, ни другим, задачу явно проще решить написав на полноценном ЯП.

Chiffchaff
()

конвейер из head | tail

не?

хотя таки проще написать что-то на питошке, один фиг позицию лучше хранить в каком-то конфиге, дабы избежать ошибок при падении

Morin ★★★★★
()
Последнее исправление: Morin (всего исправлений: 1)

Можно не повторять парсинг, а следить за приростом файла через классический tail -f. Текст после строки с меткой легко отрежет awk:

tail -f /var/yourlog | awk '/{метка}/,0' | обработка

tail -n+1 -f /var/yourlog выведет весь файл и продолжит следить.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 2)
Ответ на: комментарий от biophydener

Он - лог.

Ну и натравите на него какой-нибудь fluent-bit. Он для того и предназначен.

ugoday ★★★★★
()

Храни размер файла, делаешь ему сплит, первую часть выкидываешь, остальные смотришь на наличие метки.

ya-betmen ★★★★★
()
Ответ на: комментарий от x905

Новые события записываются в лог файл в дополнение к предыдущим.

В логе хранятся события за последние сутки.

К началу следующего дня лог архивируется, затем открывается новый файл.

biophydener
() автор топика

Короче, пытаюсь распутаться.

Мое итоговое предположение, как я могу реализовать задуманное.

  1. Определяю номер строки, в которой в лог-файле впервые отмечается искомая временная метка
grep -n '{timestamp}' $log | head -n1 | awk -F ':' '{print $1}'
  1. Использую полученный номер строки, чтобы вывести все с начала этой строки до конца файла
tail -n+${number} $log
  1. В полученном выводе отслеживаю необходимые мне показатели.

  2. В свою очередь увеличиваю временной интервал, в течение которого статистика собирается: запуск ежеминутно меняю на запуск раз в 5 минут.

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

Например, интересует разница между sed, который сначала находит pattern, а затем печатает все до конца файла и grep, который как минимум большую часть файла исследует в поиске находящейся почти в самом конце строки, а затем просто показывает ее номер.

tail, который с определенной строки до конца файла контент печатает.

В чем их принципиальная разница, почему это работает значительно быстрее и менее ресурсоемко, чем просто sed? И работает ли оно вообще быстрее?..

biophydener
() автор топика
Последнее исправление: biophydener (всего исправлений: 2)
Ответ на: комментарий от ugoday

И это заняло не меньше (или не существенно меньше) ресурсов, чем sed, печатающий файл с первого включения паттерна и до конца?

biophydener
() автор топика
Ответ на: комментарий от biophydener

Ответ зависит от того, где именно находится искомая метка. Но судя по всему, она должна быть где-то в конце. Поэтому ответ — примерно одинаково.

ugoday ★★★★★
()

Тогда из предложенного @ya-betmen: раз в интервал считать прирост файла в байтах, с помощью tail -c <bytes> выводить добавленное, а затем парсить?

biophydener
() автор топика
Ответ на: комментарий от biophydener

Символ юникода занимает от одного до четырёх байт. Так что совет может сработать, если вы точно уверены в своих данных.

ugoday ★★★★★
()
Ответ на: комментарий от biophydener

А что если после первой обработки файла сбросить количество строк в переменную или файл и следующую обработку тупо начинать со следующей строки?

papin-aziat ★★★★★
()
Ответ на: комментарий от biophydener

Не надо в интервал, надо в момент начала работы скрипта. Ну и если файл ротируется, то время создания бы неплохо проверять, что ты в тот же файл смотришь.

Если файл текстовый без безумно длинных строк то можно по строкам а не по байтам, да.

ya-betmen ★★★★★
()
Ответ на: комментарий от legolegs

Полагаю, я запутал не только себя, но и всех здесь дискутирующих. Однако. Приношу извинения за столь абстрактную риторику.

Под меткой из исходного вопроса понимался timestamp. В части лога с первого включения timestamp — в поле field — может быть не предопределенное, а уникальное (при этом не случайное), значение (value). И моя задача — определение количества вхождений для каждого такого уникального значения за последний интервал N времени. Такое количество я в итоге считаю посредством uniq.

biophydener
() автор топика
Ответ на: комментарий от biophydener
tail -f log.file | indexer /data/dir

Где indexer читает входной поток по мере поступления и складирует данные в формате /data/dir/$value/$ts.log. Содержимое $ts.log — строчки с этим значением, в рамках заданного timestamp’a.

Тогда, если нас всегда интересуют известные интервалы (например пятиминутные), подбором значений задача сводится к wc нужного файла. Если нет — просто сильно сокращается необходимость читать ненужные данные.

P.S. Главное вовремя остановиться!

ugoday ★★★★★
()
Ответ на: комментарий от TheNewDragon

Вообще-то задачка скорее для perl. Можно сбацать решение, которое будет давать статистику на заданную глубину хоть раз в секунду и при этом читать файл только один раз в жизни.

legolegs ★★★★★
()
Ответ на: комментарий от TheNewDragon

С чего бы это? Задача упирается в: а) скорость последовательного чтения с диска; б) алгоритм, избегающий чтения лишних данных.

ЯП вообще ни при чём.

P.S. Задачу можно решить вообще без всякого программирования:

  1. Поднимаем Postrgres.

  2. Натравливаем на файл fluent-bit, который будет отсылать данные в Postgres.

  3. Вытаскиваем нужные значения sql-запросом.

ugoday ★★★★★
()
Последнее исправление: ugoday (всего исправлений: 1)

Ты можешь обернуть свой сбор статистки в такой скрипт:

let OFFSET=0
while true; do
  NEXTOFFSET=$(tail -c+$((OFFSET+1)) /var/yourlog | grep -m1 -Fb '{метка}')
  NEXTOFFSET=${NEXTOFFSET%%:*}
  let OFFSET+=NEXTOFFSET
  
  tail -c+$((OFFSET+1)) /var/yourlog | твой фильтр

  sleep {период времени};
done

Если лог реально огромный (сотни мегабайт ненужных данных в начале), то прирост может быть значительным. tail -c+номер_байта_с_1 работает за константное время.

legolegs ★★★★★
()
Последнее исправление: legolegs (всего исправлений: 2)
Ответ на: комментарий от legolegs

Попытка углубиться на 1 минуту назад от текущего времени в средний лог размером ~0.5 Гб и ~1.5 млн строк (эти данные сформировались ~за треть суток) занимает ~90 секунд.

Чувствую себя DevOps’еком, который взял бензопилу, спилил сосну, и старательно вырезает этой же бензопилой из ствола зубочистку. Разница лишь в том, что место тупняка в исходном контексте осознать усердно не удается :(

biophydener
() автор топика
Последнее исправление: biophydener (всего исправлений: 1)
Ответ на: комментарий от biophydener

У тебя это всё на расбери пи крутится?

Я когда скрипт выше набрасывал, то сделал тестовый файл в 500мб. Так его греп за секунду проходил. И это притом, что у меня вместо компьютера core2duo с НЖМД.

legolegs ★★★★★
()

Я делал выборки на 50 гб текстовых файлов. Это по времени занимало ровно скорость чтения hdd, а нагрузки на процессор существенной не было. То есть всё таки прдлагаю подумать «почему у вас нагрузка на cpu»? Попробуйте поискать в архивных логах, которые не меняются. Про написание собственного софта. Ну в базе данных логи будут утилизировать cpu ещё больше. Какая то самописная программа будет работать хуже, чем отлаженные gnu утилиты. Писать на С, при этом лог переводить в бинарный вид, возможно разделяя на часть поисковой колонки и колонки с данными. Вот так победил бы. Но ещё раз, убедитесь, что на том же компьютере не заблокированные файлы также утилизируют проц.

azsx
()
Ответ на: комментарий от azsx

Кажется, все оказалось значительно проще — на машине 1 ГБ рамы, и это — причина ее утопления.

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

Разница во времени выполнения на схожих данных на хосте рядом — примерно два порядка.

Спасибо всем за участие в дискуссии. В особенности — @legolegs.

biophydener
() автор топика
Последнее исправление: biophydener (всего исправлений: 2)

Первая мысль — использовать tail для сокращения объема данных. Этот метод использовался ранее при решении подобных проблем. Однако прирост данных в файле неравномерен, поэтому заранее знать, сколько строк отсекать невозможно.

Насколько неравномерен? Есть ли какой-то адекватный максимум, больше которого не запишется? Можно взять tail вот так с запасом, дабы не читать весь объёмный файл, а дальше уже по старинке sed, соответственно.

upd: да, а дальше tail -f, правильно выше подсказали.

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)

Перед каждым парсингом файла делать logrotate, так у тебя всегда будет небольшой файл и в нём только новые еще ни разу не обработанные данные. {метка} от предыдущего просмотра автоматически становится ненужной

futurama ★★★★★
()
Последнее исправление: futurama (всего исправлений: 2)
Ответ на: комментарий от legolegs

Я когда скрипт выше набрасывал, то сделал тестовый файл в 500мб. Так его греп за секунду проходил.

Так как вы его делали, возможно он в кэше и остался.

time grep aaa /var/log/m*  >/dev/null
real	0m1.306s
Общий объем /var/log/m* 497Mb

anc ★★★★★
()

я в таких случаях всегда делаю tail -F /file | ./script.pl или -f

sergej ★★★★★
()
Ответ на: комментарий от biophydener

Кажется, все оказалось значительно проще — на машине 1 ГБ рамы, и это — причина ее утопления.
Даже не подумал

С этого начинать надо. Я реально не нахожу объяснения этому феномену, народ может долго и упорно считать тики cpu, iops-ы носителей, при этом в упор не замечая что мозги давно закончились.

anc ★★★★★
()
Ответ на: комментарий от legolegs

Да, закешировался.

Это основное!

Но даже еслиб нет, 90 секунд для 500 мб как у ТС это долго.

Да, долго. Но замечу, что ТС уже выше обнаружил что мозгов мало.

anc ★★★★★
()
Ответ на: комментарий от anc

Лично мне претит решать алгоритмическую проблему аппаратным образом. Для задачи ТС не нужно много памяти.

legolegs ★★★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.