NOP

No Operating Blog

Raspberry Pi - Seu Provedor De Banda Larga Entrega O Prometido?

O Raspberry Pi

Há algum tempo, comprei uma placa Raspberry Pi com o objetivo de conhecer o hardware e medir o desempenho do Linux nela. Enquanto a plaquinha não chegava, as ideias iam surgindo. No início, pensei em um servidor de disco para armazenar meus arquivos pessoais. Em seguida, surgiu a ideia de usar a plaquinha como um servidor multimídia, usando o XBMC. Logo em seguida, pensei em instalar o Mame para diversão.

Atualização em 2014-12-28 - Leia no fim da página…

E foram surgindo várias e várias ideias de uso para o Raspberry.

Pois bem, a placa chegou! Instalei o Debian no SD card e testei… Que maravilha !!!! Tudo funcionou conforme o script e com um desempenho satisfatório para o hardware.

Após o teste desliguei tudo, guardei a placa na minha gaveta e lá ela ficou, dormindo por quase 6 meses.

Até que um dia, surgiu mais uma ideia de uso, mas desta vez com algo que estava me dando um certo trabalho. Como eu havia assinado um novo plano de banda larga no provedor (GVT), comecei a monitorar se estava recebendo a velocidade contratada. Para tal, usava o medidor Speedtest.net. Além disso, de tempos em tempos eu usava a interface web do meu modem para checar alguns parâmetros da conexão ADSL (atenuação da linha, nível de sinal-ruído, etc.)

Daí resolvi monitorar a “qualidade” da minha conexão usando o Raspberry Pi. Como se trata de uma placa que roda Linux é possível utilizar as ferramentas disponíveis deste ambiente e escrever pouco código para atingir o objetivo. Além disso, a placa tem uma característica importante para um sistema de monitoramento 24/7 doméstico: seu consumo é baixo (por volta de 2 watts).

As ferramentas

Antes de sair escrevendo código, resolvi buscar por alguma solução pronta para o teste de velocidade do link. Encontrei um código python aqui. (tespeed)

O tespeed faz um teste de velocidade utilizando as instâncias de servidores do Speedtest.net. Ou seja, faz o que eu estava fazendo “na mão”, quando acessava o site.

Para executar o teste de velocidade, em seu teste padrão (sem parâmetros adicionais), o testpeed seleciona um servidor dentre os servidores cadastrados em uma lista, e troca dados com este servidor com o objetivo de medir a velocidade do link. Na seleção do servidor, o tespeed utiliza aquele de menor latência e localizado próximo de sua conexão com a internet (a localização é estabelecida utilizando o endereço IP fornecido pelo provedor que você assina e a latência é medida pelo tempo tempo de ping)

O único problema neste processo de seleção é que o servidor escolhido poderá ser um servidor localizado dentro da rede de seu provedor. Isto ocorre porque alguns os provedores de internet instalam instâncias do server do speedtest.net em seus próprios servidores e os adicionam na lista de servidores disponíveis para teste do speedtest.net.

Com isso, quando um servidor de seu próprio provedor é selecionado, o seu teste poderá não estar testando a sua velocidade de conexão com a Internet, de fato. Isto porque o teste de velocidade será feito através de uma conexão entre o seu computador doméstico e o servidor instalado em seu provedor. Nenhum tráfego será gerado “para a Internet”. Se a “saída” de seu provedor estiver congestionada, o teste não captará este congestionamento, uma vez que apenas a infraestrutura do próprio provedor estará sendo testada (a conexão entre sua casa e o servidor instalado no provedor).

Apesar desta característica do teste padrão executado pelo tespeed, quando planejei o teste de minha conexão com a Internet, imaginei um teste dos parâmetros da minha conexão até o outro lado da linha telefônica, por entender que este seria o ponto mais frágil de minha conexão. (o caminho da tomada telefônica, em minha residência, até o DSLAM do provedor, pelo menos)

