Aprofundando Conhecimentos

Usando o Módulo de Registro

Já demos uma breve olhada no módulo de Registro (do inglês, log ou logging) do ns-3, enquanto analisávamos o código first.cc. Agora iremos aprofundar nossos conhecimento sobre este módulo para utilizá-lo de forma mais eficiente.

Visão Geral Sobre o Sistema de Registro

A maioria dos sistemas de grande porte suportam mensagens de registros (mensagens de ‘log’ que informam o que esta ocorrendo no sistema) e o ns-3 não é nenhuma exceção. Em alguns casos, somente mensagens de erros são reportadas para o “operador do console” (que é geralmente o stderr dos sistemas baseados no Unix). Em outros sistemas, mensagens de avisos podem ser impressas detalhando informações do sistema. Em alguns casos, o sistema de registro fornece mensagens de depuração que podem rapidamente mostrar o que está ocorrendo de errado.

O ns-3 permite que o usuário tenha visões de todos os níveis do sistema através das mensagens de registro. Podemos selecionar o nível de saída das mensagens de registro(verbosty), através de um abordagem multinível. As mensagens de registro podem ser desabilitadas completamente, habilitadas componente por componente ou habilitada globalmente. Ou seja, permite selecionar o nível de detalhamento das mensagens de saída. O módulo de registro do ns-3 fornece uma forma correta e segura de obtermos informações sobre as simulações.

No ns-3 foi implementado um mecanismo de — rastreamento — de propósito geral, que permite a obtenção de saídas de dados dos modelos simulados (veja a seção Usando o Sistema de Rastreamento do tutorial para mais detalhes). O sistema de registro deve ser usado para depurar informações, alertas, mensagens de erros ou para mostrar qualquer informação dos scripts ou modelos.

Atualmente existem sete níveis de mensagens de registro definidas no sistema.

  • NS_LOG_ERROR — Registra mensagens de erro;
  • NS_LOG_WARN — Registra mensagens de alertas;
  • NS_LOG_DEBUG — Registra mensagens mais raras, mensagens de depuração ad-hoc;
  • NS_LOG_INFO — Registra mensagens informativas sobre o progresso do programa;
  • NS_LOG_FUNCTION — Registra mensagens descrevendo cada função chamada;
  • NS_LOG_LOGIC — Registra mensagens que descrevem o fluxo lógico dentro de uma função;
  • NS_LOG_ALL — Registra tudo.

Também é fornecido um nível de registro incondicional, que sempre é exibido independente do nível de registro ou do componente selecionado.

  • NS_LOG_UNCOND — Registra mensagens incondicionalmente.

Cada nível pode ser requerido individualmente ou de forma cumulativa. O registro pode ser configurado usando uma variável de ambiente (NS_LOG) ou através de uma chamada ao sistema de registro. Já havíamos abordado anteriormente o sistema de registro, através da documentação Doxygen, agora é uma boa hora para ler com atenção esta documentação no Doxygen.

Depois de ler a documentação, vamos usar nosso conhecimento para obter algumas informações importante do código de exemplo scratch/myfirst.cc.

Habilitando o Sistema de Registro

Utilizaremos a variável de ambiente NS_LOG para habilitar o sistema de Registro, mas antes de prosseguir, execute o código feito na seção anterior,

./waf --run scratch/myfirst

Veremos a saída do nosso primeiro programa ns-3 de exemplo, tal como visto anteriormente.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.413s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

As mensagens de envio e recebimentos apresentadas anteriormente são mensagens de registro, obtidas de UdpEchoClientApplication e UdpEchoServerApplication. Podemos pedir para a aplicação cliente, por exemplo, imprimir mais informações configurando o nível de registro através da variável de ambiente NS_LOG.

Aqui estamos assumindo que você está usando um shell parecido com o sh, que usa a sintaxe “VARIABLE=value”. Se você estiver usando um shell parecido com o csh, então terá que converter os exemplos para a sintaxe “setenv VARIABLE value”, requerida por este shell.

Agora, a aplicação cliente de eco UDP irá responder a seguinte linha de código scratch/myfirst.cc,

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);

