Você está na página 1de 17

Command Line HTTP Stress Client Utility (for Linux)

Mario E. Matiusso Jr. (11028407) Bacharelado em Cincias da Computao Universidade Federal do ABC (UFABC) Santo Andr, SP Brasil
mario3001[a]ig.com.br

Resumo: Neste artigo ser apresentada a implementao de um software com o objetivo de testar a capacidade de resposta de um servidor web. Escrito na linguagem C/C++ sobre o sistema operacional Linux Ubuntu 11, o software apresentado utiliza mltiplas conexes simultneas com o servidor web e atravs de anlise do cabealho de resposta HTTP gera os dados de sada do programa.

1. Introduo
Como ouve-se falar o tempo topo, qualquer escrita de I/O em sistemas Linux feita lendo ou escrevendo sobre um descritor de arquivo. Um descritor de arquivo aquele nmero inteiro que fica associado a um arquivo aberto. Ou seja, para qualquer comunicao de I/O, seja um arquivo em disco, um terminal, um PIPE ou qualquer outra coisa necessrio um descritor de arquivo. Ento para a troca de mensagens atravs de uma rede necessrio um descritor de arquivo, e este descritor obtido atravs da chamada da funo socket(). Posteriormente este descritor utilizado como parmetro para a abertura da conexo TCP com o servidor web, para o envio (funo write) e para recebimento (funo read) de mensagens. Detalhes da conexo TCP no sero abordados neste artigo. O sistema operacional Linux tem capacidade de gerenciar inmeras conexes TCP simultaneamente (seguindo suas polticas de escalonamento, etc). O software aqui apresentado utiliza-se desta capacidade do sistema operacional para criar um ambiente (atravs da criao de vrios processos filho) em que se possam simular vrios clientes conectando-se simultaneamente a um servidor web. Este ambiente multitarefa possibilita que sejam feitos testes na capacidade de resposta de um servidor web simulando a situao de muitos clientes conectados e fazendo requisies. A seqncia deste artigo consiste em descrever a arquitetura do software na seo 2, mostrar o fluxo lgico na seo 3. Na seo 4 ser mostrado um caso de uso. Por fim as concluses do artigo encontram-se na seo 5.

2. Arquitetura do software
O software est estruturado de forma em que o processo inicial (pai) tem a responsabilidade de iniciar processos filhos e aps esta tarefa apenas monitora suas atividades. A monitorao das atividades de um processo filho nada mais do que a

leitura cclica (pooling) de uma rea me memria compartilhada e escrita no terminal dos valores lidos.

Figura 1: Arquitetura do Software

Os processos filhos, por sua vez, tm a responsabilidade de iniciar, requisitar dados e fechar conexes de forma cclica com o servidor web que est sendo testado. A cada resposta do servidor web, o processo filho avalia o cabealho de resposta HTTP e incrementa um contador para cada tipo de resposta. As respostas analisadas so: 200 OK, 201 Created, 204 No Content; 302 Found, 304 Not Modified; 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 408 Request Timeout; 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout;

Caso a o cabealho HTTP de resposta seja diferente das opes listadas acima, considera-se um cabealho mal formado e um contador de erros incrementado. Caso o servidor web no responda a solicitao HTTP, o contador de erros incrementado aps o estouro do tempo de timeout.

Aps a contagem de 30 erros, um processo filho se encerra. O processo pai nunca encerrado automaticamente. A comunicao entre o processo pai e processos filhos se d atravs de uma rea de memria compartilhada. Para evitar a utilizao de semforos, variveis atmicas, etc., a rea me memria compartilhada foi dividida em pores de 80 bytes. Cada processo filho criado tem acesso a uma nica poro da rea de memria.

Figura 2: rea de memria compartilhada

Como o segmento padro de memria compartilhada mxima em Linux de 4MB, este software fica limitado a aproximadamente 235 processos filhos concorrentes. Para conseguir rodar mais processos concorrentes deve-se alterar a varivel SHMMAX no arquivo de configurao do Kernel do Linux.

