![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
zlog: Ficheros comprimidos de log de SICOzlog es un formato de ficheros de log binarios (comprimidos) propio de SICO.Características:
Algoritmo:Packing format:
How to unpack segment1data:
How to unpack segment2data:
aaaaaabb bbbbcccc ccdddddd -> 00aaaaaa 00bbbbbb 00cccccc 00dddddd
How to unpack segment3data:
Rationale: Las líneas de los ficheros de log suelen tener dos partes bien diferenciadas: el timestamp de la línea y el contenido de la línea. El algoritmo se aprovecha de que el timestamp usa un número muy restringido de carácteres; específicamente se usan "0123456789/ :" y las cadenas de los días y los meses en inglés ("Mon ", "Tue ", ... , "Sun " por un lado y "Jan ", "Feb ", ... , "Dec " por otro). Lo que hace es codificar esto un un solo nibble (dos para las cadenas), reduciendo de forma efectiva el tamaño a la mitad. Como sobran unos cuantos índices para cadenas, se usan esos índices para, en vez de representar cadenas, decir que se repite n veces adicionales el último carácter codificado. El algoritmo asume que se ha terminado el timestamp de la línea en cuanto encuentra el primer carácter que no puede codificar siguiendo el algoritmo anterior. En cuanto al contenido de la línea, normalmente son carácteres numéricos o alfabéticos. Con un poco de cuidado, estos carácteres se pueden codificar en 6 bits en vez de 8, y no queda un codepoint libre para usarlo como "escape" (que permiten codificar el resto de los carácteres usando más bits). Esto hace que para codificar 4 carácteres usemos sólo 3 bytes (4 caracteres 6 bits = 24 bits; 24 bits son 3 bytes). Los carácteres que se han escogido para codificar como 6 bits en el contenido de la línea son: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" Como cuando se pone un carácter de escape, hay que usar dos huecos de 6 bits para poder especificar el carácter de 8 bits, los 62-8=12-8=4 bits que nos sobran se usan para especificar un contador (el número de veces adicionales que se ha de poner ese carácter; si el contador es 0, es que sólo hay que poner el carácter 1 vez, si es 1, se pone 2 veces, etc.). Como manera de saber cuántos carácteres hay que codificar de esta manera, mientras se va codificando se va apuntando cuál es la longitud óptima (primero es 0 codificados+todos en "raw" -- el "raw" es el segmento 3 de la descripción de arriba), y si la longitud de codificación actual (número de caracteres codificados finales + número de raw sin codificar) es mejor que la que se ha apuntado como óptima hasta el momento, se cambia la óptima para que sea la actual. Por último, los carácteres "raw" son una serie de carácteres al final de la línea que se dejan sin codificar. Normalmente suele ser el \n final (que requiere escape y es más "barato" el ponerlo como raw) y algunos carácteres que saldría más caro poner hacer una tupla incompleta del segmento 2 con respecto a dejarlos como raw. Sobre las cabeceras: La cabecera cumple tres funciones 1. Reconocer "a priori" si esto parece una línea codificada con este algoritmo 2. Poder "saltar líneas" (hacia adelante y hacia atrás) 3. Saber cuántos carácteres hay en cada segmento de codificación Básicamente se necesita
Para saber la longitud de una línea codificada, se usa un número que dice el número de bytes usados en la parte de codificación. Como el peor de los casos es que toda la línea esté en el segmento 3 ("raw"), podemos limitar esto a un simple char forzando que el tamaño de bloque máximo sean 255 bytes. Para obtener redundancia, se repite este número al final del bloque Y para saber el número de caracteres a usar en cada tipo de codificación, se ponen tres números (otra vez de tipo char) que indican el número de carácteres después de codificar que ocupa la línea. Así cumplimos dos objetivos adicionales: 1. Podemos saber cuál era la longitud original de la línea simplemente mirando la cabecera 2. Evitamos las complicaciones que tendríamos si usáramos el número de bytes sin codificar, ya que los dos primeros segmentos necesitarían poder expresar fracciones, y seguramente necesitaríamos más bits para poder expresar el tamaño máximo de ese segmento. Con todo esto, el formato del fichero queda:
Y se cumple lo siguiente: l=seg1size+seg2size+seg3size seg1origsize+seg2origsize+seg3origsize=tamaño de la línea original Y se comprueba que el código a priori es correcto con (suponiendo que la línea está en un buffer llamado inbuf, ocupados al menos inbufsize):
inbufsize>=(1+3+l+1)
inbuf[0]==inbuf[1+3+l]
(((int)inbuf[1])+inbuf[2]+inbuf[3])=255 Así pues se hace bastante "barato" el comprobar que es un bloque válido a priori. Hace posible el buscar el inicio de la línea para empezar a descomprimir de manera eficiente, ignorando la basurilla inicial. Es initeresante ya que permite extractar el final de un fichero para mandarlo por correo, u obtener las líneas áun válidas de un fichero corrupto -- la parte corrupta sólo haría que no se pudiera leer la línea en la que está la corrupción, pero se leerían correctamente tanto las anteriores como las posteriores. Attachs (implementación inicial del zlog):zlog.h zlog.c unzlog.c Makefile unzlog.c zlog.h+ |