Processos de script em paralelo

0

Gostaria de analisar o log de acesso do apache com IPs. Eu usei o seguinte código, mas demorou quase 90 segundos.

grep "^$CLIENT_IP" /var/log/http/access.log > /tmp/access-$CLIENT_IP.log

Então eu tentei alternativa como abaixo.

sed -i -e "/^$CLIENT_IP/w /tmp/access-$CLIENT_IP.log" -e '//d' /var/log/http/access.log

até mesmo isso levou mais de 60 segundos.

Existem 1200 IPs para analisar. Eu gostaria de saber se existe alguma maneira de implementar o paralelismo para reduzir o tempo de execução.

    
por msp9011 10.04.2018 / 19:06

3 respostas

3

Suponho que você esteja fazendo isso em um loop de shell sobre todos os endereços IP, possivelmente com os endereços IP provenientes de um arquivo de texto. Sim, isso seria lento, com uma invocação de sed ou grep por endereço IP.

Em vez disso, você pode usar um único uso de sed , se você se preparar com cuidado.

Primeiro, temos que criar um script sed , e fazemos isso de um arquivo ip.list que contém os endereços IP, um endereço por linha:

sed -e 'h' \
    -e 's/\./\./g' \
    -e 's#.*#/^&[[:blank:]]/w /tmp/access-#' \
    -e 'G' \
    -e 's/\n//' \
    -e 's/$/.log/' ip.list >ip.sed

Esse sed stuff, para cada endereço IP,

  1. Copie o endereço para o "espaço de armazenamento" (um buffer extra em sed ).
  2. Altere . no "espaço padrão" (a linha de entrada) em \. (para corresponder aos pontos corretamente, seu código não fez isso).
  3. Prefixe ^ e anexe [[:blank:]]/w /tmp/access- ao espaço padrão.
  4. Anexe a linha de entrada não modificada do espaço de suspensão ao espaço de padrão com uma nova linha no meio.
  5. Excluir essa nova linha.
  6. Anexe o .log ao final da linha (e implique o resultado implicitamente).

Para um arquivo que contenha

127.0.0.1
10.0.0.1
10.0.0.100

isso criaria o script sed

/^127\.0\.0\.1[[:blank:]]/w /tmp/access-127.0.0.1.log
/^10\.0\.0\.1[[:blank:]]/w /tmp/access-10.0.0.1.log
/^10\.0\.0\.100[[:blank:]]/w /tmp/access-10.0.0.100.log

Observe que você terá que corresponder a um caractere em branco (espaço ou tabulação) após o endereço IP, caso contrário, as entradas de log para 10.0.0.100 entrariam no arquivo /tmp/access-10.0.0.1.log . Seu código omitiu isso.

Isso pode ser usado no seu arquivo de log (sem loop):

sed -n -f ip.sed /var/log/http/access.log

Eu nunca testei a gravação em 1200 arquivos de um mesmo script sed . Se não funcionar, tente a variação abaixo de awk .

Uma solução semelhante com awk envolve a leitura dos endereços IP em uma matriz primeiro e, em seguida, a correspondência deles em cada linha. Isso requer uma única invocação awk :

awk 'FNR == NR  { list[$1] = 1; next }
     $1 in list { name = $1 ".log"; print >>name; close name }' ip.list /var/log/http/access.log

Aqui, damos awk a lista de IPs e o arquivo de log ao mesmo tempo. Quando NR == FNR sabemos que ainda estamos lendo o primeiro arquivo (a lista), e adicionamos os números de IP às chaves list as da matriz associativa e continuamos com a próxima linha de entrada.

Se a condição FNR == NR não for verdadeira, estamos lendo o segundo arquivo (o arquivo de log) e testamos se o primeiro campo da linha de entrada é uma chave em list (é uma planície comparação de string, não uma correspondência de expressão regular). Se for, anexamos a linha ao arquivo apropriadamente nomeado.

Precisamos ter cuidado ao fechar o arquivo de saída, pois, de outra forma, poderíamos ficar sem descritores de arquivos abertos. Portanto, haverá muitos arquivos de abertura e fechamento para anexar, mas ainda será mais rápido do que chamar awk (ou qualquer utilitário) uma vez por endereço IP.

Eu estaria interessado em saber se essas coisas funcionam para você e qual pode ser o tempo de execução aproximado. Eu testei as soluções apenas em conjuntos extremamente pequenos de dados.

Claro, poderíamos ir com a sua ideia de apenas brute forçando-o a lançar várias instâncias de, e. grep no sistema em paralelo:

Ignorando o fato de que não combinamos os pontos nos endereços IP corretamente, poderíamos fazer algo como

xargs -P 4 -n 100 sh -c '
    for n do
        grep "^$n[[:blank:]]" /var/log/http/access.log >"/tmp/access-$n.log"
    done' sh <ip.list

Aqui, xargs dará no máximo 100 endereços IP por vez do arquivo ip.list para um script de shell curto. Ele irá organizar com quatro invocações paralelas do script.

O script de shell curto:

for n do
    grep "^$n[[:blank:]]" /var/log/http/access.log >"/tmp/access-$n.log"
done

Isso apenas itera nos 100 endereços IP que xargs fornece em sua linha de comando e aplica praticamente o mesmo comando grep que você tinha, a diferença é que haverá quatro desses loops sendo executados em paralelo.

Aumente o -P 4 para -P 16 ou algo relacionado ao número de CPUs que você possui. A aceleração provavelmente não seria linear, pois cada instância paralela de grep leria e gravaria no mesmo disco.

Exceto pelo -P flag para xargs , todas as coisas nesta resposta devem ser capazes de rodar em qualquer sistema POSIX. O -P flag para xargs é não-padrão, mas implementado no GNU xargs e nos sistemas BSD.

    
por 10.04.2018 / 21:46
1

Para várias abordagens: link

Além disso, Se você está fazendo muito isso, então um SSD é provavelmente o caminho a percorrer. Tocar no HD é o assassino de algo assim.

Você tem um grande número de diferentes greps para executar. Faça um script que lance comandos de script (digamos, um por núcleo) para o segundo plano e, em seguida, rastreie quando eles terminarem, pois eles serão lançados mais.

Quando eu estava fazendo isso, consegui que todos os 12 núcleos rodassem com 100% de uso da CPU, mas você pode achar que o seu limite de recursos é diferente. Se todos os seus trabalhos desejarem o mesmo arquivo, se você não estiver em um SSD, talvez queira copiar o arquivo para que ele não seja compartilhado.

    
por 10.04.2018 / 19:37
0

Se /var/log/http/access.log for maior que a RAM e, portanto, não puder ser armazenado em cache, executar mais processos em paralelo pode ser uma boa alternativa para ler access.log várias vezes - especialmente se você tiver vários núcleos. Isso executará um grep por IP em paralelo (+ alguns processos auxiliares de quebra automática).

pargrep() {
    # Send standard input to grep with different match strings in parallel
    # This command would be enough if you only have 250 match strings
    parallel --pipe --tee grep ^{} '>' /tmp/access-{}.log ::: "$@"
}
export -f pargrep
# Standard input is tee'ed to several pargreps.
# Each pargrep gets 250 match strings and thus starts 250 processes.
# For 1200 ips this starts 3600 processes taking around 1 GB RAM,
# but it reads access.log only once
cat /var/log/http/access.log |
  parallel --pipe --tee -N250 pargrep {} :::: ips
    
por 16.04.2018 / 00:49