3. Fluxo Lgico
Os diagramas discutidos nesta seo detalham o fluxo lgico do programa. Apenas os aspectos mais importantes do software sero discutidos. Como podemos ver no diagrama do anexo 1, a partir o passo 1 o processo pai lana os processos filhos (Passo 4).

Figura 3: Criao dos processos filho

Aps isso entra em um lao de leitura de memria. O processo pai no tem relao com as conexes TCP. Os processos filho so os que executam o trabalho de conexo, escrita e leitura. O inicio desta tarefa est no passo 5 (anexo 2). Neste passo onde criado o descritor de arquivo para a comunicao em rede.

Figura 4: Criao de um socket

Com o socket criado, o prximo passo a requisio de informaes do servidor web para o servidor DNS.

Figura 5: Informaes do Servidor

A funo gethostbyname retorna uma estrutura de dados com informaes como o nome completo do servidor (Fully Qualified Domain Name), nomes alternativos (Alias), tipo de endereo, tamanho em bytes de cada endereo, e uma lista de endereos caso o servidor tenha mais que um. Estas informaes so utilizadas como parmetro de conexo com o servidor. Esta requisio pode ser vista atravs do sniffer de rede wireshark na figura abaixo:

Figura 6: Requisio DNS

No passo 7 onde ocorre a conexo TCP, escrita e leitura HTTP.

Figura 7: Conexo TCP

A conexo feita atravs da funo connect. Note que o descritor do socket e a estrutura com os dados do servidor web so utilizados como parmetro. A conexo pode ser visualizada na figura abaixo:

Figura 8: Conexo TCP

Com a conexo estabelecida, so chamadas as funes de escrita e leitura.

Figura 9: Funes de escrita e leitura

Com a chamada das funes write e read podemos visualizar atravs do wireshark a conexo HTTP (linha 8), transmisso de dados em formato binrio (da linha 9 at 79), resposta HTTP (linha 80) e fechamento da conexo HTTP (linhas 81 e 82).

Figura 10: Transmisso de dados

Por fim no passo 8, o programa fecha a conexo TCP.

Figura 11: Fechamento da conexo TCP

Visualizamos na linha 83 o fechamento da conexo:

Figura 12: Fechamento da conexo TCP

Vale lembrar que as imagens de captura do Wireshark aqui expostas so referentes a um nico processo rodando. Quando mais processos so iniciados a seqncia das mensagens fica completamente aleatria.

4. Caso de Uso
Como teste prtico, o software foi testado a fim de testar a capacidade de carga do servidor web da ufabc. Foram configurados 150 processos com timeout de 200 segundos atravs da linha de comando no terminal Linux.
./HTTPStressClient 80 www.ufabc.edu.br index.php 150 200

Figura 13: HTTPStressClient

Tentando acessar o site da ufabc aps o software rodando, percebemos que a pgina fora do ar. O interessante que o motivo da queda da pgina no foi a queda do servidor web, mas sim o estouro do nmero de conexes do banco de dados. Desta forma atravs do teste de carga foi possvel verificar uma falha em potencial do site da ufabc.

Figura 14: Mensagem de erro no site da ufabc

5. Concluso
Com relao ao domnio deste trabalho, o desafio enfrentado foi o entendimento de cabealhos HTTP e seu funcionamento dentro de uma conexo TCP, necessitando de um embasamento terico em operaes de IO via rede e sockets.

De acordo com os resultados obtidos nos testes da seo 4, podemos analisar o grau de impacto de um sistema web mal dimensionado alm capacidade de resposta do sistema com mltiplos usurios.

6. Referencias
Brian "Beej" Hall (2001) - Beejs Guide to Network Programming Wikipdia

7. Anexo 1 - Processo pai

Figura 15: Fluxo Lgico Processo Pai

8. Anexo 2 Processo filho

Figura 16: Fluxo Lgico Processo Filho