Essa linha de código habilita o nível LOG_LEVEL_INFO de registro. Quando habilitamos um dado nível de registro, estamos habilitando este nível e todos os níveis inferiores a este. Neste caso, habilitamos NS_LOG_INFO, NS_LOG_DEBUG, NS_LOG_WARN e NS_LOG_ERROR. Podemos aumentar o nível de registro e obter mais informações sem alterar o script, ou seja, sem ter que recompilar. Conseguimos isto através da configuração da variável de ambiente NS_LOG, tal como:

export NS_LOG=UdpEchoClientApplication=level_all

Isto configura a variável de ambiente NS_LOG no shell para,

UdpEchoClientApplication=level_all

Do lado esquerdo do comando temos o nome do componente de registro que nós queremos configurar, no lado direito fica o valor que estamos passando. Neste caso, estamos ligando todos os níveis de depuração para a aplicação. Se executarmos o código com o NS_LOG configurado desta forma, o sistema de registro do ns-3 observará a mudança e mostrará a seguinte saída:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

As informações de depuração extras, apresentadas aqui, são fornecidas pela aplicação no nível de registros NS_LOG_FUNCTION. Isto é apresentado toda vez que a aplicação chamar a função. Não é obrigatório que o modelo forneça suporte a registro, no ns-3, esta decisão cabe ao desenvolvedor do modelo. No caso da aplicação de eco uma grande quantidade de saídas de log estão disponíveis.

Podemos ver agora registros de várias funções executadas pela aplicação. Se olharmos mais de perto veremos que as informações são dadas em colunas separadas por (::), do lado esquerdo está o nome da aplicação (no exemplo, UdpEchoClientApplication) e do outro lado o nome do método esperado pelo escopo C++. Isto é incremental.

O nome que está aparece no registro não é necessariamente o nome da classe, mas sim o nome do componente de registro. Quando existe uma correspondência um-para-um, entre código fonte e classe, este geralmente será o nome da classe, mas isto nem sempre é verdade. A maneira sutil de diferenciar esta situação é usar : quando for o nome do componente de registro e :: quando for o nome da classe.

Em alguns casos pode ser complicado determinar qual método gerou a mensagem. Se olharmos o texto anterior, veremos a mensagem “Received 1024 bytes from 10.1.1.2”, nesta não existe certeza de onde a mensagem veio. Podemos resolver isto usando um “OU” (OR) entre o nível de registro e o prefix_func, dentro do NS_LOG.

export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'

As aspas são requeridas quando usamos o | (pipe) para indicar uma operação de OU.

Agora, se executarmos o script devemos ver que o sistema de registro informa de qual componente de registro vem a mensagem.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

Podemos ver, depois da configuração, que todas as mensagens do cliente de eco UDP estão identificadas. Agora a mensagem “Received 1024 bytes from 10.1.1.2” é claramente identificada como sendo do cliente de eco. O restante das mensagens devem estar vindo do servidor de eco UDP. Podemos habilitar mais do que um componente usando :, para separá-los na variável NS_LOG.

export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:
               UdpEchoServerApplication=level_all|prefix_func'

Atenção: não podemos quebrar a entrada da variável em várias linhas como foi feito no exemplo, tudo deve estar em uma única linha. O exemplo ficou assim por uma questão de formatação do documento.

Agora, se executarmos o script veremos todas as mensagens de registro tanto do cliente quando do servidor. Isto é muito útil na depuração de problemas.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.406s)
UdpEchoServerApplication:UdpEchoServer()
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoServerApplication:StartApplication()
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
UdpEchoServerApplication:HandleRead(): Echoing packet
UdpEchoClientApplication:HandleRead(0x624920, 0x625160)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoServerApplication:StopApplication()
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

As vezes também é útil registrar o tempo em que uma mensagem é gerada. Podemos fazer isto através de um OU com o prefix_time, exemplo:

export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:
               UdpEchoServerApplication=level_all|prefix_func|prefix_time'

