Calcule la duración del inicio de sesión de un usuario en minutos a partir de un archivo

Calcule la duración del inicio de sesión de un usuario en minutos a partir de un archivo

Tengo un archivo en un sistema Linux que se parece al siguiente:

May 6 19:12:03 sys-login: user1 172.16.2.102 Login /data/netlogon 13473
May 6 19:15:26 sys-login: user2 172.16.2.107 Login /data/netlogon 14195
May 6 19:28:37 sys-logout: user1 172.16.2.102 Logout /data/netlogon 13473
May 6 19:33:28 sys-logout: user2 172.16.2.107 Logout /data/netlogon 14195
May 8 07:58:50 sys-login: user3 172.16.6.128 Login /data/netlogon 13272
May 8 07:58:50 sys-logout: user3 172.16.6.128 Logout /data/netlogon 13272

Y estoy tratando de calcular el tiempo que cada usuario ha pasado entre iniciar sesión y cerrar sesión en minutos. Solo habrá un inicio de sesión/cierre de sesión por usuario y quiero generar un informe para todos los usuarios a la vez.

Lo que he probado:

Primero intenté extraer a los usuarios primero:

users=$(awk -v RS=" " '/login/{getline;print $0}' data)

que devuelve los usuarios (que han iniciado sesión), y luego intento extraer la hora a la que iniciaron sesión, pero actualmente estoy atascado. ¡Cualquier ayuda sería apreciada!

Editar: puedo obtener usuarios y fechas haciendo lo siguiente:

users=$(grep -o 'user[0-9]' data)
dates=$(grep -o '[0-2][0-9]:[0-5][0-9]:[0-5][0-9]' data)

Si encuentro una solución completa, la compartiré aquí.

Respuesta1

Aunque este sitio "no es un servicio de escritura de guiones" ;), es un pequeño ejercicio agradable, por lo que propondré el siguiente awkprograma. Puedes guardarlo en un archivo calc_logtime.awk.

#!/usr/bin/awk -f

/sys-log[^:]+:.*Log/ {
    user=$5
    cmd=sprintf("date -d \"%s %d %s\" \"+%%s\"",$1,$2,$3)
    cmd|getline tst
    close(cmd)

    if ($7=="Login") {
        login[user]=tst
    }
    else if ($7=="Logout") {
        logtime[user]+=(tst-login[user])
        login[user]=0
    }
}

END {
    for (u in logtime) {
    minutes=logtime[u]/60
    printf("%s\t%.1f min\n",u,minutes)
    }
}

Esto depende del uso del datecomando GNU (parte del conjunto de herramientas estándar en los sistemas GNU/Linux) y de que el formato de hora en su archivo de registro sea el especificado. También tenga en cuenta que esto no contiene muchas comprobaciones de seguridad, pero debería tener una idea de cómo modificarlo según sus necesidades.

  • Buscará líneas que contenganambosla cadena sys-logcerca del principio y Logcerca del final para aumentar la selectividad en caso de que pueda haber otro contenido. Como ya hemos dicho, esta es una prueba muy rudimentaria, pero, de nuevo, puedes hacerte una idea de cómo hacerla más específica.
  • El usuario será extraído como el quinto campo separado por espacios de la línea.
  • La acción se extraerá como el séptimo campo de la línea separado por espacios.
  • La marca de tiempo de la acción se convertirá a "segundos desde la época" generando una datellamada sprintfy delegando la tarea al shell.
  • Si la acción es Login, la marca de tiempo se almacena en una matriz login, con el nombre de usuario como "índice de matriz".
  • Si la acción es Logout, la duración se calculará y se agregará a una matriz logtimeque contiene el tiempo de registro total de todos los usuarios hasta el momento.
  • Al final del archivo, se generará un informe iterando sobre todos los "índices de matriz" logtimey convirtiendo los tiempos de registro de segundos a minutos mediante división simple.

Puedes llamarlo vía

awk -f calc_logtime.awk logfile.dat

Respuesta2

Con GNU awk para funciones de tiempo y gensub() y matrices de matrices:

$ cat tst.awk
BEGIN {
    dateFmt = strftime("%Y") " %02d %02d %s"
    months  = "JanFebMarAprMayJunJulAugSepOctNovDec"
}
{
    date = sprintf(dateFmt, (index(months,$1)+2)/3, $2, gensub(/:/," ","g",$3))
    userSecs[$5][$7] = mktime(date)
}
$7 == "Logout" {
    printf "%s %0.2f\n", $5, (userSecs[$5]["Logout"] - userSecs[$5]["Login"]) / 60
    delete userSecs[$5]
}

