quinta-feira, 31 de maio de 2007

Gerar imagem PNG com o GnuPlot

Ao instalar o GnuPlot no servidor, verifiquei que não conseguia gerar graficos no formato png com este programa. O erro que eu obtinha é o seguinte:
gnuplot> set terminal png
^
unknown or ambiguous terminal type; type just 'set terminal' for a list

Observando o log da configuração, config.log, que é gerado assim que o comando ./configure é executado descobri qual o problema, faltava-me a libgd. Veja a linha do log mostrando o problema:
configure:16121: result:   png terminal: no (requires libgd with png support)

Sabendo disso, desinstalei o gnuplot, instalei a libgd e todas suas dependencias para finalmente reinstalar o gnuplot. Com isso, tudo funcionou perfeitamente.

quarta-feira, 30 de maio de 2007

Load alto causado por um HashMap

Recentemente tivemos um problema em produção com nosso sistema. O load do servidor aumentava gradativamente chegando a um ponto em que era necessário reinicia-lo. Para quem não sabe, o load é um cálculo que indica quantas threads estão executando ou tentando executar em um determinado período. Ou seja, este problema de load alto poderia então ser causado por uma grande quantidade de acessos, no entanto, o load não baixava em periodos de pouco acesso, e até mesmo quando tirávamos o servidor do balanceamento.

Para analisar as threads que estavam executando, lançamos mão de um Thread Dump em dois momentos, antes de tirarmos a máquina do balanceamento, e outra uma hora depois de tirá-la. No Java, para obter um Thread Dump basta executar o seguinte comando, onde o processo_id é o pid do processo Java que queremos analisar.
kill -QUIT processo_id

Com o resultado em mãos descobrimos que antes e depois de tirarmos o servidor do balanceamento havia uma thread executando (runnable) travada na linha 325 de um HashMap. Veja a descrição dessa thread abaixo:
"ExecuteThread: '94' for queue: 'default'" daemon prio=5 tid=0x00b525a0 nid=0x2c runnable [0x63bfe000..0x63bffc28]
at java.util.HashMap.get(HashMap.java:325)
(...)

Foi então que descobrimos o problemas. Procuramos no Google por esta linha e encontramos diversas pessoas com o mesmo problema e até mesmo notificações deste possível bug para Sun. Contudo, isto não é um bug. O que acontece é que a classe HashMap não é thread safe, nem mesmo para seus métodos put e get. Portanto, se diversas threads gravam e recuperam valores de um HashMap, pode ocorrer problemas como esse. De fato, se olharmos o código de HashMap, podemos perceber que o método get tem grandes possibilidades de entrar em loop infinito.

A solução recomendade pela Sun é utilizar o wrapper sincronizado para mapas Collections.synchronizedMap(new HashMap()), ou utilizar a classe ConcurrentHashMap da api de concorrência incorporada ao Java 5, a java.util.concurrent. Para quem não pode utilizar o Java 5, pode-se utilizar a versão back-port da java.util.concurrent. No nosso caso, utilizamos essa versão backport.

quinta-feira, 24 de maio de 2007

Configurando um tópico JMS no JBoss

Para configurar um tópico de Java Message Service no JBoss (a versão que estou utilizando é 4.0.5.GA) basta editar o arquivo %JBOSS_HOME%/server/default/deploy/jms/jbossmq-destinations-service.xml iserindo uma tag XML como mostrada abaixo:
<mbean code="org.jboss.mq.server.jmx.Topic" 
name="jboss.mq.destination:service=Topic,name=nomeDoMeuTopico">
<depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
<depends optional-attribute-name="SecurityManager">jboss.mq:service=SecurityManager</depends>
<attribute name="SecurityConf">
<security>
<role name="guest" read="true" write="true"/>
<role name="publisher" read="true" write="true" create="false"/>
<role name="durpublisher" read="true" write="true" create="true"/>
</security>
</attribute>
</mbean>

Não sei bem para que serve cada um dos parâmetros pois estou começando a estudar JMS agora.

Depois de configurar um tópico eu crio um servlet de exemplo que lê um parâmetro e o escreve no tópico como uma mensagem de texto. Observer que o lookup que fazemos no InitialContext para pegar o tópico utiliza o mesmo nome do tópico que configuramos adicionado com o prefixo "topic/":
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
Topic topic = null;
TopicPublisher topicPublisher = null;