Novamente, teremos que deixar tudo em uma única linha e não em duas como no exemplo anterior. Executando o script, veremos a seguinte saída:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClientApplication:SetDataSize(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
2.00369s UdpEchoServerApplication:HandleRead(): Echoing packet
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

Podemos ver que o construtor para o UdpEchoServer foi chamado pelo simulador no segundo 0 (zero). Isto acontece antes do simulador ser iniciado, mas o tempo é mostrado como zero, o mesmo acontece para o construtor do UdpEchoClient.

Lembre-se que o script scratch/first.cc inicia a aplicação servidor de eco no primeiro segundo da simulação. Repare que o método StartApplication do servidor é, de fato, chamado com um segundo. Também podemos notar que a aplicação cliente de eco é iniciada com dois segundos de simulação, como nós pedimos no script.

Agora podemos acompanhar o andamento da simulação: ScheduleTransmit é chamado no cliente, que invoca o Send e o HandleRead, que é usado na aplicação servidor de eco. Repare que o tempo decorrido entre o envio de cada pacote é de 3.69 milissegundos. Veja que a mensagem de registro do servidor diz que o pacote foi ecoado e depois houve um atraso no canal. Podemos ver que o cliente recebeu o pacote ecoado pelo método HandleRead.

Existe muita coisa acontecendo por baixo dos panos e que não estamos vendo. Podemos facilmente seguir as entradas de processo configurando todos os componentes de registro do sistema. Configure a variável de NS_LOG da seguinte forma,

export 'NS_LOG=*=level_all|prefix_func|prefix_time'

O asterisco é um componente coringa, que ira ligar todos os componentes de registro usados na simulação. Não vamos reproduzir a saída aqui (cada pacote de eco produz 1265 linhas de saída), mas podemos redirecionar esta informação para um arquivo e visualizá-lo depois em um editor de textos,

./waf --run scratch/myfirst > log.out 2>&1

utilizamos uma versão extremamente detalhada de registro quando surge um problema e não temos ideia do que está errado. Assim, podemos seguir o andamento do código e depurar o erro. Podemos assim visualizar a saída em um editor de texto e procurar por coisas que nós esperamos e principalmente por coisa que não esperávamos. Quando temos uma ideia geral sobre o que está acontecendo de errado, usamos um depurador de erros para examinarmos de forma mais detalhada o problema. Este tipo de saída pode ser especialmente útil quando nosso script faz algo completamente inesperado. Se estivermos depurando o problema passo a passo, podemos nos perder completamente. O registro pode tornar as coisas mais visíveis.

Adicionando registros ao Código

Podemos adicionar novos registros nas simulações fazendo chamadas para o componente de registro através de várias macros. Vamos fazer isto em nosso código myfirst.cc no diretório scratch

Lembre-se que nós temos que definir o componente de registro em nosso código:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

Agora que sabemos habilitar todos os registros em vários níveis configurando a variável NS_LOG. Vamos adicionar alguns registros ao código. O macro usado para adicionar uma mensagem ao nível de informação é NS_LOG_INFO, então vamos adicionar uma mensagem dessas (pouco antes de criar os nós de rede) que diz que a “Topologia foi Criada”. Isto é feito como neste trecho do código,

Abra o arquivo scratch/myfirst.cc e adicione a linha,

NS_LOG_INFO ("Creating Topology");

antes das linhas,

NodeContainer nodes;
nodes.Create (2);

Agora construa o código usando o Waf e limpe a variável NS_LOG desabilite o registro que nós havíamos habilitado anteriormente:

./waf
export NS_LOG=

Agora, se executarmos o código,

./waf --run scratch/myfirst

veremos novas mensagens, pois o componente de registro não está habilitado. Agora para ver a mensagem devemos habilitar o componente de registro do FirstScriptExample com um nível maior ou igual a NS_LOG_INFO. Se só esperamos ver um nível particular de registro, devemos habilita-lo,

export NS_LOG=FirstScriptExample=info

Agora se executarmos o código veremos nossa mensagem de registro “Creating Topology”,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
Creating Topology
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

Usando Argumentos na Linha de Comando

Sobrepondo Atributos Padrões

Podemos alterar o comportamento dos códigos do ns-3 sem precisar editar ou construir códigos, isto é feito através de linhas de comandos. Para isto o ns-3 fornece um mecanismo de analise de argumentos de linha de comando (parse), que configura automaticamente variáveis locais e globais através desses argumentos.

O primeiro passo para usar argumentos de linha de comando é declarar o analisador de linha de comandos. Isto é feito com a seguinte linha de programação, em seu programa principal,

int
main (int argc, char *argv[])
{
  ...

  CommandLine cmd;
  cmd.Parse (argc, argv);

  ...
}

Estas duas linhas de programação são muito uteis. Isto abre uma porta para as variáveis globais e atributos do ns-3. Adicione estas duas linhas no código em nosso exemplo scratch/myfirst.cc, bem no inicio da função principal (main). Na sequencia construa o código e execute-o, mas peça para o código “ajudar” da seguinte forma,

./waf --run "scratch/myfirst --PrintHelp"

Isto pede ao Waf para executar o scratch/myfirst e passa via linha de comando o argumento --PrintHelp. As aspas são necessárias para ordenar os argumentos. O analisador de linhas de comandos agora tem como argumento o --PrintHelp e responde com,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.413s)
TcpL4Protocol:TcpStateMachine()
CommandLine:HandleArgument(): Handle arg name=PrintHelp value=
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.

