после write->read

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

после write->read

Anton Maksimenkov
Hi.

 После всего обсуждения и протрезвления от глюков, которые я написал во
время него, родился и работает код. Этим кодом я и решил поделиться с
народом - кому-то пофиг, кому-то нафиг, а кому-то вдруг пригодиться.
 Скажу только - алгоритм неоптимальный, можно продумать натурально
кольцевой буфер и его обработку, у меня времени нету и мне пока
хватает и так. И вообще - в алгоритме могут быть глюки (пропуски) в
плане обработки "гигантских строк", перед использованием желательно
протестировать! И вообще мне за него стыдно.
 Основное - показать примерный код чтения увеличивающегося файла.


 Итак, задача. Некто пишет в файл лог в формате:
..
здесь_идёт_строчка_лога\n
здесь_идёт_строчка_лога\n
здесь_идёт_строчка_лога\n
..
то есть пишет строки, оканчивающиеся '\n'.
 Нам нужно читать лог и обрабатывать строки неким образом. При этом мы
можем "отставать" от писателя (тогда просто читаем и работаем), можем
"опережать" писателя (тогда читаем последннее, обрабатываем и замираем
в ожидании появления новых данных).

 Алгоритм. Читаем строку в буфер (чем больше буфер, тем реже read(),
но тем больше нагрузка на ядро, потому как данные надо передавать).
Причём читаем символов на 1 меньше длины буфера и последний байт после
прочитанного заполняем '\0', заканчивая последнюю строку. После чего
последовательно режем буфер по символам '\n' (заменяя их на '\0') на
отдельные строки и обрабатываем их.
 Если в конце осталась часть - то есть строка после последнего '\n' и
до последнего прочитанного байта (после которого поставили '\0'), и в
которой соответственно нету '\n', тогда потом перенесём её в начало
буфера. И сдвинем указатель "чтения" от начала буфера сразу за неё -
следующее чтение оставшейся строки и далее пойдёт сразу после уже
прочитанной в прошлый раз части. После чтения в буфере будет полная
строка (ну и остальные, что прочитано). То есть алгоритм нарезки и
обработки не надо изменять.
 Здесь НЕОПТИМАЛЬНОСТЬ - приходится сдвигать последнюю часть буфера в
начало, и это лишняя работа. Кому понадобится лучше - ищите алгоритмы.
 Предусмотрен вариант "гигантской строки" - например когда злостные
хрюкеры стряпают в запросе Апачу гигантский реферер и строка лога
получается о-о-о-о-очень длинной. Такие строки пропускаются и НЕ
обрабатываются. В моём случае их потеря во-первых неважна, а во-вторых
в общей статистике они даже вредны, ибо нереальные и искажают её.

 Код. Комментариями обнесены некоторые отладочные моменты.
Поставляется "как есть" под лицензией BSD :-)
 
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>

/* Не надо сувать sys/* в самый верх, как это написано, например, на
http://www.openbsd.ru/prog/examples/kevent/kevent.c
ибо не скомпилится :-))
*/
#include <sys/event.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <unistd.h>

#define LINEBUFSIZE     512 /* Желательно поставить побольше, типа 131072, чтобы поменьше read(), побольше дела */

int main(int, char * const *);
__dead static void usage(void);

extern int errno;

