Introdução ao SPF4J
1. Visão geral
O teste de desempenho é uma atividade frequentemente levada para os estágios finais do ciclo de desenvolvimento de software. Normalmente contamos comJava profilers para ajudar a solucionar problemas de desempenho.
Neste tutorial, examinaremos o Simple Performance Framework para Java (SPF4J). Ele nos fornece APIs que podem ser adicionadas ao nosso código. Como resultado, podemos fazerperformance monitoring an integral part of our component.
2. Conceitos básicos de captura e visualização de métricas
Antes de começar, vamos tentar entender os conceitos de captura e visualização de métricas usando um exemplo simples.
Vamos imaginar que estamos interessados em monitorar os downloads de um aplicativo recém-lançado em uma loja de aplicativos. Por uma questão de aprendizagem, vamos pensar em fazer este experimento manualmente.
2.1. Capturando as métricas
Primeiro, precisamos decidir sobre o que precisa ser medido. A métrica em que estamos interessados édownloads/min. Portanto,, mediremos o número de downloads.
Segundo, com que frequência precisamos fazer as medições? Vamos decidir “uma vez por minuto”.
Finalmente, quanto tempo devemos monitorar? Vamos decidir “por uma hora”.
Com essas regras em vigor, estamos prontos para conduzir o experimento. Depois que o experimento termina, podemos ver os resultados:
Time Cumulative Downloads Downloads/min
----------------------------------------------
T 497 0
T+1 624 127
T+2 676 52
...
T+14 19347 17390
T+15 19427 80
...
T+22 27195 7350
...
T+41 41321 11885
...
T+60 43395 40
As duas primeiras colunas -timeecumulative downloads - são valores diretos que observamos. A terceira coluna,downloads/min, é um valor derivado calculado como a diferença entre os valorescumulative download atuais e anteriores. Isso nos fornece o número real de downloads durante esse período.
2.2. Visualizando as métricas
Vamos traçar um gráfico linear simples detime vsdownloads/min.
Podemos ver que existem alguns picos indicando um grande número de downloads que ocorreram em algumas ocasiões. Devido à escala linear usada para o eixodownloads, os valores inferiores aparecem como uma linha reta.
Vamos mudar o eixodownloads para usar uma escala logarítmica (base 10) e traçar um gráfico log / linear.
Agora começamos a ver os valores mais baixos. E eles estão mais próximos de 100 (+/-). Observe que o gráfico linear indicou uma média de703, pois também incluiu os picos.
Se excluirmos os picos como aberrações, podemos concluir de nosso experimento usando o gráfico log / linear:
-
a médiadownloads/min está na ordem de 100s
3. Monitoramento de desempenho de uma chamada de função
Tendo entendido como capturar uma métrica simples e analisá-la do exemplo anterior, vamos agora aplicá-la em um método Java simples -isPrimeNumber:
private static boolean isPrimeNumber(long number) {
for (long i = 2; i <= number / 2; i++) {
if (number % i == 0)
return false;
}
return true;
}
Usando o SPF4J, há duas maneiras de capturar métricas. Vamos explorá-los na próxima seção.
4. Instalação e configuração
4.1. Configuração do Maven
O SPF4J fornece muitas bibliotecas diferentes para diferentes propósitos, mas precisamos apenas de algumas para o nosso exemplo simples.
A biblioteca principal éspf4j-core, que nos fornece a maioria dos recursos necessários.
Vamos adicionar isso como uma dependência Maven:
org.spf4j
spf4j-core
8.6.10
Há uma biblioteca mais adequada para monitoramento de desempenho -spf4j-aspects, que usaAspectJ.
Vamos explorar isso em nosso exemplo, então vamos adicionar isso também:
org.spf4j
spf4j-aspects
8.6.10
E, finalmente, o SPF4J também vem com uma IU simples que é bastante útil para visualização de dados, então vamos adicionarspf4j-ui também:
org.spf4j
spf4j-ui
8.6.10
4.2. Configuração de arquivos de saída
A estrutura SPF4J grava dados em um banco de dados de séries temporais (TSDB) e, opcionalmente, também pode gravar em um arquivo de texto.
Vamos configurar ambos e definir uma propriedade do sistemaspf4j.perf.ms.config:
public static void initialize() {
String tsDbFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.tsdb2";
String tsTextFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.txt";
LOGGER.info("\nTime Series DB (TSDB) : {}\nTime Series text file : {}", tsDbFile, tsTextFile);
System.setProperty("spf4j.perf.ms.config", "[email protected]" + tsDbFile + "," + "[email protected]" + tsTextFile);
}
4.3. Gravadores e fontes
A principal capacidade da estrutura SPF4J é registrar, agregar e salvar métricas, para que não haja pós-processamento necessário ao analisá-lo. Ele faz isso usando as classesMeasurementRecordereMeasurementRecorderSource.
Essas duas classes fornecem duas maneiras diferentes de gravar uma métrica. A principal diferença é queMeasurementRecorder can be invoked from anywhere, whereas MeasurementRecorderSource is used only with annotations.
A estrutura nos fornece uma classeRecorderFactory para criar instâncias de gravador e classes de origem do gravador para diferentes tipos de agregações:
-
createScalableQuantizedRecorder() ecreateScalableQuantizedRecorderSource()
-
createScalableCountingRecorder() ecreateScalableCountingRecorderSource()
-
createScalableMinMaxAvgRecorder() ecreateScalableMinMaxAvgRecorderSource()
-
createDirectRecorder() ecreateDirectRecorderSource()
Para nosso exemplo, vamos escolher agregação quantizada escalonável.
4.4. Criando um gravador
Primeiro, vamos criar um método auxiliar para criar uma instância deMeasurementRecorder:
public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;
return RecorderFactory.createScalableQuantizedRecorder(
forWhat, unitOfMeasurement, sampleTimeMillis, factor, lowerMagnitude,
higherMagnitude, quantasPerMagnitude);
}
Vejamos as diferentes configurações:
-
unitOfMeasurement - o valor da unidade sendo medido - para um cenário de monitoramento de desempenho, geralmente é uma unidade de tempo
-
sampleTimeMillis - o período de tempo para fazer medições - ou em outras palavras, com que frequência fazer medições
-
factor - a base da escala logarítmica usada para traçar o valor medido
-
lowerMagnitude - o valor mínimo na escala logarítmica - para log de base 10,lowerMagnitude = 0 significa 10 para potência 0 = 1
-
higherMagnitude - o valor máximo na escala logarítmica - para log de base 10,higherMagnitude = 4 significa 10 à potência 4 = 10.000
-
quantasPerMagnitude - número de seções dentro de uma magnitude - se uma magnitude varia de 1.000 a 10.000, entãoquantasPerMagnitude = 10 significa que o intervalo será dividido em 10 subfaixas
Podemos ver que os valores podem ser alterados conforme nossa necessidade. Portanto, pode ser uma boa ideia criar instâncias deMeasurementRecorder separadas para diferentes medições.
4.5. Criando uma fonte
A seguir, vamos criar uma instância deMeasurementRecorderSource usando outro método auxiliar:
public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
public static final MeasurementRecorderSource INSTANCE;
static {
Object forWhat = App.class + " isPrimeNumber";
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;
INSTANCE = RecorderFactory.createScalableQuantizedRecorderSource(
forWhat, unitOfMeasurement, sampleTimeMillis, factor,
lowerMagnitude, higherMagnitude, quantasPerMagnitude);
}
}
Observe que usamos os mesmos valores para as configurações de antes.
4.6. Criando uma classe de configuração
Vamos agora criar uma classe útilSpf4jConfig e colocar todos os métodos acima dentro dela:
public class Spf4jConfig {
public static void initialize() {
//...
}
public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
//...
}
public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
//...
}
}
4.7. Configurandoaop.xml
O SPF4J nos fornece a opção de anotar métodos nos quais realizar medições e monitoramentos de desempenho. Ele usa a bibliotecaAspectJ, que permite adicionar comportamento adicional necessário para monitoramento de desempenho ao código existente sem modificação do próprio código.
Vamos tecer nossa classe e aspecto usando o weaver do tempo de carregamento e colocaraop.xml em uma pastaMETA-INF:
5. UsandoMeasurementRecorder
Agora vamos ver como usarMeasurementRecorder para registrar as métricas de desempenho de nossa função de teste.
5.1. Gravando as métricas
Vamos gerar 100 números aleatórios e invocar o método de verificação principal em um loop. Antes disso, vamos chamar nossa classeSpf4jConfig para fazer a inicialização e criar uma instância da classeMeasureRecorder. Usando esta instância, vamos chamar o métodorecord() para economizar o tempo individual gasto para chamadas de 100isPrimeNumber():
Spf4jConfig.initialize();
MeasurementRecorder measurementRecorder = Spf4jConfig
.getMeasurementRecorder(App.class + " isPrimeNumber");
Random random = new Random();
for (int i = 0; i < 100; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
long startTime = System.currentTimeMillis();
boolean isPrime = isPrimeNumber(numberToCheck);
measurementRecorder.record(System.currentTimeMillis() - startTime);
LOGGER.info("{}. {} is prime? {}", i + 1, numberToCheck, isPrime);
}
5.2. Executando o código
Agora estamos prontos para testar o desempenho de nossa função simplesisPrimeNumber ().
Vamos executar o código e ver os resultados:
Time Series DB (TSDB) : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.txt
1. 406704834 is prime? false
...
9. 507639059 is prime? true
...
20. 557385397 is prime? true
...
26. 152042771 is prime? true
...
100. 841159884 is prime? false
5.3. Visualizando os resultados
Vamos iniciar a IU do SPF4J executando o comando da pasta do projeto:
java -jar target/dependency-jars/spf4j-ui-8.6.9.jar
Isso exibirá um aplicativo de interface do usuário da área de trabalho. Então, no menu, vamos escolherFile>Open. Depois disso, vamos usar a janela de navegação para localizar o arquivospf4j-performance-monitoring.tsdb2 e abri-lo.
Agora podemos ver uma nova janela aberta com uma exibição em árvore contendo nosso nome de arquivo e um item filho. Vamos clicar no item filho e, em seguida, clicar no botãoPlot acima dele.
Isso irá gerar uma série de gráficos.
O primeiro gráfico,measurement distribution, é uma variação do gráfico log-linear que vimos anteriormente. Este gráfico mostra adicionalmente um mapa de calor com base na contagem.
O segundo gráfico mostra dados agregados como min, max e média:
E o último gráfico mostra a contagem de medidas versus tempo:
6. UsandoMeasurementRecorderSource
Na seção anterior, tivemos que escrever um código extra em torno de nossa funcionalidade para registrar as medições. Nesta seção, vamos usar outra abordagem para evitar isso.
6.1. Gravando as métricas
Primeiro, removeremos o código extra adicionado para capturar e registrar as métricas:
Spf4jConfig.initialize();
Random random = new Random();
for (int i = 0; i < 50; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
isPrimeNumber(numberToCheck);
}
Em vez de todo aquele clichê, a seguir, vamos anotar o métodoisPrimeNumber() usando@PerformanceMonitor:
@PerformanceMonitor(
warnThresholdMillis = 1,
errorThresholdMillis = 100,
recorderSource = Spf4jConfig.RecorderSourceForIsPrimeNumber.class)
private static boolean isPrimeNumber(long number) {
//...
}
Vejamos as diferentes configurações:
-
warnThresholdMillis - tempo máximo permitido para o método ser executado sem uma mensagem de aviso
-
errorThresholdMillis - tempo máximo permitido para o método ser executado sem uma mensagem de erro **
-
recorderSource - uma instância deMeasurementRecorderSource
6.2. Executando o código
Vamos fazer uma construção Maven primeiro e, em seguida, executar o código passando um agente Java:
java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar
Vemos os resultados:
Time Series DB (TSDB) : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.txt
[DEBUG] Execution time 0 ms for execution(App.isPrimeNumber(..)), arguments [555031768]
...
[ERROR] Execution time 2826 ms for execution(App.isPrimeNumber(..)) exceeds error threshold of 100 ms, arguments [464032213]
...
Podemos ver que a estrutura SPF4J registra o tempo necessário para cada chamada de método. E sempre que exceder o valorerrorThresholdMillis de 100 ms, ele registra como um erro. O argumento passado para o método também é registrado.
6.3. Visualizando os resultados
Podemos visualizar os resultados da mesma maneira que fizemos anteriormente, usando a interface do usuário do SPF4J, para que possamos consultar a seção anterior.
7. Conclusão
Neste artigo, falamos sobre os conceitos básicos de captura e visualização de métricas.
Entendemos então os recursos de monitoramento de desempenho da estrutura SPF4J com a ajuda de um exemplo simples. Também usamos a ferramenta interna da interface do usuário para visualizar os dados.
Como sempre, os exemplos deste artigo estão disponíveisover on GitHub.