segunda-feira, 28 de novembro de 2016

Transporte de um Tablespace para outro Diskgroup no Oracle

Um diskgroup de minha base (11g) estava perigosamente cheio, então resolvi transportar um tablespace pouco usado para um diskgroup mais lento.

Suponha que os datafiles do tablespace TSX estejam no diskgroup +DG1 e que serão transportados ao diskgroup +DG2. A tarefa é simples e envolve os seguintes passos para cada datafile (executados no RMAN):
  1. Fazer uma cópia no diskgroup destino: backup as copy datafile file# format '+DG2';
  2. Colocar o datafile atual offline: sql 'alter database datafile file# offline';
  3. Fazer a troca para o backup: switch datafile file# to copy;
  4. Fazer recover do novo datafile: recover datafile file#;
  5. Colocar o datafile online: sql 'alter database datafile file# online';
  6. Apagar datafile original: delete datafilecopy 'filename';
Se o tablespace tiver muitos arquivos, ou a tarefa tiver que ser executada em vários ambientes, vale a pena escrever uma consulta (ou uma função) para gerar o script:

select case step.n
           when 1 then
             'backup as copy datafile '||file#||' format ''+DG2'';'
           when 2 then
             'sql ''alter database datafile '||file#||' offline'';'
           when 3 then
             'switch datafile '||file#||' to copy;'
           when 4 then
             'recover datafile '||file#||';'
           when 5 then
             'sql ''alter database datafile '||file#||' online'';'
           when 6 then
             'delete datafilecopy '''||df.name||''';'
          end
       esac
from v$tablespace ts
     inner join v$datafile df on ts.ts#=df.ts#,
    (select rownum n from all_tables where rownum<7) step
where ts.name=:TABLESPACE_NAME
order by df.file#, step.n
E o resultado será algo assim:

backup as copy datafile 106 format '+DG2';
sql 'alter database datafile 106 offline';
switch datafile 106 to copy;
recover datafile 106;
sql 'alter database datafile 106 online';
delete datafilecopy '+DG1/mydb/datafile/tsx_data.265.122421389';
backup as copy datafile 138 format '+DG2';
sql 'alter database datafile 138 offline';
switch datafile 138 to copy;
recover datafile 138;
sql 'alter database datafile 138 online';
delete datafilecopy '+DG1/mydb/datafile/tsx_data.263.183291830';

sábado, 19 de novembro de 2016

O Caos Imposto pela Ordem

Em 2050, os carros autônomos começaram a dominar o mercado de transporte individual no Brasil. Após desistir de impor a ordem ao trânsito, o país estava esperançoso: os novos veículos inteligentes trariam a paz às ruas e o ano terminaria com uma contagem de fatalidades de apenas quatro dígitos.

O típico pedestre foi o primeiro a perceber o seu novo poder. Já que ninguém o atropelaria, ele podia atravessar qualquer avenida sem medo. Ao mesmo tempo, os passageiros dessas maravilhas tecnológicas ficavam cada vez mais irritados: não podiam viajar na velocidade que desejavam e tampouco podiam estacionar onde queriam. Era um inferno.

O trânsito das grandes cidades simplesmente parou nos horários de pico. Pedestres e ciclistas retomaram as ruas. Os proprietários destes novos carros tornaram-se violentos. Primeiro, com ameaças:

- Você sabe quem eu sou?! Eu paguei caro por este carro!!

Isso não resolveu. Afinal, os pedestres estavam acostumadas a carros avançando; uns gritos nervosos não assustavam a ninguém.

Os ex-motoristas estavam deseperados. Haviam pagado caro na esperança de finalmente livrarem-se dos engarrafamentos. O resultado é que estavam perdendo tempo no trânsito como nunca.

Nas cercanias das favelas do Rio de Janeiro, alguns desocupados logo perceberam que podiam improvisar um pedágio sem grande esforço ou risco. Três pessoas paravam os carros e outros recolhiam o dinheiro. Alguns adolescentes dos Jardins, em São Paulo, também adotaram essa tática. Logo surgiu um aplicativo de celular para coordenar os pedágios e evitar a polícia.

Foi quando um delegado atirou no primeiro paisano em plena Avenida Paulista que o verdadeiro caos iniciou. O juiz do caso considerou o homicídio como uma morte de trânsito. Afinal, se fosse um carro comum, o pedestre teria sido atropelado e o caso seria apenas mais uma fatalidade do terrível trânsito brasileiro. Então, não seria justo mudar agora a jurisprudência, quando tantos motoristas já tinham sido inocentados. Além disso, não havia como determinar se o pedestre não tinha a intenção de assaltar ou simplesmente cobrar pelo direito de passagem.

Em pouco tempo, os pedestres estavam devolvendo as balas. O Congresso Nacional rapidamente reconheceu a necessidade de nova legislação e no tempo recorde de sete anos foram criados os conceitos de autodefesa veicular e autodefesa peatonal.

Em 2065, tudo tinha voltado à normalidade. Neste ano, morreram 50 mil brasileiros no trânsito (vítimas de atropelamentos e choques devidos a carros com firmware modificado para ignorar as regras de circulação). Os brasileiros estavam aliviados que os tiroteios nas ruas eram coisa do passado.

segunda-feira, 7 de novembro de 2016

Mandelbrot com expoentes complexos

Depois de explorar o mandelbrot com expoentes maiores de 2, com números reais, e com números negativos, eu tinha que verificar o que acontece com expoentes complexos.

O cálculo é um pouco mais complicado e também mais demorado.

var pr=2;
var pi=-0.009;    
    
function mandel(x,y) {
  var c=1;
  var r=x;
  var i=y;
  while(r*r+i*i<4 && c<MAXITER) {
    var a=Math.atan2(i,r);
    var b=r*r+i*i;
    var p=Math.pow(b,pr/2)*Math.pow(Math.E,-1*pi*a);
    var nr=p*Math.cos(pr*a+0.5*pi*Math.log(b));
    var ni=p*Math.sin(pr*a+0.5*pi*Math.log(b));
    r=nr+x;
    i=ni+y;
    c+=1;
  }
  return c;
}

O expoente tem uma parte real (pr) e uma parte imaginária (pi).

Valores grandes (maiores de 1) de pi distorcem muito a imagem, então usei valores pequenos. De longe, percebe-se que algo não está certo. A imagem abaixo foi gerada com expoente=2-0.009i.


De perto aparecem detalhes interessantes.


Com 2+0.1i, a distorção já é grave.


Eis algunas detalhes curiosos de 1/pi + i/e.




Clique nas imagens para vê-las no tamanho original.
E i/e merece um lugar num museu de arte moderna.





terça-feira, 1 de novembro de 2016

Porta-Retratos Digital Caseiro III (A Mágica)

O CPAN nunca decepciona: há um módulo para rotacionar as imagens conforme os metadados do JPEG. As máquinas fotográficas mais modernas gravam nos arquivos JPEG a orientação das fotos e o módulo Imager::ExifOrientation permite carregar a imagem já virada no sentido correto para exibir na tela.

use Imager::ExifOrientation;

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  my $image = Imager::ExifOrientation->rotate(path => $filename);
  scale($photo_width, $photo_height, $image);
  $photo->configure(-file=>'tmp.jpg');
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  
  $image = Imager::ExifOrientation->rotate(path => $filename);
  scale($thumb_width, $thumb_height, $image);
  $thumb->configure(-file=>'tmp.jpg');
  
  $main->update();
}
A mágica tem seus limites: se o arquivo não contiver a informação de orientação, a imagem será carregada assim como está no arquivo.

segunda-feira, 31 de outubro de 2016

Porta-Retratos Digital Caseiro II (O Bug)

O porta-retratos digital funcionou bem por cerca de 8 horas e então parou com um erro de malloc(). Após algumas simulações e pesquisa dos bugs do Tk, descobri que o defeito está na classe Tk::Photo. Quando a imagem é carregada pela memória (com -data), os bits ficam guardados para sempre. Quando são lidos do disco, os dados não persistem além da próxima leitura.

Então, reescrevi alguns trechos e o código ficou um pouco menor. A função scale() não retorna mais os dados; agora ela escreve um arquivo.

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  $scaled->write(file=>'tmp.jpg', type=>'jpeg');  
}

Por sua vez, a função next_photo() lê este arquivo:

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  scale($photo_width, $photo_height, $image);
  $photo->configure(-file=>'tmp.jpg');
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  
  $image->open(file=>$filename);
  scale($thumb_width, $thumb_height, $image);
  $thumb->configure(-file=>'tmp.jpg');
  
  $main->update();
}

Evidentemente, não quero escrever este arquivo no cartão de memória para não apressar o seu fim. Então, a solução é criar um disco em RAM e escrever lá. Ele não precisa ser muito grande, os JPEGs temporários não passam de 100KB (por isso, o script original demorou a ocupar toda a memória).

quinta-feira, 27 de outubro de 2016

Porta-Retratos Digital Caseiro

Os preços dos porta-retratos digitais estão começando a ficar interessantes, mas construir o próprio é mais divertido.

Usei um Raspberry Pi A+ (256MB de RAM e um ARM11 de 700MHz) com a última versão do Raspbian Jessie. Eu queria mesmo era usar um Raspberry Pi Zero (que custa apenas US$5), mas são muito procurados e ainda não apareceram na minha loja preferida.

Comecei muito otimista (como sempre) e tentei montar uma solução com HTML, Javascript, jQuery, transições, etc. Ela resultou ser muito pesada. Então, tentei em Tcl/Tk, mas manipular os tamanhos das imagens resultou ser muito trabalhoso (possível, mas trabalhoso).

Então, só me restou usar Perl. Foi preciso instalar dois pacotes: Tk e Imager.


sudo apt-get install perl-tk
sudo cpanm Imager


A interface é muito simples. Há um quadro grande com uma foto qualquer e uma foto pequena com a foto seguinte. A foto grande é escolhida aleatoriamente, mas a pequena é sempre a que segue (para haver uma relação entre elas). Além disso, exibo as horas e a data.

O script recebe um único parâmetro que indica o diretório onde as fotos são armazenadas. Ctrl+C suavemente encerra o programa.

Futuramente, espero exibir algo mais interessante no quadro das horas (a previsão do tempo ou as notícias).

#!/usr/bin/perl
use Tk;
use Tk::JPEG;
use Tk::Photo;
use Imager;
use File::Find;
use MIME::Base64;
use POSIX 'strftime';
use strict;

my @file_list;

find(\&wanted, $ARGV[0]);

sub wanted {
    return unless -f;
    return unless /\.jpe?g$/i;
    push @file_list, $File::Find::name;
}

my $main = MainWindow->new (
  -title => 'PiFrame',
  -background => 'black'
);

my $w=$main->screenwidth();
my $h=$main->screenheight();
$main->overrideredirect(1);
$main->MoveToplevelWindow(0,0);
$main->geometry(join('x',$w,$h));

my $photo_width=int($w*3/4);
my $photo_height=$h;
my $thumb_width=$w-$photo_width;
my $thumb_height=int($h/3);

my $canvas=$main->Canvas(
  -width=>$photo_width,
  -height=>$h,
  -background=>'black',
  -highlightthickness => 0);
$canvas->pack(-side=>'left');

my $small_canvas=$main->Canvas(
  -width=>$thumb_width,
  -height=>$thumb_height,
  -background=>'black',
  -highlightthickness => 0);
$small_canvas->pack(-side=>'bottom');

my $time;

my $clock=$main->Label(
  -textvariable=>\$time,
  -width=>100,
  -height=>100,
  -background=>'black',
  -foreground=>'white',
  -highlightthickness => 0,
  -font=>'courier 40 bold');
$clock->pack(-side=>'top');

my $photo=$canvas->Photo();
$canvas->createImage(0,0,-image=>$photo,-anchor=>'nw');
my $thumb=$small_canvas->Photo();
$small_canvas->createImage(0,0,-image=>$thumb,-anchor=>'nw');

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  my $data;
  $scaled->write(data=>\$data, type=>'jpeg');
  return encode_base64($data)
}

my $image=Imager->new();

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  $photo->configure(-data=>scale($photo_width, $photo_height, $image));
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  $image->open(file=>$filename);
  $thumb->configure(-data=>scale($thumb_width, $thumb_height, $image));
  
  $main->update();
}

$canvas->repeat(60000, sub {eval {next_photo()}});
$canvas->repeat(1000, sub {$time=strftime("%H:%M\n%A\n%d/%m", localtime)});

next_photo();

MainLoop;

O sistema operacional que usei para este projeto é o Raspbian com Pixel. As fotos estão armazenadas na pasta /data01/fotos. Então, adicionei a seguinte linha ao arquivo  /home/pi/.config/lxsession/LXDE-pi/autostart para que o sistema inicie automaticamente:

@perl /home/pi/piframe.pl /data01/fotos/

O gasto de memória oscila entre 25MB e 50MB, o que cabe confortavelmente no espaço que o A+ oferece. Um Pi 3 talvez possa executar isso ao mesmo tempo que ofereça o serviço do Amazon Echo.

quinta-feira, 15 de setembro de 2016

Melhorias ao SQL

Lendo um relato sobre um pobre programador que apagou uma tabela inteira, porque esqueceu o where num delete, fiquei a pensar que o SQL deveria ter sintaxe diferente para alguns casos.

Os updates e os deletes de tabelas inteiras deveriam indicar a intenção explicitamente:

update my_table set my_column=0 all rows;
delete other_table all rows;

E se alguém esquecesse do all rows ou do where, seria um erro de sintaxe. Aposto que essa mudança evitaria muito sofrimento.

quarta-feira, 14 de setembro de 2016

Recuperando um roteador WR1043ND com um Raspberry Pi

Um amigo apresentou-me um desafio: recuperar um roteador cuja atualização do firmware dera errado. O roteador, um TP-Link WR1043ND, não funcionava mais. Esse equipamento tem uma interface serial na placa. É uma interface de 3,3V que é compatível com a interface serial do Raspberry Pi.

Infelizmente, não basta abrir a caixa: a interface serial não tem os pinos instalados. Comprei os pinos, um pouco de fio e conectores para construir um cabinho. Depois comecei a perceber que cabinhos com os conectores adequados estão por toda parte: os mouse Genius antigos os tem, assim como os cabos de som dos drives antigos de CD. Até mesmo o cabo de um drive de disquetes ou de HD IDE serviria com algumas modificações. Também aprendi que montar um cabo sem um crimpador é difícil.

No Raspberry Pi, os pinos 6, 8, e 10 servem a porta serial, sendo, respectivamente, o terra (G), a transmissão (Tx) e a recepção (Rx). Eles são vizinhos de dois pinos com 5V (o 2 e o 4), então é preciso ter cuidado. No roteador, para minha sorte, Rx e Tx estão trocados. Percebi isso apenas depois de ter montado o cabo com pinagem idêntica nas duas pontas. Sorte de principiante.

Como os pinos são muito pequenos e não tenho nenhuma prática com soldagem, pedi ajuda à esposa amada, que, embora também não soubesse soldar, tem mais habilidade. O resultado ficou além do esperado.



Do outro lado, os pinos:


São quatro pinos, porque não havia um conector de três disponível. O quarto pino é de energia (3,3V) e é desnecessário para este projeto.

As instruções para atualizar o firmware via a interface serial estão disponíveis na wiki do OpenWRT. Para esse modelo, há duas opções para carregar o arquivo: via tftp e via a própria interface serial. Como eu estava longe do roteador e usando um Raspberry Pi que não tem uma porta ethernet, resolvi executar tudo pela interface serial.

O primeiro passo é instalar o kermit (para enviar o arquivo) e o minicom (para interagir com o roteador):

sudo apt-get install kermit minicom

A porta serial aparece como o device /dev/serial0 (que é apenas um link para /dev/ttyAMA0). Então, para iniciar a comunicação, o comando é:

minicom -b 115200 -o -D /dev/serial0

E só depois liguei o roteador. Ele não dá muito tempo para interromper a sequência de boot. Assim que aparecer o texto "Autobooting in 3 seconds" é preciso digitar tpl para interromer o processo normal. Pode ser preciso desligar e religar o roteador várias vezes.

Pelo minicom, digitei um comando para limpar um bloco de memória e outro para carregar o arquivo novo:

erase 0xbf020000 +7c0000 
loadb 0x81000000

Depois, noutro terminal, iniciei o kermit sem parâmetros e digitei os seguintes comandos:

set line /dev/serial0
set speed 115200
set carrier-watch off
set handshake none
set flow-control none
robust
set file type bin
set file name lit
set rec pack 1000
set send pack 1000
set window 5
send wr1043nd.bin 

Isso deveria durar uns 20 minutos, mas houve muitos erros na transmissão e o envio levou duas horas. Os erros atribuo à qualidade do meu cabo.


Terminado o envio, dois comandos terminam o processo, copiando o arquivo para o flash e reiniciando o roteador:

cp.b 0x81000000 0xbf020000 0x7c0000
bootm 0xbf020000

Eu entendo que a interface serial esteja escondida, porque seria fácil usar uma interface de 5V (ou 12V) e queimar o equipamento, mas acho que foi economia demais não colocar os pinos (que custam centavos). De qualquer forma, o experimento foi um sucesso e várias habilidades novas foram adquiridas.

terça-feira, 6 de setembro de 2016

A linha mais comprida de um arquivo II (O Desafio)

Em janeiro, achei a linha mais comprida de um arquivo usando comandos do shell e usando perl (que resultou ser muito mais rápido).

O Linux pediu revanche, porque eu havia ignorado o awk. Perl provou mesmo ser eficiente.


$time perl -MData::Dumper -ne '$n{length($_)}++; END {print Dumper(%n)}' arquivao.txt
$VAR1 = '1088';
$VAR2 = 349647;

real    0m1.326s
user    0m0.814s
sys     0m0.371s

$time awk 'length($0) > max { max=length($0) } END { print max }' arquivao.txt
1087

real    0m21.400s
user    0m18.596s
sys     0m0.455s

Perl deixou o awk comendo poeira. A diferença nos números deve-se ao fato do awk já ignorar o caractere de fim de linha.

segunda-feira, 5 de setembro de 2016

Múltiplos dos primos

Há anos eu havia escrito um programa para encontrar inteiros candidatos a primos, pulando todos os que eu já sabia serem múltiplos de alguns poucos primos.

Resolvi brincar com o contrário: achar sequências de pulos que levariam a múltiplos dos primeiros primos.


#!/usr/bin/perl
my @primes=qw(
      2      3      5      7     11     13     17     19     23     29 
     31     37     41     43     47     53     59     61     67     71 
     73     79     83     89     97    101    103    107    109    113 
    127    131    137    139    149    151    157    163    167    173 
    179    181    191    193    197    199    211    223    227    229 
    233    239    241    251    257    263    269    271    277    281 
    283    293    307    311    313    317    331    337    347    349 
    353    359    367    373    379    383    389    397    401    409 
    419    421    431    433    439    443    449    457    461    463 
    467    479    487    491    499    503    509    521    523    541 
    547    557    563    569    571    577    587    593    599    601 
    607    613    617    619    631    641    643    647    653    659 
    661    673    677    683    691    701    709    719    727    733 
    739    743    751    757    761    769    773    787    797    809 
    811    821    823    827    829    839    853    857    859    863);

my @steps=(2);
my $product=1;
my @sub=@primes[0..5];
map { $product*=$_ } @sub;
for my $i (2..$product) {
  $found=0;
  for (@sub) {
    if ($i%$_==0) {
      $found=1;
      last;
    }
  }
  if($found) {
    push @steps, $s;
    $s=1;
  } else {
    $s++;
  }
}
print "@steps\n";

A série precisa ir até o produtório dos n primos. Para n=2 (2 e 3), ela vai até 6. Para n=3 (2, 3, e 5), vai até 30, e assim por diante. Os passos são sempre de 1 ou 2, porque um passo maior que 2, pularia um par.

Os primeiros resultados são:

n=1 
2
n=2 
2 1 1 2
n=3 
2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2
n=4 
2 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 
2 2 1 1 1 1 2 1 1 2 1 1 1 1 2 1 1 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 2 2 
1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 1 1 1 1 1 1 2

A soma é igual ao produto, obviamente. Ademais, os inversos das médias são as sequência de frações da última investigação sobre primos. Isto é, para n=2, a média é 6/4 e os dois primos são fatores de 2/3 dos números inteiros. Além disso, as duas metades das sequências são espelhadas, o que podemos usar para economizar espaço e processamento.


quinta-feira, 1 de setembro de 2016

Renovação da habilitação

Parecia mais um dia normal de 2050. Zveg entrou em seu veículo elétrico multipassageiro e iniciou o sistema. Após a breve rotina de inicialização e verificação, o veículo indagou:

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?

Zveg irritou-se, mas conformou-se a finalmente a executar o teste, ao menos para deixar de ter que responder a essa pergunta.

-Certo, vamos logo com isso.
-Iniciando teste de habilitação. Fase 1, teste de visão. Quais são as letras exibidas no parabrisas?
-T, P, E...
-Por favor, mantenha sua cabeça contra o encosto.
-Certo, então, T, P, E, V, e X.
-Resposta satisfatória. Tendo em vista o número de pontos acumulados, Zveg, é preciso executar o teste de simulação e sinalização e o escore mínimo é 73.

O veículo escureceu o ambiente e iniciou a simulação. Zveg estava um pouco preocupado; andava dirigindo com desatenção e agora precisava de um desempenho melhor para compensar as faltas.

Durante 20 minutos, o teste de simulação e sinalização correu. O veículo apresentava distintas situações de tráfego e expedia ordens. Em alguns casos, as ordens levariam a infrações e Zveg devia indicar a ação correta a ser tomada:

-Este local não é adequado para estacionar e tampouco posso fazer o retorno, então devo procurar outra rua.

Ao fim do teste, o veredito foi informado prontamente e sem rodeios:

-O resultado é 76. A próxima renovação será em 180 dias. Deseja repetir o teste de simulação e sinalização? Com 80 pontos, a renovação só será necessária em 365 dias.

Zveg já estava farto.

-Não!
-Enviarei os dados do teste para o registro central. Aguarde um insta..zuuuup.

O veículo escureceu e então as luzes piscaram violentamente por alguns segundos.

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?



quarta-feira, 31 de agosto de 2016

Cobertura dos n primeiros primos

Uma questão bastante simples serviu para eu revisar e combinar duas ferramentas matemáticas: o princípio da inclusão-exclusão e as combinações.

A dúvida era: dados n primeiros primos, qual porcentagem dos inteiros é de múltiplos de ao menos um deles.

Se n=1, o único primo a ser considerado é o 2. Obviamente, metade dos inteiros é múltiplo de 2.

Com n=2, temos os primos 2 e 3. É preciso somar 1/2 e 1/3, mas também subtrair 1/6, porque esses 1/6 já foram contabilizados num caso ou noutro. Aí já vemos que precisamos das combinações e logo depois do princípio da inclusão-exclusão: somam-se as porcentagens de cada primo, depois subtraem-se as combinações dois a dois.

Então, preciso de duas funções e uma lista de primos. A função de combinações (choose) está descrita em detalhes noutro artigo.


#!/usr/bin/perl

my @primes=qw(2 3 5 7 11 13 17 19 23 29);

sub choose {
  my $n=shift;
  my $k=shift;
  my $callback=shift;
  my @rest=@_;
  
  if($k>0) {
    for my $m ($k..$n) {
      choose($m-1, $k-1, $callback, $m, @rest); 
      &$callback($m, @rest) if $k==1;
    }
  } 
}

for my $n (1..50) {
  my @g=@primes[0..$n-1];
  my $sum=0;
  my $op=1;
  for my $k (1..$n) {
    choose($n, $k, sub {
      my $product=1;
      map { $product*=$g[$_-1] } @_;
      $sum+=($op*(1/$product));
      print "$sum\r"; 
    });
    $op*=-1;
  }    
  print "$sum\n";
}

Esse código imprime a porcentagem dos inteiros que são múltiplos dos n primeiros primos, até o n=50. Eu reduzi a lista dos primos para encurtar o código, mas é fácil obter a lista dos primeiros 1000 primos.

O cálculo começa a ficar lento com n=15 ou nessa redondeza, conforme o computador.

O gráfico abaixo mostra a evolução. Ela parece convergir para algo perto de 0,9, mas na verdade ela vai muito lentamente em direção ao 1.


Agora, o que me interessa são as frações que esses números representam. Os primeiros são 1/2, 2/3, 11/15, e 27/35.

terça-feira, 23 de agosto de 2016

Telegram bot no OpenWRT

O roteador TP-Link WR842ND, além de ser relativamente barato, possui uma porta USB e permite rodar o OpenWRT. O desempenho dele (266,64 Bogomips) é semelhante ao de um Pentium 133. Ele possui 8MB de Flash e 32MB de RAM. Então, é uma máquina com potencial. Seria uma pena que fosse usada apenas para oferecer wifi.

Adicionei um pendrive de 16GB e um amigo gentilmente instalou e configurou o OpenWRT (Gargoyle, para ser mais preciso). Além de roteador, com muitos gigas livres, ele pode virar um servidor de arquivos e posso deixar um download grande rodando nele durante a noite, por exemplo.

Ele não oferece o X, obviamente, mas tem uma linha de comando completa via o BusyBox. Já que tenho mais memória via o pendrive, instalei o Perl para facilitar as tarefas.

Para gerenciá-lo remotamente, resolvi escrever um bot do Telegram. E para dar mais emoção, decidi usar o que estivesse já instalado. Só abri exceção para o curl, porque não sou tão masoquista. O shell disponível é o ash: limitado, mas pequeno e eficiente.

O Telegram oferece uma interface sobre HTTP com mensagens em JSON. Por sorte, o OpenWRT já oferece uma ferramenta para processar JSON: jsonfilter. Por azar, não tem nenhuma documentação. Só encontrei o modo de usar no código-fonte.

Então, a única peça que faltava era o curl.

opkg install curl


E esse comando instala um executável de apenas 69KB.

Para enviar uma mensagem com o IP do roteador, um script simples basta:

#!/bin/ash

ip=$(ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2}')

api=https://api.telegram.org/bot<key>
curl -k -s -X POST $api/sendMessage?chat_id=<id>&text=$ip


A opção -k indica que o curl pode ignorar os erros de certificado, o -s suprime as mensagens e o -X precede o POST.

Resolvi colocar cada comando em um arquivo distinto. Para retornar o resultado ao script de controle, duas variáveis são necessárias: telegram_reply para indicar se houve sucesso no processamento e telegram_message com o texto da resposta. Talvez no futuro seja preciso adicionar telegram_type, se eu quiser enviar fotos, por exemplo. O script para tratar o comando /ip fica assim:

#!/bin/ash

ip=$(ifconfig eth0 | grep inet | grep -v inet6 | awk '{print $2}')

telegram_message=$ip
telegram_reply=true

O método getUpdates (que informa os últimos comandos) pode receber um parâmetro para indicar quais comandos já foram respondidos. Para manter registro desse parâmetro, criei um arquivo /tmp/bot_vars.sh. Ele conterá apenas uma linha na forma offset=123.

Então, o script que solicita os comandos e os encaminha para os scripts específicos precisa:
  1. Solicitar os comandos;
  2. Extrair os ids das solicitações, os ids dos solicitantes, e os comandos propriamente ditos;
  3. Para cada comando, separar os parâmetros e invocar o respectivo script;
  4. Se o script retornou com sucesso, enviar a mensagem produzida;
  5. atualizar o arquivo /tmp/bot_vars.sh com o id do último comando.

#!/bin/ash

key='segredo';
api="https://api.telegram.org/bot$key"
source /tmp/bot_vars.sh
offset=$(($offset+1))
status=0
updates=$(curl -s -k -X GET $api/getUpdates?offset=$offset)
status=$(jsonfilter -s "$updates" -e $.ok)
if [ $status = 'true' ]; then
  update_ids=$(jsonfilter -s "$updates" -e $.result[*].update_id)

  for update_id in $update_ids
  do
    sender=$(jsonfilter -s "$updates" \
                        -e "$.result[@.update_id=$update_id].message.from.id")
    command=$(jsonfilter -s "$updates" \
                         -e "$.result[@.update_id=$update_id].message.text")
    cmd=$(echo $command |  awk '{print $1}')
    parms=$(echo $command | awk '{$1=""; print $0}')

    telegram_reply=false
    case $cmd in
      '/ip') source ip.sh $parms ;;
    esac

    if [ $telegram_reply = 'true' ]; then
      curl -k -s -X POST \
        $api/sendMessage -d chat_id=$sender -d text="$telegram_message"
    fi
    
  done

  if [ -n "$update_ids" ]; then
    echo "offset=$update_id" > /tmp/bot_vars.sh
  fi
fi

Não é o quadro mais lindo do museu, mas, sendo shell, não se poderia esperar muito mais. Agora, tenho que me preocupar com os problemas de segurança que isso deve ter.

quinta-feira, 9 de junho de 2016

Logins

Com quase dois milhões de logins registrados no meu log, resolvi verificar em quais horários as pessoas estão mais alertas e erram menos suas senhas.

Os resultados foram surpreendentes.

Hora Erros/Sucessos Total de tentativas
00 10,48 7270
01 36,94 7551
02 97,12 7163
03 104,19 6522
04 440,07 6175
05 73,77 6281
06 8,46 6928
07 0,52 45142
08 0,42 169947
09 0,47 209327
10 0,47 223268
11 0,47 175897
12 0,47 79421
13 0,42 163371
14 0,45 200312
15 0,44 187376
16 0,43 164907
17 0,51 75282
18 0,83 27162
19 1,61 14589
20 2,12 12932
21 2,52 12246
22 2,77 11047
23 4,24 8708

As primeiras horas da manhã não são as melhores horas para trabalhar. Eu não faria testes numa usina nuclear nestas horas. Os 440 erros para cada sucesso às 4h da manhã são deveras surpreendentes. Imagino que as pessoas tentem uma vez ou outra e simplesmente desistam, resolvendo esperar o sol aparecer.

Excluindo os IPs suspeitos (e devidamente bloqueados), a situação melhora um tanto.

Hora Erros/Sucessos Total de tentativas
00 6,11 4504
01 23,01 4779
02 58,9 4373
03 59,19 3732
04 244,64 3439
05 41,07 3534
06 4,7 4173
07 0,43 42420
08 0,4 167186
09 0,45 206585
10 0,45 220532
11 0,44 173169
12 0,42 76697
13 0,4 160585
14 0,43 197555
15 0,42 184612
16 0,4 162143
17 0,45 72495
18 0,65 24442
19 1,13 11882
20 1,46 10194
21 1,73 9517
22 1,82 8274
23 2,56 5919

As melhores horas foram 8h, quando presumo que as pessoas estejam começando a jornada, e 13h, quando presumo que estejam voltando do almoço.

Visando encontrar algum sentido nos números da madrugada, excluí todas as linhas relativas a tentativas vindas de IPs dos quais nunca houve um login com sucesso.

Hora Erros/Sucessos Total de tentativas
00 0,81 1155
01 4,54 1108
02 6,23 528
03 0,72 107
04 2,64 51
05 1,07 174
06 0,26 929
07 0,32 39324
08 0,37 164102
09 0,42 203523
10 0,42 216503
11 0,41 169326
12 0,35 73287
13 0,35 156826
14 0,39 193020
15 0,38 180017
16 0,36 157655
17 0,37 68708
18 0,41 20924
19 0,49 8317
20 0,57 6521
21 0,54 5365
22 0,49 4368
23 0,45 2426

Com isto, os números da madrugada revelam que há muitos engraçadinhos tentando entrar onde não deviam.

quarta-feira, 1 de junho de 2016

Ajustando tablespaces com facilidade

Uma tarefa recorrente na manutenção de um banco de dados Oracle é o de colocar objetos nos tablespaces corretos. Para facilitar o trabalho, recorri ao metadados. A rotina abaixo procura todas as tabelas, todos os índices e todos os LOBs de um schema e verifica se estão nos tablespaces indicados.

create or replace procedure 
  ajustar_tablespaces(p_schema varchar2, 
                      p_table_ts varchar2, 
                      p_index_ts varchar2, 
                      p_lob_ts varchar2) is
begin

  --Indices
  for r in 
   (select 'alter index '||p_schema||'.'|| index_name 
         ||' rebuild tablespace '||p_index_ts cmd 
    from all_indexes
    where owner=upper(p_schema) 
      and tablespace_name<>upper(p_index_ts))
  loop
    dbms_output.put_line(r.cmd);
    execute immediate r.cmd;
  end loop;

  --Tabelas
  for r in 
   (select 'alter table '||p_schema||'.'||table_name
         ||' move tablespace '||p_table_ts cmd 
    from all_tables 
    where owner=upper(p_schema) 
      and tablespace_name<>upper(p_table_ts))
  loop
    dbms_output.put_line(r.cmd);
    execute immediate r.cmd;
  end loop;

  --LOBs
  for r in 
   (select 'alter table '||p_schema||'.'||table_name
         ||' move lob('||column_name||') store as (TABLESPACE '||p_lob_ts||')' cmd 
    from all_lobs 
    where owner=upper(p_schema) 
      and tablespace_name<>upper(p_lob_ts))
  loop
    dbms_output.put_line(r.cmd);
    execute immediate r.cmd;
  end loop;
  
  --Reconstroi os indices
  for r in 
   (select 'alter index '||p_schema||'.'||index_name
         ||' rebuild' cmd  
    from all_indexes 
    where owner=p_schema 
      and status='UNUSABLE') 
  loop
    dbms_output.put_line(r.cmd);
    execute immediate r.cmd;
  end loop;

end;
Então, para corrigir os tablespaces dos objetos de um schema, eu poderia fazer uma chamada como esta:

  exec ajustar_tablespaces('INFO', 'ts_data01', 'ts_index02', 'ts_lobs');

Os comandos são sendo impressos para que se possa acompanhar o andamento do trabalho, principalmente se os objetos forem grandes.

sexta-feira, 27 de maio de 2016

Flashback de código no Oracle

Depois de uma tentativas desastrada de parcialmente atualizar uma package, precisei usar o flashback para recuperar o código original. Isso é fácil no Oracle:

  select text 
  from all_source as of timestamp sysdate -1/24 --uma hora
  where owner=:owner and name=:package_name and type='PACKAGE BODY'

Infelizmente, o SQLDeveloper exporta o texto com linhas adicionais. Contornar isso com um pouco de Perl é simples:

  perl -ne "print if !/^$/" package.sql > package_sem_linhas_vazias.sql

Esta linha de Perl imprime todas as linhas do arquivo (último parâmetro) que tiverem alguma coisa (/^$/ é verdadeiro para linhas vazias).

sábado, 13 de fevereiro de 2016

Solução caseira para segurança doméstica

As soluções de segurança que tenho visto são caras, então resolvi desenvolver uma solução caseira. Decidi que com um Raspberry Pi, uma câmera barata e um pouco de código, poderia fazer algo tão bom quanto sistemas dez vezes mais caros.

O mais importante é que o sistema deveria enviar notificações para o celular. O Whatsapp não permite robôs, então recorri ao Telegram.

Desenvolvi usando um Raspberry Pi B, mas o sistema final roda num Raspberry Pi A+ (que é ainda menor e mais barato). Acredito que funcionaria igualmente bem num Raspberry Pi Zero (que custa apenas US$5).

A ideia do projeto é bastante simples: a câmera, quando ativada por movimento, copia imagens para o Raspberry Pi e este, por sua vez, as encaminha para uma conta do Telegram. O Telegram entrega as fotos para o meu celular.

Se o código da câmera fosse aberto, ou ela oferecesse alguma API, seria possível que ela falasse diretamente com o Telegram. Ponto negativo para o código fechado.

Ingredientes
  • Um Raspberry Pi B (512MB de RAM, 700MHz de CPU e custa ~US$25);
  • Uma câmera D-Link 930LB (custa menos de R$200,00);
  • Uma conta no Telegram (Whatsapp não aceita robôs);
  • Um cartão de memória SDHC de 16GB (um de 8GB já resolveria);
  • Um pouco de Perl.
Passos

1. Gravar Raspbian no cartão.

2. Instalar um servidor de ftp (sudo apt-get install vsftpd).

3. Configurar o servidor de ftp (/etc/vsftpd.conf).

É preciso habilitar a possibilidade de escrever e de fazer login com contas locais:


local_enable=YES
write_enable=YES


4. Configurar um ramdisk (para não desgastar o cartão de memória, se o número de escritas fugir ao controle e porque é mais rápido).

É preciso adicionar esta linha ao arquivo /etc/fstab:


tmpfs  /home/pi/tmp  tmpfs  nodev,nosuid,size=1M 0 0


Esse comando cria um espaço de 1MB que fica montado no diretório tmp/ dentro do home do usuário pi, mas poderia ser qualquer outro lugar. Os arquivos não passam de 50KB, então essa área poderia ser muito menor.

5. Configurar a câmera.

Configurei a câmera para fazer o ftp da imagem quando for detectado um movimento (usei sensibilidade de 30%, porque ela envia muitas imagens com mais que isso).

O nome do arquivo indiquei como DCS-930LB e isso acaba resultando num arquivo JPEG chamado DCS-930LB.jpg.

6. Criar uma conta no Telegram.

É preciso procurar a conta @BotFather e conversar. Ele dá todas as instruções.

7. Programar em Perl (essa é a melhor parte).

É preciso instalar algumas bibliotecas, o curl, o cpanm e o módulo WWW::Telegram::BotAPI:

sudo apt-get install libnet-ssleay-perl libio-socket-ssl-perl --fix-missing
sudo apt-get install curl
sudo curl -L http://cpanmin.us | perl - --sudo App::cpanminus
sudo cpanm WWW::Telegram::BotAPI

Usei um modelo de robô e fiz algumas adaptações.

Em primeiro lugar, criei ums lista das contas autorizadas (para que estranhos não brinquem com meu robô):


my %chat_ids=(
  123456789=>'forinti',
  987654321=>'derpina'
);


Depois, mudei o loop principal para descansar 5s a cada iteração, verificar se a imagem mudou (pela hora de criação), e enviar a imagem se algo novo surgiu.


my $paused=0;
my $last_update=get_last_update();
while(1) {
  sleep(5);
  my $update=get_last_update();
  if(!$paused && $update>$last_update) {
    for my $id (keys %chat_ids) {
      $api->sendPhoto({
         chat_id=>$id, 
         photo=>{file=>'/home/pi/tmp/DCS-930LB.jpg'}
      });
    } 
    $last_update=$update;
  }
  ...


Finalmente, criei comandos para recuperar a última imagem (/li), parar o envio (/pause) e reiniciar o envio (/go).

sub get_last_update {
  return (stat('/home/pi/tmp/DCS-930LB.jpg'))[9];
}

sub get_last_image {
  return {
    method=>'sendPhoto',
    photo=>{ file=> '/home/pi/tmp/DCS-930LB.jpg' }
  };
}

sub pause {
  $paused=1;
  return 'Parou. Por enquanto...';
}

sub go {
  $paused=0;
  return 'Reiniciou.';
}


Resultado

Consegui produzir um sistema de segurança bastante simples e barato. Ele consome pouquíssima energia (menos de R$10,00 para operar um ano inteiro) e não precisa ser monitorado já que envia alertas. Por usar pouca energia, poderia funcionar ligado a uma bateria ou a um painel solar. Se conectasse a uma rede de celular, seria realmente resiliente.

terça-feira, 19 de janeiro de 2016

A linha mais comprida de um arquivo

Eu precisava descobrir quantas colunas tinha a linha mais comprida de um arquivo. O único porém é que o arquivo é muito grande.

Primeiro, tentei um pouco de shell.

while read line; do echo -n "$line" | wc -c; done< arquivao  | sort | uniq -c

Isso deveria apresentar o número de linhas com determinado comprimento e o número de ocorrências de cada comprimento (não há muita variação nos comprimentos das linhas, embora haja muitas linhas).

Infelizmente, não tive paciência de deixar esta pequena solução terminar. Resolvi averiguar se o Perl seria mais rápido.

perl -MData::Dumper -n -e '$n{length($_)}++; END {print Dumper(%n)}' arquivao


A solução é bem simples e muito mais rápida que a primeira. Com os parâmeteros -n e -e, o bloco de código é aplicado a cada linha do arquivo (último parâmetro). Cada tamanho de linha vira uma chave no hash %n e é incrementada a cada ocorrência. No fim, uso o módulo Data::Dumper para apresentar o que há dentro de %n.

sexta-feira, 15 de janeiro de 2016

Gotas de otimização Oracle

Há dois problemas de SQL comuns que vejo, com frequência, sendo resolvidos com complexidades e ineficiências desnecessárias.

O primeiro é o de precisar o número de registros de uma consulta junto com os dados. A solução ineficiente é a de executar a consulta duas vezes; usando count() primeiro e buscando os dados depois. As funções analíticas são pouco conhecidas ainda, infelizmente.


select count(1)
from pessoas;

select nome
from pessoas;


Usando a versão analítica de count(), as duas consultas podem ser unidas.

select nome, count(1) over ()
from pessoas


E ganha-se uma coluna com o total de linhas.

O segundo problema é o de retirar acentos. Já vi soluções com múltiplos ifs, com case, e até com muitos replace() aninhados.

A minha solução preferida é muito simples e fácil de ler:

select nome, translate(nome,
                'ÀÁÃÂÄÈÉÊËÙÚÛÜÌÍÎÏÒÓÔÕÖÇÑàáãâäèéêëùúûüìíîïòóôõöçñ',
                'AAAAAEEEEUUUUIIIIOOOOOCNaaaaaeeeeuuuuiiiiooooocn') 
from pessoas;


Talvez haja uma solução mais curta (sei que há em Java), mas não creio que haja uma tão legível.

quarta-feira, 13 de janeiro de 2016

Os problemas médicos da informática

O modelo de medicina no Brasil tem um problema fundamental. Os médicos são remunerados por procedimentos, exames, consultas, etc. Isto é, sua remuneração não aumenta com a saúde dos pacientes. Ela aumenta, isto sim, quanto pior for a saúde das pessoas.

Na informática, isso seria como pagar os programadores por linha de código. Fosse essa a nossa realidade, teríamos legiões de programadores Java e algumas poucas almas iluminadas usando Perl.

domingo, 3 de janeiro de 2016

Receitas

A comparação de programas e algoritmos com receitas é antiga e comum, mas sempre me incomodou.

Acho que achei uma maneira de melhorá-la.

Um programa é como uma receita, mas uma receita que requer cinco mil ingredientes, dois milhões de passos, e dezesseis panelas (para um fogão de quatro bocas).