Vamos focar na opção --PrintAttributes. Já demos a dica sobre atributos no ns-3 enquanto explorávamos o código do first.cc. Nós olhamos as seguintes linhas de código,

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

e mencionamos que DataRate é um atributo de PointToPointNetDevice. Vamos usar os argumentos de linha de comando para ver os atributos de PointToPointNetDevice. A lista de ajuda diz que nós devemos fornecer um TypeId, este corresponde ao nome da classe do atributo. Neste caso será ns3::PointToPointNetDevice. Seguindo em frente digite,

./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"

O sistema irá mostrar todos os atributos dos tipos de dispositivos de rede (net device). Entre os atributos veremos,

--ns3::PointToPointNetDevice::DataRate=[32768bps]:
  The default data rate for point to point links

32768 bits por segundos é o valor padrão que será usado quando criarmos um PointToPointNetDevice no sistema. Vamos alterar este valor padrão do PointToPointHelper. Para isto iremos usar os valores dos dispositivos ponto-a-ponto e dos canais, deletando a chamada SetDeviceAttribute e SetChannelAttribute do myfirst.cc, que nós temos no diretório scratch.

Nosso código agora deve apenas declarar o PointToPointHelper sem configurar qualquer operação, como no exemplo a seguir,

...

NodeContainer nodes;
nodes.Create (2);

PointToPointHelper pointToPoint;

NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);

...

Agora construa o novo código com o Waf (./waf) e depois vamos habilitar alguns registros para o servidor de eco UDP e ligar o prefixo de informações sobre tempo de execução.

export 'NS_LOG=UdpEchoServerApplication=level_all|prefix_time'

Agora ao executar o código veremos a seguinte saída,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.405s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Lembre-se que o último tempo que vimos na simulação quando recebemos um pacote de eco no servidor, foi de 2.00369 segundos.

2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1

Agora o pacote é recebido em 2.25732 segundos. Isto porque retiramos a taxa de transferência do PointToPointNetDevice e portanto foi assumido o valor padrão 32768 bits por segundos ao invés de cinco megabits por segundo.

Se nós passarmos uma nova taxa de dados usando a linha de comando, podemos aumentar a velocidade da rede novamente. Nós podemos fazer isto da seguinte forma, usando um help:

./waf --run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps"

Isto ira configurar o valor do atributo DataRate para cinco megabits por segundos. Ficou surpreso com o resultado? Acontece que para obtermos o resultado do código original, teremos que configurar também o atraso do canal de comunicação. Podemos fazer isto via linha de comandos, tal como fizemos com o dispositivo de rede:

./waf --run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"

Então descobrimos que o atributo Delay do canal esta configurado com o seguinte valor padrão:

--ns3::PointToPointChannel::Delay=[0ns]:
  Transmission delay through the channel

Podemos configurar ambos valores via linha de comando,

./waf --run "scratch/myfirst
  --ns3::PointToPointNetDevice::DataRate=5Mbps
  --ns3::PointToPointChannel::Delay=2ms"

