[Rio-pm] Ajuda com Threads - tempo de abertura

João André Simioni jasimioni em gmail.com
Domingo Julho 11 09:52:32 PDT 2010


Sim, normalmente o problema é IO e você tem razão nas observações
sobre Fork. Porém está errado sobre o tempo de resposta.

De 4770 hosts, eu normalmente não tenho mais do que 100 sem resposta.
Ou seja, a grande maioria das threads tem tempo de execução de ms.
Além disso, inicialmente eu gostaria de abrir uma thread por teste,
controlando somente o limite de 250 existentes ao mesmo tempo (nem sei
se é uma boa abordagem).

Como eu comentei antes, as threads estão ficando presas em apenas um
processador. Então eu mudei o script para que quebre os meus elementos
em 8 grupos, abrindo um novo processo para cada grupo usando fork e
cada processo abre 30 threads.

Inverti a ordem dos testes - faço primeiro o teste UDP, depois o TCP e
só então disparo o ping do sistema - isso evita os forks adicionais.
Realmente era aqui que o bicho tava pegando.

O resultado ficou já dentro do que eu queria - consigo fazer todos os
testes, disparar os alarmes para o centralizador de falhas e fazer os
registros necessários no banco de dados em cerca de 60 segundos. E o
load da máquina está em 2,5 durante a execução. Ou seja, provavelmente
eu consiga otimizar mais ainda.

E vou ver se eu mesmo dou uma olhada no código do Net::Ping - um
problema que eu vi reportado é que ele coloca o id do processo no
pacote ICMP e espera esse retorno - ou seja, a resposta do host A
poderia dar ok para o teste do host B (essa fonte não é 100%
confiável, achei numa thread há alguns dias). Vou ver se tem como
mudar esse comportamento.

Obrigado pela ajuda

[]'s

João André Simioni





