sexta-feira, 15 de dezembro de 2017

Docker com DBD::Oracle

São poucas as aplicações que não requerem conexão a um banco de dados. Então, após conseguir criar uma imagem de Docker com Perl e alguns pacotes adicionais, resolvi experimentar algo mais complicado: instalar o DBD::Oracle. Este pacote já é naturalmente difícil de instalar, mas com alguma experimentação, descobri uma maneira rápida e simples de resolver este problema.

Em primeiro lugar, é preciso buscar os rpms do cliente da Oracle. Inicialmente, usei os mais modernos (versão 12.2), mas estes não tinham tudo que o DBD::Oracle verifica na fase de testes (e a instalação falha). Então, usei os seguintes arquivos da versão 11.2:
  • oracle-instantclient11.2-devel-11.2.0.3.0-1.x86_64.rpm
  • oracle-instantclient11.2-basic-11.2.0.3.0-1.x86_64.rpm
  • oracle-instantclient11.2-sqlplus-11.2.0.3.0-1.x86_64.rpm
Dentro da pasta do projeto, é preciso extrair os arquivos dos rpms, desta maneira: 

rpm2cpio oracle-instantclient11.2-devel-11.2.0.3.0-1.x86_64.rpm | cpio -idmv
rpm2cpio oracle-instantclient11.2-basic-11.2.0.3.0-1.x86_64.rpm | cpio -idmv
rpm2cpio oracle-instantclient11.2-sqlplus-11.2.0.3.0-1.x86_64.rpm | cpio -idmv

A ordem não é importante. O resultado será uma pasta usr/ com várias subpastas e diversos arquivos ocupando cerca de 183MB.

O Dockerfile requer apenas que seja adicionada essa pasta e que sejam preparadas as variáveis de ambiente.

FROM        perl:latest
MAINTAINER  forinti

ENV ORACLE_HOME /usr/lib/oracle/11.2/client64
ENV PATH $PATH:$ORACLE_HOME
ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:$ORACLE_HOME/lib:/usr/lib64

COPY usr/ /usr/
RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm DBI
RUN cpanm -v DBD::Oracle
RUN cpanm HTML::Parser
RUN cpanm Dancer2

EXPOSE 3000

CMD perl /app/hello.pl


As três linhas com ENV ajustam os valores das variáveis de ambiente. A opção -v na linha de instalação do DBD::Oracle faz com que todo o andamento da instalação seja impresso na tela. Sem essa opção, o cpanm escreve num arquivo de log que acaba sendo perdido quando ele falha e o docker termina.

Testes simples comprovaram que o driver funciona.

quarta-feira, 13 de dezembro de 2017

Primeiros passos com Docker

Um dilema que enfrento com frequência é o de instalar pacotes novos em servidores nos quais não quero mudar muito ou onde já existem versões conflitantes. Outro problema é o de manter registro de todos os pacotes que uma instalação complexa requer.

O Docker permite isolar as aplicações e então decidi experimentar criar uma imagem com a última versão do Perl, mas que executasse uma aplicação definida alhures e montada em tempo de execução.

Então, escrevi uma pequena aplicação com Dancer:

#!/usr/bin/perl
use Dancer2;

get '/hello/:name' => sub {
    return "Why, hello there " . params->{name};
};

dance;

É muito simples. Ela criar um servidor que atende a requisições do tipo http://localhost:3000/hello/nome. A porta default do Dancer é a 3000.

Então, o próximo passo foi definir uma imagem para o Docker a partir da última imagem do Perl. O Dockerfile contém:

FROM        perl:latest
MAINTAINER  forinti

RUN curl -L http://cpanmin.us | perl - App::cpanminus
RUN cpanm Dancer2

EXPOSE 3000

CMD perl /app/hello.pl


As seções são:
  • FROM - indica a imagem inicial;
  • MAINTAINER - serve apenas para registrar o dono do projeto;
  • RUN - executa os comandos exatamente como numa linha de comando;
  • EXPOSE - indica a porta que estará disponível para comunicação; e
  • CMD - indica o comando que será executado quando o contêiner for criado.