neste caso voltamos com os tempos de DataRate e Delay que tínhamos inicialmente no código original:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.417s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.00369s Received 1024 bytes from 10.1.1.1
2.00369s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Repare que o pacote é recebido novamente pelo servidor com 2.00369 segundos. Então desta forma, podemos configurar qualquer atributo usado no código. Em particular nós podemos configurar o atributo MaxPackets do UdpEchoClient para qualquer outro valor.

É importante lembrar que devemos retirar todas as configurações com valores explícitos do código. Depois disto devemos re-construir o código (fazer novamente os binários). Também teremos que achar a sintaxe do atributo usando o help da linha de comando. Uma vez que tenhamos este cenário estaremos aptos para controlar o números de pacotes ecoados via linha de comando. No final a linha de comando deve parecer com algo como:

./waf --run "scratch/myfirst
  --ns3::PointToPointNetDevice::DataRate=5Mbps
  --ns3::PointToPointChannel::Delay=2ms
  --ns3::UdpEchoClient::MaxPackets=2"

Conectando Seus Próprios Valores

Podemos também adicionar conectores (opções que alteram valores de variáveis) ao sistema de linha de comando. Isto nada mais é do que criar uma opção na linha de comando, a qual permitirá a configuração de uma variável dentro do código. Isto é feito usando o método AddValue no analisador da linha de comando.

Vamos usar esta facilidade para especificar o número de pacotes de eco de uma forma completamente diferente. Iremos adicionar uma variável local chamada nPackets na função main. Vamos iniciar com nosso valor anterior. Para permitir que a linha de comando altere este valor, precisamos fixar o valor no parser. Fazemos isto adicionando uma chamada para AddValue. Altere o código scratch/myfirst.cc começando pelo seguinte trecho de código,

int
main (int argc, char *argv[])
{
  uint32_t nPackets = 1;

  CommandLine cmd;
  cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
  cmd.Parse (argc, argv);

  ...

Dê uma olhada um pouco mais para baixo, no código e veja onde configuramos o atributo MaxPackets, retire o 1 e coloque em seu lugar a variável nPackets, como é mostrado a seguir:

echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));

Agora se executarmos o código e fornecermos o argumento --PrintHelp, deveremos ver nosso argumento de usuário (User Arguments), listado no help.

Execute,

./waf --run "scratch/myfirst --PrintHelp"
Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.403s)
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.
User Arguments:
    --nPackets: Number of packets to echo

Agora para especificar o número de pacotes de eco podemos utilizar o argumento --nPackets na linha de comando,

./waf --run "scratch/myfirst --nPackets=2"

Agora deveremos ver,

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
'build' finished successfully (0.404s)
0s UdpEchoServerApplication:UdpEchoServer()
1s UdpEchoServerApplication:StartApplication()
Sent 1024 bytes to 10.1.1.2
2.25732s Received 1024 bytes from 10.1.1.1
2.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
Sent 1024 bytes to 10.1.1.2
3.25732s Received 1024 bytes from 10.1.1.1
3.25732s Echoing packet
Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
UdpEchoServerApplication:DoDispose()
UdpEchoServerApplication:~UdpEchoServer()

Agora, ecoamos dois pacotes. Muito fácil, não é?

Usando o ns-3, podemos usar argumentos via linha de comando para controlar valores de atributos. Ao construir modelos de simulação podemos adicionar novos atributos aos objetos e isto ficará disponível, automaticamente para ajustes através do sistema via linha de comando. Ao criarmos nossos códigos poderemos adicionar novas variáveis e conectá-las ao sistema de forma simples.

Usando o Sistema de Rastreamento

O ponto principal da simulação é gerar informações de saída para estudos futuros e o sistema de rastreamento (Tracing System) do ns-3 é o mecanismo primário para isto. Devido ao fato do ns-3 ser um programa escrito em C++, as funcionalidades para se gerar saídas podem ser utilizadas:

#include <iostream>
...
int main ()
{
  ...
  std::cout << "The value of x is " << x << std::endl;
  ...
}