int
main (int argc, char * const *argv)
{
        char linebuf[LINEBUFSIZE];
        char *curpos, *strpos, *p;
        int ch, inf, nr;
        int f_hugeline; /* Флаг "гигантская строка". Такая строка НЕ обрабатывается (пропускается вплоть до '\n') */
        struct kevent *kev;
        int kq, nev;

        while ((ch = getopt(argc, argv, "f:")) != -1) {
                switch(ch) {
                case 'f':
                        if ((inf = open(optarg, O_RDONLY, 0)) == -1)
                                err(errno, "open() on %s", optarg);
                        break;
                case '?':
                default:
                        usage();
                        /* NOTREACHED */
                }
        }
        argc -= optind;
        argv += optind;
        if (argc != 0)
                usage();

        if ((kq = kqueue()) < 0)
                err(EX_UNAVAILABLE, "kqueue");
        if ((kev = malloc(sizeof(struct kevent))) == NULL)
                err(EX_UNAVAILABLE, "malloc");
        EV_SET(&kev[0], inf, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE | NOTE_EXTEND, NULL, NULL);

        /* Входим в рабочий режим */
        f_hugeline = 0;
        curpos = linebuf;
        for(;;) {
                nr = 0;
                while (nr == 0) {
                        if ((nr = read(inf, curpos, LINEBUFSIZE - 1 - (curpos - linebuf))) == -1)
                                err(errno, "read()");
                        if (nr == 0) {
                                /*printf("nr = %d, start kevent here...\n", nr);*/

                                if ((nev = kevent(kq, kev, 1, kev, 1, NULL)) < 0)
                                        err(EX_UNAVAILABLE, "kevent");
                                /*printf("kevent() returned %d: ", nev);
                                if (kev[0].fflags & NOTE_WRITE)
                                        printf(" WRITE");
                                if (kev[0].fflags & NOTE_EXTEND)
                                        printf(" EXTEND");
                                printf("\n");*/
                        }
                }
                *(curpos + nr) = '\0';
                /*printf("after read:\t[%s]\n----------\n", linebuf);*/

                strpos = linebuf;
                if ((p = strchr(strpos, '\n')) == NULL) {
                        f_hugeline = 1;
                        curpos = linebuf;
                        /*printf("it IS huge line - will NOT processed\n");*/
                } else {
                        if (f_hugeline == 1) {
                                *p = '\0';
                                strpos = p + 1;
                                f_hugeline = 0;
                                /*printf("it WAS huge line - part of huge line will NOT processed\n");*/
                        }
                        while ((p = strchr(strpos, '\n')) != NULL) {
                                *p = '\0';
                                if (*strpos != '\0') {
                                 /*printf("process:\t[%s]\n----------\n", strpos);*/
                                 /********************
                                 *
                                 * ЗДЕСЬ ВЫПОЛНЯТЬ ОБРАБОТКУ: func_process_string(strpos)
                                 *
                                 * strpos указывает на выделенную '\0'-terminated строку
                                 *
                                 ********************/
                                }
                                strpos = p + 1;
                                /*printf("strpos:\t\t[%s]\n----------\n", strpos);*/
                        }
                        if (strpos != curpos + nr) {
                                /*printf("part of string uncompleted - will be completed after read() and then processed\n");*/
                                /*printf("linebuf: %lu\tcurpos: %lu\tstrpos: %lu\tdelta: %lu\tnr: %d\n", (unsigned long int)linebuf, (unsigned long int)curpos, (unsigned long int)strpos, (unsigned long int)(strpos - curpos), nr);*/
                                bcopy(strpos, linebuf, nr - (strpos - curpos));
                                curpos = linebuf + (nr - (strpos - curpos));
                        } else {
                                curpos = linebuf;
                                /*printf("entire string was processed\n");*/
                        }
                }
        }

        close(inf);
        printf("By...\n");
        return (EX_OK);
}

__dead static void
usage(void)
{
        extern char *__progname;

        (void)fprintf(stderr, "usage: %s -f file\n", __progname);
        exit(EX_USAGE);
}


--
engineer


Reply | Threaded
Open this post in threaded view
|

Re: после write->read

Oleg Safiullin
>                                 if ((nev = kevent(kq, kev, 1, kev, 1, NULL)) < 0)
>                                         err(EX_UNAVAILABLE, "kevent");

Первая единица нужна только первый раз. Дальше 0. Это список изменений событий.
Поскольку здесь постоянное - его не нужно каждый раз менять - оно само будет держаться
пока файл не закроешь.


Reply | Threaded
Open this post in threaded view
|

Re: после write->read

Alexander Yurchenko
In reply to this post by Anton Maksimenkov
On Mon, Dec 05, 2005 at 12:03:41AM +0500, [hidden email] wrote:
> /* Не надо сувать sys/* в самый верх, как это написано, например, на
> http://www.openbsd.ru/prog/examples/kevent/kevent.c
> ибо не скомпилится :-))

Еще как скомпилится. Просто sys/types.h должен идти самым первым.

> extern int errno;

Так делать нельзя, надо включать errno.h и пользоваться определением
errno оттуда.

> --
> engineer
>

--
   Alexander Yurchenko


Reply | Threaded
Open this post in threaded view
|

Re: после write->read

Oleg Safiullin
> On Mon, Dec 05, 2005 at 12:03:41AM +0500, [hidden email] wrote:
>> /* Не надо сувать sys/* в самый верх, как это написано, например, на
>> http://www.openbsd.ru/prog/examples/kevent/kevent.c
>> ибо не скомпилится :-))
>
> Еще как скомпилится. Просто sys/types.h должен идти самым первым.

Как собственно в приведенном выше примере и сделано, отчего он и компилится
без проблем :)