Para facilitar a gerência do contêiner, resolvi usar o docker-compose, conforme o arquivo de configuração abaixo (docker-compose.yml):

version: '3.3'
services:
  hello:
    build: .
    container_name: hello
    restart: unless-stopped
    volumes:
      - type: bind
        source: /home/forinti/hello/
        target: /app
    ports:
      - "3000:3000"

Está definido um serviço (hello:) que servirá a porta 3000 (ports: define que a porta 3000 de dentro do contêiner corresponderá à porta 3000 fora do contêiner). O diretório /home/forinti/hello será montado dentro da imagem como /app. Além disso, o serviço será reiniciado, exceto se explicitamente terminado.

O diretório /home/forinti/hello contém os seguintes arquivos:

total 20
drwxrwxr-x  2 forinti forinti 4096 Dez 13 15:28 ./
drwxr-xr-x 49 forinti forinti 4096 Dez 13 12:37 ../
-rw-rw-r--  1 forinti forinti  223 Dez 13 15:27 docker-compose.yml
-rw-rw-r--  1 forinti forinti  172 Dez 13 15:28 Dockerfile
-rwxrw-r--  1 forinti forinti  120 Nov 21 16:19 hello.pl*

Para criar a imagem, basta rodar "docker-compose build". E, para iniciar o serviço, "docker-compose up".

$curl -XGET localhost:3000/hello/forinti
Why, hello there forinti


Dá para pegar gosto pela coisa.

sexta-feira, 24 de novembro de 2017

Perl é ilegível

...para quem não sabe Perl. Claro. Mas é uma provocação que escuto muito de meus colegas que gostam de Java. Eu também gostava, mas antes dela tentar tanto se transformar numa linguagem hipster.

Bom, resolvi comparar a legibilidade com um problema simples: tenho um array e quero criar um string com a concatenação de todos os elementos não vazios.


my $s=join ';', grep {length>0} @lista;

Parece bem razoável. Um colega apresentou uma solução em Python.

s=';'.join([x for x in lista if len(x)>0])

Gostei. Simples e claro. Dá para usar. Já minha primeira tentativa em Java foi um pouco mais prolixa:

String s=String.join(";",(new ArrayList(lista))
  .removeAll(Arrays.asList("", null)));

String e String, ArrayList, Arrays e asList. Parece um poema concreto.

Um colega apontou o fato de haver uma alternativa mais moderna:

String s=lista.stream()
  .filter(s->s!=null && s.length()>0)
  .collect(Collectors.joining(";"));

Tive que quebrar esta solução moderna em 3 linhas para caber neste pequeno blog.

sexta-feira, 19 de maio de 2017

Consertando um Kobo

Há alguns anos ganhei um Kobo Touch. Ele funcionou muito bem até que, no último verão, a tela falhou. Por excesso de manuseio, ela deve ter fissurado e deixou de atualizar boa parte de sua área.