2010/7/11 Blabos de Blebe <blabos em gmail.com>:
> Só que tem umas pegadinhas aí.
>
> Primeiramente seu gargalo não é processamento, é I/O, certo? Já que rede é I/O.
>
> Dado que você executa um ping externo, invariavelmente, seu script vai
> fazer um fork para executar esse ping. Então, se você está usando 250
> threads ou 250 forks dá no mesmo pois cada thread/fork vai fazer ao
> menos um fork adicional.
>
> Aliás se o seu processo externo pode demorar até seg no caso do host
> não responder, a diferença entre criação de thread ou fork passa a ser
> irrelevante.
>
> Fiz alguns testes aqui na minha rede local (range de 254 ips com 2
> ativos - perto do pior caso) com icmp usando:
>
> qx: 56.500s
> system: 66.959s
> Net::Ping: 26.135s
>
> Fiz os meus testes com o script abaixo:
> http://gist.github.com/470412
>
> O que já mostra muita diferença.
>
> Eu observei não-cientificamente o comportamento do meu sistema via top.
>
> Usando qx e system, vi os ontes de processos ping sendo criados e o
> meu script não conseguiu passar de 50% de uso da cpu, pois, a thread
> dispara o novo processo que vai fazer I/O e ela por tabela espera.
>
> Já com o Net::Ping consegui bater na marca de 71%.
>
> blabos em host:~/workspace/threads$ cat /proc/cpuinfo
> processor       : 0
> vendor_id       : GenuineIntel
> cpu family      : 6
> model           : 13
> model name      : Intel(R) Celeron(R) M processor          900MHz
> stepping        : 8
> cpu MHz         : 900.195
> cache size      : 512 KB
> fdiv_bug        : no
> hlt_bug         : no
> f00f_bug        : no
> coma_bug        : no
> fpu             : yes
> fpu_exception   : yes
> cpuid level     : 2
> wp              : yes
> flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov
> clflush dts acpi mmx fxsr sse sse2 ss tm pbe nx up bts
> bogomips        : 1800.39
> clflush size    : 64
> cache_alignment : 64
> address sizes   : 32 bits physical, 32 bits virtual
> power management:
>
>
> Não adianta muito você escolher entre fork ou thread se sua thread vai
> fazer fork.
>
> Além do mais deve haver para o seu hardware/SO um limite ideal de
> threads/forks para vc minimizar o chaveamento de contexto. Também não
> adianta um milhão de threads se você vai passar a maior parte do tempo
> chaveando entre elas.
>
> Como vc tem bem mais hosts que isso, procure o número ideal de
> threads/processos que minimize a perda por chaveamento de contexto e
> use algum módulo de pool de thread ou fork. Sugiro pra fork o
> Parallel::ForkManager, mais por facilidade mesmo.
>
> Mas lembre-se, seu gargalo *NÃO* é o sistema de threads.
>
> Quanto ao Net::Ping se enrolar, está fora da minha jurisdição, mas eu
> notei um comportamento estranho sim, e adicionei um sleep 3 no final
> do for, que aparentemente resolveu. O que eu notei é que para um host
> down, as 9 etapas de verificação levavam apenas 6 seg, mesmo com
> timeout de 1 seg pra cada. Adicionei o sleep 3 e ficou tudo 'normal'.
>
> Há pessoas muito mais habilitadas na lista do que eu para resolver
> isso. Sugiro de antemão, confirmar essa afirmação cientificamente e/ou
> então submeter um patch.
>
> Gurus? Agora é com vcs. :)
>
> Abraços
>
>
>
> 2010/7/10 João André Simioni <jasimioni em gmail.com>:
>> Fiz um teste agora colocando um sleep 10 no inicio da função checkIp.
>> As threads abrem rápido assim, mas o desempenho ficou ruim. Além
>> disso, notei que as threads não utilizam os demais processadores.
>>
>> Minha solução atual está com um fork no começo, quebrando o código em
>> 8 processos distintos (número de processadores), cada processo com um
>> sub-grupo e criando suas threads (30 threads por grupo).
>>
>> Obrigado pela ajuda
>>
>> João André
>>
>> 2010/7/10 João André Simioni <jasimioni em gmail.com>:
>>> Blabos,
>>>
>>> o loop interno não é nem usado nesse código, como observado pelo
>>> Bruno, porque ele define a quantidade de threads no início. Usualmente
>>> eu disparo uma thread para cada elemento que vou gerenciar, e defino
>>> um limite de threads abertas no início do código e é por isso que esse
>>> loop existe, para controlar o limite de threads (e funciona bem em
>>> diversos outros scripts que não têm o mesmo problema de desempenho).
>>>
>>> Segue os comentários no código:
>>>
>>>> #!/usr/bin/perl
>>>>
>>>> use strict;
>>>> use DBI;
>>>> use Net::Ping;
>>>> use Time::HiRes qw/usleep tv_interval gettimeofday/;
>>>> use threads ('yield', 'stack_size' => 32*4096, 'exit' =>
>>>> 'threads_only', 'stringify');
>>>>
>>>> my $host = '192.168.160.179';
>>>> my $sid  = 'HOMOLOG';
>>>> my $user = 'mac_user';
>>>> my $pass = 'mac_user';
>>>>
>>>> my $dbh = DBI->connect("dbi:Oracle:host=$host;sid=$sid", $user, $pass,
>>>> { AutoCommit => 1 });
>>>>
>>>> my $sth = $dbh->prepare('SELECT CPE_ID, CLIENTE_ID, CPE_DESC,
>>>> CPE_IP_WAN, CPE_NODE, CPE_IF FROM MAC_CPE WHERE CPE_PING = ? AND
>>>> CPE_ATIVO = ?');
>>>> $sth->execute('1', 'A');
>>>>
>>>> my @cpes;
>>>>
>>>> while (my (@row) = $sth->fetchrow_array) {
>>>>     push @cpes, [ @row ];
>>>> }
>>>>
>>>> $dbh->disconnect();
>>>
>>> Até aqui somente listei os elementos a partir do banco de dados.
>>>
>>> O código abaixo quebra o numero de elementos em 250 threads.
>>>
>>>> my $maxThreads = 250;
>>>> my $total      = @cpes;
>>>>
>>>> my $cpePerThread = int($total / $maxThreads);
>>>>
>>>> my @toMonitor;
>>>> my $i = 0;
>>>> my $c = 0;
>>>>
>>>> foreach my $cpe (@cpes) {
>>>>     push @{$toMonitor[$i]}, $cpe;
>>>>     $c++;
>>>>
>>>>     if ($c > $cpePerThread) {
>>>>         $i++;
>>>>         $c = 0;
>>>>     }
>>>> }
>>>>
>>>> print "Para $total cpes, e $maxThreads threads, tenho no final $i
>>>> grupos, com ", scalar @{$toMonitor[0]}, " elementos cada\n";
>>>
>>>
>>>
>>> E imprimo - vou ter o total de elementos dividido em 250 grupos (cada
>>> grupo atendido por uma thread)
>>>
>>>
>>>
>>>> sub processResult {
>>>>     my $r = shift;
>>>>     if (ref $r ne 'ARRAY') {
>>>>         print STDERR "Erro na thread\n";
>>>>         return 0;
>>>>     }
>>>>     my ($result, $host, $ifs) = @$r;
>>>>
>>>> }
>>>
>>> Funcao de processamento do resultado da thread -- aqui não faz nada
>>>
>>>> sub testeClient {
>>>>     my $threadNum = shift;
>>>>     my $cpeGroup  = shift;
>>>>     foreach my $cpe (@{$cpeGroup}) {
>>>>         my ($cpeId, $clientId, $cpeDesc, $cpeIp, $cpeNode, $cpeIf) = @$cpe;
>>>>         my $pingOk = &checkIp($cpeIp);
>>>>         my $status = $pingOk ? 'RESPONDE' : 'MORTO';
>>>>         # print join(", ", $threadNum, $cpeId, $clientId, $cpeDesc,
>>>> $cpeIp, $cpeNode, $cpeIf, $status), "\n";
>>>>     }
>>>>     # print "Finishing thread $threadNum\n";
>>>>     return([1, 0]);
>>>> }
>>>
>>> Código da thread - basicamente chama a sub 'checkIp' para ver se o
>>> host está vivo.
>>>
>>> Abaixo o código de criação das threads - para cada grupo, cria uma thread.
>>>
>>>> my $threadCount = 1;
>>>> foreach my $cpeGroup (@toMonitor) {
>>>>     print "Trying to create thread";
>>>>     my $t0 = [gettimeofday];
>>>>
>>>>     print " in ", scalar localtime $t0->[0], ".", $t0->[1], "\n";
>>>>
>>>>     my $thr = threads->create({scalar => '1'}, 'testeClient',
>>>> $threadCount, $cpeGroup);
>>>>     my $elapsed = tv_interval ( $t0, [gettimeofday]);
>>>>     print "Created thread $thr ($threadCount) - took $elapsed seconds
>>>> to create\n";
>>>>     $threadCount++;
>>>
>>> Esse bloco pode ser removido, pois nunca entra - foi a primeira coisa
>>> que tirei, porque achei que a chamada a threads->list poderia estar
>>> influenciando. Esse método retorna em menos de 1ms em todas as
>>> execuções. Mas se preferir só ignora.
>>>
>>>>     while (threads->list() >= $maxThreads) {
>>>>         my @joinable = threads->list(threads::joinable);
>>>>         for (@joinable) {
>>>>             my $r = $_->join();
>>>>             &processResult($r);
>>>>         }
>>>>         usleep(10000);
>>>>     }
>>>>     my $elapsed = tv_interval ( $t0, [gettimeofday]);
>>>
>>> Finaliza a criação de threads
>>>
>>>> }
>>>
>>> Aguarda as threads concluirem.
>>>
>>>> while (threads->list()) {
>>>>     my @joinable = threads->list(threads::joinable);
>>>>     for (@joinable) {
>>>>         my $r = $_->join();
>>>>         &processResult($r);
>>>>     }
>>>>     usleep(10000);
>>>> }
>>>
>>> Fim de execução
>>>
>>>> sub checkIp {
>>>>     my $ip = shift;
>>>>     my $pingOk = 0;
>>>
>>>
>>> Essa rotina faz 3 testes para cada elemento. Em cada teste, tenta um
>>> ping ICMP, um UDP e um TCP. Tenho que executar o ping do S.O. porque
>>> senão sou obrigado a rodar o script como root e o Net::Ping faz
>>> confusão com o ICMP. Se eu testo um host e ele nao responde no tempo
>>> de timeout e eu em seguida testar outro host, se o primeiro responder
>>> ele acha que o segundo respondeu.
>>>
>>>>     eval {
>>>>         for (1..3) {
>>>>             my $ping = `/bin/ping -c 1 -w 1 $ip`;
>>>>
>>>>             if (grep { / 0% packet loss/ } $ping) {
>>>>                 $pingOk = 1;
>>>>                 return 1;
>>>>             }
>>>>
>>>>             if ($pingOk == 0) {
>>>>                 my $p = Net::Ping->new('icmp');
>>>>                 $p->service_check(1);
>>>>                 if ($p->ping($ip, 1)) {
>>>>                     $pingOk = 1;
>>>>                     return 1;
>>>>                 }
>>>>             }
>>>>
>>>>             if ($pingOk == 0) {
>>>>                 my $p = Net::Ping->new('udp');
>>>>                 $p->service_check(1);
>>>>                 if ($p->ping($ip, 1)) {
>>>>                     $pingOk = 1;
>>>>                     return 1;
>>>>                 }
>>>>             }
>>>>
>>>>             sleep 3;
>>>>         }
>>>>     };
>>>>
>>>>     return $pingOk;
>>>> }
>>>>
>>>
>>> Quanto a usar Java, é que tenho um grupo de desenvolvimento Java do
>>> meu lado - eu posso solicitar a eles, mas eu não vejo porque Perl não
>>> pode atender bem nesse caso.
>>>
>>> Obrigado
>>>
>> _______________________________________________
>> Rio-pm mailing list
>> Rio-pm em pm.org
>> http://mail.pm.org/mailman/listinfo/rio-pm
>>
> _______________________________________________
> Rio-pm mailing list
> Rio-pm em pm.org
> http://mail.pm.org/mailman/listinfo/rio-pm
>


Mais detalhes sobre a lista de discussão Rio-pm