Lobo, Continuous Tuning
Manual do Usuário
Clique aqui para acessar a última versão da API do Lobo.
1. Conceitos
Caso: um caso é uma classe que contêm ao menos um dos seus métodos anotado como @Profile, o que representa um cenário.
Cenário: é um método dentro de um caso que é anotado como @Profile. Um cenário sempre contêm ao menos uma métrica.
Métrica: uma métrica é alguma coisa sendo medida no cenário. Um cenário possui pelo menos uma simples métrica padrão. Outras métricas são gerenciadas através da classe Telemetry. A classe MetricType contêm contantes usadas para setar o tipo das métricas, que podem ser valor simples, médio, máximo ou mínimo.
XML de métricas do build: é o artefato que armazena as métricas específicas de um build, categorizado por cenário e caso.
XML de histórico de métricas: é o artefato que armazena todo o histórico de performance do projeto.
Cenário: é um método dentro de um caso que é anotado como @Profile. Um cenário sempre contêm ao menos uma métrica.
Métrica: uma métrica é alguma coisa sendo medida no cenário. Um cenário possui pelo menos uma simples métrica padrão. Outras métricas são gerenciadas através da classe Telemetry. A classe MetricType contêm contantes usadas para setar o tipo das métricas, que podem ser valor simples, médio, máximo ou mínimo.
XML de métricas do build: é o artefato que armazena as métricas específicas de um build, categorizado por cenário e caso.
XML de histórico de métricas: é o artefato que armazena todo o histórico de performance do projeto.
2. Ciclo de vida dos testes de performance
O Lobo é projetado para monitorar a performance da sua aplicação na medida em que ela está sendo desenvolvida. Desta forma, você pode analizar como suas mudanças têm impactado na performance. Para isto, o Lobo precisa coletar a armazenar as métricas que você especificar.
O Lobo define duas tasks do Ant principais, uma é coletar as métricas a cada build, e outra é armazenar as informações provenientes de diversos builds em um só arquivo consolidado. Existe também outra task que é utilizada para gerar um relatório gráfico do arquivo de dados consolidados. A imagem a seguir ilustra isto:
O Lobo define duas tasks do Ant principais, uma é coletar as métricas a cada build, e outra é armazenar as informações provenientes de diversos builds em um só arquivo consolidado. Existe também outra task que é utilizada para gerar um relatório gráfico do arquivo de dados consolidados. A imagem a seguir ilustra isto:

