Teste de Carga com The Grinder
Transcrição
Teste de Carga com The Grinder
Teste de Carga com The Grinder Uma alternativa viável ao JMeter Filipe Tagliatti [email protected] Graduado em Sistemas de Informação, com quase 4 anos de experiencia em desenvolvimento de sistemas web em PHP e Java, atualmente dedicasse no desenvolvimento em soluções completas para organizações do Terceiro Setor como OMGs e fundações. Cesar Roberto de S. Loureiro Júnior Graduando em Sistema de Informação pela Faculdade Metodista Granbery. Atualmente Analista de TI na empresa Virtual Agência Digital. De que se trata o artigo O artigo mostra uma alternativa ao JMeter para teste de carga, o The Grinder. Mostrando como instalar, configurar e como ele pode ajudar no dia a dia do tester ou desenvolvedor, com funcionalidades que lhe dão uma maior liberdade na criação dos seus testes. Em que situação o tema é útil O artigo é útil para aqueles que querem começar a aplicar a técnica de teste de carga em seus sistemas ou para aqueles que já conhecem sobre o assunto, mas pode através desse artigo conhecer uma nova ferramenta para auxilia-lo nessa tarefa. TESTE DE CARGA COM THE GRINDER O artigo começa introduzindo o leitor sobre alguns conceitos de teste de software e teste de carga. Em sequencia é mostrado como baixar e instalar o The Grinder e fazer um teste de carga de exemplo. Logo após apresentados um pouco da estrutura do software e as estatísticas que ele gera. Também alguns scripts prontos que você pode aproveitar no dia a dia e por fim apresentamos um estudo de caso onde fazendo um teste de carga e analisamos seus resultados. Introdução Nesse artigo vamos abordar a configuração e a forma de utilizar a ferramenta para testes de carga em sites chamada Grinder, porém antes de começar a falar sobre o software é preciso intender um pouco sobre a importância de fazer testes, o que vem a ser uma ferramenta de teste de carga, e quais são as diferenças entre os testes. O teste em software é muito importante hoje em dia, porém não é uma novidade pois desde o início do desenvolvimento de sistemas o teste e usado para garantir pelo menos o funcionamento básico do que foi contratado para fazer o sistema, muitas empresas utilizam a desculpa de que “[...] testar o software, é que teste custa caro, mas Pressman já apresentou em seu Livro de Engenharia de Software que o custo do defeito é progressivo, ou seja, encontrar o defeito na fase de engenharia de requisitos custa 1 enquanto encontrar o defeito durante a fase de uso custa 100 vezes mais, então utilizar o teste, reduz custo e não aumenta”. ROCHA Fabio Gomes. Sendo que os gastos com manutenções para correção de defeitos pode ter um custo muito elevado para a empresa causando gastos desastrosos para a manutenção, para evitar possíveis problemas com clientes por ser desenvolvidos sistemas com muitas falhas e alto custo na manutenção, foi buscado métodos para garantir a qualidade de softwares. Bastos et. al. abordam alguns pontos dentro da qualidade de software que precisam ser consideradas: a confiança que é quando o sistema resiste a falhas durante a execução, a funcionalidade quando o sistema comporta o que foi definido por seus requisitos e a performance que é quando o sistema responde todas as funcionalidade em tempo hábil. Hoje o teste tem sua área específica em muitas instituições garantindo a qualidade de todo sistema que e produzidos, e para aproximar os testes do desenvolvimento LOPES Mateus Bruno Teixeira et. al. apresentam em seu artigo o modelo “V” (Figura 1) focado no desenvolvimento durante todo o ciclo. Figura 1. Representação do modelo “V” (GRAIC e JASKIEl, 2002). E o teste de carga é um dos tipos de testes de softwares onde “[...] onde o volume de gerado pela ferramenta de geração de carga é crescente no decorrer do tempo. O objetivo principal é encontrar o limite de capacidade da aplicação e identificar qual o limitante (codificação, hardware, tempo de resposta excessivo). Da mesma forma que os outros testes, não é objetivo do teste de carga encontrar problemas funcionais na aplicação”. NETO Oscar Nogueira. com o teste de carga podemos descobrir até mesmo se o hardware que usamos está sendo desperdiçado ou não funciona de forma correta, se o site possui páginas que demoram muito para ser carregadas ou se o acesso ao banco demora muito para carregar as informações, esses fatores podem contribuir para o uso de equipamentos de balanceamento de carga, e até servidores de contenção. The Grinder The Grinder é um framework de testes de carga que facilita a execução de testes de carga distribuídas entre vários servidores. Os scripts de teste são escritos em Jython, fornecendo suporte para testar uma ampla gama de protocolos de rede. The Grinder também vem com um plug-in para testar serviços HTTP. MAGNANI Mauricio, 2012 Download e Instalação Esse tópico poderia ser desnecessário se a ferramenta contasse com um método simples de instalação, mas não é o que acontece, apesar de não ser impossível o processos pode causar uma certa dor de cabeça em uma primeira vez. A primeira coisa é fazer o download da ferramenta, que pode ser baixada no site oficial http://grinder.sourceforge.net/, a versão mais atual até a publicação desse artigo é a 3. Logo depois de baixar o arquivo .zip. Depois de descompactar o arquivo no local desejado vamos ao “pulo do gato” para executa-lo. Primeiro vamos criar um script apenas para teste, para isso crie um arquivo chamado “teste,py” e coloque o código abaixo no arquivo (Listagem 1): Listagem 1. Script de Exemplo from net.grinder.script import Test from net.grinder.script.Grinder import grinder from net.grinder.plugin.http import HTTPRequest test1 = Test(1, "Request resource") request1 = HTTPRequest() test1.record(request1) class TestRunner: def __call__(self): result = request1.GET("http://www.google.com.br") Depois disso abra o arquivo grinder.properties e procure pelo parâmetro grinder.script, substitua o valor pelo nome do arquivo que criamos para teste, que no nosso caso se chama teste.py. Agora vamos a execução da ferramenta para isso é recomendado cria um arquivo bat se for Windows ou sh se for Linux. Para esse exemplo utilizamos a plataforma Windows. Figura 2 A Figura 2 mostra o primeiro script que podemos chamar de console por se tratar do console da ferramenta. Logo após dever executar o próximo script. Figura 3 A Figura 3 mostra o segundo script que deve ser executado que podemos chamar de agente que tem o papel de ler as configurações executar os teste, já o console apenas nos mostra o resultado de forma gráfica. Uma observação importante, os scripts devem ser executados na ordem citada, primeiro o console depois o agente. Quando o script do agente for executado ou se foi executado, mas ainda está processando o botão de start fica bloqueado (Figura 4), e quando o agente está devidamente iniciado e pronto para executar, esse botão é desbloqueado (Figura 5). Figura 4 Figura 5 Depois que estiver tudo ok é só clicar no botão start e que os testes irão começar (Figura 6). Figura 6. Estrutura Antes de começar a trabalhar com o Grinder é importante saber como ele é estruturado, para assim podermos como utilizar a ferramenta corretamente e extrair ao máximo dela. Agentes (Agents) - Você geralmente têm um único agente em cada máquina injetora de carga, que irá iniciar um número configurado de processos de trabalho. Se os agentes podem se conectar ao console, eles vão esperar por um sinal para começar antes de passar fora de um local de grinder.properties arquivo para os processos de trabalho. Trabalhadores (Workers) - Trabalhadores, como você pode imaginar, faz o trabalho de teste. Ou seja, eles realmente executam os scripts de teste de carga. O arquivo grinder.properties que é passado para o trabalhador pelo agente define, entre outras coisas, o script que o trabalhador irá executar contra o alvo, quantos threads o trabalhador vai gerar, e quantas vezes cada um desses tópicos ira executar o script. Console - Esta é uma interface gráfica que pode ser usado para controlar os agentes, e também exibe as estatísticas recolhidas que são dos trabalhadores. TCPProxy - O TCPProxy se interpõe entre o navegador e o servidor de destino, e pode ser usado para gerar scripts, registrando a atividade do seu navegador, que podem posteriormente ser executada pelos processos de trabalho. Isto é realmente útil para a geração de testes de carga que simulam a interação do usuário com uma aplicação web. Estatísticas Nesse ponto vamos abordar as estatísticas que são os geradas nos relatórios feitos de cada site testado pelo programa exibidos no console e salvos em “*.data”, onde (*) significa um nome qualquer para o arquivo, como demonstra a Figura 7: Figura 7. Arquivo de estatísticas geradas pelo sistema A Figura 7 mostra como são armazenados os resultados que são gerados ao logo de todo o processo de análise. As estatísticas geradas são demonstradas em uma aba Graphs contendo as seguintes informações: TPS, media de MS, média de TPS, pico de TPS, número de testes e erros nos testes. Como é demonstrado na Figura 8. Figura 8. Gráfico gerado através das estatísticas do sistema Também e possível observar os resultados no console em forma de tabela dinâmica contendo as informações da aba Graphs e muitas outras atualizações em tempo real, como: testes bem sucedidos, tempo médio, tempo médio para o desvio padrão, comprimento médio de resposta, bytes de resposta por segundo, erros de resposta, tempo médio para resolver o host, tempo médio para estabelecer conexão, tempo médio até o primeiro byte. Como demonstra a Figura 9. Figura 9. Resultados gerados a partir do teste Vamos falar um pouco sobre o que é cada um desses campos que aparecem no console: Testes bem sucedidos: São teste de HTTP feitos no host determinado Tempo médio: É o tempo médio de acesso ao host. Comprimento médio de resposta: Tempo médio para resposta do host para responder aos testes. Bytes de resposta por segundo: É o tamanho da resposta em Bytes emitidos pelo host. Erros de resposta: Quantidade de erros que ocorreram ao tentar receber respostas dos hosts. Tempo médio para resolver o host: Tempo para conseguir encontrar a fonte do host. Tempo médio para estabelecer conexão: O tempo para conseguir um conexão estável para fazer os testes. Tempo médio até o primeiro byte: Tempo médio até o primeiro byte de resposta do host. Grinder properties O grinder.properties é o arquivo onde aprendemos e configuramos algumas propriedades do software Grinder, pode ser configurado por exemplo: o número de processos, o número de threads de trabalho, o endereço IP ou nome do host o agente trabalhara, entre outros, como demonstra a figura abaixo: Figura 2. parte da configuração do arquivo grinder.properties Número de processos: é a quantidade de processos que pode ser executados ao mesmo tempo, funciona como os grupos de usuário para quem já utilizou o JMeter. Número de threads de trabalho: número de threads que trabalharão ao mesmo tempo em cada processo, sendo que cada thread corresponde a um usuário realizando o teste de caraga. Endereço IP ou nome do host: configurar qual será o host acessado para testes. Tempo para iniciar o próximo teste: você pode configurar em quanto tempo o próximo teste será realizado depois que o anterior for realizado. Duração do teste: você pode programar quanto tempo o teste de carga vai durar. Scripts Antes de iniciar o teste de desempenho, você precisa ter um script de teste. Scripts de teste no Grinder são escritos na linguagem Jython, que é uma implementação da linguagem Python para rodar na JVM (Java Virtual Machine). Você pode escrever seus testes a partir do zero se quiser. Para ajudar nessa tarefa a documentação é bem escrita e vai ajudar nessa tarefa, outra ajuda é o fato dos desenvolvedores ter disponibilizados vários exemplos, uma para cada funcionalidade do software. Vamos ver alguns dos principais scripts que podem ajudar no dia a dia do tester. O código da Listagem 2 representa o teste em formulários HTTP inclusive com o upload de arquivos: Listagem 2. Script para teste em formulários # HTTP multipart form submission # # This script uses the HTTPClient.Codecs class to post itself to the # server as a multi-part form. Thanks to Marc Gemis. from from from from from net.grinder.script.Grinder import grinder net.grinder.script import Test net.grinder.plugin.http import HTTPRequest HTTPClient import Codecs, NVPair jarray import zeros test1 = Test(1, "Upload Image") request1 = HTTPRequest(url="http://localhost:7001/") test1.record(request1) class TestRunner: def __call__(self): files = ( NVPair("self", "form.py"), ) parameters = ( NVPair("run number", str(grinder.runNumber)), ) # This is the Jython way of creating an NVPair[] Java array # with one element. headers = zeros(1, NVPair) # Create a multi-part form encoded byte array. data = Codecs.mpFormDataEncode(parameters, files, headers) grinder.logger.output("Content type set to %s" % headers[0].value) # Call the version of POST that takes a byte array. result = request1.POST("/upload", data, headers) Outra funcionalidade muito interessante é fazer teste de envio de e-mail, podendo então fazer testes em servidores de e-mail, ou mesmo no SMTP da hospedagem do seu site (Listagem 3). Listagem 3. Script para teste em servidor de e-mail # # # # # # # Email Send email using Java Mail (http://java.sun.com/products/javamail/) This Grinder Jython script should only be used for legal email test traffic generation within a lab testbed environment. Anyone using this script to generate SPAM or other unwanted email traffic is violating the law and should be exiled to a very bad place for a very long time. from net.grinder.script.Grinder import grinder from net.grinder.script import Test from java.lang import System from javax.mail import Message, Session from javax.mail.internet import InternetAddress, MimeMessage emailSendTest1 = Test(1, "Email Send Engine") class TestRunner: def __call__(self): smtpHost = "mailhost" properties = System.getProperties() properties["mail.smtp.host"] = smtpHost session = Session.getInstance(System.getProperties()) session.debug = 1 message = MimeMessage(session) message.setFrom(InternetAddress("[email protected] ")) message.addRecipient(Message.RecipientType.TO, InternetAddress("[email protected]") ) message.subject = "Test email %s from thread %s" % (grinder.runNumber, grinder.th readNumber) # One could vary this by pointing to various files for content message.setText("SMTPTransport Email works from The Grinder!") transport = session.getTransport("smtp") # Instrument transport object. emailSendTest1.record(transport) transport.connect(smtpHost, "username", "password") transport.sendMessage(message, message.getRecipients(Message.RecipientT ype.TO)) transport.close() Mais um teste presente no dia a dia do desenvolvedor é o de carga do banco de dados, com o script da Listagem 4 você consegue fazer inserções e/ou seleções simultâneas para conferir se seu banco está otimizado ou se o servidor de banco de dados ira aquentar o “tranco”. Listagem 4. Script de teste em banco de dados # # # # Grinding a database with JDBC Some simple database playing with JDBC. To run this, set the Oracle login details appropriately and add the Oracle thin driver classes to your CLASSPATH. from from from from java.sql import DriverManager net.grinder.script.Grinder import grinder net.grinder.script import Test oracle.jdbc import OracleDriver test1 = Test(1, "Database insert") test2 = Test(2, "Database query") # Load the Oracle JDBC driver. DriverManager.registerDriver(OracleDriver()) def getConnection(): return DriverManager.getConnection( "jdbc:oracle:thin:@127.0.0.1:1521:mysid", "wls", "wls") def ensureClosed(object): try: object.close() except: pass # One time initialisation that cleans out old data. connection = getConnection() statement = connection.createStatement() try: statement.execute("drop table grinder_fun") except: pass statement.execute("create table grinder_fun(thread number, run number)") ensureClosed(statement) ensureClosed(connection) class TestRunner: def __call__(self): connection = None insertStatement = None queryStatement = None try: connection = getConnection() insertStatement = connection.createStatement() test1.record(insertStatement) insertStatement.execute("insert into grinder_fun values(%d, %d)" % (grinder.threadNumber, grinder.runNumber)) test2.record(queryStatement) queryStatement.execute("select * from grinder_fun where thread=%d" % grinder.threadNumber) finally: ensureClosed(insertStatement) ensureClosed(queryStatement) ensureClosed(connection) O próximo script que apesar de ser um pouco mais complicado de se entender, tem a função de fazer testes com cookies (Listagem 5). Listagem 5. Script de teste em banco de dados # HTTP cookies # HTTP example which shows how to access HTTP cookies. # # The HTTPClient library handles cookie interaction and removes the # cookie headers from responses. If you want to access these cookies, # one way is to define your own CookiePolicyHandler. This script defines # a CookiePolicyHandler that simply logs all cookies that are sent or # received. # # The script also demonstrates how to query what cookies are cached for # the current thread, and how add and remove cookies from the cache. # # If you really want direct control over the cookie headers, you # can disable the automatic cookie handling with: # HTTPPluginControl.getConnectionDefaults().useCookies = 0 from net.grinder.script.Grinder import grinder from net.grinder.script import Test from net.grinder.plugin.http import HTTPRequest, HTTPPluginControl from HTTPClient import Cookie, CookieModule, CookiePolicyHandler from java.util import Date log = grinder.logger.info # Set up a cookie handler to log all cookies that are sent and received. class MyCookiePolicyHandler(CookiePolicyHandler): def acceptCookie(self, cookie, request, response): log("accept cookie: %s" % cookie) return 1 def sendCookie(self, cookie, request): log("send cookie: %s" % cookie) return 1 CookieModule.setCookiePolicyHandler(MyCookiePolicyHandler()) test1 = Test(1, "Request resource") request1 = HTTPRequest() test1.record(request1) class TestRunner: def __call__(self): # The cache of cookies for each at # the start of each run. worker thread will be reset result = request1.GET("http://localhost:7001/console/?request1") # If the first response set any cookies for the domain, # they willl be sent back with this request. result2 = request1.GET("http://localhost:7001/console/?request2") # Now let's add a new cookie. threadContext = HTTPPluginControl.getThreadHTTPClientContext() expiryDate = Date() expiryDate.year += 10 cookie = Cookie("key", "value","localhost", "/", expiryDate, 0) CookieModule.addCookie(cookie, threadContext) result = request1.GET("http://localhost:7001/console/?request3") # Get all cookies for the current thread and write them to the log cookies = CookieModule.listAllCookies(threadContext) for c in cookies: log("retrieved cookie: %s" % c) # Remove any cookie that isn't ours. for c in cookies: if c != cookie: CookieModule.removeCookie(c, threadContext) result = request1.GET("http://localhost:7001/console/?request4") O intuído desse tópico foi apenas apresentar alguns dos principais scripts que vem com o Grinder, vários outros podem ser vistos na pasta examples. Estudo de Caso Agora apresentamos um exemplo pratico do uso da ferramenta, mais precisamente analisar as estatísticas e chegar a uma conclusão sobre esse teste de carga. Para isso criamos uma servlet Java que tem a função de apenas contar o número de acessos ao site. O código fonte desse servlet pode ser visto na Listagem 6. Listagem 6. Servlet de contagem de acessos import import import import import import import java.io.IOException; java.io.PrintWriter; javax.servlet.ServletException; javax.servlet.annotation.WebServlet; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; @WebServlet(value="/RequestCounter") public class RequestCounter extends HttpServlet{ private static final long serialVersionUID = 1L; private int requestCount; public void init() { requestCount = 0; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); requestCount++; PrintWriter out = response.getWriter(); String title = "Total de Requisições: "; String docType = "<!doctype html public \"-//w3c//dtd html 4.0 " + "transitional//en\">\n"; out.println(docType + "<html>\n" + "<head><title>" + title + "</title></head>\n" + "<body bgcolor=\"#FFFFAA\">\n" + "<h1 align=\"center\">" + title + "</h1>\n" + "<h2 align=\"center\">" + requestCount + "</h2>\n" + "</body></html>"); } } O script utilizado foi o mesmo mostrado no tópico de Download e Instalação, apenas trocamos a url para a do nosso servlet (http://localhost:8080/TesteCarga/RequestCounter). Para a configuração do arquivo grynder.properties foi utilizado apenas os parâmetros grinder.script para apontar o arquivo de script, grinder.processes = 1 que faz com que tenhamos apenas um grupo de usuários acessando o site, grinder.threads = 1000 que determina o numero de usuários que vai acessar o site, ou seja, mil usuários e por ultimo grinder.runs = 0 que apenas determina que vamos usar o Grinder com interface gráfica. O computador utilizado foi um Intel I7 com 3.10 GB de RAM e o servidor web utilizador foi o Glassfish 3, sendo que não foi feito nenhum tipo de otimização no servidor e na JVM. Após alguns minutos de teste obtivemos o seguinte resultado (Figura 10 e 11). Figura 10. Resultado do Teste de Carga Figura 11. Resultado do Teste de Carga Figura 12. Total de requisições ao site Foram feitos 1.513.838 de testes bem sucedidos e 12.106 erros, o que mostra que apenas oito por cento dos acessos não foram bem sucedidos, isso mostra como o servidor aguentou bem nossos mil usuários, vale lembrar que são mil acessos simultâneos por segundo, sendo que pode ser que não seja exatamente sincronizado que no mesmo segundo os mil usuários consigam acessar. Também podemos ver na Figura 12 o número total de usuários que acessaram o site, que confere se juntarmos os testes bem sucedidos com os erros. Outro parâmetro importante é o TPS (Transações por Segundo) que foi de 1580, isso significa que mesmo mil usuários acessando o site ainda temos 580 transações que poderiam ser convertidos em novos usuários. Algumas estatísticas de tempo médio também são importantes por mostrar como anda a performance do nosso servidor e do site, como o Mean Time, Meam Response Length, entre outros. Conclusão O The Grinder é uma boa opção para os testes de carga, ele possui suporte para diversos protocolos e métodos de teste. Ele não possui a melhor interface, isso dificulta em um primeiro momento o contato com a ferramenta para quem nunca utilizou esse tipo de software, mas depois de algumas horas testando a ferramenta você acaba acostumando. Um ponto que pode dificultar um pouco a utilização dele é o fato de você ter que criar os scripts para o teste e principalmente por esses scripts ter que ser escritos em Jython, isso expande a capacidade da criação de testes, mas não oferece uma alternativa mais simples para os mais leigos, mas por outro lado ele oferece uma ótima documentas e ótimos exemplos de uso, trazendo diversos scripts prontos. Referências MAGNANI Mauricio Jr, The Grinder – Um Framework Java Para Testes de Carga. Disponível em: <http://jbossdivers.wordpress.com/2012/09/30/thegrinder-um-framework-java-para-testes-de-carga/>. Acessado em: 03/12/2013. ASTON Philip, FITZGERALD Calum, User Guide. Disponivel <http://grinder.sourceforge.net/g3/manual.html>. Acessado em 28/11/2013. em: ROCHA Fabio Gomes, A importância dos testes para a qualidade do software. Disponivel em: < http://www.devmedia.com.br/a-importancia-dos-testes-para-aqualidade-do-software/28439> Acessado em 09/12/2013. LOPES Mateus Bruno Teixeira, CARNEIRO Allan Guerreiro. A Importância do Processo de Teste de Software em TI. Disponível em: < http://www.univicosa .com.br/arquivos_internos/artigos/ImportanciadoProcessodeTestedeSoftwareemTI.pdf >, Acessado em 07/12/2013. SILVA Renata Milena dos Anjos. A importância dos Testes de Qualidade no desenvolvimento de Software. Disponível em: <http://portal.ftc.br/eventos/ wie/2012/artigos/8%20-%20A%20import%C3%A2ncia%20dos%20Testes%20de %20Qualidade%20no%20%20desenvolvimento%20de%20Software.pdf>. Acessado em: 08/12/2013. NETO Oscar Nogueira. Qual a diferença entre teste de carga, stress e performance?. Disponível em: <http://jmeter.com.br/2013/04/qual-a-diferenca-entreteste-de-stress-performance-e-carga/>. Acessado em: 08/12/2013. Eric Woods. The Grinder – Load Testing Web Applications. Disponivel em: <http://blog.credera.com/technology-insights/java/the-grinder-load-testing-webapplications/>. Acessado em 11/12/2013.