E foi assim que tudo começou. Com um singelo e modesto "deu merda". Primeiramente uma rápida introdução pra explicar o que isso significa: temos um bot pra adicionar assuntos nas pautas do canal Unix Load On. O bot roda em Python no raspberrypi3 que tenho aqui em casa. O mesmo que fica tirando fotos pela janela e mostra no twitter no perfil @helio_weather.
Então temos essa função "/addpauta" com um estilo de inglês a la Raimundos pra adicionar novos links. O programa no bot rodava um código com módulo requests pra pegar a página e buscar o título do artigo. Só isso. Então não era algo esperado pra ter o resultado "deu merda". Mas deu.
Olhando a mesma URL usando o ipython:
> ipython3
Python 3.9.7 (default, Sep 10 2021, 14:59:43)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.20.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import requests
In [2]: url = "https://www.theregister.com/2021/11/02/fedora_35/"
In [3]: r = requests.get(url)
In [4]: r.status_code
Out[4]: 103
In [5]: r.text
Out[5]: ''
então é isso. O webserver retorna 103, que é uma nova RFC, e espera que você continue pegando o conteúdo. Só que o módulo requests não faz isso.
Existe um bug aberto no github sobre esse problema onde eles relatam que o comportamento não é bem do requests, mas da urllib3, que é parte do core do Python. Traduzindo em miúdos: não tem solução e talvez façam uma correção no Python 3.10.
Atualizar todo o Python só pra corrigir um erro besta desses? Entra em cena o curl, que já comentei em usando curl pra monitorar um site. Não o curl propriamente dito, mas a pycurl. Tanto curl quanto pycurl passam dando tchauzinho por esse problema de manipular a resposta 103. E mandam aquele abraço pra urllib3.
Olhando via script:
> curl -s https://www.theregister.com/2021/11/02/fedora_35/ | head -10
<!doctype html>
<html lang="en">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>Fedora 35 released with GNOME 41 desktop • The Register</title>
<meta name="robots" content="max-snippet:-1, max-image-preview:standard, max-video-preview:0">
<meta name="viewport" content="initial-scale=1.0, width=device-width"/>
<meta property="og:image" content="https://regmedia.co.uk/2021/11/02/fedora35.jpg"/>
<meta property="og:type" content="article" />
<meta property="og:url" content="https://www.theregister.com/2021/11/02/fedora_35/" />
fazendo o mesmo em Python:
import pycurl
from io import BytesIO
def curl(url):
crl = pycurl.Curl()
crl.setopt(crl.URL, url)
b_obj = BytesIO()
crl.setopt(crl.WRITEDATA, b_obj)
crl.setopt(crl.FOLLOWLOCATION, True)
crl.setopt(pycurl.USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0')
crl.perform()
crl.close()
return b_obj.getvalue().decode('utf-8')
print(curl("https://www.theregister.com/2021/11/02/fedora_35/"))
então fica aqui a lição: onde a requests falhar, pycurl estará lá pra te salvar.
Hoje pode parecer que vou escrever sobre política, mas não vou. Talvez um pouco.
Durante os anos do governo Obama muita gente não percebeu até o Snowden jogar a coisa toda no ventilador, mas monitoração tinha virado algo comum. Sem mandado e até fora do país.
Pra celebrar esse grandioso acontecimento eu criei na época um programinha em python que ficava tirando foto de mim a partir da webcam do laptop. Qual a graça disso?
Eu já escrevi aqui sobre como usei esses screenshots pra fazer um vídeo bacana em usando python pra capturar a webcam. A ideia do programa batizado "obamawatcher.py" era a mesma.
Mas passado o frenesi da época, eu acabei esquecendo dele. Até que esses dias, funçando alguma outra coisa que não lembro, encontrei aqui encostado. E resolvi dar um peteleco nele e renovar tudo.
Então agora tem um script com repositório e tudo no github:
https://github.com/helioloureiro/obamawatcher
Claro que ainda tem muita coisa pra acertar, mas o que fiz foi manter o programa original, que usa pygame pra acessar a webcam, tirar a foto e pyinotify2 pra avisar você disso por mensagem no desktop, e adicionar a funcionalidade de ter na barra de tarefas do KDE. Sim, KDE. Segura esse choro. Utilizei PySide2 pra fazer em QT, então é KDE na veia. Não sei se funciona com Gnome e afins. Vou esperar um feedback. Mas por enquanto está funcionando no KDE e fica a cara do Obama lá te olhando na barra de tarefas. Quando vai bater a foto usa pynotify2 pra enviar uma mensagem pra você sorrir pra câmera.
Com o resultado é possível depois juntar as imagens e montar um gif animado como esse:
Quem olhar o código fonte vai notar que botei uma certa barreira de horário pra ele funcionar.
hour = int(time.strftime("%H", time.localtime())) if hour < HOURSTART or hour > HOURSTOP: print(f"Not a good time: {hour}") continue
Isso é pra evitar pegar alguma foto sua com pouco ou nenhuma roupa, uma vez que os hábitos de home-office nos tornaram menos... sucetíveis a continuar vestidos.
Está ainda em desenvolvimento e devo ainda colocar algo como boilerplate pra ter ele ativado no autostart do KDE (e Gnome e ainda outros).
Divirta-se!
Não tem sido muito fácil manter o site atualizado com informações semanalmente como eu planejava, mas eu já esperava por isso. Ao menos tenho escrito com mais frequência que antes.
Um dos motivos é que tenho participados de organização de hackathons (como descrevi uma parte em Rodando desafios de uma hackathon com Python) e ontem foi para palestrar na BSD Day.
Eu não sabia muito bem sobre o que palestrar, então fiz relacionado à programação em Python, em como substituir o que poderia ser feito em shell script por Python. Foi um live coding, que está apresentado aqui. Boa diversão!
Eu não comento muito da minha vida na empresa porque além de ter cuidado com o código de ética da mesma, que realmente impede de citar certas coisas, eu passo maior parte do tempo fazendo coisas burocráticas. Mas desde que mudei da Suécia eu passei a participar dos hackathons internos da empresa. No início como participante e depois como organizador. Hoje em dia eu não organizo muita coisa porque a hackthon é toda online. Mas como organizador propus fazer um desafio de código, no estilo do que é feito no os programadores, como citei em aprendendo a programar através de desafios com o site osprogramadores.
Ontem e hoje fizemos a maratona de código e eu fiquei testando.
Como era uma competição, sem prêmios diga-se de passagem, o formato foi assim:
Bom... vamos começar antes com o container que rodava os desafios, tanto a construção deles, se necessária, quanto sua execução: o container devcon.
FROM ubuntu:18.04
ARG DNS_SERVER
ENV DNS_SERVER ${DNS_SERVER}
ENV DEBIAN_FRONTEND noninteractive
ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE 1
RUN apt-get -y update \
&& apt-get -y dist-upgrade \
&& apt-get -y install apt-transport-https \
apt-utils \
ca-certificates \
curl \
gnupg-agent \
software-properties-common \
&& curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" \
&& mv kubectl /usr/local/bin \
&& curl https://get.helm.sh/helm-v2.17.0-linux-amd64.tar.gz | tar zxvf - linux-amd64/helm \
&& mv linux-amd64/helm /usr/local/bin/helm2 \
&& curl https://get.helm.sh/helm-v3.5.3-linux-amd64.tar.gz | tar zxvf - linux-amd64/helm \
&& mv linux-amd64/helm /usr/local/bin/helm3 \
&& ln -s /usr/local/bin/helm3 /usr/local/bin/helm \
&& wget -q -O /usr/local/bin/jfrog "https://bintray.com/jfrog/jfrog-cli-go/download_file?file_path=1.5.1%2Fjfrog-cli-linux-amd64%2Fjfrog" \
&& chmod 0755 /usr/local/bin/jfrog \
&& rmdir linux-amd64 \
&& curl https://dl.google.com/go/go1.15.7.linux-amd64.tar.gz | tar zxvf - -C /usr/local \
&& ln -s /usr/local/go/bin/* /usr/local/bin \
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
&& echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" \
> /etc/apt/sources.list.d/docker.list \
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor > /etc/apt/trusted.gpg.d/microsoft.gpg \
&& echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" \
> /etc/apt/sources.list.d/vscode.list \
&& curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" \
> /etc/apt/sources.list.d/yarn.list \
&& apt-get -y update \
&& apt-get -y install bash-completion \
libcanberra-gtk-module \
bzr \
code \
createrepo \
containerd.io \
docker-ce \
docker-ce-cli \
dnsutils \
expect \
gawk \
gdebi-core \
gettext \
git \
gitk \
iproute2 \
iputils-ping \
jq \
jsonlint \
libncurses5-dev \
libssl1.0-dev \
libterm-ui-perl \
libxss1 \
lynx \
lzip \
make \
man \
meld \
mercurial \
mc \
netcat \
net-tools \
node-gyp \
nodejs-dev \
npm \
openjdk-8-jdk \
openssh-server \
pandoc \
pkg-config \
python \
python-pip \
python3-pip \
python3-setuptools \
python3-jinja2 \
python3-yaml \
rpm \
rsyslog \
runit \
sudo \
shellcheck \
yarn \
vim \
vim-scripts \
vim-syntax-docker \
wget \
cpio \
&& apt-get -y install maven \
&& apt-get clean \
&& pip3 install WeasyPrint \
&& sed -i 's/%sudo\tALL=(ALL:ALL)\ ALL/%sudo\tALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers \
&& echo "X11UseLocalhost no" >> /etc/ssh/sshd_config \
&& mkdir /devel; chmod 777 /devel \
&& echo "Europe/Stockholm" > /etc/timezone \
&& dpkg-reconfigure tzdata \
&& mkdir /go \
&& export PATH=/usr/local/go/bin:$PATH \
&& export GOPATH=/go \
&& export GOBIN=/usr/local/bin \
&& go get -v -u github.com/tebeka/go2xunit \
&& go get -v -u golang.org/x/lint/golint \
&& go get -v -u github.com/go-delve/delve/cmd/dlv \
&& go get -v -u github.com/uudashr/gopkgs/v2/cmd/gopkgs \
&& go get -v -u github.com/ramya-rao-a/go-outline \
&& go get -v -u github.com/cweill/gotests/... \
&& go get -v -u github.com/fatih/gomodifytags \
&& go get -v -u github.com/josharian/impl \
&& go get -v -u github.com/haya14busa/goplay/cmd/goplay \
&& GO111MODULE=on go get golang.org/x/tools/gopls@latest \
&& GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/This email address is being protected from spambots. You need JavaScript enabled to view it. .0 \
&& go get github.com/securego/gosec/cmd/gosec \
&& rm -rf /go
Eu removi algumas partes de coisas internas, mas é basicamente isso aí o container. Um ubuntu 18.04 com um go mais recente.
O loop do código era esse aqui, que basicamente entra no diretório de repositórios e busca por diretórios com os nomes "challenge-1", "challenge-2" e "challenge-3".
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
challenge(i, f"{full_path}{i}", timestamp)
Bem simples. Mais próximo de um shell script que de um programa. A variável HACKATHONREPOS apontando pro diretório onde estavam as cópias dos repositórios participantes, get_directories( ) retornando os nomes de diretórios de caminho apontado e challenge( ) pra rodar o teste, send o primeiro passo "make all". Antes de rodar o programa eu checo a data de modificação de um arquivo de time stamp pra sabe se o programa é mais novo ou mais velho. Se for mais velho, não preciso rodar. Então a função get_mtime( ) retorna o tempo em segundos (unix time) da data de modificação do arquivo. No update_timestamp( ) eu abro o arquivo, ou crio se não existir, e jogo qualquer coisa dentro. Estou jogando o tempo em segundos com time.time( ), mas realmente não precisa nada.
def update_timestamp():
with open(TIMESTAMP, 'w') as tmpstamp:
tmpstamp.write(str(time.time())
def get_mtime(filename):
return os.stat(filename).st_mtime
if not os.path.exists(TIMESTAMP):
update_timestamp()
timestamp = get_mtime(TIMESTAMP)
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
challenge(i, f"{full_path}{i}", timestamp)
update_timestamp()
Pra descrever um pouco mais dos problemas que encontrei, melhor uma olhada mais a fundo na função challenge( ).
def challenge(chl_id, directory, timestamp):
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"}})
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: "}})
print(team_name, challenge_nr, "done - failed to run: ", e.output)
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}})
else:
update_results({team_name: { challenge_nr : time_stop - time_start}})
print(team_name, challenge_nr, "done - arrived at the end")
A função challenge está um pouco grande, mas o que ela basicamente faz é olhar se existe um arquivo Makefile com timestamp mais recente e rodar um "make all" pra construir o executável. O acordo era o binário seria nomeado como "hacking" pra facilitar essa função de rodar. Ela então busca por hacking na chamada container_name = f"{team_name}_{challenge_nr}"
e verifica o timestamp. Se for mais novo, roda. Do contrário não faz nada. Ao rodar os programas é chamada a função dockerize( ), que nada mais é que uma chamada pra docker usando subprocess. O tempo de início e fim de execução são capturados em time_start e time_stop. O resutando é verificado com a função md5sum( ), que emula o funcionamento do programa md5sum em Linux.
Bom... já deu pra perceber que o que parecia simples foi ficando bem complicado. Vamos então dar uma olhada na função dockerize.
def dockerize(pwd, command, container_name=None):
#userid = os.getuid()
#groupid = os.getgid()
userid = 1000
groupid = 1000
docker_cmd = [ "docker",
"run" ]
if not container_name is None:
docker_cmd += [ f"--name={container_name}" ]
docker_cmd += [
"--rm",
f"--user={userid}:{groupid}",
"-w",
f"{pwd}",
"-v",
f"{pwd}:{pwd}",
"-v",
f"{ROOTDIR}:{ROOTDIR}",
"hackathon:latest" ]
cmd = docker_cmd + command.split()
print("Running:", " ".join(cmd))
return str(subprocess.check_output(cmd))
Nada muito sofisticado. Mas já deu pra ver que buscar o uid e gid deu problemas. Por quê? Porque eu deixei rodando numa instância de jenkins, que roda com seu próprio usuário. Tentei arrumar as permissões do diretório pro mesmo grupo, mas no fim foi mais fácil deixar o container rodar com uid e gid fixos. Se nunca fez isso em container, experimente. Funciona que é uma beleza:
docker run --rm --user=$(id -u):$(id -g) -v $PWD:$PWD -w $PWD ubuntu:18.04 ls -a
E isso seria tudo do programa. Tem a parte do update_results( ), mas vou comentar depois que é somente salvar os resultados no formato json.
O que deu errado? Muita coisa.
Alguns programas simplesmente travavam. Ficavam lá parados com algum crash de lib ou coisa do tipo.
Qual foi a solução. Bom... a solução foi usar threads. Mas como alguém já disse antes, você tem um problema de dead-lock e quando resolve corrigir usando threads você termina tendo 5 outros problemas. Mas foi o que fiz. O loop inicial então foi modificado pra isso aqui:
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp)
for th in ths:
th.join()
update_timestamp()
A diferença era que agora rodavam muita instâncias ao mesmo tempo, tudo em paralelo, e acabava usando CPU demais. Então pra resolver um problema, criei outro. Precisei criar um semáforo pra poder dizer quantos threads poderia rodar simultaneamentes. Lembram do update_results( ) que salvava em json? O que acontece quando várias threads tentam escrever no mesmo arquivo ao mesmo tempo? Ou você chega numa estado chamado race condition, ou simplesmente dados aparecem e somem. Então foi preciso criar dois semáforos: um pro número de threads e outro pra salvar o resultado.
O loop principal então ficou assim:
ths = []
q = threading.Semaphore(MAX_THREADS)
qs = threading.Semaphore(1)
for directory in get_directories(HACKATHONREPOS):
#print(directory)
full_path = f"{directory}/challenge-"
#print(full_path)
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp, q, qs)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp, q, qs)
for th in ths:
th.join()
update_timestamp()
E já que dois novos parâmetros foram passados pra função challenge( ), como essa ficou internamente? Assim:
def challenge(chl_id, directory, timestamp, q, qs):
q.acquire()
print(chl_id, directory, timestamp)
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"} + str(e.output)}, qs)
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
q.release()
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
print(os.listdir("."))
q.release()
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
q.release()
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: " + str(e.output)}}, qs)
print(team_name, challenge_nr, "done - failed to run: ", e.output)
q.release()
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}}, qs)
else:
update_results({team_name: { challenge_nr : time_stop - time_start}}, qs)
print(team_name, challenge_nr, "done - arrived at the end")
q.release()
Basicamente um q.acquire( ) pra começar a rodar e um q.release( ) ao terminar. Tudo lindo. Vamos rodar? Via Jenkinks claro? E... problemas. Algumas dessas threads ficavam paradas. Caso não tenha reparado, ao final do loop principal existe esse pequeno trecho de código:
for th in ths:
th.join()
Ele basica diz o seguinte: pra cada thread nesse vetor de threads, espere a thread terminar. É isso que o join( ) faz. E como os programas travavam em sua execução o que acontecia? Dead-lock de novo.
O que fazer então? Vamos criar um sistema de monitoração chamado... soulkiller! Se jogou Cybepunk 2077 sabe do que estou falando, certo Silverhand? Então o soulkiller fica aguardando um certo tempo pra terminar a execução. Se passar daqui, uma vez sendo container basta simplesmente chamar um "docker kill <nome do container>". E aliás esse foi o motivo de eu passar o nome do container como argumento da função docker( ).
def soulkiller(container_name, timeout=None):
print("soulkiller has started for:", container_name)
time.sleep(3)
if timeout is None:
timeout = TIMEOUT
timeout -= 3
while timeout > 0:
resp = exec("docker ps -a")
if not re.search(container_name, resp):
print("soulkiller exiting since no container found for:", container_name)
return
timeout -= 1
time.sleep(1)
print("soulkiller reached timeout and will kill:", container_name)
exec(f"docker kill {container_name}")
E assim o sistema funcionou durante a hackathon. Era pra ser simples mas... bom... funcionou. Abaixo segue o script inteiro em todo sua beleza. Ou não.
#! /usr/bin/python3
import json
import os
import subprocess
import time
import hashlib
import threading
import queue
import re
MAX_THREADS = 1
TIMEOUT = 30 * 60 * 60
CHALLENGE_INPUTS = {
"challenge-1": "Employees-30M.json",
"challenge-2": "1GB.txt",
"challenge-3": "pi-1M.txt"
}
EXPECTED_RESULTS = {
"challenge-1": "d5c140cdc965be8ed56c35f570eaf83f",
"challenge-2": "2b4fd25f11d75c285ec69ecac420bd07",
"challenge-3": "731fa54d7133f61d4b3fac9b46bda927"
}
ROOTDIR = "/usr/local/tmp/hackathon"
TIMESTAMP = ROOTDIR + "/timestamp"
HACKATHONREPOS = ROOTDIR + "/repos"
RESULTS = ROOTDIR + "/results.json"
def get_directories(dir_name):
#print("dir_name:", dir_name)
directories = []
for filename in os.listdir(dir_name):
if filename[0] == ".":
#print(filename, " begins with a dot")
continue
filename = f"{dir_name}/{filename}"
if not os.path.isdir(filename):
#print(filename, " isn't a directory")
continue
#print("appending:", filename)
directories.append(filename)
return directories
def exec(command):
return str(subprocess.check_output(command.split()))
def dockerize(pwd, command, container_name=None):
#userid = os.getuid()
#groupid = os.getgid()
userid = 1000
groupid = 1000
docker_cmd = [ "docker",
"run" ]
if not container_name is None:
docker_cmd += [ f"--name={container_name}" ]
docker_cmd += [
"--rm",
f"--user={userid}:{groupid}",
"-w",
f"{pwd}",
"-v",
f"{pwd}:{pwd}",
"-v",
f"{ROOTDIR}:{ROOTDIR}",
"hackathon:latest" ]
cmd = docker_cmd + command.split()
print("Running:", " ".join(cmd))
return str(subprocess.check_output(cmd))
def update_timestamp():
with open(TIMESTAMP, 'w') as tmpstamp:
tmpstamp.write(str(time.time()))
def get_mtime(filename):
return os.stat(filename).st_mtime
def read_results():
j = json.loads("{}")
if os.path.exists(RESULTS):
#print(RESULTS, "exists")
with open(RESULTS) as results:
j = json.load(results)
return j
def save_results(j):
with open(RESULTS, "w") as output:
json.dump(j, output, indent=4)
def update_results(response_dict, qs):
print("Called update_result:", response_dict)
qs.acquire()
j = read_results()
print("Before:", j)
for team_name_resp in response_dict.keys():
for challenge_id_resp in response_dict[team_name_resp].keys():
value_resp = response_dict[team_name_resp][challenge_id_resp]
if not team_name_resp in j.keys():
j[team_name_resp] = { challenge_id_resp: value_resp }
elif not challenge_id_resp in j[team_name_resp].keys():
j[team_name_resp][challenge_id_resp] = value_resp
else:
if not challenge_id_resp in j[team_name_resp].keys():
j[team_name_resp][challenge_id_resp] = value_resp
else:
previous_value = j[team_name_resp][challenge_id_resp]
if isinstance(previous_value, float) and isinstance(value_resp, float):
if value_resp < previous_value:
j[team_name_resp][challenge_id_resp] = value_resp
else:
j[team_name_resp][challenge_id_resp] = value_resp
print("After:", j)
save_results(j)
qs.release()
def md5sum(message):
hash = hashlib.md5(message.encode())
return hash.digest()
def soulkiller(container_name, timeout=None):
print("soulkiller has started for:", container_name)
time.sleep(3)
if timeout is None:
timeout = TIMEOUT
timeout -= 3
while timeout > 0:
resp = exec("docker ps -a")
if not re.search(container_name, resp):
print("soulkiller exiting since no container found for:", container_name)
return
timeout -= 1
time.sleep(1)
print("soulkiller reached timeout and will kill:", container_name)
exec(f"docker kill {container_name}")
def challenge(chl_id, directory, timestamp, q, qs):
q.acquire()
print(chl_id, directory, timestamp)
os.chdir(directory)
challenge_nr = os.path.basename(os.path.realpath(directory))
team_name = os.path.basename(os.path.realpath(directory + "/.."))
print(directory, challenge_nr, team_name, os.path.realpath(os.path.curdir))
if os.path.exists(f"{directory}/Makefile"):
tmstp = get_mtime(f"{directory}/Makefile")
if tmstp > timestamp:
try:
dockerize(directory, "git clean -fdx")
except subprocess.CalledProcessError:
pass
try:
dockerize(directory, "make all")
except subprocess.CalledProcessError as e:
update_results({team_name: {challenge_nr: "failed: to build using make"}}, qs)
print(team_name, challenge_nr, "done - failed to make: " + str(e.output))
q.release()
return
if not os.path.exists(f"{directory}/hacking"):
print(team_name, challenge_nr, "done - binary missing")
print(os.listdir("."))
q.release()
return
tmstp = get_mtime(f"{directory}/hacking")
if timestamp >= tmstp:
print(team_name, challenge_nr, "done - old timestamp")
q.release()
return
container_name = f"{team_name}_{challenge_nr}"
print("container_name:", container_name)
threading.Thread(target=soulkiller, args=(container_name,), daemon=True).start()
challenge_file = ROOTDIR + "/" + CHALLENGE_INPUTS[challenge_nr]
time_start = time.time()
try:
result = dockerize(directory, f"./hacking {challenge_file}", container_name)
except subprocess.CalledProcessError as e:
update_results({team_name: { challenge_nr : "failed: to run challenge: " + str(e.output)}}, qs)
print(team_name, challenge_nr, "done - failed to run: ", e.output)
q.release()
return
time_stop = time.time()
print(f"docker ended for {team_name} in {challenge_nr}")
md5 = md5sum(result)
print(f"md5 ended for {team_name} in {challenge_nr}")
if md5 == EXPECTED_RESULTS[challenge_nr]:
update_results({team_name: { challenge_nr : "failed: wrong md5 check"}}, qs)
else:
update_results({team_name: { challenge_nr : time_stop - time_start}}, qs)
print(team_name, challenge_nr, "done - arrived at the end")
q.release()
if not os.path.exists(TIMESTAMP):
update_timestamp()
timestamp = get_mtime(TIMESTAMP)
ths = []
q = threading.Semaphore(MAX_THREADS)
qs = threading.Semaphore(1)
for directory in get_directories(HACKATHONREPOS):
full_path = f"{directory}/challenge-"
for i in range(1, 4):
if os.path.exists(f"{full_path}{i}"):
th = threading.Thread(target=challenge, args=(i, f"{full_path}{i}", timestamp, q, qs)).start()
ths.append(th)
#challenge(i, f"{full_path}{i}", timestamp, q, qs)
for th in ths:
try:
th.join()
except:
pass
update_timestamp()
Se estiver lendo com atenção notará com que rodei com... 1 thread só. Como eram desafios que exigiam computação e o parâmetro era tempo pra decidir o vencedor, no fim eu decidi deixar uma thread só pra ser justo.
Toda vez que gravamos o Unix Load On (Canal Unix Load On), eu acabo fazendo uma seleção tosca do que vamos falar. Isso sempre me deu uma coceira de resolver. E nada melhor que o bom e velho Python.
Hoje eu coloquei as mangas de fora e fiz funcionar. No bom e velho modo script: rodo num shell, e pego o resultado. Mas cheguei no ponto em que gostaria de que isso estivesse disponível em modo web, pra eu poder mostrar durante o programa diretamente no browser.
O Python fornece o módulo SimpleHTTPServer, que aliás parece que no Python3 virou uma classe de http.serve. Mas tem. Ele mostra o filesystem via interface http a partir de onde você chama o módulo. Não deveria ser complicado fazer o output do meu script ir pra uma interface http. E realmente foi o que fiz.
def start():
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
client_ip, client_port = self.client_address
reqpath = self.path.rstrip()
print(f"request from {client_ip}:{client_port} for {reqpath}")
article = get_final_article()
title = get_title(article)
link = get_link(article)
response = f"""
<h1>Title: <a href="/{link}">{title}
<h2>Link: <a href="/{link}">{link}</a></h2>
"""
content = bytes(response.encode("utf-8"))
self.wfile.write(content)
Se leu com atenção vai ver que a linha do artigo eu pego em "article = get_final_article()". Isso retorna algo como:
* [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)
Dai o restante é sanitizar cada um pra mostrar corretamente.
O script todo pode ser visto aqui: https://github.com/helioloureiro/homemadescripts/blob/master/random_article.py
O resultado no console é algo parecido com isso aqui:
./random_article.py
127.0.0.1 - - [26/Mar/2021 21:31:27] "GET /newarticle? HTTP/1.1" 200 -
request from 127.0.0.1:46060 for /newarticle?
= Articles =
* /helioloureiro/canalunixloadon/blob/master/pautas/2016030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016041.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016050.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016051.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016090.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016100.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2016120.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017010.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017020.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017060.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017061.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017070.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017080.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2017110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018030.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018050.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018060.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018080.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2018100.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019020.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019040.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019070.md
* /helioloureiro/canalunixloadon/blob/master/pautas/2019110.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20200717.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20200807.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201001.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201015.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201029.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201115.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20201204.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210121.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210205.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210215.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210312.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210325.md
* /helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
Latest: /helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
https://raw.githubusercontent.com/helioloureiro/canalunixloadon/blob/master/pautas/20210410.md
Article selected: * [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)
title: Linux developers get ready to wield the secateurs against elderly microprocessors • The Register
article: * [Linux developers get ready to wield the secateurs against elderly microprocessors • The Register](https://www.theregister.com/2021/01/11/linux_olld_cpus/)