Mesmo assim, para quem estiver pensando em executar o teste de velocidade com algum servidor específico (dentre os disponíveis no speedtest.net), é possível fixar o servidor no tespeed, chamando o programa com um parâmetro adicional. (verifique o manual ou o help do programa)

Além do código do tespeed, neste projeto foram utilizadas outras ferramentas:

  • bash - dispensa comentários. Um manual pode ser encontrado aqui.
  • RRDtool - armazenamento dos valores medidos e geração dos gráficos
  • Python - usei as funções de parser de arquivos HTML
  • lighttpd - servidor html
  • wget - utilizado para ler a página de estatísticas da conexão ADSL, gerada pelo modem
  • cron

Script de definição de parâmetros (defs.sh)

(defs.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/bin/bash
# 
# defs.sh
# Parameters used by all programs
#
# 201301 - Christian D Freitas


#Database name
DATABASE=db.rrd
#Modem address
MODEM_ADDR=
#Modem page (where to get statistics)
MODEM_DSL_PAGE="/index.cgi?page=statdsl"
#Modem admin account
MODEM_USER=
#Modem password
MODEM_PASS=

#Arrays (avoiding multidimensional arrays!!)

#Generate graphics only for parameters which index is present in the ARR_IDXS list
ARR_IDXS="0 1 6 7 8 9"
#Last array index
ARR_LAST_IDX=13

#Array format
#ARR_DATABASE_DS_NAME[] = DS_name of parameter (rrdtool)
#ARR_GRAPH_TITLE[]= Graphic title  (rrdgraph)
#ARR_GRAPH_VERTICAL_LABEL[]= (rrdgraph)
#ARR_GRAPH_FILENAME[]= filename (generated by rrdgraph)
#ARR_MODEM_STRING[]=parameter search string
#   All modem parameters (ADSL statistcs) are gathered from the html file. The search string is (somewhat) used  to locate the parameter
#VALUE_FROM_HTML[]= Value gathered from the html file (or from tespeed.py

ARR_DATABASE_DS_NAME[0]="Speed_Download"
ARR_GRAPH_TITLE[0]="Downstream Speed"
ARR_GRAPH_VERTICAL_LABEL[0]="Mbps"
ARR_GRAPH_FILENAME[0]="downspeed"
ARR_MODEM_STRING[0]="Downstream Current Rate"
VALUE_FROM_HTML[0]=0

ARR_DATABASE_DS_NAME[1]="Speed_Upload"
ARR_GRAPH_TITLE[1]="Upstream Speed"
ARR_GRAPH_VERTICAL_LABEL[1]="Mbps"
ARR_GRAPH_FILENAME[1]="uploadspeed"
ARR_MODEM_STRING[1]="Upstream Current Rate"
VALUE_FROM_HTML[1]=0

ARR_DATABASE_DS_NAME[2]="Mdm_Down_C_Rate"
ARR_GRAPH_TITLE[2]="Downstream Current Rate"
ARR_GRAPH_VERTICAL_LABEL[2]="Mbps"
ARR_GRAPH_FILENAME[2]="downcurrrate"
ARR_MODEM_STRING[2]="Downstream Current Rate"
VALUE_FROM_HTML[2]=0

ARR_DATABASE_DS_NAME[3]="Mdm_Up_C_Rate"
ARR_GRAPH_TITLE[3]="Upstream Current Rate"
ARR_GRAPH_VERTICAL_LABEL[3]="Mbps"
ARR_GRAPH_FILENAME[3]="upcurrrate"
ARR_MODEM_STRING[3]="Upstream Current Rate"
VALUE_FROM_HTML[3]=0

ARR_DATABASE_DS_NAME[4]="Mdm_Down_Max_Rate"
ARR_GRAPH_TITLE[4]="Downstream Max Rate"
ARR_GRAPH_VERTICAL_LABEL[4]="Mbps"
ARR_GRAPH_FILENAME[4]="downmaxrate"
ARR_MODEM_STRING[4]="Downstream Max Rate"
VALUE_FROM_HTML[4]=0

ARR_DATABASE_DS_NAME[5]="Mdm_Up_Max_Rate"
ARR_GRAPH_TITLE[5]="Upstream Max Rate"
ARR_GRAPH_VERTICAL_LABEL[5]="Mbps"
ARR_GRAPH_FILENAME[5]="upmaxrate"
ARR_MODEM_STRING[5]="Upstream Max Rate"
VALUE_FROM_HTML[5]=0

ARR_DATABASE_DS_NAME[6]="Mdm_Down_Noise_M"
ARR_GRAPH_TITLE[6]="Downstream Noise Margin"
ARR_GRAPH_VERTICAL_LABEL[6]="dB"
ARR_GRAPH_FILENAME[6]="downnoisemargin"
ARR_MODEM_STRING[6]="Downstream Noise Margin"
VALUE_FROM_HTML[6]=0

ARR_DATABASE_DS_NAME[7]="Mdm_Up_Noise_M"
ARR_GRAPH_TITLE[7]="Upstream Noise Margin"
ARR_GRAPH_VERTICAL_LABEL[7]="dB"
ARR_GRAPH_FILENAME[7]="upnoisemargin"
ARR_MODEM_STRING[7]="Upstream Noise Margin"
VALUE_FROM_HTML[7]=0

ARR_DATABASE_DS_NAME[8]="Mdm_Down_Att"
ARR_GRAPH_TITLE[8]="Downstream Attenuation"
ARR_GRAPH_VERTICAL_LABEL[8]="dB"
ARR_GRAPH_FILENAME[8]="downatt"
ARR_MODEM_STRING[8]="Downstream Attenuation"
VALUE_FROM_HTML[8]=0

ARR_DATABASE_DS_NAME[9]="Mdm_Up_Att"
ARR_GRAPH_TITLE[9]="Upstream Attenuation"
ARR_GRAPH_VERTICAL_LABEL[9]="dB"
ARR_GRAPH_FILENAME[9]="upatt"
ARR_MODEM_STRING[9]="Upstream Attenuation"
VALUE_FROM_HTML[9]=0

ARR_DATABASE_DS_NAME[10]="Mdm_Down_Power"
ARR_GRAPH_TITLE[10]="Downstream Power"
ARR_GRAPH_VERTICAL_LABEL[10]="dbm"
ARR_GRAPH_FILENAME[10]="downpower"
ARR_MODEM_STRING[10]="Downstream Power"
VALUE_FROM_HTML[10]=0

ARR_DATABASE_DS_NAME[11]="Mdm_Up_Power"
ARR_GRAPH_TITLE[11]="Upstream Power"
ARR_GRAPH_VERTICAL_LABEL[11]="dbm"
ARR_GRAPH_FILENAME[11]="uppower"
ARR_MODEM_STRING[11]="Upstream Power"
VALUE_FROM_HTML[11]=0

ARR_DATABASE_DS_NAME[12]="Mdm_Down_FEC"
ARR_GRAPH_TITLE[12]="Downstream FEC"
ARR_GRAPH_VERTICAL_LABEL[12]="corrected blocks"
ARR_GRAPH_FILENAME[12]="downfec"
ARR_MODEM_STRING[12]="Downstream FEC"
VALUE_FROM_HTML[12]=0

ARR_DATABASE_DS_NAME[13]="Mdm_Up_FEC"
ARR_GRAPH_TITLE[13]="Upstream FEC"
ARR_GRAPH_VERTICAL_LABEL[13]="corrected blocks"
ARR_GRAPH_FILENAME[13]="upfec"
ARR_MODEM_STRING[13]="Upstream FEC"
VALUE_FROM_HTML[13]=0

Neste script são definidos todos os parâmetros que serão compartilhados pelos módulos do projeto.

As variáveis MODEM_ADDR, MODEM_DSL_PAGE, MODEM_USER e MODEM_PASS definem os parâmetros necessários para que um script (que será apresentado mais adiante) obtenha as informações do modem. _(é necessário editar o arquivo defs.sh e atribuir os valores das variáveis MODEM_ADDR, MODEM_USER e MODEM_PASS)

Aqui cabe um comentário: geralmente, o protocolo SNMP é utilizado para obter informações de um dispositivo de rede (switchs, roteadores, modems, etc.). Acontece, porém, que o provedor que assino (GVT) não disponibiliza este protocolo para acesso às informações no modem (PowerBox). Caso disponibilizasse, seria possível utilizar o aplicativo snmpget para obter as informações do dispositivo e alimentar o banco de dados do RRDTools.

Para contornar o problema, a solução que implementei pega as informações diretamente de uma página web gerada pelo modem (as tradicionais páginas de configuração do dispoitivo, acessadas pelo browser). Para realizar esta tarefa, usei o aplicativo wget, que lê a página gerada pelo modem utilizando como parâmetro as variáveis definidas neste script (endereço do modem, username, password e local da página html).

Os demais parâmetros definidos nas linhas 36 em diante são ordenados na forma de um array, onde casa posição deste array armazena os dados referentes a alguma informação medida (ex: velocidadei de Download), ou obtida do modem (ex: Downstream Attenuation).

Os seguintes elementos fazem parte deste array:

  • ARR_DATABASE_DS_NAME[] - nome que será utilizado no RRDTool (DS - database source)
  • ARR_GRAPH_TITLE[] - nome que será exibido como título do gráfico gerado pelo rrdgraph (RRDTool)
  • ARR_GRAPH_VERTICAL_LABEL[] - título do eixo vertical do gráfico gerado pelo rrdgraph
  • ARR_GRAPH_FILENAME[] - nome do arquivo de saída gerado pelo rrdgraph (o rrdgraph gerará um arquivo no formato PNG)
  • ARR_MODEM_STRING[] - string utilizada pelo script para localizar a posição da informação que será obtida do arquivo html que contém as estatísticas do modem
  • VALUE_FROM_HTML[] - valor capturado do arquivo html gerado pelo modem (ou valores medidos pelo script de teste de velocidade)

Script de criação o banco de dados do RRDTool (create_database.sh)

Este script cria o banco de dados que será utilizado pelo RRDTool para armazenar os valores medidos e capturados do modem.

(create_database.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/bin/bash
# 
# create_database.sh
# Creates rrd (rrdtool)  database
#
# 201301 - Christian D Freitas

# Import definitions
source defs.sh

# Remove old database
rm $DATABASE

# Database creation 
# rrd
#rrdtool create filename --start time --step secs \
#DS:Label:Type:Heartbeat:Min:Max \
#RRA:Consolidation Function:XFF:Steps:Rows
# sketch  - This database was created to store speedtest results and modem DSL statistcs
# Modem: sagemcom 2764
#rrdtool create $DATABASE \
#         --start now \
#         --step 3600 \
#         DS:Speed_Download:GAUGE:7200:0:U \
#         DS:Speed_Upload:GAUGE:7200:0:U \
#         DS:Mdm_Down_C_Rate:GAUGE:7200:0:U \
#         DS:Mdm_Up_C_Rate:GAUGE:7200:0:U \
#         DS:Mdm_Down_Max_Rate:GAUGE:7200:0:U \
#         DS:Mdm_Up_Max_Rate:GAUGE:7200:0:U \
#         DS:Mdm_Down_Noise_M:GAUGE:7200:0:U \
#         DS:Mdm_Up_Noise_M:GAUGE:7200:0:U \
#         DS:Mdm_Down_Att:GAUGE:7200:0:U \
#         DS:Mdm_Up_Att:GAUGE:7200:0:U \
#         DS:Mdm_Down_Power:GAUGE:7200:0:U \
#         DS:Mdm_Up_Power:GAUGE:7200:0:U \
#         DS:Mdm_Down_FEC:GAUGE:7200:0:U \
#         DS:Mdm_Up_FEC:GAUGE:7200:0:U \
#         RRA:AVERAGE:0.5:1:744 \
#         RRA:AVERAGE:0.5:24:365 
#
# No primeiro RRA acima, serah calculada a media, a cada novo valor (1), e armazenado 744 medias (744 = 24h de  medidas por 31 dias)
# No segundo RRA acima, serah calculada a media, dos ultimos 24 valores, e armazenados 365 medias (ultimos 24 valores = 24h. 24*367 = 1 ano)

# create execution string
str_exec="rrdtool create $DATABASE --start now --step 3600 "

# mount string
for idx in `seq 0 $ARR_LAST_IDX`
do
    str_exec="$str_exec DS:${ARR_DATABASE_DS_NAME[$idx]}:GAUGE:7200:0:U"
done

# append RRAs to the end of string
str_exec="$str_exec RRA:AVERAGE:0.5:1:744 RRA:AVERAGE:0.5:24:365"

# Call string (create database)
$str_exec

A função deste script é montar a string que define os data-source do banco de dados e executar o aplicativo rrdtool para criar o banco de dados.

A string é dinamicamente montada, usando os valores definidos no script defs.sh.

(Deixei, como referência, o código comentado da string que será montada para a criação do banco de dados.)

Script de atualização do banco de dados (update_database.sh)

Este script, um dos mais importantes do projeto, é responsável pela atualização do banco de dados.

(update_database.sh) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash
# 
# update_database.sh
# Updates the rrd (rrdtool)  database
#
# 201301 - Christian D Freitas

# Import definitions
source defs.sh


# Executes tespeed - measures Download and Upload speed 
eval $(python -u tespeed.py -w -s | awk 'BEGIN {FS = ","} ; {print "Download="$1; print "Upload="$2}' )

# Gets html file (statistics) from the modem
modem_out=$(wget -qO- --user=$MODEM_USER --password=$MODEM_PASS $MODEM_ADDR$MODEM_DSL_PAGE)

# Extracts values from the wget output (stored in modem_out) 
# The python code outputs an assignment VALUE_FROM_HTML[idx]=value that is evaluated by eval
for idx in `seq 0 $ARR_LAST_IDX`
do
    eval $(echo "$modem_out" | grep "${ARR_MODEM_STRING[$idx]}"  | python extract_html_data.py "VALUE_FROM_HTML[$idx]")
done

# Stores the values measured by tespeed in the VALUE_FROM_HTML array (TODO: change this variable name)
VALUE_FROM_HTML[0]=$Download
VALUE_FROM_HTML[1]=$Upload


#DEBUG
#for idx in $ARR_IDXS
#do
#    echo ${ARR_GRAPH_TITLE[idx]}"-->"${VALUE_FROM_HTML[$idx]}
#done

# Builds the command that will be used to update the rrdtool database
str_exec="rrdtool update $DATABASE N"
for idx in `seq 0 $ARR_LAST_IDX`
do
    #Concatenates values to the string
    str_exec="$str_exec:${VALUE_FROM_HTML[idx]}"
done

#Exec rrdtool update
$str_exec

A linha de código que executa o teste da velocidade é a seguinte:

1
eval $(python -u tespeed.py -w -s | awk 'BEGIN {FS = ","} ; {print "Download="$1; print "Upload="$2}' )

Esta linha chama o interpretador python, que executa o tespeed.py, pega os valores de sua saída e armazena nas variáveis Download e Upload.

Para a leitura dos parâmetros do modem, há a necessidade de acessar a página html gerada por este dispositivo. Esta tarefa é realizada pela seguinte linha:

1
modem_out=$(wget -qO- --user=$MODEM_USER --password=$MODEM_PASS $MODEM_ADDR$MODEM_DSL_PAGE)

Esta linha executa o wget e armazena o resultado de sua saída na variável modem_out, utilizando o recurso command substitution do bash. Veja um trecho deste código html armazenado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<tr class="aodd" id="newline1">   <td class="aleft" />   <td style="width: 300px; padding-bottom:5px; padding-top:5px;">Downstream Current Rate</td>   <td class="amid" />   <td style="width: 150px; padding-bottom:5px; padding-top:5px;">17661</td>   <td class="aright" />  </tr>  <tr>
 <td class="asep"><img src="/images/array/t2_left_rowsep.gif" width="9" height="1" /></td>
 <td class="asepleft" />
 <td class="asep"><img src="/images/array/t2_crosssep.gif" width="12" height="1" /></td>
 <td class="asepmid" />
 <td class="asepright" />
</tr>
<tr class="aodd" id="newline1">   <td class="aleft" />   <td style="width: 300px; padding-bottom:5px; padding-top:5px;">Upstream Current Rate</td>   <td class="amid" />   <td style="width: 150px; padding-bottom:5px; padding-top:5px;">1187</td>   <td class="aright" />  </tr>  <tr>
 <td class="asep"><img src="/images/array/t2_left_rowsep.gif" width="9" height="1" /></td>
 <td class="asepleft" />
 <td class="asep"><img src="/images/array/t2_crosssep.gif" width="12" height="1" /></td>
 <td class="asepmid" />
 <td class="asepright" />
</tr>
<tr class="aodd" id="newline1">   <td class="aleft" />   <td style="width: 300px; padding-bottom:5px; padding-top:5px;">Downstream Max Rate</td>   <td class="amid" />   <td style="width: 150px; padding-bottom:5px; padding-top:5px;">20188</td>   <td class="aright" />  </tr>  <tr>
 <td class="asep"><img src="/images/array/t2_left_rowsep.gif" width="9" height="1" /></td>
 <td class="asepleft" />
 <td class="asep"><img src="/images/array/t2_crosssep.gif" width="12" height="1" /></td>
 <td class="asepmid" />
 <td class="asepright" />
</tr>
<tr class="aodd" id="newline1">   <td class="aleft" />   <td style="width: 300px; padding-bottom:5px; padding-top:5px;">Upstream Max Rate</td>   <td class="amid" />   <td style="width: 150px; padding-bottom:5px; padding-top:5px;">1188</td>   <td class="aright" />  </tr>  <tr>
 <td class="asep"><img src="/images/array/t2_left_rowsep.gif" width="9" height="1" /></td>
 <td class="asepleft" />
 <td class="asep"><img src="/images/array/t2_crosssep.gif" width="12" height="1" /></td>
 <td class="asepmid" />
 <td class="asepright" />
</tr>

As seguintes linhas de código fazem o trabalho de extrair os valores que interessam (as estatísticas do modem):

1
2
3
4
for idx in `seq 0 $ARR_LAST_IDX`
do
    eval $(echo "$modem_out" | grep "${ARR_MODEM_STRING[$idx]}"  | python extract_html_data.py "VALUE_FROM_HTML[$idx]")
done

Este loop pega, em cada iteração, uma string do array ARR_MODEM_STRING definido em defs.sh e a utiliza para filtrar a saída que foi armazenada na variável modem_out.

Exemplificando: no caso em que ARR_MODEM_STRING[2]=”Downstream Current Rate”, no arquivo defs.sh, o grep extrairá a seguinte linha:

1
<tr class="aodd" id="newline1">   <td class="aleft" />   <td style="width: 300px; padding-bottom:5px; padding-top:5px;">Downstream Current Rate</td>   <td class="amid" />   <td style="width: 150px; padding-bottom:5px; padding-top:5px;">17661</td>   <td class="aright" />  </tr>  <tr>

Esta saída será repassada para o programa python extract_html_data.py, cujo código é reproduzido a seguir:

(extract_html_data.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 
# extract_html_data.py
# Extracts the values from html file (statistics values generated by the PowerBox Modem)
#
# 201301 - Christian D Freitas

from HTMLParser import HTMLParser
import sys

# create a subclass and override the handler methods
class MyHTMLParser(HTMLParser):
    strdata = []
    def handle_data(self, data):
        #Ignore space (as first char)
        if data[0] != ' ':
            # Alguns campos "data" possuem a unidade de medida (ex: 10 dB). Como a unidade eh separada por espacco, o split separa a string e somente o valor e adicionado ao array strdata
            # Gets only value (ignores measurement units)
            self.strdata.append(data.split(' ')[0])


# instantiate the parser and fed it some HTML
parser = MyHTMLParser()
parser.feed(sys.stdin.readlines()[0].strip('\n'))

print sys.argv[1]+"="+parser.strdata[1]


#O html parser encontra duas strings (data), que sao armazenados em um array
# 0 - Uma string que descreve o significado do campo
# 1 - Uma string que contem o valor do campo

Este programa faz um parse no código html que é lido da entrada padrão (pipe da saída do grep) e extrai o valor de interesse (no código html exibido anteriormente, o valor que será extraído é o 17761). Em seguida, o programa escreve este valor na saída padrão, utilizando o parâmetro que foi utilizado na chamada (“VALUE_FROM_HTML[$idx]”).

Como resultado, no caso da iteração em que a variável idx tem o valor “2”, a saída será:

VALUE_FROM_HTML[2]=17761

Este a saída será avaliada pelo “eval”, que armazenará o valor extraído do código html, na variável VALUE_FROM_HTML[].

É importante ressaltar que nos arrays definidos no arquivo defs.sh, a primeira e a segunda posição (índices 0 e 1) são utizadas para armazenar os valores obtidos pela execução do testpeed, e não valores lidos do modem. E é justamente o que faz as seguintes linhas de código:

# Stores the values measured by tespeed in the VALUE_FROM_HTML array (TODO: change this variable name)
VALUE_FROM_HTML[0]=$Download
VALUE_FROM_HTML[1]=$Upload

Por fim, o trecho final do script update_database.sh executa o comando rrdtool update para armazenar no banco de dados do RRDTool os valores obtidos. A string que executa este comando é montada dinamicamente, da mesma maneira que foi montada a string do comando de criação do banco de dados (script create_database.sh)

Visualização dos dados coletados (gráficos)

Para visualização dos dados coletados, criei uma página web. Desta forma, todos os gráficos gerados pelo RRDTool são facilmente visualizados utilizando um browser. Além disso, a utilização de um servidor web no Raspberry permite acessar a página remotamente, bastando para isso utilizar um serviço de DNS dinâmico (o software do modem GVT implementa a facilidade de DNS dinâmico).

Usei como template um exemplo de código HTML5 que encontrei neste endereço.

Código HTML:

(speedtest.html) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<html>
<head>
<title> ADSL Statistics </title>
<link rel="stylesheet" href="speedtest.css" type="text/css" />

</head>

<h1> ADSL Statistics </h1>
<nav id="nav">	
	<ul>		
		<li>
			<a href="javascript:create_graphs('d');"><span>1 Dia</span></a>
		</li>
		<li>
			<a href="javascript:create_graphs('w');"><span>1 Semana</span></a>
		</li>
		<li>
			<a href="javascript:create_graphs('m');"><span>1 M&ecircs</span></a>
		</li>
		<li>
			<a href="javascript:create_graphs('y');"><span>1 Ano</span></a>
		</li>
	</ul>
</nav>


<iframe id="frame" width="100%"  height = "90%"> </iframe>

<script>
var my_frame=document.getElementById("frame");



function create_graphs(str) {
var addr="./create_graphs.cgi?"+str;
my_frame.src=addr;
};

</script>

</html>

O código CSS não será apresentado aqui, mas pode ser baixado neste link

O resultado deste código (html e CSS) é exibido seguir:

pagina_web1

Ao clicar em um dos “botões” na página, a função create_graphs será chamada, que por sua vez, executará o cgi create_graphs.cgi, exibido a seguir:

(create_graphs.cgi) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

source defs.sh

echo "Content-type: text/html"
echo ""
echo "<html><head><title>CGI"
echo "</title></head><body>"


for idx in $ARR_IDXS
do
   filename="${ARR_GRAPH_FILENAME[$idx]}_$QUERY_STRING.png"
   rrdtool graph $filename --start -1$QUERY_STRING --slope-mode --title="${ARR_GRAPH_TITLE[$idx]}" --vertical-label=${ARR_GRAPH_VERTICAL_LABEL[$idx]} -z \
   DEF:a=$DATABASE:${ARR_DATABASE_DS_NAME[$idx]}:AVERAGE \
   LINE:a#FF0000 > /dev/null

   echo "<img src=$filename>"
   echo "<br>"
done

echo "<br>"
echo "<center>Information generated on $(date)</center>"
echo "<br><br><br><br><br><br><br>"
echo "</body></html>"

Este cgi é um script bash que gera dinamicamente o código html que será exibido dentro do iframe definido na página html (speedtest.html).

O script chama o rrdtool para gerar o gráfico com o parâmetro “-z” (lazy), de forma que os gráficos só são gerados quando houver alteração no gráfico gerado anteriormente, ou no caso de nenhum gráfico ter sido gerado ainda. Esta opção evita a geração desnecessária de gráficos, se o usuário ficar navegando na pagina html. (importante pois estamos usando um SD card para armazenamento dos dados!)

Instalando a “tralha” toda

  • Instalação do lighttpd:
1
sudo apt-get install lightttpd

Duas pequenas alterações são necessárias para que o lighttpd permita a execução de cgi bash. Para isto, edite o arquivo lighttpd.conf localizado no diretório /etc e inclua a seguinte linha nas definição do array server.modules:

1
"mod_cgi",

O resultado final será (trecho do arquivo):

1
2
3
4
5
6
7
8
server.modules = (
    "mod_cgi",
    "mod_access",
    "mod_alias",
    "mod_compress",
    "mod_redirect",
#       "mod_rewrite",
)

Adicione também, logo abaixo a definição da variável server.breakagelog, a seguinte linha:

1
cgi.assign                  = ( ".py" => "/usr/bin/python", ".sh" => "/bin/bash", ".cgi" => "/bin/bash" )

O resultado final será (trecho do arquivo):

1
2
3
4
5
6
server.port                 = 80

server.breakagelog          = "/var/log/lighttpd/breakage.log"
cgi.assign                  = ( ".py" => "/usr/bin/python", ".sh" => "/bin/bash", ".cgi" => "/bin/bash" )

index-file.names            = ( "index.php", "index.html", "index.lighttpd.html" )
  • Editar o arquivo /etc/groups e adicionar o usuário corrente do raspberry no grupo www-data (substituir o NOME_DO_USUARIO, na linha abaixo, pelo usuário que rodará o cron no seu raspberry):
1
www-data:x:33:NOME_DO_USUARIO
  • Instalação do wget:
1
sudo apt-get install wget
  • Instalação do RRDTool:
1
sudo apt-get install rrdtool
  • Copiar todos os arquivos para o diretório /var/www/speedtest:
1
2
3
4
cd /var/www
mkdir speedtest
cd speedtest
cp /ondeosarquivosestao/* .
  • Executar o comando para criar o banco de dados
1
2
cd /var/www
bash create_database.sh
  • Editar o crontab e adicionar as linhas que atualizam o banco de dados
1
crontab -e

Adicionar as seguintes linhas:

1
00 * * * * cd /var/www/speedtest; bash update_database.sh

Com esta configuração do cron, o script update_database.sh será executado de hora em hora, coletando os dados e atualizando o banco de dados do RRDTool.

Resultado final

Para abrir a página onde os gráficos gerados serão exibidos, digite o seguinte endereço em seu browser: http://endereco_ip_do_raspberry/speedtest/speedtest.html.

O resultado final, quando a página abrigada no servidor www for exibida pelo browser, para os gráficos do dia:

página_web2

Para os gráficos da semana:

página_web3

E aqui, o Raspberry, sem case, sem local definido, repousando temporariamente sobre o meu modem…

Raspberry

Os códigos estão disponíveis no repositório Github

2014-12-28 - Update: Devido a algumas alterações no site speedtest.net, o tespeed foi modificado para contemplá-las. Use o código mais recente do tespeed, encontrado em tespeed

Comments