sexta-feira, 24 de maio de 2013

Garimpando logs do Apache

Devido a um surto de acessos, resolvi descobrir quem estava vasculhando meus sistemas.

Cada linha do log do Apache tem essa aparência:
  66.249.75.122 - - [24/May/2013:00:01:17 -0300] "GET /a.html HTTP/1.1" 403 213
Meu objetivo era obter uma lista de IPs organizados por números de acessos. Após lutar com os parâmetros de xargs e grep, descobri que algumas opções do sort e do uniq resolvem o problema.

A solução final foi esta:
  cat access_log | cut -d - -f 1 | sort | uniq -c | sort -nr
Os detalhes são estes:
  1. cat imprime o arquivo;
  2. cut -d - -f 1 corta cada linha em campos separados por "-" e extrai o primeiro deles;
  3. sort ordena as linhas
  4. uniq -c elimina as linhas repetidas e adiciona o número de linhas
  5. sort -nr ordena as linhas numericamente (usando o primeiro número em cada uma) em ordem decrescente.
As primeiras 3 linhas foram estas:
  3149 66.249.75.122
  3108 66.249.75.22
  2747 66.249.75.143
Para descobrir a quem pertencem esses IPs, posso usar o nslookup adicionando o seguinte:
  | sed -n '1,3p' | cut -d ' ' -f 5 | nslookup
As adições foram estas:
  1. sed -n '1,3p' imprime as 3 primeiras linhas (os 3 IPs com mais ocorrências no log);
  2. cut -d ' ' -f 5 recorta o IP (porque eles estão acompanhados do número de ocorrências);
  3. nslookup procura os nomes associados aos IPs.
E, como resultado, descobri que o maior usuário tem sido o Google. Talvez haja uma maneira mais simples, mas duvido que seja tão divertida.

quinta-feira, 9 de maio de 2013

Relato de uma travessia a um novo banco de dados

No qual narram-se os trabalhos e as dificuldades enfrentados por 60GB para alcançar um novo banco de dados. 

Neste ano do nosso Senhor de dois mil e treze, 60GB de dados empreenderam com grande valor uma mui laboriosa jornada até um novo banco de dados. Este que vos narra demonstra como as ferramentas do Linux contribuiram para amenizar os trabalhos e compensar a inexperiência da tripulação com as ferramentas da Oracle.

A jornada iniciou-se pelo porto de Toad, a partir do qual foram gerados arquivos ctl para as tabelas que transportariam os dados. Sendo estas naus mui frágeis, logo apresentaram problemas com as âncoras das colunas, sendo estas o conhecido ponto-e-vírgula. Ademais, muitos campos de texto possuíam aspas duplas; carácter este que serve também para delimitar os conteúdos.

Sendo assim, voltamos ao porto e buscamos novas naus, desta vez ancoradas com o tilde. Para as aspas, buscamos auxílio com uns comerciantes mouros que passavam com seus camelos.

  perl -pe "s/(?<=[^~])\"(?=[^~])/\"\"/g" dados.ctl > dados2.ctl

Essa singela expressão regular substitui as aspas que não estejam na vizinhança de um tilde por duas aspas e assim evitamos problemas com o SQL Loader da Oracle.

Infelizmente, os mouros estragaram os cabeçalhos. Nosso Senhor há de perdoar nossa falha e nunca voltaremos a negociar com esses infiéis. Ao menos, roubamos-lhes a sapiência a respeito dos operadores de lookahead (?=) e lookbehind (?<=) que, buscando revertê-los ao serviço de nosso Senhor, permitem procurar sem consumir a fonte. Essa habilidade nos é muito útil na presença de aspas separadas por um único carácter (este seria consumido pela expressão [^~] e impediria a segunda aspa de ser reconhecida).

Buscamos o cabeçalho original e recortamo-lo assim:

  $grep -n BEGINDATA dados.ctl
  66
  $sed -n '1,66p' dados.ctl > dados.cabecalho
  $sed '1,66d' dados2.ctl > dados3.ctl
  $cat dados.cabecalho dados3.ctl > dados4.ctl

E, com a graça de nosso Senhor, procedemos à carga, que obteve sucesso e muita riqueza tratará ao nosso reino.

quarta-feira, 24 de abril de 2013

Novas mídias, velhos problemas

