Eu sou um físico médico acadêmico. Faço experimentos que geram uma quantidade razoável de dados e são caros de executar. Minha universidade tem um sistema de backup que consiste em uma biblioteca de fitas robô em uma mina de sal abandonada que usa o Spectrum Protect da IBM (chamado como dsmc
) que eu uso para backups externos. Embora não haja limite para o tamanho total que posso enviar para a mina de sal, há um limite de transferência por dia de 200 gigabytes. Até onde eu sei, não há como fazer com que o cliente Spectrum Protect respeite esse limite e pare depois que o limite de transferência for atingido.
Se alguém ultrapassar esse limite, o servidor bloqueará o nó e eu terei que enviar um e-mail de desculpas rastejante a alguém para pedir que o desbloqueie. Eles me repreendem por usar muita largura de banda e, algo como 24 a 48 horas depois, desbloqueiam o nó.
Para contornar o fato de que crio dados em partes discretas (nos dias de experimento) e estou bem abaixo do limite de largura de banda por mês ou por semana, escrevi um script de wrapper simples para analisar a saída dsmc
e matar a transferência se ficar muito grande.
A análise é feita tratando a saída de dsmc
como um documento aqui no bash com um script python simples:
#!/bin/bash
# A silly wrapper script to halt TSM backups
#
# Usage: sudo /path/to/script /path/to/backup/location
#
# Requires python3 accessible as python3, and the regex / os modules.
# Tested on MacOS and Linux
BYTES_SENT=0;
#MAX_SIZE_TO_SEND=150 #Bytes, for testing
MAX_SIZE_TO_SEND=$[185*(2**30)]
args=("$@")
sudo rm -f /tmp/dsmc-script.PID
function outputParser() {
python3 <<'EOF'
import os, re
rex=re.compile(r"Normal File\-\-\>\s*?([,0-9]*,?)\s*?\/")
valueToParse=os.environ.get('line');
match=rex.match(valueToParse);
try:
stringToReturn = str(match.group(1));
stringToReturn =stringToReturn.replace(',','');
except AttributeError:
stringToReturn = "";
#Check for failed transfers
failedResults = re.findall(r"\*\* Unsuccessful \*\*", valueToParse);
nFailedResults = len(failedResults);
if (nFailedResults >0):
stringToReturn = "";
print(stringToReturn);
EOF
} #I am sure that the above is a one-liner in sed or awk. I just don't know what the one line is.
function trapCaught() {
#Do cleanup, not shown
echo ", quitting."
}
trap trapCaught sigint
killCount=0
startTime=$SECONDS
while read -r line; do
echo "$line"
export line;
X=$(export line=$line; outputParser)
if [[ ! -z "$X" ]]; then
BYTES_SENT=$[$BYTES_SENT + $X]
echo "Sent $X bytes, $BYTES_SENT in total"
fi
if (( BYTES_SENT > MAX_SIZE_TO_SEND )); then
if (( killCount < 1)); then
echo "STOPPED BACKUP BECAUSE $BYTES_SENT is GREATER THAN THE PERMITTED MAXIMUM OF $MAX_SIZE_TO_SEND";
killStartTime=$(( SECONDS - startTime ))
pid=$(cat /tmp/dsmc-script.PID)
echo "PID is $pid"
echo $pid | sudo xargs kill
fi
killCount=$[$killCount + 1];
timeKillNow=$(( SECONDS - killStartTime ))
rm -f /tmp/dsmc-script.PID
if (( killCount > 100 || timeKillNow > 30 )); then
echo "Taking too long to die; retrying"
echo $pid | sudo xargs kill -9;
sleep 0.1;
sudo kill -9 0;
fi
fi
done < <( sudo dsmc incr ${args[0]} & echo $! > /tmp/dsmc-script.PID )
Isso funciona e atende aos meus propósitos. No entanto, o desempenho é ruim, beirando o terrível, e acho que isso ocorre porque cada iteração através do while
loop gera outra instância do combo interpretador/script python.
Dado que não posso alterar o limite ou o comportamento do blob compilado binário dsmc
, tenho três perguntas relacionadas:
(a) Essa é uma abordagem sensata para resolver esse problema ou há uma maneira muito mais fácil que estou perdendo, como vodu avançado com netstat
?
(b) Dado que o que o python realmente faz é essencialmente exatamente o mesmo em cada iteração no loop, existe uma maneira de armazenar em cache a tradução do código do interpretador e, portanto, acelerar enormemente a coisa toda?
(c) Se eu fosse substituir o script python por um equivalente sed
ou awk
construção, suspeito que tudo isso seria muito, muito mais rápido. Por quê? É possível fazer esse tipo de aritmética facilmente, ou isso é outro arenque vermelho para descer?
Edit : A saída de exemplo dsmc
para aqueles que não estão familiarizados está abaixo - um arquivo é enviado apenas se "Arquivo normal" aparecer em uma string, seguido por seu tamanho em bytes. Então, abaixo, o arquivo spclicert.kdb
é enviado, mas nem TSM.PWD
e nem o diretório CaptiveNetworkSupport
:
# dsmc incr /
< header message containing personal information>
Incremental backup of volume '/'
ANS1898I ***** Processed 79,000 files *****
Directory--> 0 /Library/Preferences/SystemConfiguration/CaptiveNetworkSupport [Sent]
Normal File--> 5,080 /Library/Preferences/Tivoli Storage Manager/Nodes/SHUG2765-MACBOOKPRO-PHYSICS/spclicert.kdb [Sent]
Updating--> 224 /Library/Preferences/Tivoli Storage Manager/BrokenOrOld/TSM.PWD (original) [Sent]
Assim, o script acima retira o tamanho em bytes de cada arquivo enviado e simplesmente os soma.
Supondo que a conexão seja confiável, um truque simples seria usar um modelador de tráfego de espaço do usuário. Basta configurá-lo para não usar mais do que a largura de banda máxima por dia.
Um exemplo usando
trickle
, um arquivo grande foo escp
:E
trickle
desaceleraria a transferência para 2.314 K por segundo, o que atingiria não mais que 199.929.600.000 bytes por dia. O programa de transferência de arquivos não precisa serscp
, pode ser qualquer coisa (até mesmo um navegador da web), (oudsmc
), apenas para ser iniciado a partir da linha de comando.Uma vantagem deste método é que não seria necessário desmembrar o arquivo foo se ele fosse maior que o limite diário. É claro que levaria um tempo para enviar foo (se foo fosse 1 TB, levaria 5 dias), mas levaria tanto tempo de qualquer maneira.
trickle
tem uma versão do daemon chamadatrickled
, que controla cada execução subsequente de `trickle. Exemplo:Supondo que cada um dos arquivos foo , bar e baz tivesse 1 TB de tamanho, ainda
trickled
manteria a transferência dentro do limite de 200 GB /dia.Sua entrada pode ser analisada inteiramente em
bash
. Aqui está uma amostra:Se você estiver realmente limitado pelo tempo necessário para gerar um subprocesso, isso evita a sobrecarga até mesmo de chamar
awk
oused
.