[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