• Publicidad

Módulos para sacar promedios, valor más alto, más bajo.

¿Apenas comienzas con Perl? En este foro podrás encontrar y hacer preguntas básicas de Perl con respuestas aptas a tu nivel.

Re: Módulos para sacar promedios, valor más alto, más bajo.

Notapor explorer » 2013-04-06 18:48 @825

Tienes toda la razón, no hay problema: el propio módulo Text::CSV::Slurp se encarga de transformar las columnas en entradas de hash, así que no importa el orden de llegada de los campos de los registros.

Esta es una solución que he encontrado, según la entrada y salida que nos has mandado.
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. #
  3. # Saca estadísticas de utilización de recursos a partir del registro de actividad
  4. # en un archivo CSV.
  5. #
  6. # El objetivo es leer los datos resultados de las sondas, y emitir resúmenes por
  7. # hora de las magnitudes medidas, con sus valores mínimo, máximo y medio, en esa hora.
  8. #
  9. # La entrada consiste en un archivo que se pasa como primer argumento, que consiste
  10. # en una serie de registros, uno por línea, en que cada registro contiene los
  11. # siguientes campos, separados por ';':
  12. # * fecha y hora de la medición, en formato 'd/mm/aa h:mm'
  13. # * uno o más de los datos de actividad de un servidor, consistentes en:
  14. #   + nombre del servidor, seguido de cada uno de los siguientes valores
  15. #     - CPU % libre % CPU Libre
  16. #     - CPU % libre Round Trip Time
  17. #     - CPU % libre Status
  18. #     - SWAP % libre % SWAP Libre
  19. #     - SWAP % libre Round Trip Time
  20. #     - SWAP % libre Status
  21. #     - RAM % libre % RAM Libre
  22. #     - RAM % libre Round Trip Time
  23. #     - RAM % libre Status
  24. #     (no necesariamente en este orden. En caso de no existir medición aparece el
  25. #     texto "No hay datos").
  26. #
  27. # Ejemplo:
  28. #    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
  29. #    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
  30. #    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
  31. #    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
  32. #    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
  33. #
  34. # La salida es hacia la salida estándar, consistente en una serie de registros,
  35. # uno por línea, con los siguientes campos separados por ';':
  36. # * lapso de tiempo referido a una hora entera, en formato 'dd/mm/aa hh:00'
  37. # * un valor, redondeado a entero, calculado según indica la etiqueta que sigue
  38. # * nombre del servidor al que se refiere el resultado anterior
  39. # * etiqueta de texto al que se refiere el valor anterior, en el formato:
  40. #   '<magnitud>_%_libre_<medida>', siendo el valor de <magnitud> uno de
  41. #   + CPU
  42. #   + SWAP
  43. #   + RAM
  44. #   y siendo <medida>, uno de
  45. #   + minimo
  46. #   + maximo
  47. #   + medio
  48. #
  49. # Los registros salen en el siguiente orden:
  50. #   1 - Por la magnitud
  51. #   2 - Por nombre del servidor
  52. #   3 - Por tiempo de la medición
  53. #
  54. # Ejemplo:
  55. #    Timestamp;Valor;Entidad;Metrica
  56. #    09/03/13 11:00;25;trulf01;CPU_%_libre_minimo
  57. #    09/03/13 12:00;26;trulf01;CPU_%_libre_minimo
  58. #    09/03/13 11:00;27;trulf02;CPU_%_libre_minimo
  59. #    09/03/13 12:00;28;trulf02;CPU_%_libre_minimo
  60. #    09/03/13 11:00;95;trulf01;CPU_%_libre_maximo
  61. #    09/03/13 12:00;96;trulf01;CPU_%_libre_maximo
  62. #    09/03/13 11:00;97;trulf02;CPU_%_libre_maximo
  63. #    09/03/13 12:00;96;trulf02;CPU_%_libre_maximo
  64. #
  65.  
  66. use autodie;
  67. use feature 'say';
  68. use Text::CSV::Slurp;
  69. use List::Util qw(min max sum);
  70. #use Data::Dumper;
  71.  
  72.  
  73. ####################################################################################
  74. # Inicialización
  75. ####################################################################################
  76. my %magnitudes = (
  77.     'CPU % libre % CPU Libre'   => 'CPU_%_libre',
  78.     'SWAP % libre % SWAP Libre' => 'SWAP_%_libre',
  79.     'RAM % libre % RAM Libre'   => 'RAM_%_libre',
  80. );
  81.  
  82.  
  83. ####################################################################################
  84. ## Lectura del archivo
  85. ####################################################################################
  86. if (@ARGV != 1) {
  87.     die "Uso: $0 <archivo CSV a procesar>\n";
  88. }
  89. my $archivo_csv = shift @ARGV;
  90. -f $archivo_csv or die "ERROR: el archivo $archivo_csv no existe\n";
  91.  
  92. my $datos_ref = Text::CSV::Slurp->load(
  93.         file       => $archivo_csv,
  94.         sep_char   => ';',
  95.         quote_char => '"'
  96. );
  97. #say Dumper $datos_ref; # mostraría estos datos
  98.  
  99. ####################################################################################
  100. ## Procesado de los datos
  101. ####################################################################################
  102. # Construimos una expresión regular para capturar las medidas que nos interesan
  103. # junto con el nombre del servidor
  104. my $medidas_interesantes = join q[|] => keys %magnitudes;
  105. $medidas_interesantes = qr/^(?<servidor>.+) +(?<magnitud>$medidas_interesantes)$/;
  106.  
  107. my %medidas;                                            # aquí guardamos las medidas
  108.  
  109. for my $medicion_ref ( @{ $datos_ref } ) {              # por cada línea leída
  110.  
  111.     my $fecha = $medicion_ref->{'Tiempo'};              # formateamos la fecha
  112.     my @fecha = $fecha =~ /(\d+)/g;
  113.     $fecha    = sprintf "%02d/%02d/%02d %02d", @fecha;  # redondeado a la hora
  114.  
  115.     while (my($medida,$valor) = each %{ $medicion_ref }) {
  116.  
  117.         next if $medida eq 'Tiempo';                    # ya la hemos leído antes
  118.         next if $valor eq 'No hay datos';               # pasamos al siguiente si no hay datos
  119.         next if $valor !~ /^\d+$/;                      # lo mismo, si no es un número entero
  120.  
  121.         if ($medida =~ /$medidas_interesantes/) {       # si la medida es de las que nos interesa
  122.  
  123.             push @{                                     # guardamos en un array
  124.                 $medidas                                # todos los valores correspondientes a
  125.                       { $magnitudes{ $+{'magnitud' }}}  # la misma magnitud
  126.                       { $+{'servidor'} }                # el mismo servidor
  127.                       { $fecha }                        # la misma fecha (la misma hora)
  128.                 },
  129.                 $valor;
  130.         }
  131.     }
  132. }
  133. #say Dumper \%medidas;
  134.  
  135.  
  136. ####################################################################################
  137. ## Escritura del archivo
  138. ####################################################################################
  139. say "Timestamp;Valor;Entidad;Metrica";
  140.  
  141. for my $magnitud (sort keys %medidas) {
  142. #    say $magnitud;
  143.  
  144.     my(@minimos, @maximos, @medios);
  145.  
  146.     for my $servidor (sort keys %{ $medidas{$magnitud} }) {
  147. #        say "\t$servidor";
  148.  
  149.         for my $fecha (sort keys %{ $medidas{$magnitud}{$servidor} }) {
  150.  
  151.             my @medidas = @{ $medidas{$magnitud}{$servidor}{$fecha} };
  152.            
  153.             next if @medidas != 12;                     # filtro principal
  154.  
  155.             my($minimo, $maximo, $media) = (min(@medidas), max(@medidas), sum(@medidas)/12);
  156.             $media = redondeo_SI($media);               # redondeamos la media
  157.  
  158.             # confección de los registros finales
  159.             push @minimos, join ';' => "$fecha:00", $minimo, $servidor, "${magnitud}_minimo";
  160.             push @maximos, join ';' => "$fecha:00", $maximo, $servidor, "${magnitud}_maximo";
  161.             push @medios,  join ';' => "$fecha:00", $media,  $servidor, "${magnitud}_media";
  162.  
  163. #            say "\t\t$fecha : $minimo, $maximo, $media";
  164.         }
  165.     }
  166.  
  167.     for (@minimos, @maximos, @medios) {                 # salida de los datos, en ese orden
  168.         say;
  169.     }
  170. }
  171.  
  172.  
  173. ### Subrutinas #####################################################################
  174. sub redondeo_SI {                                       # redondeo a enteros según el SI
  175.     my $cifra = shift;
  176.  
  177.     my $parte_entera = int $cifra;
  178.  
  179.     $cifra -= $parte_entera;                            # solo quedan los decimales
  180.  
  181.     if ($cifra > 0.5) {
  182.         ++$parte_entera;
  183.     }
  184.     elsif ($cifra < 0.5) {
  185.         ;
  186.     }
  187.     else {                                              # el problema de la frontera
  188.         if (substr($parte_entera, -1, 1) % 2 != 0) {    # si es impar el último dígito,
  189.             ++$parte_entera;                            # lo incrementamos
  190.         }
  191.     }
  192.  
  193.     return   $parte_entera;
  194. }
  195.  
  196.  
  197. __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.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Publicidad

Anterior

Volver a Básico

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 12 invitados