Algumas pessoas têm extrema dificuldade de comunicar-se. Recentemente, por exemplo, um senhora estava trancando a entrada do meu prédio enquanto balbuciava "não sei se é por aqui". Perguntei, solicitamente, aonde a senhora desejava ir. A reposta foi uma hesitante "minha irmã". Prontamente, desisti. Algumas pessoas, simplesmente, não conseguem comunicar-se com eficiência.

Há poucos dias recebi por email um boletim que consistia de um arquivo PDF. Exceto pelos símbolos e informações institucionais, o PDF continha apenas um link para o Twitter. No Twitter, havia um link para uma página e nesta página estava, finalmente, o artigo. Este artigo, por mais difícil que possa ser acreditar, estava guardado dentro de um arquivo PDF.

Esse tipo de coisa, tenho certeza, exige um trabalho colaborativo. Uma única pessoa não seria capaz de formular uma forma tão obtusa de comunicação. Ou talvez aquela senhora conseguisse.

quinta-feira, 11 de abril de 2013

Atualização desanimadora

Chegaram máquinas novas no trabalho, mas elas têm apenas 6GB de memória. Digo apenas 6GB, porque as atuais têm 4GB e não lembro de ter adquirido uma máquina nova que não tivesse ao menos 4 vezes mais memória que a anterior.

Primeiro, parti de um 6502 com 128KB para um 286 com 1MB. Então, para um 486DX com 8MB e depois para um Pentium 166MMX com 64MB. Depois disso, atualizei para um Pentium 4 com 1GB. Atualmente, tenho um i3 com 4GB. Pode ser que tenha havido um 386 no meio disso tudo, mas faz tanto tempo que não lembro mais.

Então, um incremento de apenas 50% é um pouco desapontador. Do jeito que as coisas estão, não vejo motivo para atualizar as máquinas como antigamente. Por isso, essa também não deve ser uma boa época para os fabricantes. É melhor começarem a produzir tablets.

sexta-feira, 5 de abril de 2013

Renda e IDH dos estados brasileiros e países sul-americanos

Resolvi juntar os dados dos países da América do Sul com os dados dos estados brasileiros. O resultado não foi inesperado, mas ainda assim a imagem ilustra muito bem como os estados brasileiros ocupam todo o espectro da renda e do desenvolvimento humano da América do Sul. Clique na imagem para ampliá-la.


O eixo vertical indica o IDH e o eixo horizontal indica a renda. As linhas cinzas indicam as médias para o continente.

Oito unidades federativas estão acima da renda média sul-americana: MT, PR, RS, ES, SC, RJ, SP, e DF. Além disso, 21 estão acima do IDH médio: todos menos PI, MA, AL, PB, CE, e PE.

As unidades federativas RJ, SP e DF estão acima da renda e do IDH de todos os países sul-americanos. Elas possuem mais de 57 milhões de habitantes, o que equivale aproximadamente à população do Chile e da Argentina somadas (são os dois países de melhor renda e IDH do continente).

Ainda mais interessante é o fato de que 6 estados têm IDH maior que o Chile (campeão sul-americano). Isso significa que quase 85 milhões de brasileiros vivem numa região com IDH superior ao IDH dos 17 milhões de chilenos.

No outro extremo, PI e MA são mais pobres que a Bolívia (em renda per capita), mas, ao menos, mantêm-se acima do IDH desse país.


