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

Blabos de Blebe blabos em gmail.com
Domingo Julho 11 10:22:49 PDT 2010


> De 4770 hosts, eu normalmente não tenho mais do que 100 sem resposta.

Não tenho tantos hosts :)

> Além disso, inicialmente eu gostaria de abrir uma thread por teste,

*Eu* acho que é uma boa abordagem, desde que vc não fique terninando e
criando denovo as threads, mas sinceramente, só se o tempo de
inicialização for mesmo preocupante.

> 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.

Eu não sei se/como dá pra setar algum parâmetro de afinidade das
threads pela API em Perl, mas essa abordagem resolve bem também.

> 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.

Boa!

Talvez seja melhor também inverter a ordem do for. Ao invés de testar
um protocolo de cada vez e esperar, de repente testar cada um três
vezes antes de passar pro próximo, mas to só especulando agora.

...

Não que você tenha afirmado isso, mas eu fiquei preocupado em mostrar
que a inicialização de threads não é tão custosa assim.

Tem uma lenda urbana mesmo entre os programadores C mais "antigos",
que inicializar thread é lento. Isso vem da época que thread era
implementada via fork, lá nos idos do kernel 2.4. A partir do 2.6, se
não me engano, threads são implementadas como threads mesmo (!?).

...

No fonte do Net::Ping, lá pela linha 413 tem:
use constant ICMP_STRUCT      => "C2 n3 A"; # Structure of a minimal ICMP packet

Já na linha 451 temos:
$msg = pack(ICMP_STRUCT . $self->{"data_size"}, ICMP_ECHO, SUBCODE,
              $checksum, $self->{"pid"}, $self->{"seq"}, $self->{"data"});

O pid é armazenado como short (0 a 64k - 1). Eu não faço idéia se dá
pra mexer nessa mensagem.

Realmente parece um problema, mas aqui já to saindo da minha área denovo :)

Abraços

2010/7/11 João André Simioni <jasimioni em gmail.com>:
> 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
>>
> _______________________________________________
> 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