try
{
jndiContext = new InitialContext();

topicConnectionFactory = (TopicConnectionFactory) jndiContext.lookup("TopicConnectionFactory");
topic = (Topic) jndiContext.lookup("topic/nomeDoMeuTopico");

topicConnection = topicConnectionFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topicPublisher = topicSession.createPublisher(topic);

TextMessage mensagem = topicSession.createTextMessage();
mensagem.setText( request.getParameter("m") );
topicPublisher.publish(mensagem);
}
catch (NamingException e)
{
log.error("",e);
}
catch (JMSException e)
{
log.error("",e);
}
finally
{
if (topicPublisher != null) try { topicPublisher.close(); } catch (JMSException e) {}
if (topicConnection != null) try { topicConnection.close(); } catch (JMSException e) {}
}

response.getWriter().print("Mensagem publicada");
}

Para ler as mensagens enviadas ao tópico é preciso definir um listener, implementando a interface MessageListener, como é mostrado no código abaixo:
public class MensagemDeTextoListener implements MessageListener
{
public void onMessage(Message mensagem)
{
TextMessage msg = null;

try
{
if (mensagem instanceof TextMessage)
{
msg = (TextMessage) mensagem;
System.out.println("Leu mensagem: " + msg.getText());
}
else
{
System.out.println("Mensagem de tipo invalido: " + mensagem.getClass().getName());
}
}
catch (JMSException e)
{
log.error("",e);
}
}
}

Finalmente é preciso inscrever uma instancia deste listener como leitor do tópico. No exemplo abaixo é mostrado como inscrever um listener no tópico anteriormente definido, através do método contextInitializer de um ServletContextListener.
private TopicConnection topicConnection;

private void contextInitialized(ServletContextEvent evento)
{
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicSession topicSession = null;
Topic topic = null;
TopicSubscriber topicSubscriber = null;
MensagemDeTextoListener topicListener = null;
TextMessage message = null;

try
{
jndiContext = new InitialContext();
topicConnectionFactory = (TopicConnectionFactory)
jndiContext.lookup("TopicConnectionFactory");
topic = (Topic) jndiContext.lookup("topic/mensagemTopic");

topicConnection = topicConnectionFactory.createTopicConnection();
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
topicSubscriber = topicSession.createSubscriber(topic);
topicListener = new MensagemDeTextoListener();
topicSubscriber.setMessageListener(topicListener);
topicConnection.start();
}
catch (NamingException e)
{
log.error("",e);
}
catch (JMSException e)
{
log.error("",e);
}
}
private void finalizarSubscriber()
{
try
{
topicConnection.close();
}
catch (JMSException e)
{
log.error("",e);
}
}

Pronto, aparentemente o exemplo funcionaria, mas não, quando inicializar seu JBoss, ocorrerá o seguinte erro: javax.jms.IllegalStateException in JBOSS: This method is not applicable inside the application server. See the J2EE spec, e.g. J2EE1.4 Section 6.6. Esse erro se dará exatamente na linha em que você adiciona o listener das mensagens ao tópico.

Ainda não entendi bem o porque isso acontece, mas achei a solução: Basta alterar o arquivo %JBOSS_HOME%/server/default/deploy/jms/jms-ds.xml adicionando o parâmetro Strict à connection-factory de jndi-name JmsXA. Veja abaixo:
<tx-connection-factory>
<jndi-name>JmsXA</jndi-name>
<xa-transaction/>
<rar-name>jms-ra.rar</rar-name>
<connection-definition>org.jboss.resource.adapter.jms.JmsConnectionFactory</connection-definition>
<config-property name="SessionDefaultType" type="java.lang.String">javax.jms.Topic</config-property>
<config-property name="JmsProviderAdapterJNDI" type="java.lang.String">java:/DefaultJMSProvider</config-property>
<config-property name="Strict" type="java.lang.Boolean">false</config-property>
<max-pool-size>20</max-pool-size>
<security-domain-and-application>JmsXARealm</security-domain-and-application>
</tx-connection-factory>

quarta-feira, 23 de maio de 2007

Cliente de IRC em Java

Na verdade neste artigo não faremos um cliente de IRC, faremos um simple bot. Mas, a biblioteca que apresento também serve para fazer clientes de IRC, é a PircBot (Java IRC Bot Framework). Para funcionar você pode utilizar um servidor de IRC já existente, ou instalar um na sua máquina. No caso eu preferi intalar um, e utilizei o UnrealIRCd, que possui versões para Windows e Linux.