Ciclo de vida de um teste de performance
- O escopo do build;
- E o escopo da integração contínua.
O Escopo do Build
O build do seu projeto deve ser responsável por coletar as métricas, ou seja, ele deve traduzir as classes de produção e casos de testes em uma série de métricas de performance.
Isto é feito ao chamar a task <profile>, que gera um XML com dados específicos daquele build, também chamado de XML de métricas do build.
Isto tudo é feito no escopo do build.
O Escopo de Integração Contínua
O escopo de integração contínua está ciente de que o build irá rodar diversas vezes. Ele é responsável por definir o lugar em que o arquivo merge será mantido, sendo crucial que o arquivo merge não seja deletado ao longo das execuções dos builds, pois o build do projeto não é responsável por isto.
No caso de você estar utilizando CruiseControl como sua ferramenta de integração contínua, utilize o build do CruiseControl para inserir as tasks no escopo da integração contínua. Estas tasks devem ser colocadas logo após o build do projeto ser chamado, para então os resultados de performance serem sempre coletados.
A primeira task no escopo da integração contínua irá armazenar as métricas coletadas no escopo do build em um arquivo merge.xml, criando o histórico de performance do seu projeto.
A segunda task irá gerar os relatórios como um HTML, contendo os gráfico que ilustram a evolução das métricas.
No caso de você estar utilizando CruiseControl como sua ferramenta de integração contínua, utilize o build do CruiseControl para inserir as tasks no escopo da integração contínua. Estas tasks devem ser colocadas logo após o build do projeto ser chamado, para então os resultados de performance serem sempre coletados.
A primeira task no escopo da integração contínua irá armazenar as métricas coletadas no escopo do build em um arquivo merge.xml, criando o histórico de performance do seu projeto.
A segunda task irá gerar os relatórios como um HTML, contendo os gráfico que ilustram a evolução das métricas.
3. Tasks do Ant
Lobo disponibiliza as seguintes tasks Ant:
Task <profile>
A task profile recebe como entrada as classes de produção e as classes dos casos de teste, os executa e então gera um arquivo XML com as métricas do build. Confira alguns exemplos:
- Task do escopo do build:
- Task <profile>
- Tasks do escopo de integração contínua:
- Task <profile-merge>
- Task <profile-report>
Task <profile>
|
Classe da task: |
br.com.oncast.dev.lobo.task.ProfilerTask |
|
Entrada: |
|
|
Saída: |
|
A task profile recebe como entrada as classes de produção e as classes dos casos de teste, os executa e então gera um arquivo XML com as métricas do build. Confira alguns exemplos:
Usando classpath e batchprofile:
|
<target
name="performance-profile" depends="compile"> <mkdir dir="build/classes" /> <profile output="build/profile.output"> <classpath> <path location="build/classes"/> <path refid="classpath"/> </classpath> <batchProfile> <fileset dir="build/classes" includes="**/Profile*.class" /> </batchProfile> </profile> </target> |
Usando classpathref e batchprofile:
|
<target
name="performance-profile" depends="compile"> <mkdir dir="build/classes" /> <profile output="build/profile.output" classpathref="classpath"> <batchProfile> <fileset dir="build/classes" includes="**/Profile*.class" /> </batchProfile> </profile> </target> |
Task <profile-merge>
|
Classe da task: |
br.com.oncast.dev.lobo.task.ProfilerMergeTask |
|
Entrada: |
|
|
Entrada-saída: |
|
A task <profile-merge> centraliza o histórico de métricas em um único arquivo XML. Você pode considerar este arquivo merge.xml como o repositório final para as informações de performance do seu projeto. Toda vez que a task <profile-merge> é executada, o arquivo merge.xml será atualizado com os últimos resultados de performance.
Sua utilização é muito simples. Confira o seguinte exemplo:
Sua utilização é muito simples. Confira o seguinte exemplo:
|
<target
name="execute-profiler-merge-no-merged-source"> <profile-merge buildname="1.11" buildmetrics="build/profile-source.xml" merge="../safe-place/merge-output.xml" /> </target> |
O nome do build (parâmetro buildname) ainda é obrigatório. Futuras versões desta task irão inferir o nome do build através de arquivos de build anteriores. Atualmente, é uma boa idéia usar a sua infra-estrutura de integração contínua para injetar o nome do build no seu arquivo merge. Este parâmetro será usado para rotular os builds nos relatórios de evolução da performance.
Task <profile-report>
|
Classe da task: |
br.com.oncast.dev.lobo.task.ProfilerReporterTask |
|
Entrada: |
|
|
Saída: |
|
A task <profile-report> simplesmente formata as métricas de evolução em um formato apropriado para análise. O relatório de saída consiste em um HTML que lista todas as métricas coletadas, categorizadas por cenário e caso. Cada métrica é formatada como uma linha em um gráfico, listando todos os builds no eixo X e o tempo de execução no eixo Y.
Abaixo segue um exemplo de utilização desta task:
Abaixo segue um exemplo de utilização desta task:
|
<target
name="execute-report"> <profile-report input="../safe-place/merged-report.xml" output="../artifacts/report" /> </target> |
4. Lobo em ação
O Lobo é usado para medir o tempo de execução de algum trecho de código.
A anotação de método @Profile
é usada para indicar que algum método é um cenário, ou seja, que o método
tem alguma métrica a ser coletada. Todo cenário tem ao menos uma métrica de
tipo simples, que cobre toda a execução do método. A não ser que o nome da
métrica desejada seja expressamente configurado na anotação
@Profile, como em
@Profile("myMetric"),
o nome da métrica padrão será "scenario-timespan". Todas as outras métricas
coletadas em um cenário são controladas pela
API da classe Telemetry, que é fácil, como segue...
A API para controle dos testes de performance é muito simples. A classe Telemetry contêm os seguintes métodos que controlam a coleta das métricas:
A API para controle dos testes de performance é muito simples. A classe Telemetry contêm os seguintes métodos que controlam a coleta das métricas:
-
startMetric(String metricName [, MetricType type]): o método startMetric pode ser chamado com um parâmetro, apenas o nome da métrica, ou dois parâmetros, o nome da métrica seguido do tipo da métrica. Iniciar uma métrica significa cronometrar até um endMetric ser chamado;
-
endMetric(String metricName): dado o nome de uma métrica previamente iniciada, este método pára a cronometragem a registra o tempo de execução;
- switchMetric(String metricName): chamar este método significa parar, registrar e reiniciar a cronometragem de uma métrica. Este método não tem utilidade para métricas do tipo simples, porque uma métrica simples considera apenas o primeiro registro da métrica. Como explicado abaixo, reiniciar uma métrica é útil para coletar o valor médio, máximo e mínimo.
Abaixo estão os tipos de métricas suportadas pelo Lobo:
- MetricType.SINGLE: considera somente o registro da primeira métrica , ou seja, o primeiro par de chamadas startMetric e endMetric;
- MetricType.AVERAGE: calcula a média aritmética para todos os tempos registrados;
- MetricType.MAX: indica o maior valor de todos os tempos registrados;
- MetricType.MIN: indica o menor valor de todos os tempos registrados;
A seguir, alguns exemplos de utilização da API da classe Telemetry e de todos os tipos de métricas:
Exemplo 1
Exemplo 1
|
import
br.com.oncast.dev.lobo.Profile; (...) @Profile public void singleProfile() { Collections.binarySearch(someList, someKey); } |
Este é o jeito mais simples de configurar um cenário. O nome do cenário é o
nome do método "singleProfile"
e a métrica padrão é (como seu nome não foi
informado) "scenario-timespan". A métrica padrão é uma métrica simples, que
cronometra o tempo de execução de todo o cenário.
Example 2
|
import
static br.com.oncast.dev.lobo.Telemetry.startMetric; import static br.com.oncast.dev.lobo.Telemetry.endMetric; import br.com.oncast.dev.lobo.Profile; (...) @Profile public void singleSearchCost() { startMetric("binarySearchCost"); Collections.binarySearch(someList, someKey); endMetric("binarySearchCost"); startMetric("mySearchCost"); MyAlgorithm.search(someList, someKey); endMetric("mySearchCost"); } |
Este exemplo adiciona algumas informações em comparação com o exemplo anterior. Ele usa a
API da classe Telemetry para configurar duas métricas simples chamadas
"binarySearchCost" e "mySearchCost".
Este é um cenário hipotético, que considera que o programador gostaria de comparar duas
implementações de um algoritmo de busca: a busca binária da classe Collections e seu próprio algoritmo.
O nome do cenário é o nome do método "singleSearchCost"
e há também uma métrica padrão chamada "scenario-timespan", que irá registrar o tempo de execução do cenário completo.
Exemplo 3
|
import static
br.com.oncast.dev.lobo.Telemetry.startMetric; import static br.com.oncast.dev.lobo.Telemetry.endMetric; import static br.com.oncast.dev.lobo.Telemetry.switchMetric; import br.com.oncast.dev.lobo.MetricType; import br.com.oncast.dev.lobo.Profile; (...) @Profile("fullExecutionMetric") public void averageProfile() { startMetric("searchAverage", MetricType.AVERAGE); Collections.binarySearch(firstList, someKey); switchMetric("searchAverage"); Collections.binarySearch(secondList, someKey); switchMetric("searchAverage"); Collections.binarySearch(thirdList, someKey); endMetric("searchAverage"); } |
O código acima mostra um cenário com a métrica padrão renomeada para
"fullExecutionMetric" e
uma métrica de média chamada "searchAverage".
A "fullExecutionMetric" mede
o tempo de execução do cenário inteiro. Enquanto a métrica
"searchAverage" é registrada
3 vezes, uma para cada pesquisa binária executada. O valor resultante da
métrica é a média aritmética de todos os registros de "searchAverage".
Exemplo 4
|
import static
br.com.oncast.dev.lobo.Telemetry.startMetric; import static br.com.oncast.dev.lobo.Telemetry.endMetric; import static br.com.oncast.dev.lobo.Telemetry.switchMetric; import static br.com.oncast.dev.lobo.MetricType.AVERAGE; import static br.com.oncast.dev.lobo.MetricType.MAX; import static br.com.oncast.dev.lobo.MetricType.MIN; import br.com.oncast.dev.lobo.Profile; (...) @Profile("100timesBusinessMethod") public void profileMyBusinessLayer() { startMetric("avgBusinessMethod", AVERAGE); startMetric("maxBusinessMethod", MAX); startMetric("minBusinessMethod", MIN); BusinessLayer.businessMethod(); for (int i = 0; i < 100; i++) { switchMetric("avgBusinessMethod"); switchMetric("maxBusinessMethod"); switchMetric("minBusinessMethod"); BusinessLayer.businessMethod(); } endMetric("avgBusinessMethod"); endMetric("maxBusinessMethod"); endMetric("minBusinessMethod"); } |
Este é um exemplo bem completo da utilização do Lobo e, mesmo assim, ainda é muito simples.
O código acima executa um cenário que tem cem execuções de um método da camada de negócios. A métrica padrão é renomeada para "100timesBusinessMethod" e existem mais três métricas chamadas "avgBusinessMethod", "maxBusinessMethod" e "minBusinessMethod". Estas métricas registram o tempo de execução médio, máximo e mínimo de todos as 100 chamados ao método businessMethod().
O código acima executa um cenário que tem cem execuções de um método da camada de negócios. A métrica padrão é renomeada para "100timesBusinessMethod" e existem mais três métricas chamadas "avgBusinessMethod", "maxBusinessMethod" e "minBusinessMethod". Estas métricas registram o tempo de execução médio, máximo e mínimo de todos as 100 chamados ao método businessMethod().
5. Analizando resultados
Lobo gera gráficos que mostram a evolução do tempo de execução ao longo da linha dos builds. O eixo Y representa o tempo de execução em mili segundos, desta forma, quanto mais alto o ponto no gráfico, mais lento é o seu código. O eixo X representa a evolução dos builds. Confira no exemplo:

Gráfico 1: o tempo de execução crescendo ao longo dos builds.
O exemplo acima indica que o cenário chamado "performaticMethod"
tem sua performance degradada ao longo das releases. No build 1.1, o seu tempo de execução era de 2,8ms e após
dez builds, no build 1.10, seu tempo de execução alcançou 11ms. Isto não significa necessariamente que sua
performance é ruim, pois 11ms passa durante um piscar de olhos. Entretanto, talvez o trecho de código sendo
testado representa uma porção crítica do software, sendo executado milhões de vezes. Neste caso, um incremento
de 2,8ms para 11ms poderia representar um crescimento realmente grande. O gráfico gerado pelo Lobo ajudaria
você a focar suas atenções neste método.
O exemplo abaixo apresenta um cenário onde a necessidade de atenção é ainda mais sutil:
O exemplo abaixo apresenta um cenário onde a necessidade de atenção é ainda mais sutil:

Gráfico 2: o tempo de execução parece estável, exceto no último build.
Enquanto o primeiro exemplo expõe uma contínua degradação da performance, o segundo gráfico mostra um cenário mais sutil. O tempo de execução tem alguns aumentos pontuais até o build 1.9, então no último build ele cresce quase 100ms. Num caso como este, Lobo auxiliaria você a reconhecer rapidamente que as modificações do último build possivelmente impactaram na performance dos cenários. Desta forma, uma eventual investigação para melhorias de performance é realmente mais focada, sendo muito mais provável alcançar algum ganho de performance.
Apêndice A. Limitações da versão 1.0.alpha
As seguintes limitações foram identificadas na versão atual do Lobo:
- Relatórios em páginas únicas;
- Gráficos de histórico não escaláveis (apresentam sempre todos os resultados);
- Não pode inferir o nome das versões;
- Interferência da carga de trabalho da máquina;
-
Precisão.
Relatórios em páginas únicas
Os relatórios atualmente são consolidados em um único arquivo HTML. Quando sua infra-estrutura de build começa a coletar muitas métricas fica muito difícil de achar a métrica que você quer. Uma abordagem melhor de relatórios (possivelmente utilizando múltiplos HTMLs e um FRAMESET, como o JUnit) estará disponível nas próximas versões.
Gráficos de histórico não escaláveis
A geração dos gráficos apresenta todos os valores coletados em todos os builds de uma métrica. Se você tem um mês de dados e seu CruiseControl roda duas vezes por dia, você provavelmente terá 60 valores em cada gráfico, o que pode torná-los ilegíveis. Estratégias para resolver este problema, como agregação de dados ou projeção apenas dos valores mais recentes, irão resolver este problema em futuras versões.
Não pode inferir o nome das versões
O nome da versão (utilizado para nomear o eixo X dos gráficos) é uma entrada para a task <profile-merge> e deve sempre ser especificada. Prevê-se um comportamento de incremento automático caso esta propriedade não seja setada, permitindos assim uma utilização mais intuitiva.
Interferência da carga de trabalho da máquina
A carga de trabalho da máquina intefere nos resultados de performance. É recomendável utilizar um servidor que possua um número fixo de serviços rodando (seu servidor de integração contínua provavelmente irá servir). Programe também a análise de performance para ser feita num momento que o servidor dificilmente é utilizado.
Precisão
Lobo ainda é muito vulnerável ao escalonamento do sistema operacional e da máquina virtual, portanto variações de millisegundos podem ser consideradas normais de execução em execução. Se você está avaliando a execução de um trecho de código que leva muito tempo para executar (alguns segundos por exemplo) você não precisa se preocupar. Mas se você está analizando trechos mais rápidos, então você deve executar o trecho várias vezes e coletar a métrica média (MetricType.AVERAGE) para ter um valor mais confiável.
Apêndice B. Dependências
A atual distribuição do Lobo é testada para trabalhar com java 5 ou java 6 e também ant 1.6 ou ant 1.7.
A distribuição do Lobo é dependente das seguintes bibliotecas:
As bibliotecas listadas acima - jcommon e jfreechart - estão embutidas na distribuição "com dependências": oncast-lobo-with-deps-1.0.alpha.jar.
A distribuição completa do Lobo contêm seu código fonte. Para compilá-lo você irá precisar de:
Todas estas bibliotecas, incluindo dependências para execução e dependências para testes (exceto java e ant), código fonte, binários, o tutorial "O Seu Primeiro Teste: Passo a Passo" e estes manual do usuário podem ser encontrados na distribuição completa: oncast-lobo-1.0.alpha.zip.
A distribuição do Lobo é dependente das seguintes bibliotecas:
- jcommon 1.0.9;
-
jfreechart 1.0.5.
As bibliotecas listadas acima - jcommon e jfreechart - estão embutidas na distribuição "com dependências": oncast-lobo-with-deps-1.0.alpha.jar.
A distribuição completa do Lobo contêm seu código fonte. Para compilá-lo você irá precisar de:
- junit 4.2;
- ant-testunit;
- ant-launcher;
-
mockobjects 0.09.
Todas estas bibliotecas, incluindo dependências para execução e dependências para testes (exceto java e ant), código fonte, binários, o tutorial "O Seu Primeiro Teste: Passo a Passo" e estes manual do usuário podem ser encontrados na distribuição completa: oncast-lobo-1.0.alpha.zip.
Appendix C. Licença
Lobo é um software livre e é licenciado sob a versão 2 da GPL (ou qualquer outra versão mais recente).
O Lobo é distribuido DA FORMA COMO É, SEM NENHUMA GARANTIA. Confira a licença GNU GPL para mais detalhes. A licença completa está disponível na distribuição completa: oncast-lobo-1.0.alpha.zip.
O Lobo é distribuido DA FORMA COMO É, SEM NENHUMA GARANTIA. Confira a licença GNU GPL para mais detalhes. A licença completa está disponível na distribuição completa: oncast-lobo-1.0.alpha.zip.