Dool Plot¶
Objetivo¶
PLOT de dados originados de arquivos .csv
contendo estatísticas de redes extraídos do comando dool
(https://github.com/scottchiefbaker/dool)
Setup do Ambiente¶
Para a execução desse Jupyter recomenda-se a versão Python 3.12.4 e a utilização de venv
Na mesma pasta do .ipynb executar:
`python -m venv venv`
Ativar o script usando a seguinte linha para linux
.\venv\Scripts\activate
ou a seguinte linha para Windows:
.\venv\Scripts\activate`
Executar a seguir a instalação de requisitos:
pip install -r requirements.txt
Utilização¶
Na ferramenta Dool é possível selecionar as diferentes métricas, conforme o exemplo abaixo para geração de um .csv:
dool -m -cC all -dD md0,nvme0n1 -nN enp59s0np0 -r --aio --disk-avgqu --disk-avgrq --disk-svctm --disk-tps --disk-util --disk-wait --md-status --output cenarioX-server-async-p8-r1.csv
Será gerado um arquivo .csv com as métricas organizadas por colunas e subcolunas, conforme o exemplo abaixo:
┄┄┄┄┄┄memory┄usage┄┄┄┄┄┼┄┄dsk/md0┄┄┄┄dsk/nvm01┄┼net/enp59s0┼async>
used free cach avai│ read writ read writ│ recv send│ #aio>
1396M 4079M 244G 247G│ 0 0 ┊ 0 0 │ 0 0 │ 0 >
1395M 4080M 244G 247G│ 0 0 ┊ 0 0 │1200b 2016b│ 0 >
csv
used free cach avai dsk/md0:read dsk/md0:writ dsk/nvm01:read dsk/nvm01:writ recv send
1881911296 2,66493E+11 83075072 2,65385E+11 27.075.893.609 3.563.977.421 3.386.332.280 445.591.370 0 0
1881452544 2,66493E+11 83521536 2,65385E+11 0 0 0 0 0 0
1880059904 2,66494E+11 83521536 2,65387E+11 0 0 0 0 0 0
1878884352 2,66496E+11 83521536 2,65388E+11 0 0 0 0 624 688
1986740224 2,64402E+11 2069295104 2,64949E+11 10203922432 0 1277722624 0 1449232 2828768032
1845006336 2,56492E+11 10101891072 2,65082E+11 32078036992 0 3997171712 0 15632624 32612024672
Para a geração dos gráficos, será necessário alterar os seguintes parâmetros no bloco de plot:
1. Caminho para o arquivo de texto:
Alterar a variável file_path com o path do arquivo relativo ao diretório do notebook, conforme exemplo abaixo:
file_path = 'cenarioX-server-async-p8-r1.csv'
2. Seleção de colunas para virarem gráficos:
Como pode ser observado no .csv de exemplo acima, há colunas superiores e subcolunas. Por exemplo, a coluna principal "memory usage" possui subcolunas used, free, cach e avai. As colunas e subcolunas a serem plotadas são selecionadas por meio da variável columns_dict: para cada subcoluna selecionada é gerado um gráfico separado. No exemplo abaixo serão gerados 6 gráficos separados: (2 gráficos para par6ametros de rede, um para parâmetros do disco dsk/md0, 2 gráficos para parâmetros de memória e um gráfico para parâmetros da CPU)
columns_dict = {
'net': ['send', 'recv'],
'dsk/md0': ['dsk/md0:read'],
'memory usage': ['used', 'cach'],
'cpu': ['usage']
}
Caso queira gerar apenas gráficos do uso de memória e o throughput recebido, a seleção pode ser feita assim:
columns_dict = {
'net': ['recv'],
'memory usage': ['used']
}
3. (opcional) Eixo X fixo:
Caso seja necessário, para fins de comparação entre gráficos, a fixação de um tempo no eixo X, deve-se a lterar a variável x_axis_fixed_time, conforme o exemplo abaixo. Caso a variável não esteja configurada, será selecionada uma escalade dinâmica, conforme os dados coletados:*
x_axis_fixed_time = 350
Isso significa 350 segundos exibidos no eixo X (largura) do gráfico.
Processar o arquivo e obter o resultado:
process_file(file_path, columns_dict, x_axis_fixed_time)
Caso não seja definido um valor para x_axis_fixed_time este será 0.
Ao decorrer do notebook há blocos de códigos com exemplos
Código Principal¶
Não é necessário alterar os blocos de código desta sessão.
# Imports
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re, io
def bytes_threshold(lista_sem_tratamento, bytes_validos):
array_list = np.array(lista_sem_tratamento)
# [False, False, True ]
boolean_list = array_list > bytes_validos
first_index = np.argmax(boolean_list)
last_index = len(lista_sem_tratamento) - 1 - np.argmax(np.flip(boolean_list))
list_treated = lista_sem_tratamento[first_index:last_index + 1]
return list_treated
def process_file(file_path, col_dict, x_axis_fixed_time = 0):
# Caminho para o seu arquivo CSV
#file_path = 'cenario-01-server-p4-r1.csv'
host_side = ''
try:
# --- 1. Limpeza do Texto ---
# Aparentemente há caracteres de suporte a cores no terminal que
# está presente no csv
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
raw_text = f.read()
raw_text = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', raw_text)
# Construção do MultiIndex
lines = raw_text.splitlines()
# As linhas de cabeçalho (contendo colunas e subcolunas)
# são a 5 e a 6 (índices 4 e 5)
header_lvl0_raw = lines[4].split(',')
header_lvl1_raw = lines[5].split(',')
if 'client' in file_path.lower():
host_side = 'client'
elif 'server' in file_path.lower():
host_side = 'server'
# Corrigir o nível 0 (superior)
'''
Há colunas em branco que atrapalham o trabalho com multi index. Exemplo:
memory usage, (vazio), (vazio), (vazio)
used, free, cach, avai
Após correção:
memory usage, memory usage, memory usage, memory usage
used, free, cach, avai
'''
level0_fixed = []
last_valid_header = ''
for h in header_lvl0_raw:
# Remove aspas e espaços
current_header = h.strip().replace('"', '')
if current_header:
last_valid_header = current_header
level0_fixed.append(last_valid_header)
else:
# Preenche com o último cabeçalho válido se a célula estiver vazia
level0_fixed.append(last_valid_header)
# Limpa o nível 1 (inferior/subcolunas)
level1_fixed = [h.strip().replace('"', '') for h in header_lvl1_raw]
# Cria o MultiIndex a partir das listas corrigidas
new_columns = pd.MultiIndex.from_arrays([level0_fixed, level1_fixed])
# --- 2. Carregamento do DataFrame ---
# Lê os dados pulando as 6 linhas de metadados/cabeçalho, sem interpretar cabeçalho
df = pd.read_csv(io.StringIO(raw_text), skiprows=6, header=None, engine='python')
# Descarta a última coluna se ela for extra e vazia (problema comum em CSVs)
if df.shape[1] > len(new_columns):
df = df.iloc[:, :len(new_columns)]
# Atribui o cabeçalho corrigido
df.columns = new_columns
# --- 3. Percorre as colunas ---
'''
Exemplo:
level0_col: memory usage
level1_col: used, free, cach, avai
'''
for level0_col, level1_col_list in col_dict.items():
for level1_col in level1_col_list:
y_label = ''
y_axis = 0
media_vazao = 0
column_value = []
cpu_total_usage_names = []
cpu_num = set()
# Match automático do nome da interface
# para que não seja necessário ser passado pelo usuário
if 'net' in level0_col.lower():
for col in df.columns.get_level_values(0):
if re.match(r'net/.+', col):
level0_col = col
break
elif 'cpu' in level0_col.lower():
for col in df.columns.get_level_values(0):
if re.match(r'cpu(\d+) usage', col):
cpu_num.add(re.match(r'cpu(\d+) usage', col).group(1))
new_columns2 = {}
for num in sorted(cpu_num):
col_cpu_usr = (f'cpu{num} usage', f'cpu{num} usage:usr')
col_cpu_sys = (f'cpu{num} usage', f'cpu{num} usage:sys')
new_columns2[f'cpu{num}_total_usage'] = df[col_cpu_usr].astype(float) + df[col_cpu_sys].astype(float)
df_novos_dados = pd.DataFrame(new_columns2)
df = pd.concat([df, df_novos_dados], axis=1)
cpu_total_usage_names = list(new_columns2.keys())
# ---- 4. Plotagem ----
# Definição da Figura
fig, ax = plt.subplots(figsize=(15, 5))
# Eixo X: Segundos
if x_axis_fixed_time != 0:
ax.set_xlim(1, x_axis_fixed_time + 1)
x_axis = range(1, len(df) + 1)
# Se for uma coluna que possui valores de transmissão de rede
# então há a Conversão para Gbits
if 'memory' in level0_col.lower():
y_label = "Quantidade de memoria (GB)"
y_axis = df[(level0_col, level1_col)].astype(float) * 1e-9
elif 'cpu' in level0_col.lower():
y_label = "Porcentagem de CPU"
else:
y_axis = df[(level0_col, level1_col)]
ax.set_ylabel(y_label, fontsize=20)
# Bytes Válidos = 1MB+
# Bytes abaixo de 1MB foram observados como bytes
# que não faziam parte dos momentos de teste/transferencia.
bytes_valid = 1000000
if 'cpu' not in level0_col.lower():
# Refere-se a lista contendo resquícios de dados (vários 0/vazio) após o término de transferência
lista_sem_tratamento = df[level0_col, level1_col]
lista_tratada = bytes_threshold(lista_sem_tratamento, bytes_valid)
column_value = lista_tratada
# Cálculo da média já com os zeros das extremidades aparadas:
media_vazao = column_value.mean() * 1e-9 if 'cpu' not in level0_col else 0
ax.set_xlabel('Tempo (segundos)', fontsize=20)
ax.grid(True)
# 1 Gráfico com múltiplas linhas de cada Core.
if 'cpu' in level0_col.lower() and cpu_num is not None:
for i in cpu_num:
y_axis = df[f'cpu{i}_total_usage'].astype(float)
ax.plot(x_axis, y_axis)
cpu_num = sorted(list(cpu_num), key=int)
ax.set_title(f'CPUs: 1 - {int(cpu_num[-1]) + 1} ({host_side})', fontsize=20)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.show()
# PLOT Separado do uso da Média Geral
plt.close()
fig, ax = plt.subplots(figsize=(15, 5))
df['overall_avg_cpu_usage'] = df[cpu_total_usage_names].mean(axis=1).astype(float)
y_axis = df['overall_avg_cpu_usage']
ax.set_title(f'Média uso de CPU geral ({host_side})', fontsize=20)
ax.set_xlabel('Tempo (segundos)', fontsize=20)
ax.set_ylabel(f'Porcentagem de uso (%)', fontsize=20)
if x_axis_fixed_time != 0:
ax.set_xlim(1, x_axis_fixed_time + 1)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
ax.plot(x_axis, y_axis)
plt.show()
elif 'net' in level0_col.lower():
# Linha com média da transferência
if 'enp67s0f0np0' in level0_col:
level0_col_name = 'net/Server'
elif 'enp59s0np0' in level0_col:
level0_col_name = 'net/Client'
else:
level0_col_name = level0_col
ax.set_title(f'Colunas: {level0_col_name}: {level1_col}', fontsize=20)
ax.set_ylabel(f'Gbps', fontsize=20)
y_axis = df[(level0_col, level1_col)].astype(float) * 1e-9
ax.plot(x_axis, y_axis)
ax.axhline(y=media_vazao, color='black',
linestyle='--', linewidth=2,
label=f'Média: {media_vazao:.3f}')
plt.text(x_axis[0], media_vazao,
f'média: {media_vazao:.2f}',
fontsize=22,
verticalalignment='bottom',
horizontalalignment='left',
fontweight="bold")
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.show()
elif 'dsk' in level0_col.lower():
#if not (y_preenchido != 0).any():
y_axis = df[(level0_col, level1_col)].astype(float) * 1e-9
ax.set_ylabel(f'Gbps', fontsize=20)
ax.set_title(f'({host_side}) Colunas: {level0_col}: {level1_col}', fontsize=20)
#ax.set_ylim(-5, 105)
ax.axhline(y=media_vazao, color='black',
linestyle='--', linewidth=2,
label=f'Média: {media_vazao:.2f}')
plt.text(x_axis[0], media_vazao,
f'média: {media_vazao:.2f}',
fontsize=22,
verticalalignment='bottom',
horizontalalignment='left',
fontweight="bold")
ax.plot(x_axis, y_axis, color='red')
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.show()
else:
ax.plot(x_axis, y_axis, color='green')
ax.set_title(f'({host_side}) Colunas: {level0_col}: {level1_col}', fontsize=20)
# Change font size for x and y axis ticks
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.show()
#plt.show()
except FileNotFoundError:
print(f"Erro: O arquivo '{file_path}' não foi encontrado.")
except KeyError as e:
print(f"Erro: {e}")
except Exception as e:
print(f"Falha ao processar o CSV: {e}")
Exemplo de plotagem com o .csv¶
Esta é a maneira como o arquivo .csv disponibilizado como exemplo nesse pacote de instalação é plotado. Para cada arquivo deverá ser criado um bloco equivalente.
x_axis_fixed_time = 0
file_path = r'teste.csv'
columns_dict = {
'net': ['send', 'recv'],
'dsk/md0': ['dsk/md0:read'],
'memory usage': ['cach'],
'cpu': ['usage']
}
process_file(file_path, columns_dict,x_axis_fixed_time)