Monitorar uma explosão de eventos com inotifywait

5

Eu tenho um serviço que publica esporadicamente conteúdo em um determinado diretório do lado do servidor via rsync . Quando isso acontece, eu gostaria de acionar a execução de um procedimento do lado do servidor.

Graças ao comando inotifywait , é bastante fácil monitorar um arquivo ou diretório para alterações. Eu gostaria, no entanto, de ser notificado apenas uma vez para cada explosão de modificações , já que o procedimento de pós-upload é pesado e não quero executá-lo para cada arquivo modificado.

Não deve ser um grande esforço para criar um hack baseado no timestamp do evento… Acredito, no entanto, que este é um problema bastante comum. Eu não consegui encontrar nada de útil.

Existe algum comando inteligente que pode descobrir uma explosão? Eu estava pensando em algo que eu posso usar desta maneira:

inotifywait -m "$dir" $opts | detect_burst --execute "$post_upload"
    
por Dacav 07.09.2017 / 12:12

4 respostas

1

Com base na sua própria resposta, se você quiser usar o shell read , aproveite a opção -t timeout, que define o código de retorno para > 128 se houver um tempo limite. Por exemplo, o seu script burst pode se tornar, vagamente:

interval=$1; shift
while :
do  if read -t $interval
    then    echo "$REPLY"            # not timeout
    else    [ $? -lt 128 ] && exit   # eof
            "$@"
            read || exit    # blocking read infinite timeout
            echo "$REPLY"
    fi
done

Você pode começar com uma leitura de bloqueio inicial para evitar detectar um fim de burst no início.

    
por 11.09.2017 / 14:48
2

OP aqui. Uma possível solução pode ser semelhante a

inotifywait -m "$dir" -e moved_to --timefmt='%s' --format '%T' | stdbuf -oL uniq | ...

EDITAR: Veja a minha outra resposta a esta pergunta, uma vez que a segunda tomada é melhor para a IMHO

Funciona com o tempo desde a impressão do Epoch a cada move_to , que é a etapa final de uma transferência de arquivo único.

Isso funciona decentemente, mesmo que possa acionar o procedimento de pós-carregamento várias vezes se um upload durar mais de um segundo.

Uma granularidade diferente pode ser obtida alterando o sinalizador --timefmt para algum valor diferente.

Postando aqui como uma ideia semi-decente, mesmo que eu não goste muito ... ainda pensei em compartilhá-lo.

    
por 07.09.2017 / 12:27
1

Op aqui.

A solução com a qual saí: é um script chamado burst :

inotifywait -e moved_to "$monitored_dir" -m \
    | burst 2 'echo run post-upload'

O script burst :

#!/bin/bash

help() {
    >&2 echo "Usage: $0 <interval> <command>"
}

set -e
trap help EXIT
interval=${1:?missing interval}; shift
: ${1:?missing command}

trap - EXIT
set +e
exec 3> >(sed "s/^/burst: /" >&2)
while read line; do
    echo "$line" >&3
    test -n "$!" && kill -term $! 2>/dev/null
    (sleep $interval && $SHELL -c "$*") &
done

Basicamente, isso cria uma sub-rede que aguarda um tempo definido antes de iniciar o comando real ( (sleep $interval && $SHELL -c "$*") . Qualquer nova linha lida por inotifywait simplesmente eliminará o shell (se existir algum) e o criará novamente.

Quando o burst de linhas terminar, o shell poderá finalizar o sleep , e o comando será executado.

Tem algumas desvantagens:

  1. Ele mata e gera um processo para cada linha no stdin. O que significa que você quer reduzir o número de linhas em primeiro lugar (daí o filtro -e moved_to em %código%). Não poderia caber desempenho de usuário se o número de arquivos enviados for grande o suficiente!

  2. Se uma segunda explosão chegar após inotifywait segundos (ou uma transferência de arquivo leva esse tempo) as chances são de que vai matar a execução pós-atualização processo. Enquanto esse processo for idempotente ou transacionado, isso será bem ... então cabeças.

por 11.09.2017 / 11:58
0

Você pode fazer um loop que verifica a hora atual e ignora eventos idênticos (ou, em geral, semelhantes de uma forma que você pode definir) a um evento anterior. No bash, ksh ou zsh, o seguinte loop simples pula eventos repetidos em um período de 10 segundos:

inotifywait -m … | {
  previous_SECONDS=0
  previous_event=
  while IFS= read -r event; do
    if [[ "$event" = "$previous_event" && $SECONDS <= $previous_SECONDS + 10 ]]; then
      continue
    fi
    previous_SECONDS=$SECONDS previous_event=$event
    "$post_upload" "$event"
  done
}

Os shells não são bons em processamento de texto de alto desempenho, então pode ser melhor usar uma ferramenta diferente. Você pode usar o awk, que por razões históricas tem uma maneira muito estranha de expor o tempo (como número de segundos desde a época): chamar srand() retorna a hora da chamada anterior para srand() .

inotifywait -m … | awk '
    {srand(); current_time = srand()}
    $0 == previous_event && current_time <= previous_time + 10 {
        system(ENVIRON["post_upload"])
    }
    {previous_event = $0; previous_time = current_time}
'

A menos que você realmente precise de portabilidade, eu descartaria isso e usaria uma linguagem de script mais avançada, como Perl, Python ou Ruby. Esses idiomas têm uma interface para inotify, portanto, não é necessário chamar inotifywait e fazer qualquer análise de texto.

    
por 09.09.2017 / 00:49