Imediatamente procurei uma forma de consertá-lo e descobri que é possível comprar a tela (da China, obviamente). Abri o leitor e descobri que o modelo da tela é ED060SCE(LF). Encontrei vários vendedores no AliExpress e decidi por um que vendia um ED060SCE(LF)C1. Não era exatamente o modelo do meu, mas servia. Infelizmente, não encontrei uma explicação para as variantes (para poder, ao menos, escolher a mais recente). Aparentemente, ED indica o fabricante (E-Ink); 060 indica o tamanho (6"); e SCE(LF) indica o modelo. Depois disso, as letras e os números indicam variações que só o fabricante conhece. Neste caso, encontrei T1 e C1.

Paguei US$18,00 (menos de R$60) e isso inclui ferramentas: quatro chaves de fenda (só foi preciso usar uma) e duas peças de plástico (uma para abrir e outra que não achei utilidade). Menos de um quinto do valor de um aparelho novo, embora a Livraria Cultura (que representava a canadense Kobo no Brasil) tenha desistido de vendê-lo. É uma pena, porque é um aparelho muito bom. Por outro lado, considerando o valor da tela, percebe-se que o valor de venda do aparelho era excessivo (eu comprei uma tela só e imagino que uma compra grande consiga um preço ainda melhor).



Duas coisas interessantes aparecem dentro do Kobo, Primeiro, há um cartão microSDHC de 2GB. Portanto, é possível aumentar a capacidade de armazenamento. Não testei, porque 2GB de livros já é uma quantidade infindável de leitura e, de qualquer maneira, há também um slot externo. Em segundo lugar, há uma porta serial. Se um dia a tela voltar a falhar, posso tranformar o Kobo noutra coisa.

A maior dificuldade na troca foi descolar a tela original. A placa-mãe está aparafusada numa base de plástico (que é forte, por sorte) e esta base é aparafusada à caixa. A tela fica colada tanto à caixa como à base de plástico. E a cola é forte. Foi preciso quebrar a tela em muitos pedacinhos para poder extraí-la da armação. A cola permaneceu e bastou para encaixar tudo novamente.

Acho triste que esses projetos sejam feitos de maneira a dificultar a manutenção. A caixa é montada por encaixes, mas eles exigem força e não é difícil quebrar a tela no processo. Além disso, a bateria é colada e os fios soldados: não é fácil trocá-la.

Embora não fosse tecnicamente interessante, esse conserto deu a satisfação de dar nova vida a um aparelho que, doutra sorte, iria para o lixo.

sexta-feira, 5 de maio de 2017

Anagramas

Inspirado num artigo publicado no do Hacker News, resolvi explorar os anagramas da língua portuguesa. O algoritmo é muito simples. Cada palavra é transformada numa chave que é composta por suas letras em ordem alfabética. Todas as palavras são guardadas num hash de acordo com a chave. Então, todas as palavras que possuem as mesmas combinações de letras são naturalmente agrupadas.


use strict;

my $dict;
my %anagrams;
open($dict, '<', 'wordlist-preao-latest.txt');
while (my $word=<$dict>) {
  chomp $word;
  my $index=$word;
  $index=~s/\-//g;  
  my $sorted = join "", sort split //, $index;
  push @{$anagrams{$sorted}}, $word;
}

for my $words (values %anagrams) {
  print "@$words\n" if @$words>1;
}


Modifiquei o código para que ignorasse os traços. O dicionário que encontrei possui, inclusivamente, todas as conjugações dos verbos.

O campeão de variações (22) foi este:


amestrei eremitas estiar-me estreiam 
estremai mastreei mastreie meterias 
rasteiem remateis remetais remetias 
reteimas ser-te-iam seriam-te teimar-se 
teimares temerias ter-me-ias ter-se-iam 
teriam-se terias-me


E os anagramas mais longos não são muito interessantes, porque a maioria é de diferentes conjugações. O mais longo é:

transcendentalizar-lhe-ias transcendentalizar-lhes-ia

E há muitos desses antes deste:

constitucionalizardes desconstitucionalizar

Pelo menos são verbos diferentes.

Com uma pequena adição ao programa, gerei um arquivo com todos os anagramas (um por linha) ordenados por ordem decrescente de comprimento:

my @sorted= sort { length $b <=> length $a } keys %anagrams;
for my $i (0..$#sorted) {
  if(scalar(@{$anagrams{$sorted[$i]}})>1) {
    print join ' ',@{$anagrams{$sorted[$i]}};
    print "\n";
  }
} 

São mais de 114 mil linhas e cerca de 3MB. A evolução natural é transformar isso num webservice e vender o AaaS (Anagrams as a Service).

quinta-feira, 6 de abril de 2017

Mandelbrot com expoentes complexos e animados

Depois de descobrir como gerar um mandelbrot com expoentes complexos, caí no dilema de como fazer uma animação disso. O dilema é qual intervalo escolher. Tendo duas dimensões, os números complexos podem variar tanto no eixo real quanto no eixo imaginário. Há infinitas variações possíveis.

Dado que a exponenciação com números imaginários tem o efeito de girar os números, decidi animar usando vetores de tamanho 2 em torno do zero. Então, eu começaria com 2 (o mandelbrot clássico). Girando o vetor por 90 graus no sentido anti-horário, eu chegaria a 0+2i. Mais 90 graus me levariam a -2, depois 0-2i e, finalmente, com 360 graus, eu estaria de volta a 2.

Então, a parte real varia com o cosseno e a parte imaginária varia com o seno. Basta divir os 2*pi radianos do círculo pelo número de quadros que quisermos.

Testei primeiro com 4 passos e obtive as seguintes imagens:





Pareceu promisor. Então, decidi fazer uma animação com 1000 quadros.

use GD;
use strict;
use constant PI => 4 * atan2(1, 1);
use constant E => exp(1);
use constant MAXITER => 256;
use constant SIDE => 512;
use constant FRAMES => 1000;

sub mandel($$$$) {
  my $c=1;
  my ($x, $y, $rp, $ip)=@_;
  my $r=$x;
  my $i=$y;
  while($r**2+$i**2<4 && $c<MAXITER) {
    my $a=atan2($i,$r);
    my $b=$r**2+$i**2;
    my $p=($b**($rp/2))*(E**(-1*$ip*$a));
    my $nr=$p*cos($rp*$a+0.5*$ip*($b!=0?log($b):0));
    my $ni=$p*sin($rp*$a+0.5*$ip*($b!=0?log($b):0));
    $r=$nr+$x;
    $i=$ni+$y;
    $c++;
  }
  return $c;
}

my $img=new GD::Image(SIDE,SIDE); 
my @colours=();
for my $c (0..255) {
  $colours[$c]=$img->colorAllocate(($c*7)%255, ($c*13)%255, ($c*19)%255),
};

for my $frame (0..FRAMES-1) {
  my $theta=0;
  if($frame>0) {
    $theta=(2*PI)*($frame/FRAMES);
  }
  my $ip=2*sin($theta);
  my $rp=2*cos($theta);
  for my $a (0..SIDE) {
    for my $b (0..SIDE) {
      my $d=mandel(-2+(4*$a/SIDE), -2+(4*$b/SIDE), $rp, $ip);
      $img->setPixel($a, $b, $colours[$d]);
    }
  } 
  open(IMAGE, sprintf(">frame%04d.png", $frame));
  binmode IMAGE;
  print IMAGE $img->png;
  close IMAGE;
}

O código é simples e a biblioteca GD torna fácil a geração de PNGs (ou GIFs ou JPEGs...). Depois, usei ffmpeg para juntar tudo numa animação.

ffmpeg  -i frame%04d.png -vf curves=preset=lighter video.mp4

E o resultado final é este vídeo:


O vídeo está no youtube também.

segunda-feira, 3 de abril de 2017

Cobertura dos n primeiros primos II

Quando calculei a porcentagem de números inteiros que possuem fatores dentre os n primeiros primos, procurei calcular diretamente o valor. Deixei escapar uma maneira muito mais simples que é a de calcular quantos números não são cobertos pelos n primeiros primos.

Esse cálculo é mais simples. Calcular da maneira mais complexa me ensinou algumas coisas, mas a solução mais simples também vale a pena.

Os números que não são múltiplos de 2 são 1/2 do total. Os que não são múltiplos de 3 são 2/3 do total. Os que não são múltiplos de 5 são 4/5. E assim por diante.

Os que não são múltiplos de 2, 3, ou 5, são (1-1/2)(1-1/3)(1-1/5) do total. É muito mais simples a conta.

Em menos de 1s, um programa calculou que os primeiros 10.000 primos fazem parte de 95,14% de todos os inteiros. O programa original não consegue chegar a n=13 nesse tempo. Os primeiros 100.008 ajudam a quebrar a barreira dos 96%, mas o primeiro milhão não chega a 97% (96,6%).

O gráfico abaixo mostra o resultado dessas contas. Ele mostra, em escala, logarítmica, quantos inteiros não têm um fator dentre os primeiros n primos.



O código é tão insosso quanto o gráfico, então não vou publicá-lo.