Podemos usar o módulo de registro (visto anteriormente) para verificar pequenas estruturas de nossas soluções. Porém, os problemas gerados por esta abordagem já são bem conhecidos e portanto fornecemos um subsistema para rastrear eventos genéricos para localizar problemas importantes.

Os objetivos básicos do sistema de rastreamento ns-3 são:

  • Para as tarefas básicas, o sistemas de rastreamento fornece ao usuário um rastreamento padrão através de rastreamentos conhecidos e customização dos objetos que geram o rastreamento;
  • Os usuários podem estender o sistema de rastreamento para modificar os formatos das saídas geradas ou inserir novas fontes de rastreamento, sem modificar o núcleo do simulador;
  • Usuários avançados podem modificar o núcleo do simulador para adicionar novas origens de rastreamentos e destino do rastreamento.

O sistema de rastreamento do ns-3 é feito através de conceitos independentes de rasteamento na origem e no destino, e um mecanismo uniforme para conectar a origem ao destino. O rastreador na origem são entidades que podem demonstrar eventos que ocorrem na simulação e fornece acesso aos dados importantes. Por exemplo, um rastreador de origem podem indicar quando um pacote é recebido por um dispositivo de rede e prove acesso aos comentários de pacote para os interessados no rastreador do destino.

Os rastreadores de origem não são usados sozinhos, eles devem ser “conectados” a outros pedaços de código que fazem alguma coisa útil com a informação fornecida pelo destino. Rastreador de destino são consumidores dos eventos e dados fornecidos pelos rastreadores de origem. Por exemplo, pode-se criar um rastreador de destino que (quando conectado ao rastreador de origem do exemplo anterior) mostrará saídas de partes importantes de pacotes recebidos.

A lógica desta divisão explicita é permitir que os usuários apliquem novos tipos de rastreadores de destinos em rastreadores de origem existentes, sem precisar editar ou recompilar o núcleo do simulador. Assim, no exemplo anterior, o usuário pode definir um novo rastreador de destino em seu código e aplicar isto a um rastreador de origem definido no núcleo da simulação editando, para isto, somente o código do usuário.

Neste tutorial, abordamos alguns rastreadores de origem e de destino já predefinidos e demonstramos como esses podem ser customizados, com um pouco de esforço. Veja o manual do ns-3 ou a seção de how-to para informações avançadas sobre configuração de rastreamento incluindo extensão do namespace de rastreamento e criação de novos rastreadores de origem.

Rastreamento ASCII

O ns-3 fornece uma funcionalidade de ajuda (helper) que cobre rastreamento em baixo nível e ajuda com detalhes envolvendo configuração e rastros de pacotes. Se habilitarmos essa funcionalidade, veremos as saídas em arquivos ASCII (texto puro) — daí o nome. Isto é parecido com a saída do ns-2. Este tipo de rastreamento é parecido com o out.tr gerado por outros códigos.

Vamos adicionar algumas saídas de rastreamento ASCII em nosso código scratch/myfirst.cc. Antes de chamar Simulator::Run () adicione as seguintes linhas de código:

AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

Tal como já vimos no ns-3, este código usa o objeto Assistente para criar o rastreador ASCII. A segunda linha aninhada duas chamadas para métodos. Dentro do método CreateFileStream() é utilizado um objeto para criar um outro objeto, que trata um arquivo, que é passado para o método. Iremos detalhar isto depois, agora tudo que precisamos saber é que estamos criando um objeto que representa um arquivo chamado “myfirst.tr”. Estamos dizendo para o ns-3 tratar problemas de criação de objetos e também para tratar problemas causados por limitações do C++ com objetos relacionados com cópias de construtores.

Fora da chamada, para EnableAsciiAll(), dizemos para o Assistente que esperamos habilitar o rastreamento ASCII para todo dispositivo ponto-a-ponto da simulação; e esperamos rastrear destinos e escrever as informações de saída sobre o movimento de pacotes no formato ASCII.

Para queles familiarizados com ns-2, os eventos rastreados são equivalentes aos populares pontos de rastreadores (trace points) que registram eventos “+”, “-“, “d”, e “r”

Agora podemos construir o código e executa-lo:

./waf --run scratch/myfirst