$ awk -f tst.awk file
user1 16.57
user2 18.03
user3 0.00

Esto se ejecutará órdenes de magnitud más rápido que llamar a Unix datedesde awk, ya que este último tiene que generar una subcapa cada vez para hacerlo.

Si también desea obtener un informe de los usuarios que iniciaron sesión pero no cerraron sesión cuando ejecuta el script, por ejemplo, user4en este archivo de entrada modificado:

$ cat file
May 6 19:12:03 sys-login: user1 172.16.2.102 Login /data/netlogon 13473
May 6 19:15:26 sys-login: user2 172.16.2.107 Login /data/netlogon 14195
May 6 19:28:37 sys-logout: user1 172.16.2.102 Logout /data/netlogon 13473
May 6 19:33:28 sys-logout: user2 172.16.2.107 Logout /data/netlogon 14195
May 8 07:58:50 sys-login: user3 172.16.6.128 Login /data/netlogon 13272
May 8 07:58:50 sys-logout: user3 172.16.6.128 Logout /data/netlogon 13272
Jun 15 08:30:26 sys-login: user4 172.16.2.107 Login /data/netlogon 14195

Luego simplemente modifica el guión:

$ cat tst.awk
BEGIN {
    dateFmt = strftime("%Y") " %02d %02d %s"
    months  = "JanFebMarAprMayJunJulAugSepOctNovDec"
}
{
    date = sprintf(dateFmt, (index(months,$1)+2)/3, $2, gensub(/:/," ","g",$3))
    userSecs[$5][$7] = mktime(date)
}
$7 == "Logout" {
    printf "%s %0.2f %s\n", $5, (userSecs[$5]["Logout"] - userSecs[$5]["Login"]) / 60, "Complete"
    delete userSecs[$5]
}
END {
    now = systime()
    for (user in userSecs) {
        printf "%s %0.2f %s\n", user, (now - userSecs[user]["Login"]) / 60, "Partial"
    }
}

$ awk -f tst.awk file
user1 16.57 Complete
user2 18.03 Complete
user3 0.00 Complete
user4 51.10 Partial

Si necesitara encontrar casos en los que un usuario haya iniciado sesión nuevamente mientras ya había iniciado sesión sin cerrar sesión en el medio o manejar los cierres de sesión sin inicio de sesión asociado de manera diferente o hacer cualquier otra cosa, entonces todo eso también serían ajustes triviales.

Respuesta3

El siguiente perlscript utiliza elFecha::Analizarmódulo de laHora Fechacolección para analizar las fechas y horas de cada registro en lugar de depender de la fecha de GNU para hacerlo. Probablemente esté empaquetado para su distribución (en Debian, apt install libtimedate-perl); de lo contrario, instálelo con cpan.

El script funciona utilizando el último campo de cada línea de entrada (que parece ser un ID de sesión) como clave de nivel superior para una estructura de datos Hash-of-Hashes (HoH) llamada %sessions. Cada elemento de %sessions es un hash anónimo que contiene las claves user, loginy logout.

Una vez que se ha leído y analizado todo el archivo, se calculan los totales acumulados para cada usuario (y se almacenan en otra matriz asociativa %users) y luego se imprimen. La salida se ordena por nombre de usuario.

#!/usr/bin/perl -l

use strict;
use Date::Parse;

my %sessions;
my %users;

# read the input file, parse dates, store login and logout times into session hash
while (<>) {
  next unless (m/\ssys-log(?:in|out):\s/);

  my ($M, $D, $T, $type, $user, $ip, undef, undef, $s) = split;
  $type =~ s/^sys-|://g;

  $sessions{$s}->{user} = $user;
  $sessions{$s}->{$type} = str2time(join(" ", $M, $D, $T));
  # $session{$s}->{IP} = $ip; # not used
};

# add up session totals for each user
foreach my $s (keys %sessions) {
  # ignore sessions without both a login and logout time, it's
  # impossible to calculate session length.
  next unless ( defined($sessions{$s}->{login}) &&
                defined($sessions{$s}->{logout}) );

  $users{$sessions{$s}->{user}} += $sessions{$s}->{logout} - $sessions{$s}->{login};
};