9. Anexo 3 Cdigo Fonte


#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <iostream> #include <signal.h> #include <time.h> #include <sys/ipc.h> #include <sys/shm.h> //definies usadas na confeco do relatorio #define C_200OK "HTTP/1.1 200 OK" #define C_201CREATED "HTTP/1.1 201 Created" #define C_204NOCONTENT "HTTP/1.1 204 No Content" #define C_302FOUND "HTTP/1.1 302 Found" #define C_304NOTMODIFIED "HTTP/1.1 304 Not Modified" #define C_400BADREQUEST "HTTP/1.1 400 Bad Request" #define C_401UNAUTHORIZED "HTTP/1.1 401 Unauthorized" #define C_403FORBIDDEN "HTTP/1.1 403 Forbidden" #define C_404NOTFOUND "HTTP/1.1 404 Not Found" #define C_408REQUESTTIMEOUT "HTTP/1.1 408 Request Timeout" #define C_500INTERNALSERVERERROR "HTTP/1.1 500 InternalServerError" #define C_502BADGATEWAY "HTTP/1.1 502 Bad Gateway" #define C_503SERVICEUNAVAILABLE "HTTP/1.1 503 Service Unavailable" #define C_504GATEWAYTIMEOUT "HTTP/1.1 504 Gateway Timeout" #define CHAVE 69 #define TEMPOTIMEOUT 200 extern char *shmat(); // Tipos de Dados typedef void Sigfunc(int); struct mssg { int tipo; char content[1024]; };

using namespace std; //prototipos de funo char *construtorQuery_GET(char *host, char *pagina); void error(const char *msg); Sigfunc *mysignal(int signo, Sigfunc *func); //interrupo de timeout