Veremos algumas mensagens do Waf, seguida da mensagem “‘build’ finished successfully”, bem como algumas mensagens do programa.

Quando isto for executado, o programa criará um arquivo chamado myfirst.tr. Devido a forma que o Waf trabalha, o arquivo não é criado no diretório local, mas sim no diretório raiz do repositório. Se você espera controlar o que é salvo, então use a opção -cwd do Waf. Agora mude para o diretório raiz do repositório e veja o arquivo myfirst.tr com um editor de texto.

Análise de Rastros ASCII

Uma grande quantidade de informação é gerada pelo sistema de rastreamento e pode ser difícil analisá-las de forma clara e consistente.

Cada linha do arquivo corresponde a um evento de rastreamento (trace event). Neste caso são eventos rastreados da fila de transmissão (transmit queue). A fila de transmissão é um lugar através do qual todo pacote destinado para o canal ponto-a-ponto deve passar. Note que cada linha no arquivo inicia com um único caractere (com um espaço depois). Este caractere tem o seguinte significado:

  • +: Uma operação de enfileiramento (bloqueio) ocorreu no dispositivo de fila;
  • -: Uma operação de desenfileiramento (desbloqueio) ocorre no dispositivo de fila;
  • d: Um pacote foi descartado, normalmente por que a fila está cheia;
  • r: Um pacote foi recebido por um dispositivo de rede.

Vamos detalhar mais a primeira linha do arquivo de rastreamento. Vamos dividi-la em seções com números de dois dígitos para referência:

00 +
01 2
02 /NodeList/0/DeviceList/0/$ns3::PointToPointNetDevice/TxQueue/Enqueue
03 ns3::PppHeader (
04   Point-to-Point Protocol: IP (0x0021))
05   ns3::Ipv4Header (
06     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
07     length: 1052 10.1.1.1 > 10.1.1.2)
08     ns3::UdpHeader (
09       length: 1032 49153 > 9)
10       Payload (size=1024)

A primeira linha do evento expandido (referência número 00) é a operação. Temos um caractere +, que corresponde a uma operação de enfileiramento na fila de transmissão. A segunda linha (referência 01) é o tempo da simulação em segundos. Lembre-se que pedimos ao UdpEchoClientApplication para iniciar o envio de pacotes depois de dois segundos (aqui podemos confirmar que isto está acontecendo).

A próxima linha do exemplo (referência 02) diz qual rastreador de origem iniciou este evento (expressado pelo namespace de rastreamento). Podemos pensar no namespace do rastreamento como algo parecido com um sistema de arquivos. A raiz do namespace é o NodeList. Este corresponde a um gerenciador de container no núcleo ns-3 que contém todos os nós de rede que foram criados no código. Assim, como um sistema de arquivos pode ter diretórios dentro da raiz, podemos ter nós de rede no NodeList. O texto /NodeList/0 desta forma refere-se ao nó de rede 0 (zero) no NodeList, ou seja é o “node 0”. Em cada nós existe uma lista de dispositivos que estão instalados nestes nós de rede. Esta lista aparece depois do namespace. Podemos ver que este evento de rastreamento vem do DeviceList/0 que é o dispositivo 0 instalado neste nó.

O próximo texto, $ns3::PointToPointNetDevice informa qual é o tipo de dispositivo na posição zero da lista de dispositivos para o nó 0 (node 0). Lembre-se que a operação + significa que uma operação de enfileiramento está acontecendo na fila de transmissão do dispositivo. Isto reflete no segmento final do caminho de rastreamento, que são TxQueue/Enqueue.

As linhas restantes no rastreamento devem ser intuitivas. As referências 03-04 indicam que o pacote é encapsulado pelo protocolo ponto-a-ponto. Referencias 05-07 mostram que foi usado o cabeçalho do IP na versão 4, o endereço IP de origem é o 10.1.1.1 e o destino é o 10.1.1.2. As referências 08-09 mostram que o pacote tem um cabeçalho UDP e finalmente na referência 10 é apresentado que a área de dados possui 1024 bytes.

A próxima linha do arquivo de rastreamento mostra que o mesmo pacote inicia o desenfileiramento da fila de transmissão do mesmo nó de rede.