O UnrealIRCd é um bom servidor de IRC pois é fácil, na medida do possível, de configurar. Isso porque as mensagens de erro dele são bem explicativa, dizendo até mesmo em qual linha do arquivo de configuração há uma entrada incorreta ou faltando.

Depois de escolher servidor criamos uma classe extendendo org.jibble.pircbot.PircBot, essa classe representará o usuário que criamos para se conectar no IRC. A idéia é fazer um usuário que desconecte do IRC quando receber uma mensagem "Sai vagabundo" de qualquer pessoa em qualquer sala.
public class BotVagabundo extends PircBot
{
public BotVagabundo(String nome)
{
this.setName(nome);
}

@Override protected void onMessage(String channel, String sender, String login, String hostname, String message)
{
System.out.println("Recebi em [" + channel + "] do usuario [" + sender + "] a mensagem [" + message + "]");

if("Sai vagabundo".equals(message))
{
this.disconnect();
this.dispose();
}
}
}

O método PircBot.setName(String) define o nick do usuário. O método onMessage, é acionado quando alguma mensagem é recebida, seja num dos canais em que ele se encontra ou em um chat privado.

Agora vamos ver como se faz para conectar esse usuário no IRC:
BotVagabundo bot = new BotVagabundo("Chapun");

bot.setVerbose(true);
bot.connect("irc.chapun.com");
bot.joinChannel("#brasil");

bot.sendMessage("#brasil", "Eu sou um bot");

No caso eu não só fiz o bot conectar no servidor irc.chapun.com (coloquei o meu servidor local com este host) como fiz ele entrar no canal #brasil e enviar uma mensagem informando que é um bot.

Viu como é fácil?

Além disso, o PircBot tem diversas outras opções, como pegar a lista de canais, lista de usuários em um canal, kikar um usuário... Vale o tempo perdido dar uma olhada no site dele.

quarta-feira, 16 de maio de 2007

Teste unitário em funções javascript utilizando o JUnit

Um dos grandes problemas do javascript é a falta de testabilidade. Vou mostrar aqui nesse artigo uma forma que encontrei de testar as funções de arquivos javascripts utilizando o JUnit. Ah! Claro, isso só é possível se você estiver utilizando o Java 6, que possui suporte a linguagens de script e já vem nativamente com suporte a javascript.

Primeiro criamos o arquivo funcoes.js contendo uma função para validar um determinado formulário. O objetivo é retornar verdadeiro se todos os campos estiverem preenchidos e falso no caso contrário. O formulário html deve ter dois campos: nome e senha. Inicialmente deixaremos essa função sem nenhum código, pois criaremos os testes primeiro:
function validaFormulario(f)
{
return false;
}

Em seguida criamos uma classe para realizar os testes unitários contendo o método testValidaFormulario e outras duas classes internas para servir de Mock do formulário html e dos campos do formulário, HtmlFormMock e HtmlFieldMock respectivamente. Segue o código:
package junit.script;
import junit.framework.TestCase;

public class FuncoesTest extends TestCase
{
public void testValidaFormulario() throws FileNotFoundException, ScriptException, NoSuchMethodException
{
//Aqui entrarão os testes
}

public class HtmlFormMock implements Serializable
{
public HtmlFieldMock nome = new HtmlFieldMock();
public HtmlFieldMock senha = new HtmlFieldMock();
}
public class HtmlFieldMock implements Serializable
{
public String name;
public String value;
}
}

No método testValidaFormulario da classe de testes começamos então importanto o script funcoes.js, e criando um Invocable para poder executar funções no javascript:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.eval( new FileReader( "C:/Projetos/testes/web/funcoes.js" ) );
Invocable invocable = (Invocable) engine;

Em seguida preenche-se o Mock de formulário da seguinte forma:
HtmlFormMock f1 = new HtmlFormMock();
f1.nome.name = "nome";
f1.nome.value = "Tiago";
f1.senha.name = "senha";
f1.senha.value = "123456";

Tendo o invocador de funções de script (Invocable) e o objeto mock, basta alterar os atributos deste objetos para utilizá-lo como parâmetro do formulário por diversas vezes. Veja abaixo os testes que defini para esta função, repare que fiz dois testes para verificar se o campo "nome" e o campo "senha" existem no formulário, pois é possível que criem um formulário html sem o campos:

