Esta es una solución que he encontrado, según la entrada y salida que nos has mandado.
Using perl Syntax Highlighting
- #!/usr/bin/perl
- #
- # Saca estadísticas de utilización de recursos a partir del registro de actividad
- # en un archivo CSV.
- #
- # El objetivo es leer los datos resultados de las sondas, y emitir resúmenes por
- # hora de las magnitudes medidas, con sus valores mínimo, máximo y medio, en esa hora.
- #
- # La entrada consiste en un archivo que se pasa como primer argumento, que consiste
- # en una serie de registros, uno por línea, en que cada registro contiene los
- # siguientes campos, separados por ';':
- # * fecha y hora de la medición, en formato 'd/mm/aa h:mm'
- # * uno o más de los datos de actividad de un servidor, consistentes en:
- # + nombre del servidor, seguido de cada uno de los siguientes valores
- # - CPU % libre % CPU Libre
- # - CPU % libre Round Trip Time
- # - CPU % libre Status
- # - SWAP % libre % SWAP Libre
- # - SWAP % libre Round Trip Time
- # - SWAP % libre Status
- # - RAM % libre % RAM Libre
- # - RAM % libre Round Trip Time
- # - RAM % libre Status
- # (no necesariamente en este orden. En caso de no existir medición aparece el
- # texto "No hay datos").
- #
- # Ejemplo:
- # 9/03/13 11:37;96;4 sec;0;93;0 sec;0;64;0 sec;0;94;4,02 sec;0;61;0 sec;0;92;0 sec;0
- # 9/03/13 11:42;98;4,02 sec;0;93;0 sec;0;64;0 sec;0;93;4 sec;0;61;0 sec;0;92;0 sec;0
- # 9/03/13 11:47;94;4 sec;0;93;0 sec;0;64;0 sec;0;93;4 sec;0;61;0 sec;0;92;0 sec;0
- # 9/03/13 11:52;94;4 sec;0;93;0 sec;0;64;0 sec;0;95;4 sec;0;61;0 sec;0;92;0 sec;0
- # 9/03/13 11:57;97;4,02 sec;0;93;0 sec;0;64;0 sec;0;94;4,02 sec;0;61;0 sec;0;92;0 sec;0
- #
- # La salida es hacia la salida estándar, consistente en una serie de registros,
- # uno por línea, con los siguientes campos separados por ';':
- # * lapso de tiempo referido a una hora entera, en formato 'dd/mm/aa hh:00'
- # * un valor, redondeado a entero, calculado según indica la etiqueta que sigue
- # * nombre del servidor al que se refiere el resultado anterior
- # * etiqueta de texto al que se refiere el valor anterior, en el formato:
- # '<magnitud>_%_libre_<medida>', siendo el valor de <magnitud> uno de
- # + CPU
- # + SWAP
- # + RAM
- # y siendo <medida>, uno de
- # + minimo
- # + maximo
- # + medio
- #
- # Los registros salen en el siguiente orden:
- # 1 - Por la magnitud
- # 2 - Por nombre del servidor
- # 3 - Por tiempo de la medición
- #
- # Ejemplo:
- # Timestamp;Valor;Entidad;Metrica
- # 09/03/13 11:00;25;trulf01;CPU_%_libre_minimo
- # 09/03/13 12:00;26;trulf01;CPU_%_libre_minimo
- # 09/03/13 11:00;27;trulf02;CPU_%_libre_minimo
- # 09/03/13 12:00;28;trulf02;CPU_%_libre_minimo
- # 09/03/13 11:00;95;trulf01;CPU_%_libre_maximo
- # 09/03/13 12:00;96;trulf01;CPU_%_libre_maximo
- # 09/03/13 11:00;97;trulf02;CPU_%_libre_maximo
- # 09/03/13 12:00;96;trulf02;CPU_%_libre_maximo
- #
- use autodie;
- use feature 'say';
- use Text::CSV::Slurp;
- use List::Util qw(min max sum);
- #use Data::Dumper;
- ####################################################################################
- # Inicialización
- ####################################################################################
- my %magnitudes = (
- 'CPU % libre % CPU Libre' => 'CPU_%_libre',
- 'SWAP % libre % SWAP Libre' => 'SWAP_%_libre',
- 'RAM % libre % RAM Libre' => 'RAM_%_libre',
- );
- ####################################################################################
- ## Lectura del archivo
- ####################################################################################
- if (@ARGV != 1) {
- die "Uso: $0 <archivo CSV a procesar>\n";
- }
- my $archivo_csv = shift @ARGV;
- -f $archivo_csv or die "ERROR: el archivo $archivo_csv no existe\n";
- my $datos_ref = Text::CSV::Slurp->load(
- file => $archivo_csv,
- sep_char => ';',
- quote_char => '"'
- );
- #say Dumper $datos_ref; # mostraría estos datos
- ####################################################################################
- ## Procesado de los datos
- ####################################################################################
- # Construimos una expresión regular para capturar las medidas que nos interesan
- # junto con el nombre del servidor
- my $medidas_interesantes = join q[|] => keys %magnitudes;
- $medidas_interesantes = qr/^(?<servidor>.+) +(?<magnitud>$medidas_interesantes)$/;
- my %medidas; # aquí guardamos las medidas
- for my $medicion_ref ( @{ $datos_ref } ) { # por cada línea leída
- my $fecha = $medicion_ref->{'Tiempo'}; # formateamos la fecha
- my @fecha = $fecha =~ /(\d+)/g;
- $fecha = sprintf "%02d/%02d/%02d %02d", @fecha; # redondeado a la hora
- while (my($medida,$valor) = each %{ $medicion_ref }) {
- next if $medida eq 'Tiempo'; # ya la hemos leído antes
- next if $valor eq 'No hay datos'; # pasamos al siguiente si no hay datos
- next if $valor !~ /^\d+$/; # lo mismo, si no es un número entero
- if ($medida =~ /$medidas_interesantes/) { # si la medida es de las que nos interesa
- push @{ # guardamos en un array
- $medidas # todos los valores correspondientes a
- { $magnitudes{ $+{'magnitud' }}} # la misma magnitud
- { $+{'servidor'} } # el mismo servidor
- { $fecha } # la misma fecha (la misma hora)
- },
- $valor;
- }
- }
- }
- #say Dumper \%medidas;
- ####################################################################################
- ## Escritura del archivo
- ####################################################################################
- say "Timestamp;Valor;Entidad;Metrica";
- for my $magnitud (sort keys %medidas) {
- # say $magnitud;
- my(@minimos, @maximos, @medios);
- for my $servidor (sort keys %{ $medidas{$magnitud} }) {
- # say "\t$servidor";
- for my $fecha (sort keys %{ $medidas{$magnitud}{$servidor} }) {
- my @medidas = @{ $medidas{$magnitud}{$servidor}{$fecha} };
- next if @medidas != 12; # filtro principal
- my($minimo, $maximo, $media) = (min(@medidas), max(@medidas), sum(@medidas)/12);
- $media = redondeo_SI($media); # redondeamos la media
- # confección de los registros finales
- push @minimos, join ';' => "$fecha:00", $minimo, $servidor, "${magnitud}_minimo";
- push @maximos, join ';' => "$fecha:00", $maximo, $servidor, "${magnitud}_maximo";
- push @medios, join ';' => "$fecha:00", $media, $servidor, "${magnitud}_media";
- # say "\t\t$fecha : $minimo, $maximo, $media";
- }
- }
- for (@minimos, @maximos, @medios) { # salida de los datos, en ese orden
- say;
- }
- }
- ### Subrutinas #####################################################################
- sub redondeo_SI { # redondeo a enteros según el SI
- my $cifra = shift;
- my $parte_entera = int $cifra;
- $cifra -= $parte_entera; # solo quedan los decimales
- if ($cifra > 0.5) {
- ++$parte_entera;
- }
- elsif ($cifra < 0.5) {
- ;
- }
- else { # el problema de la frontera
- if (substr($parte_entera, -1, 1) % 2 != 0) { # si es impar el último dígito,
- ++$parte_entera; # lo incrementamos
- }
- }
- return $parte_entera;
- }
- __END__
Coloreado en 0.007 segundos, usando GeSHi 1.0.8.4
El problema estaba en que se trata de procesar unos datos siguiendo un orden, pero luego hay que extraerlos siguiendo en otro, por lo que hay que hacer es una transformación en el orden en que estamos almacenando los datos para que salgan en otro distinto.
Por ejemplo, en las líneas 159 a 161 vamos guardando los cálculos hechos en tres arrays, para que luego los saquemos en otro orden en la línea 167. Es igual a transponer una matriz (¿recuerdas las clases de álgebra?).
Notar también que estamos usando una matriz tridimensional para guardar los datos, en las líneas 123 a 129. En un eje de ese cubo imaginario tenemos la magnitud (CPU, RAM, SWAP), en otro tenemos el nombre del servidor (que extraemos al mismo tiempo que la medida) y el tercer eje tenemos la fecha y hora a la que se produce la medida. Y dentro del cubo, lo que almacenamos son arrays, con todos los valores correspondientes a esa magnitud, servidor y hora (así que... en realidad... son cuatro dimensiones ).
Supondremos que no habrá más de 12 lecturas por hora, pero si en el futuro nos da igual que haya once, diez o incluso menos lecturas, podemos arreglar el programa con tan solo comentar la línea 153 y modificar la operación
sum(@medidas)/12
por
sum(@medidas)/@medidas
y ya saca la media de todas las medidas, sea cual sea el número de ellas en esa hora. (Excepto 0, que entonces es una división por cero... el programa explota... mejor cambiar la línea 153 por
next if @medidas < 1;
'1' o el número mínimo de medidas para que consideremos que los datos son significativos para el siguiente paso.
Para hacer las operaciones de máximo, mínimo y media, he decidido usar las funciones que trae otro módulo más simple, que procesa listas.