A terceira linha no arquivo mostra o pacote sendo recebido pelo dispositivo de rede no nó que representa o servidor de eco. Reproduzimos o evento a seguir.

00 r
01 2.25732
02 /NodeList/1/DeviceList/0/$ns3::PointToPointNetDevice/MacRx
03   ns3::Ipv4Header (
04     tos 0x0 ttl 64 id 0 protocol 17 offset 0 flags [none]
05     length: 1052 10.1.1.1 > 10.1.1.2)
06     ns3::UdpHeader (
07       length: 1032 49153 > 9)
08       Payload (size=1024)

A operação agora é o r e o tempo de simulação foi incrementado para 2.25732 segundos. Se você seguiu os passos do tutorial isto significa que temos o tempo padrão tanto para DataRate quanto para o Delay. Já vimos este tempo na seção anterior.

Na referência 02, a entrada para o namespace foi alterada para refletir o evento vindo do nó 1 (/NodeList/1) e o recebimento do pacote no rastreador de origem (/MacRx). Isto deve facilitar o acompanhamento dos pacotes através da topologia, pois basta olhar os rastros no arquivo.

Rastreamento PCAP

Também podemos usar o formato .pcap para fazer rastreamento no ns-3. O pcap (normalmente escrito em letras minúsculas) permite a captura de pacotes e é uma API que inclui a descrição de um arquivo no formato .pcap. O programa mais conhecido para ler o mostrar este formato é o Wireshark (formalmente chamado de Etherreal). Entretanto, existem muitos analisadores de tráfego que usam este formato. Nós encorajamos que os usuários explorem várias ferramentas disponíveis para análise do pcap. Neste tutorial nos concentraremos em dar uma rápida olhada no tcpdump.

O código usado para habilitar o rastreamento pcap consiste de uma linha.

pointToPoint.EnablePcapAll ("myfirst");

Insira esta linha depois do código do rastreamento ASCII, no arquivo scratch/myfirst.cc. Repare que passamos apenas o texto “myfirst” e não “myfirst.pcap”, isto ocorre por que é um prefixo e não um nome de arquivo completo. O assistente irá criar um arquivo contendo um prefixo e o número do nó de rede, o número de dispositivo e o sufixo “.pcap”.

Em nosso código, nós iremos ver arquivos chamados “myfirst-0-0.pcap” e “myfirst-1-0.pcap” que são rastreamentos pcap do dispositivo 0 do nó 0 e do dispositivo 0 do nó de rede 1, respectivamente.

Uma vez que adicionamos a linha de código que habilita o rastreamento pcap, podemos executar o código da forma habitual:

./waf --run scratch/myfirst

Se olharmos no diretório da distribuição, veremos agora três novos arquivos de registro: myfirst.tr que é o arquivo ASCII, que nós examinamos na seção anterior. myfirst-0-0.pcap e myfirst-1-0.pcap, que são os novos arquivos pcap gerados.

Lendo a saída com o tcpdump

A forma mais confortável de olhar os arquivos pcap é usando o tcpdump.

tcpdump -nn -tt -r myfirst-0-0.pcap
reading from file myfirst-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.514648 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024

tcpdump -nn -tt -r myfirst-1-0.pcap
reading from file myfirst-1-0.pcap, link-type PPP (PPP)
2.257324 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.257324 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024

Podemos ver no primeiro dump do arquivo myfirst-0-0.pcap (dispositivo cliente), que o pacote de eco é enviado com dois segundos de simulação. Olhando o segundo dump veremos que o pacote é recebido com 2.257324 segundos. O pacote é ecoado de volta com 2.257324 segundos e finalmente é recebido de volta pelo cliente com 2.514648 segundos.

Lendo saídas com o Wireshark

Podemos obter o Wireshark em http://www.wireshark.org/, bem como sua documentação.

O Wireshark é um programa que usa interface gráfica e pode ser usado para mostrar os arquivos de rastreamento. Com o Wireshark, podemos abrir cada arquivo de rastreamento e visualizar conteúdo como se tivéssemos capturando os pacotes usando um analisador de tráfego de redes (packet sniffer).