//Todo formulario preenchido
Boolean b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertTrue("Formulario todo preenchido deve retornar true", b.booleanValue());

//Senha preenchida com nulo
f1.senha.value = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Senha preenchida com nulo deve retornar falso", b.booleanValue());

//Senha preenchida com vazio
f1.senha.value = "";
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Senha preenchida com vazio deve retornar falso", b.booleanValue());

//Nome preenchido com nulo
f1.senha.value = "outra";
f1.nome.value = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Nome preenchido com nulo deve retornar falso", b.booleanValue());

//Nome preenchido com vazio
f1.nome.value = "";
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Nome preenchido com vazio deve retornar falso", b.booleanValue());

//Campo nome do formulario nao existir deve retornar falso
f1.nome = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Campo nome do formulario nao existe entao deve retornar falso", b.booleanValue());

//Campo senha nao pode ser nulo
f1.nome = new HtmlFieldMock();
f1.nome.name = "nome";
f1.nome.value = "Tiago";
f1.senha = null;
b = (Boolean) invocable.invokeFunction("validaFormulario", new Object[] { f1 } );
assertFalse("Campo senha do formulario nao existe entao deve retornar falso", b.booleanValue());

Executando esse teste com o JUnit teremos uma falha logo no primeiro teste, pois o este espera que haja um retorno de true e nossa função javascript está sempre retornando false. Alteremos então a função javascript dando-lhe um código aceitável:
function validaFormulario(f)
{
if( f.nome && f.nome.value && f.nome.value.length() > 0 &&
f.senha && f.senha.value && f.senha.value.length() > 0 )
{
return true;
}
return false;
}

Pronto, tendo a função javascript codificada, basta rodarmos o teste no JUnit novamente para verificar que finalmente a função está funcionando de acordo com o específicado. E o melhor, sempre que ocorrer uma alteração no código dela, saberemos se houve alguma quebra de contrato.

Também é possível testar métodos de classes javascript. Basta utilizar o método invokeMethod da interface Invocable, ao invés de invokeFunction. Mas isso eu deixo pra outro artigo.

quinta-feira, 10 de maio de 2007

Como utilizar o ForEach Controller do JMeter

A estrutura de repetição, ForEach Controller do JMeter exige que existam váriaveis definidas com um padrão contendo um prefixo seguido de um número sequencial (Exemplo: x1, x2, x3, ou , n_1, n_2, n_3). Para definir um grande número de váriaveis sem grande esforço, pode-se utilizar um CSV Data Set Config que define váriaveis apartir de um arquivo texto.

No campo "Filename" do CSV Data Set Config indica-se o path de um arquivo de texto contendo os valores separados por vírgula (Exemplo: 10,56,345,12). A seguir no campo "Variable names" define-se um nome de váriavel para cada valor definido no arquivo texto. Assim, se no arquivo texto tiver cinco valores, devem ser definidos cinco nomes de váriaveis (Exemplo: n_1,n_2,n_3,n_4,n_5).

No ForEach Controller deve-se indicar o prefixo da váriavel em "Input variable prefix". Se o padrão de váriaveis suas possuir um _ antes do número, pode-se marcar a opção "Add _ before number". O campo "Output variable name" recebe o valor da variável que receberá o valor de uma das variáveis a cada iteração. Ou seja, a primeira vez que executar o valor será de n_1, na segunda vez será de n_2 e assim por diante.

Assim, os itens que estiverem dentro do ForEach Controller passam a poder utilizar a váriavel definida em "Output variable name" utilizando um padrão parecido com de Expression Language do JSP 2.0: ${nomeVariavel}.

quarta-feira, 9 de maio de 2007

Número de threads no Tomcat

Para aumentar o número de threads que o seu Tomcat utiliza para atender requisições é só alterar o server.xml, localizado em TOMCAT_HOME/conf/server.xml. É necessário informar alguns novos atributos (ou alterar) na tag Connector que é utilizada para receber as requisições, ou seja aquela que possui o atributo "port" com valor igual a porta http. Veja abaixo:
<Connector 
className="org.apache.coyote.tomcat4.CoyoteConnector" port="80"
...
maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75">
...
</Connector>

