Calcule a duração do logon de um usuário em minutos a partir de um arquivo

Calcule a duração do logon de um usuário em minutos a partir de um arquivo

Eu tenho um arquivo em um sistema Linux parecido com o seguinte:

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

E estou tentando calcular o tempo que cada usuário gastou entre o login e o logout em minutos. Haverá apenas um login/logout por usuário e quero gerar um relatório para todos os usuários de uma vez.

O que eu tentei:

Tentei primeiro extrair os usuários primeiro:

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

que retorna os usuários (logados), e então tento extrair o horário em que eles fizeram login, mas no momento estou travado. Qualquer ajuda seria apreciada!

Editar: consigo obter usuários e datas fazendo o seguinte:

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

Se eu encontrar uma solução completa, compartilharei aqui.

Responder1

Embora este site "não seja um serviço de escrita de scripts" ;), este é um pequeno exercício interessante, então proporei o seguinte awkprograma. Você pode salvá-lo em um arquivo 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)
    }
}

Isso depende do uso do comando GNU date(parte do conjunto de ferramentas padrão em sistemas GNU/Linux) e do formato de hora em seu arquivo de log conforme especificado. Observe também que isso não contém muitas verificações de segurança, mas você deve ter uma ideia de como modificá-lo de acordo com suas necessidades.

  • Ele irá procurar por linhas que contenhamambosa string sys-logperto do início e Logperto do fim para aumentar a seletividade, caso haja outro conteúdo. Como afirmado, este é um teste muito rudimentar, mas, novamente, você pode ter uma ideia de como torná-lo mais específico.
  • O usuário será extraído como o quinto campo separado por espaço da linha.
  • A ação será extraída como o 7º campo separado por espaço da linha.
  • O carimbo de data/hora da ação será convertido em "segundos desde a época" gerando uma datechamada sprintfe delegando a tarefa ao shell.
  • Se a ação for Login, o carimbo de data/hora é armazenado em um array login, com o nome de usuário como "índice do array".
  • Se a ação for Logout, a duração será calculada e adicionada a uma matriz logtimecontendo o tempo total de log de todos os usuários até o momento.
  • No final do arquivo, um relatório será gerado, iterando todos os "índices de array" logtimee convertendo os tempos de log de segundos para minutos por divisão simples.

Você pode ligar através

awk -f calc_logtime.awk logfile.dat

Responder2

Com GNU awk para funções de tempo e gensub() e matrizes de matrizes:

$ 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

Isso executará ordens de magnitude mais rápido do que chamar o Unix datedo awk, já que o último precisa gerar um subshell toda vez para fazer isso.

Se você também deseja obter um relatório de usuários que efetuaram login, mas não efetuaram logout, ao executar o script, por exemplo, user4neste arquivo 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

Depois é só ajustar o script:

$ 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

Se você precisasse encontrar casos em que um usuário efetuou login novamente enquanto já estava conectado sem nenhum logout no meio ou lidar com logouts sem login associado de maneira diferente ou fazer qualquer outra coisa, então tudo isso também seria apenas ajustes triviais.

Responder3

O perlscript a seguir usa oData::Analisarmódulo doHoraDatacoleção para analisar as datas e horas de cada registro em vez de depender da data GNU para fazer isso. Provavelmente está empacotado para sua distribuição (no debian apt install libtimedate-perl), caso contrário, instale-o com cpan.

O script funciona usando o último campo de cada linha de entrada (que parece ser um ID de sessão) como a chave de nível superior para uma estrutura de dados Hash-of-Hashes (HoH) chamada %sessions. Cada elemento de %sessions é um hash anônimo contendo as chaves user, logine logout.

Depois que todo o arquivo tiver sido lido e analisado, os totais cumulativos de cada usuário são calculados (e armazenados em outra matriz associativa, %users) e depois impressos. A saída é classificada por nome de usuário.

#!/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); 
};

Salve-o como, por exemplo, login-times.ple torne-o executável com a extensão chmod +x login-times.pl. Execute 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 sua informação, os dados no %sessionsHoH são assim:

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

É perfeitamente possível que uma sessão não tenha um carimbo de data/hora de login ou logout. Seria fácil imprimir uma mensagem para STDERR se alguma delas estivesse faltando. Ou para lidar com essas anomalias da maneira que você escolher. O script acima simplesmente os ignora.

Para completar, os dados %usersacabam ficando assim:

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

Aliás, essas estruturas de dados foram impressas com oDados::Despejomódulo, que é muito útil para depuração, etc. O nome do pacote Debian é libdata-dump-perl, outras distros provavelmente o possuem. Caso contrário, instale-o com cpan.

Para imprimi-los, adicionei o seguinte no final do script:

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

Finalmente, o endereço IP é capturado com a splitfunção no script, mas não é usado. Isso pode ser facilmente adicionado ao hash da sessão e usado para imprimir um resumo de uma linha de cada par de login e logout. OFormato de datamódulo da mesma Time::Datecoleção pode ser usado para formatar as datas.

Por exemplo:

  1. adicione use Date::Format;depois da use Date::Parse;linha

  2. Remova o comentário $session{$s}->{IP} = $ip;no while(<>)loop.

  3. Use algo como o seguinte para imprimir os dados:

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;
};

A saída seria 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

Responder4

Isso parece um trabalho para dateutils. Pesque as peças relevantes com 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

Saída:

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

E canalize para um loop 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

Saída:

user1   994
user2   1082
user3   0

Nota: você pode alterar a opção ddiffdo comando -fpara escolher um formato de hora diferente. Aqui estamos usando segundos decorridos.

informação relacionada