# print them
foreach my $u (sort keys %users) {
   printf "%s has logged in for %s minutes\n", $u, int($users{$u}/60); 
};

Guárdelo como, por ejemplo, login-times.ply hágalo ejecutable con chmod +x login-times.pl. Ejecútelo como:

$ ./login-times.pl data
user1 has logged in for 16 minutes
user2 has logged in for 18 minutes
user3 has logged in for 0 minutes

Para su información, los datos en %sessionsHoH se ven así:

%sessions = {
  13272 => { login => 1620424730, logout => 1620424730, user => "user3" },
  13473 => { login => 1620292323, logout => 1620293317, user => "user1" },
  14195 => { login => 1620292526, logout => 1620293608, user => "user2" },
}

Es completamente posible que una sesión no tenga una marca de tiempo de inicio o cierre de sesión. Sería fácil imprimir un mensaje en STDERR si faltara alguno de ellos. O para manejar tales anomalías como usted elija. El script anterior simplemente los ignora.

Para completar, los datos %usersterminan luciendo así:

%users = { user1 => 994, user2 => 1082, user3 => 0 }

Por cierto, estas estructuras de datos se imprimieron con elDatos::Volcadomódulo, que es muy útil para depurar, etc. El nombre del paquete Debian es libdata-dump-perl, probablemente otras distribuciones lo tengan. De lo contrario, instálelo con cpan.

Para imprimirlos, agregué lo siguiente al final del script:

use Data::Dump qw(dump);
print "%sessions = ", dump(\%sessions);
print "%users = ", dump(\%users)

Finalmente, la dirección IP se captura con la splitfunción en el script pero no se usa. Esto podría agregarse fácilmente al hash de la sesión y usarse para imprimir un resumen de una línea de cada par de inicio y cierre de sesión. ElFormato de fechaEl módulo de la misma Time::Datecolección podría usarse para formatear las fechas.

Por ejemplo:

  1. agregar use Date::Format;después de la use Date::Parse;línea

  2. Descomentar $session{$s}->{IP} = $ip;en el while(<>)bucle.

  3. Utilice algo como lo siguiente para imprimir los datos:

my $tfmt = "%Y-%m-%d %H:%M:%S";

printf "%s\t%-20s\t%-20s\t%7s\t%s\n", "USER", "LOGIN", "LOGOUT", "MINUTES", "IP";

# sort the session keys by their 'user' fields.
foreach my $s (sort { $sessions{$a}->{user} cmp $sessions{$b}->{user} } keys %sessions) {
  my $in  = $sessions{$s}->{login};
  my $out = $sessions{$s}->{logout};
  next unless ($in && $out);

  my $user = $sessions{$s}->{user};
  my $ip   = $sessions{$s}->{IP};

  my $minutes = int(($out-$in)/60);
  $in  = time2str($tfmt,$in); 
  $out = time2str($tfmt,$out);

  printf "%s\t%-20s\t%-20s\t%7i\t%s\n", $user, $in, $out, $minutes, $ip;
};

La salida sería como:

USER    LOGIN                   LOGOUT                  MINUTES IP
user1   2021-05-06 19:12:03     2021-05-06 19:28:37          16 172.16.2.102
user2   2021-05-06 19:15:26     2021-05-06 19:33:28          18 172.16.2.107
user3   2021-05-08 07:58:50     2021-05-08 07:58:50           0 172.16.6.128

Respuesta4

Esto suena como un trabajo para dateutils. Saque las piezas relevantes con awk:

awk -v OFS='\t' '
$4 == "sys-login:"  { login[$5]  = $1" "$2" "$3 }
$4 == "sys-logout:" { logout[$5] = $1" "$2" "$3 }
END {
  for (user in login)
    print user, login[user], logout[user]
}' infile

Producción:

user1   May 6 19:12:03  May 6 19:28:37
user2   May 6 19:15:26  May 6 19:33:28
user3   May 8 07:58:50  May 8 07:58:50

Y canalícelo a un bucle while:

while IFS=$'\t' read username starttime endtime; do
  printf "%s\t%s\n" $username \
    $(dateutils.ddiff -i "%b %d %H:%M:%S" -f "%S" "$starttime" "$endtime")
done

Producción:

user1   994
user2   1082
user3   0

Nota: puede cambiar el interruptor ddiffdel comando -fpara elegir un formato de hora diferente. Aquí estamos usando los segundos transcurridos.

información relacionada