static void interrupcao(int signo) { printf("Temporizador expirou!\n"); return;

int main(int argc, char *argv[]) { //************ pegar esses dados do usuario

char *porta = "80"; //numero da porta de conexo //char *servidor = "10.168.16.100"; // nome do servidor (receber do usurio)

char *servidor = "www.saopaulo.sp.gov.br"; char *pagina = "index.html"; int numProcessos = 107; //************ int pid; //O processo pai tem funco apenas de impresso no terminal //Recebe as informaes dos processos filhos atravs de pipes //cias os processos filhos pid = getpid(); for (int i=1;i<=numProcessos;i++){ if (pid >0){ pid = fork() - i; //-i para dar um identificador nico para cada filho } } if (pid > 0) { //Processo PAI int pooling=0; int l = 0; int k; //leitura da rea de memria compartilhada dos filhos unsigned long int int sh; sh = shmget(CHAVE, 1024, 0777 | IPC_CREAT); if( sh < 0 ) { printf("++++++++++ exit(0); } buff = (unsigned long int *)shmat( sh, 0, 0 ); Erro: Area Compartilhada nao Criada. ++++++++++\n"); *buff;

//limpa rea de memoria compartilhada for (k=1; k<=numProcessos;k++){ l = (20*k)-20; buff[l+15] =0; buff[l+1]=0; buff[l+9]=0; buff[l+11]=0; buff[l+13]=0; } while (1){ system("clear"); //limpa tela

cout<< "PROCESSO PAI -> pid = " << pid << " - Pooling n "<< pooling << " - CTRL + C para finalizar" << endl << endl;

// escreve na tela os dados de relatorio - Somente principais cout << "nProcesso\tErros\t200OK\t404 Unavailable" << endl; for (k=1; k<=numProcessos/2;k++){ l = (20*k)-20; cout<<"Processo:" << k << "\t" << buff[l+15] << "\t" << buff[l+1] << "\t" << buff[l+9] << "\t\t" << buff[l+11] << "\t\t\t\t" << buff[l+13] << endl; } sleep (1); for (k=numProcessos/2; k<=numProcessos;k++){ l = (20*k)-20; cout<<"Processo:" << k << "\t" << buff[l+15] << "\t" << buff[l+1] << "\t" << buff[l+9] << "\t\t" << buff[l+11] << "\t\t\t\t" << buff[l+13] << endl; } sleep(1); Not Found\t500 Internal Server Error\t503 Service

pooling ++; //controle do pooling if (pooling > 32766) pooling = 0; }

}else{ //continua com filho int sockfd; // status do socket (erro se menor que 0) int bytesEnviados; //numero de bytes enviados int bytesRecebidos; //numero de baites recebidos char bufferRecepcao[40]; //40 pois s estou interessado na primeira linha do cabealho http char *bufferEnvio; //dados enviados struct sockaddr_in serv_addr; struct hostent *server; mysignal(SIGALRM, interrupcao); // timeout para conexo //auxiliar na medio de tempo de resposta e comparacao de strings long antes=0; long depois=0; char *contemSubstring; //ponteiro para substring //dados para relatorio- apenas principais do HTTP 1.1 unsigned long int _200ok=0;//200 OK unsigned long int _201Created=0; unsigned long int _204NoContent=0;//204 No Content unsigned long int _302Found=0;//302 Found unsigned long int _304NotModified=0;//304 Not Modified unsigned long int _400BadRequest=0;//400 Bad Request unsigned long int _401Unauthorized=0;//401 Unauthorized unsigned long int _403Forbidden=0;//403 Forbidden unsigned long int _404NotFound=0;//404 Not Found unsigned long int _408RequestTimeout=0;//408 Request Timeout unsigned long int _500InternalServerError=0;//500 Internal Server Error unsigned long int _502BadGateway=0;//502 Bad Gateway unsigned long int _503ServiceUnavailable=0;//503 Service Unavailable unsigned long int _504GatewayTimeout=0;//504 Gateway Timeout

unsigned long int erro=0; unsigned long int tempoResposta=0;

//memoria compartilhada unsigned long int *memoria; int i, j, sh; while (erro < 30){ // sleep(1); //cria um socket do tipo TCP e retorna um status sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0){ error("Erro ao abrir o Socket"); } server = gethostbyname(servidor); // preenche a estrutura com os dados do servidor if (server == NULL) { fprintf(stderr,"Erro ao encontrar o servidor"); exit(0); } //Inicializa buffer bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; //familia de sockets de internet com ipv4

//preenche buffer serv_addr bcopy((char *)server->h_addr,(char *)&serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(atoi(porta));

//CONEXO COM O SERVIDOR - TIMEOUT DE 5 SEGUNDOS alarm(TEMPOTIMEOUT); connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)); alarm(0);

//ENVIO DA REQUISIO HTTP +++++++++++++++++++++++++++++++++++++ bufferEnvio = construtorQuery_GET(servidor,pagina); antes = clock(); alarm(TEMPOTIMEOUT); bytesEnviados = write(sockfd,bufferEnvio,strlen(bufferEnvio)); alarm(0);

// //

if (bytesEnviados < 0) error("ERROR writing to socket");

//RESPOSTA REQUISIO HTTP +++++++++++++++++++++++++++++++++++++ bzero(bufferRecepcao,40); alarm(TEMPOTIMEOUT); bytesRecebidos = read(sockfd,bufferRecepcao,39); alarm(0); depois = clock(); // analisar buffer e colocar na estatstica tempoResposta = (depois - antes) * 1000 / CLOCKS_PER_SEC; if (bytesRecebidos <= 0){

erro++; cout << "erro = "<< erro << endl; close(sockfd); sleep(15); goto fim2; } //"HTTP/1.1 200 OK" contemSubstring = strstr(bufferRecepcao,C_200OK); if (contemSubstring){ _200ok++; goto fim; } //"HTTP/1.1 404 Not Found" contemSubstring = strstr(bufferRecepcao,C_404NOTFOUND); if (contemSubstring){ _404NotFound++; goto fim; } //"HTTP/1.1 408 Request Timeout" contemSubstring = strstr(bufferRecepcao,C_408REQUESTTIMEOUT); if (contemSubstring){ _408RequestTimeout++; goto fim; } //"HTTP/1.1 500 InternalServerError" contemSubstring = strstr(bufferRecepcao,C_500INTERNALSERVERERROR); if (contemSubstring){ _500InternalServerError++; goto fim; } //"HTTP/1.1 201 Created" contemSubstring = strstr(bufferRecepcao,C_201CREATED); if (contemSubstring){ _201Created++; goto fim; } //"HTTP/1.1 204 No Content" contemSubstring = strstr(bufferRecepcao,C_204NOCONTENT); if (contemSubstring){ _204NoContent++; goto fim; } //"HTTP/1.1 302 Found" contemSubstring = strstr(bufferRecepcao,C_302FOUND); if (contemSubstring){ _302Found++; goto fim; } //HTTP/1.1 304 Not Modified"

contemSubstring = strstr(bufferRecepcao,C_304NOTMODIFIED); if (contemSubstring){ _304NotModified++; goto fim; } //"HTTP/1.1 400 Bad Request" contemSubstring = strstr(bufferRecepcao,C_400BADREQUEST); if (contemSubstring){ _400BadRequest++; goto fim; } //"HTTP/1.1 401 Unauthorized" contemSubstring = strstr(bufferRecepcao,C_401UNAUTHORIZED); if (contemSubstring){ _401Unauthorized++; goto fim; } //"HTTP/1.1 403 Forbidden" contemSubstring = strstr(bufferRecepcao,C_403FORBIDDEN); if (contemSubstring){ _403Forbidden++; goto fim; } //"HTTP/1.1 502 Bad Gateway" contemSubstring = strstr(bufferRecepcao,C_502BADGATEWAY); if (contemSubstring){ _502BadGateway++; goto fim; } //"HTTP/1.1 503 Service Unavailable" contemSubstring = strstr(bufferRecepcao,C_503SERVICEUNAVAILABLE); if (contemSubstring){ _503ServiceUnavailable++; goto fim; } //"HTTP/1.1 504 Gateway Timeout" contemSubstring = strstr(bufferRecepcao,C_504GATEWAYTIMEOUT); if (contemSubstring){ _504GatewayTimeout++; goto fim; } //se cheguei at aqui ento porque no cabealho HTTP // ou um cdigo desconhecido erro++; cout << "erro mal formao string = "<< erro << endl; sleep(15); close(sockfd); goto fim2; fim:

close(sockfd); fim2: // ENVIA DADOS DE RELATORIO PARA AREA DE MEMORIA COMPARTILHADA sh = shmget(CHAVE, 1024, 0777 | IPC_CREAT); memoria = (unsigned long int *) shmat(sh,0,0); j = pid * -1; //fica positivo //calcula ndice j = (20*j)-20; //cout << "j = " << j<< endl; //grava na memoria memoria[j] = tempoResposta; //0 j++; memoria[j] = _200ok; //1 j++; memoria[j] = _201Created; //2 j++; memoria[j] = _204NoContent; //3 j++; memoria[j] = _302Found; j++; memoria[j] = _304NotModified; //5 j++; memoria[j] = _400BadRequest; j++; memoria[j] = _401Unauthorized;//7 j++; memoria[j] = _403Forbidden; j++; memoria[j] = _404NotFound; //9 j++; memoria[j] = _408RequestTimeout; j++; memoria[j] = _500InternalServerError; //11 j++; memoria[j] = _502BadGateway; j++; memoria[j] = _503ServiceUnavailable; //13 j++; memoria[j] = _504GatewayTimeout; j++; memoria[j] = erro; //15 j++; memoria[j] = 0; //area de memoria reserva j++; memoria[j] = 0; //area de memoria reserva j++; memoria[j] = 0; //area de memoria reserva j++; memoria[j] = 0; //area de memoria reserva

} cout<< "O processo filho " << pid << " foi finalizado por timeout" << endl; } return 0; }