O atributo "maxThreads" indica o máximo de threads que o tomcat irá abrir, o "minSpareThread" e "maxSpareThreads" são respectivamente o mínimo e o máximo de Threads que o Tomcat disponibilizará para servirem de steps.

Essa configuração funcionou para a versão 4.1.31 do Tomcat, mas creio não deve ter mudado para novas versões.

segunda-feira, 7 de maio de 2007

Busca com OpenSearch no Firefox e IE7

Recentemente fiz texto explicando como disponibilizar a busca de seu site no Firefox. Semana retrasada aqui no trabalho utilizamos uma nova forma de disponibilizar a nossa busca nos navegadores dos usuário. Dessa vez, funciona tanto para Internet Explorer 7, quanto para o Firefox.

Isso foi feito utilizando o padrão OpenSearch. E é bem similar ao padrão do artigo anterior, veja abaixo o código do XML que é preciso disponibilizar:
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Programando sem cafeina</ShortName>
<Description>Blog sobre tecnologia principalmente para a area de desenvolvimento de sistemas</Description>
<Url type="text/html" template="http://programandosemcafeina.blogspot.com/search/label/{searchTerms}"/>
<LongName>Programando sem cafeina - Blog sobre tecnologia principalmente para a area de desenvolvimento de sistemas</LongName>
<Image height="64" width="64" type="image/png">http://programandosemcafeina.blogspot.com/imagems/busca-openserach.png</Image>
<Image height="16" width="16" type="image/png">http://programandosemcafeina.blogspot.com/imagems/busca-openserach.png</Image>
<Language>pt-br</Language>
<OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding>
</OpenSearchDescription>

Esse XML tem apenas as opções básicas, para saber o restante das opções visite a página: Descrição do documento OpenSearch.

Continuando então, para disponibilizar a busca não basta apenas colocar o XML lá no seu servidor, é preciso oferecer um link com um código javascript que adicione a busca na barra de ferramentas do navegador do usuário. Veja o exemplo abaixo de como fazer isso:
<a href="javascript:void(window.external.AddSearchProvider('http://programandosemcafeina.blogspot.com/minhabusca.xml'));">
Adicionar busca do meu blog
</a>

Viu como é fácil? Quem quizer testar para ver como ficou a busca para o Globo Vídeos basta adicioná-la clicando em Adicionar busca do Globo Vídeos em seu navegador. É bom lembrar que em navegadores muito antigos isso não funciona. No Internet Explorer 6 mesmo não funciona.

quinta-feira, 3 de maio de 2007

Problemas com Post do XMLHttpRequest

Recentemente tive problemas ao tentar utilizar chamadas assíncronas no javascript utilizando o método post do protocolo http. Na verdade, o que ocorria é que os parâmetros enviados por post não eram recebidos em meu servlet. Descobri então como contornar isso, mas não entendi o porque desse problema ter ocorrido.

No javascript eu fazia a seguinte chamada, onde a variavel r era um objeto XMLHttpRequest:
r.open("POST", "http://localhost/MeuServlet", true);
r.send("nome=tiago");

No servlet MeuServlet eu apenas pegava o parâmetro nome e o imprimia na saída padrão:
String nome = request.getParameter("nome");
System.out.println("nome enviado = " + nome);

A saída era sempre "nome enviado = null". Olhando no debugger de chamadas assíncronas do firebug dava pra ver que os parâmetros estavam sendo enviados com sucesso. O problemas estava mesmo na recepção. Eis então que achei um exemplo na internet com o que faltava para meu código javascript:
r.open("POST", "http://localhost/MeuServlet", true);
r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");
r.send("nome=tiago");

Colocando essa linha indicando o Content-Type no header da requisição a chamada por post passou a funcionar. Se alguém tiver uma explicação do porque disso será de grande ajuda.

quarta-feira, 2 de maio de 2007

Rolar um DIV via javascript

Segue um exemplo de como rolar um div via javascript:
function praBaixo()
{
var c = document.getElementById("meuId");
c.scrollTop = c.scrollHeight;
}

No exemplo, a função "praBaixo" colocaria a barra de rolagem de um div para baixo de forma a mostrar o ultimo conteudo do div. É bom lembrar que para um div ter barra de rolagem é preciso indicar seu tamanho, width para rolagem horizontal e height para rolagem vertical, e a propriedade "overflow: auto" no css.