<html>
<head>
<script src="processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="canvas">
  class Pais {
    int populacao, idh, renda, cor;

    Pais(int p, int i, int r, int c) {
      populacao=p;
      idh=i;
      renda=r;
      cor=c;
    }
  }

  HashMap paises=new HashMap();
  paises.put("Argentina", new Pais(40482000,797,17516,4));
  paises.put("Bolivia", new Pais(9863000,663,4789,1));
  paises.put("Brasil", new Pais(191241714,718,11769,0));
  paises.put("Chile", new Pais(16928873,805,17222,0));
  paises.put("Colombia", new Pais(45928970,710,10249,1));
  paises.put("Equador", new Pais(14573101,720,8492,1));
  paises.put("Guiana", new Pais(772298,633,7465,3));
  paises.put("Paraguai", new Pais(6831306,665,5413,3));
  paises.put("Peru", new Pais(29132013,725,10062,2));
  paises.put("Suriname", new Pais(472000,680,9475,3));
  paises.put("Uruguai", new Pais(3477780,783,15113,4));
  paises.put("Venezuela", new Pais(26814843,735,12568,2));

  int[] cores=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};
  
  class Estado {
    int populacao, idh, renda, regiao;

    Estado(int p, int i, int r, int s) {
      populacao=p;
      idh=i;
      renda=r;
      regiao=s;
    }
  }

  HashMap estados=new HashMap();
  estados.put("AC", new Estado(707125,751,7041,0));
  estados.put("AL", new Estado(3093994,677,5164,1));
  estados.put("AP", new Estado(648553,780,8543,0));
  estados.put("AM", new Estado(3350773,780,11829,0));
  estados.put("BA", new Estado(13633969,742,6922,1));
  estados.put("CE", new Estado(8180087,723,5636,1));
  estados.put("DF", new Estado(2469489,874,37600,2));
  estados.put("ES", new Estado(3392775,802,15236,3));
  estados.put("GO", new Estado(5849105,800,9962,2));
  estados.put("MA", new Estado(6424340,683,4628,1));
  estados.put("MT", new Estado(3033991,796,12350,2));
  estados.put("MS", new Estado(2404256,802,10599,2));
  estados.put("MG", new Estado(19159260,800,11028,3));
  estados.put("PA", new Estado(7443904,755,6241,0));
  estados.put("PB", new Estado(3753633,718,5507,1));
  estados.put("PR", new Estado(10266737,820,13158,4));
  estados.put("PE", new Estado(8541250,718,6528,1));
  estados.put("PI", new Estado(3086448,703,4213,1));
  estados.put("RJ", new Estado(15180636,832,17695,3));
  estados.put("RN", new Estado(3121451,738,6754,1));
  estados.put("RS", new Estado(10576758,832,14310,4));
  estados.put("RO", new Estado(1535625,776,8391,0));
  estados.put("RR", new Estado(425398,750,9075,0));
  estados.put("SC", new Estado(6178603,840,15638,4));
  estados.put("SP", new Estado(39924091,833,19548,3));
  estados.put("SE", new Estado(2036277,742,7560,1));
  estados.put("TO", new Estado(1373551,756,7210,0));

  int[] regioes=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};
  
  int W=2000;

  void setup() {
    size(W,W/2);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = paises.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String nome=me.getKey();
      Pais pais=me.getValue();
      float x=W*(pais.renda/40000);
      float y=W*(pais.idh/1000);
      float r=2+pais.populacao/1000000;
      fill(cores[pais.cor], 150);
      stroke(cores[pais.cor], 255);      
      ellipse(x, W-y, r, r);
      fill(0, 255);
      text(nome, x, W+4-y+r/2);
    }
    
    Iterator i2 = estados.entrySet().iterator();
    while (i2.hasNext()) {
      Map.Entry me = (Map.Entry)i2.next();
      String sigla=me.getKey();
      Estado estado=me.getValue();
      float x=W*(estado.renda/40000);
      float y=W*(estado.idh/1000);
      float r=2+estado.populacao/1000000;
      fill(regioes[estado.regiao], 100);
      stroke(regioes[estado.regiao], 255);      
      ellipse(x, W-y, r, r);
      fill(#000000, 255);
      text(sigla, x, W+4-y+r/2);
    }
    
    stroke(#AAAAAA, 255);
    float rendaMedia=(11962/40000)*W;
    line(rendaMedia, 0, rendaMedia, W);
    float idhMedio=W-(729/1000)*W;
    line (0, idhMedio, W, idhMedio);
    
    save("paises_estados.jpg");
  }
</script>
</head>
<body>
<canvas id="canvas">
</body>
</html>

terça-feira, 26 de março de 2013

Como encontrar valores distintos em arquivos de texto

Recebi um arquivo de texto com quase 300MB e 300 mil linhas para uma carga que executo mensalmente. Esse arquivo veio com uma alteração de formato e também com valores novos numa coluna crítica.

Para verificar se a alteração no formato não tinha provocado uma falha na carga, resolvi inspecionar o arquivo com as ferramentas que o Linux oferece.

Descobri uma maneira bastante simples de enumerar os valores distintos de uma coluna:

cut -c N-M arquivo.txt | sort | uniq

Os valores N e M indicam, respectivamente, a coluna inicial (baseado em 1) e a coluna final do trecho interessante de cada linha. Com um arquivo compactado, fiz o seguinte:

gunzip -c arquivo.txt.gz | cut -c N-M | sort | uniq

O cut pareceu-me um pouco lento, então resolvi verificar se o Perl pode fazer o mesmo com maior velocidade.

perl -pe '$_=substr($_, C, L)."\n"' arquivo.txt | sort | uniq

E, de fato, a solução, embora mais complexa, é mais rápida. Neste caso, C indica a coluna inicial (baseada em 0) e L o tamanho do campo (ou seja, C=N-1 e L=M-N+1).

Posso usar o perl com arquivos compactados assim:

gunzip -c arquivo.txt.gz | perl -pe '$_=substr($_, C, L)."\n"' | sort | uniq

Se eu quisesse apenas saber o número de valores distintos, bastaria adicionar o wc ao fim:

cut -c N-M arquivo.txt | sort | uniq | wc -l

E, com isso, pude confirmar que tinha carregado os dados corretamente. É interessante que o comando uniq não busca valores distintos, ele apenas elimina linhas sucessivas iguais. Por isso, é preciso usar também o comando sort.

quarta-feira, 6 de março de 2013

Renda e desenvolvimento da América do Sul

Aproveitei o código do artigo sobre a renda e o IDH dos estados brasileiros para fazer uma comparação dos países sul-americanos. Aproveitei para adicionar duas informações: a renda média e o IDH médio. Esses dados são representados, respectivamente, por uma linha vertical e uma linha horizontal. O tamanho de cada círculo representa o número de habitantes.

Clique na imagem para aumentá-la.

Eu ignorei as colônias (Malvinas e Guiana Francesa), porque não possuem informação sobre IDH.

Dado o seu tamanho, o Brasil só poderia mesmo estar nas redondezas das médias. Infelizmente, ele está um pouco abaixo das duas.

Argentina e Chile lideram com bastante folga, cada um desses países liderando num quesito, mas por pequena margem. Isso me remete ao fato de que, na nossa mídia, o Chile é frequentemente referido como um sucesso retumbante enquanto a Argentina é um fracasso trágico. Resulta que são muito parecidos. O Uruguai não está muito atrás no que concerne o IDH, mas a sua renda é sensivelmente menor.

Orbitam o Brasil a Venezuela, o Peru e a Colômbia. Esses países dificilmente são retratados em bons termos na nossa mídia, curiosamente. O Equador tem renda um tanto menor que a desse grupo, mas um IDH semelhante.

A Guiana e o Suriname destoam por terem IDHs pequenos para seus respectivos níveis de renda.

Eis o código:

<html>
<head>
<script src="processing-1.4.1.min.js"></script>
<script type="text/processing" data-processing-target="canvas">
  class Pais {
    int populacao, idh, renda, cor;

    Pais(int p, int i, int r, int c) {
      populacao=p;
      idh=i;
      renda=r;
      cor=c;
    }
  }

  HashMap paises=new HashMap();
  paises.put("Argentina", new Pais(40482000,797,17516,4));
  paises.put("Bolivia", new Pais(9863000,663,4789,1));
  paises.put("Brasil", new Pais(191241714,718,11769,0));
  paises.put("Chile", new Pais(16928873,805,17222,0));
  paises.put("Colombia", new Pais(45928970,710,10249,1));
  paises.put("Equador", new Pais(14573101,720,8492,1));
  paises.put("Guiana", new Pais(772298,633,7465,3));
  paises.put("Paraguai", new Pais(6831306,665,5413,3));
  paises.put("Peru", new Pais(29132013,725,10062,2));
  paises.put("Suriname", new Pais(472000,680,9475,3));
  paises.put("Uruguai", new Pais(3477780,783,15113,4));
  paises.put("Venezuela", new Pais(26814843,735,12568,2));

  int[] cores=new int[] {#98db11, #ff9900, #ffd42a, #ff0000, #5599ff};

  void setup() {
    size(1024,1024);
    background(255);
    noLoop();
  }

  void draw() {
    Iterator i = paises.entrySet().iterator();
    while (i.hasNext()) {
      Map.Entry me = (Map.Entry)i.next();
      String nome=me.getKey();
      Pais pais=me.getValue();
      float x=1024*(pais.renda/20000);
      float y=1024*(pais.idh/1000);
      float r=2+pais.populacao/1000000;
      fill(cores[pais.cor], 150);
      stroke(cores[pais.cor], 255);      
      ellipse(x, 1024-y, r, r);
      fill(0, 255);
      text(nome, x, 1032-y+r/2);
    }
    stroke(#AAAAAA, 255);
    float rendaMedia=(11962/20000)*1024;
    line(rendaMedia, 0, rendaMedia, 1024);
    float idhMedio=1024-(729/1000)*1024;
    line (0, idhMedio, 1024, idhMedio);
    
  }
</script>
</head>
<body>
<canvas id="canvas">
</body>
</html>