Eu acabei quebrando minha promessa de ano novo de postar aqui semanalmente durante os últimos meses. Mas tenho uma boa justificativa. Eu escrevi um artigo pra Revista Espírito Livre, fiz duas palestras na Latinoware, organizei a PyCon Suécia, organizei a hackathon na firma e fiz uma última palestra pra Tchelinux.
Muito disso ainda não foi publicado. Quando aparecer, com certeza crio um post sobre o assunto.
Mas entre o que já foi publicado está a palestra sobre carreira na Latinoware.
Foquei mais nas perguntas que geralmente aparecem no grupo do telegram. E, claro, tinha mais assunto pra falar, mas vou guardar pra outra oportunidade :)
Seguindo os artigos trabalhando de home-office e trabalhando de casa - atualização de 2021, aqui vai mais uma atualização.
Eu decidi investir num teclado novo. Mecânico, claro. Decidi não ter mais um full size, que tem o teclado numérico, mas num um pouco mais curto. Depois de muito olhar e pesquisar, finalmente decidi pegar um Keychron C1 com brown switch.
Fiz um vídeo do unboxing onde tem até uma comparação de som com o teclado que usava anteriormente, que é do tipo blue switch.
Mas nem tudo foi uma maravilha em Linux.
Pra fazer o teclado funcionar corretamente eu precisei usar o modo Mac. No modo Windows, de jeito nenhum eu consegui fazer funcionar as teclas de funções. Mesmo no modo Mac eu precisei ajustar uns parâmetros pra tudo dar certo.
Precisei criar o arquivo /etc/moprobe.d/hid_apple.conf da seguinte maneira:
echo "options hid_apple fnmode=0" >> /etc/moprobe.d/hid_apple.conf
Com isso o kernel reconhece o teclado e aplica a configuração correta. Então as teclas de função F1-F12 são o padrão e preciso apertar a tecla "fn" pra usar as funções.
Existem várias opções de troca de firmware pra mudar cores, etc, mas confesso que não é algo que eu realmente pense em fazer. Pra mim basta ser um teclado mecânico confortável, o que realmente é, e ter um teclado luminoso (às vezes trabalho no escuro e isso ajuda).
Como reinstalei meu laptop acabei percebendo que dica de cima faltaram algumas coisas.
Primeiro que pra ativar manualmente o teclado, basta rodar o seguinte comando como root:
echo 0 >> /sys/module/hid_apple/parameters/fnmode
depois que ao criar o arquivo no modprobe, é preciso também re-gerar o arquivo de initram com o comando:
update-initramfs -u -k all
Durante a útima gravação do Unix Load On, Ingo comentou sobre aceleração de hardware via VAAPI. Eu imaginava que isso apenas era possível com GPUs da NVIDIA, mas depois que ele comentou sobre VAPI rodando em Intel, fui atrás de como isso funcionava.
Então fiz o teste de criar um vídeo a partir das imagens que fiz da troca de pneus da bicicleta pros de inverno.
O primeiro foi usando a CPU.
$ rm -f out.mp4; time ffmpeg -r 60 -i image-%03d.jpg -c:v libx264 -vf fps=60 -pix_fmt yuv420p out.mp4 ffmpeg version 3.4.8-0ubuntu0.2 Copyright (c) 2000-2020 the FFmpeg developers built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04) configuration: --prefix=/usr --extra-version=0ubuntu0.2 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared libavutil 55. 78.100 / 55. 78.100 libavcodec 57.107.100 / 57.107.100 libavformat 57. 83.100 / 57. 83.100 libavdevice 57. 10.100 / 57. 10.100 libavfilter 6.107.100 / 6.107.100 libavresample 3. 7. 0 / 3. 7. 0 libswscale 4. 8.100 / 4. 8.100 libswresample 2. 9.100 / 2. 9.100 libpostproc 54. 7.100 / 54. 7.100 Input #0, image2, from 'image-%03d.jpg': Duration: 00:00:25.24, start: 0.000000, bitrate: N/A Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 4000x3000 [SAR 1:1 DAR 4:3], 25 fps, 25 tbr, 25 tbn, 25 tbc Stream mapping: Stream #0:0 -> #0:0 (mjpeg (native) -> h264 (libx264)) Press [q] to stop, [?] for help [swscaler @ 0x56448c3106c0] deprecated pixel format used, make sure you did set range correctly [libx264 @ 0x56448c268ee0] using SAR=1/1 [libx264 @ 0x56448c268ee0] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2 [libx264 @ 0x56448c268ee0] profile High, level 6.0 [libx264 @ 0x56448c268ee0] 264 - core 152 r2854 e9a5903 - H.264/MPEG-4 AVC codec - Copyleft 2003-2017 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00 Output #0, mp4, to 'out.mp4': Metadata: encoder : Lavf57.83.100 Stream #0:0: Video: h264 (libx264) (avc1 / 0x31637661), yuv420p, 4000x3000 [SAR 1:1 DAR 4:3], q=-1--1, 60 fps, 15360 tbn, 60 tbc Metadata: encoder : Lavc57.107.100 libx264 Side data: cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: -1 frame= 631 fps=1.1 q=-1.0 Lsize= 64381kB time=00:00:10.46 bitrate=50388.8kbits/s speed=0.0188x video:64372kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.013232% [libx264 @ 0x56448c268ee0] frame I:11 Avg QP:23.13 size:216485 [libx264 @ 0x56448c268ee0] frame P:236 Avg QP:25.43 size:133712 [libx264 @ 0x56448c268ee0] frame B:384 Avg QP:25.38 size: 83278 [libx264 @ 0x56448c268ee0] consecutive B-frames: 14.1% 11.7% 7.6% 66.6% [libx264 @ 0x56448c268ee0] mb I I16..4: 16.8% 81.8% 1.4% [libx264 @ 0x56448c268ee0] mb P I16..4: 24.8% 44.9% 0.4% P16..4: 16.1% 1.8% 0.9% 0.0% 0.0% skip:11.1% [libx264 @ 0x56448c268ee0] mb B I16..4: 12.5% 18.4% 0.1% B16..8: 21.0% 2.2% 0.3% direct: 2.5% skip:43.1% L0:50.8% L1:46.8% BI: 2.4% [libx264 @ 0x56448c268ee0] 8x8 transform intra:62.8% inter:92.5% [libx264 @ 0x56448c268ee0] coded y,uvDC,uvAC intra: 29.1% 18.4% 0.7% inter: 11.4% 16.9% 0.1% [libx264 @ 0x56448c268ee0] i16 v,h,dc,p: 41% 27% 23% 9% [libx264 @ 0x56448c268ee0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 29% 22% 42% 2% 1% 1% 1% 1% 1% [libx264 @ 0x56448c268ee0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 31% 17% 19% 6% 6% 6% 6% 5% 3% [libx264 @ 0x56448c268ee0] i8c dc,h,v,p: 66% 16% 18% 1% [libx264 @ 0x56448c268ee0] Weighted P-Frames: Y:33.5% UV:16.5% [libx264 @ 0x56448c268ee0] ref P L0: 40.1% 13.1% 23.2% 17.1% 6.5% [libx264 @ 0x56448c268ee0] ref B L0: 59.5% 30.9% 9.6% [libx264 @ 0x56448c268ee0] ref B L1: 83.0% 17.0% [libx264 @ 0x56448c268ee0] kb/s:50142.36 1190.99user 25.61system 9:17.29elapsed 218%CPU (0avgtext+0avgdata 2714684maxresident)k 117280inputs+128944outputs (45major+630483minor)pagefaults 0swaps
9 minutos e 17 segundos. Usando 218% de CPU, de um laptop com 4 CPUs. Esse é o funcionamento normal.
E usando a GPU pra isso? Eu demorei um pouco pra acertar os parâmetros pra usar a VAAPI, mas no fim deu certo.
$ rm -f out.mp4; time ffmpeg -vaapi_device /dev/dri/renderD128 -r 60 -i image-%03d.jpg -vf 'format=nv12,hwupload,fps=60' -c:v h264_vaapi -pix_fmt vaapi_vld out.mp4 ffmpeg version 3.4.8-0ubuntu0.2 Copyright (c) 2000-2020 the FFmpeg developers built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04) configuration: --prefix=/usr --extra-version=0ubuntu0.2 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared libavutil 55. 78.100 / 55. 78.100 libavcodec 57.107.100 / 57.107.100 libavformat 57. 83.100 / 57. 83.100 libavdevice 57. 10.100 / 57. 10.100 libavfilter 6.107.100 / 6.107.100 libavresample 3. 7. 0 / 3. 7. 0 libswscale 4. 8.100 / 4. 8.100 libswresample 2. 9.100 / 2. 9.100 libpostproc 54. 7.100 / 54. 7.100 Input #0, image2, from 'image-%03d.jpg': Duration: 00:00:25.24, start: 0.000000, bitrate: N/A Stream #0:0: Video: mjpeg, yuvj420p(pc, bt470bg/unknown/unknown), 4000x3000 [SAR 1:1 DAR 4:3], 25 fps, 25 tbr, 25 tbn, 25 tbc Stream mapping: Stream #0:0 -> #0:0 (mjpeg (native) -> h264 (h264_vaapi)) Press [q] to stop, [?] for help [swscaler @ 0x5577c4cfb3c0] deprecated pixel format used, make sure you did set range correctly Output #0, mp4, to 'out.mp4': Metadata: encoder : Lavf57.83.100 Stream #0:0: Video: h264 (h264_vaapi) (High) (avc1 / 0x31637661), vaapi_vld, 4000x3000 [SAR 1:1 DAR 4:3], q=0-31, 60 fps, 15360 tbn, 60 tbc Metadata: encoder : Lavc57.107.100 h264_vaapi frame= 631 fps=9.3 q=-0.0 Lsize= 133604kB time=00:00:10.48 bitrate=104401.7kbits/s speed=0.154x video:133596kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.006032% 59.94user 1.03system 1:08.18elapsed 89%CPU (0avgtext+0avgdata 120480maxresident)k 3504inputs+267264outputs (0major+44766minor)pagefaults 0swaps
Resultado? 1 minuto e 08 segundos. Incrível. Usou 89% de CPU, o que significa que das 4 CPUs, uma somente ficou ocupada. E não 100%, apenas 90%.
Realmente incrível.
Não são todos os formatos de vídeo que são suportados, mas pra ter esse desempenho acho que vale sacrificar os formatos pra gerar um simples mp4.
UPDATE: 2021-11-01. O vídeo que renderizei pros testes.
Se você chegou agora e está perdido com o título, leia o primeiro artigo:
Países considerados os mais felizes do mundo escondem problemas graves
Pra você que leu e talvez não tenha ficado claro o motivo de eu abordar as fotos nas revistas no primeiro artigo: eu tentei mostrar que existe uma exigência de padrão de beleza bem menor que em países como o Brasil. Aqui cada um veste o que quiser e do jeito que quiser. E, claro, isso contribui pra felicidade da população.
E agora vamos falar um pouco das crianças. Falar sem ver. Por quê? Porque a maioria dos lugares que eu adoraria mostrar não permitem fotos. Tive de recorrer ao Google Maps para pegar algumas, mas as partes mais legais não aparecem porque em geral as escolas ficam dentro de parques.
Primeiramente que escola é obrigatória aqui. A partir dos... acho que 6 anos (mas posso estar enganado, então pode ser que seja aos 7). Começa no förskola (fer-is-cuo-la), ou primeira escola (primário), e depois vai do 1° ao 9˘ ano. A partir daí é o colegial de 3 anos. A escola em geral é gratuita, paga pelos impostos, mas descobri recentemente que alguma escolas internacionais em inglês são pagas. Existem escolas em inglês que não são pagas, mas essas têm filas de 4, 5, e até mais anos. Em geral é uma boa a escola em sueco pra criançada pegar o idioma, mas isso vai da decisão dos pais e suas possibilidades.
Independentemente de onde morar, é obrigatório a cidade oferecer uma vaga em até 3 meses na escola sueca. Isso pode ser doloroso se estiver com aluguel de moradia curta de 6 meses, que é meio que regra aqui (e isso é muito ruim).
Na escola são dados materiais didáticos como cadernos, livros e canetas ou lápis. Então basicamente você só precisa deixar a criança na escola e buscar depois. Sobre o horário, muitas escolas abrem por volta das 6:30 da manhã, pra permitir aos pais que trabalham ou longe ou nesse horário pra ter onde deixar as crianças. Pra buscar pode ser até às 7:30 da noite. Mas se não me engano isso é de algumas escolas e o horário obrigatório é às 06:30 da tarde. Na escola recebem alimentação pra ficar o dia inteiro. E já esquecia desse ponto: as escolas são o dia todo.
Além das aulas normais como de língua e matemática, nas escolas têm também aulas de culinária, marcenaria e costura. Não aquela maravilha, mas vez ou outra sai umas pizzas de forno (pão árabe, molho de tomate, presunto e queijo). É comum também a escola levar a turma toda pra passeios em fazendas com animais. Por quê? Pra conhecerem os animais. Então eles brincam com os coelhos, correm das galinhas e dão comida pros pôneis. A escola também os leva pra aproveitar as piscinas indoor durante o inverno e a outdoor, que é bem rasinha, no verão. Tem também passeios durante o inverno pra patinarem no gelo e, no ponto mais central, até esquiarem (existe um morro famoso aqui no centro que permite isso e não é muito alto). Na escola ainda eles têm direito a 1 hora de aula da língua materna por semana.
Pra deixar bem claro: não é escola com aulas o dia todo, mas pra ter onde deixar as crianças. A escola começa por volta das 8:30 e termina por volta das 14:00. Alguns dias um pouco mais tarde, em outros um pouco mais cedo. Desde o começo já aprendem a andar pela cidade. Todas as atividades são ou nos parques ao redor das escolas ou em museus que em geral ficam em algum lugar distante, mais central, ou ainda nas tais fazendo que mencionei acima. Então as crianças desde pesquenas são orientadas em como usar o transporte público. Que é gratuito até os 8 anos. E depois? Continua gratuito pra maiores de 8 anos que precisam pegar transporte público pra ira à escola. Eles oferecem um cartão que não paga nada até às 7:30 da noite durante a semana. E, claro, o transporte de estudantes custa mais barato que o passe normal, caso precise comprar passagem pro fim de semana.
Em geral as escolas são próximas das residências. Ao ponto das crianças irem sozinhas já com 7 ou 8 anos. E eles incentivam isso. Quando chegam numa certa idade, do ginásio, daí são escolas maiores e geralmente poucas por bairros. E assim as escolas vão afunilando a localização conforme a criança vai crescendo. É comum ver jovens indo pra escola de bicicleta. Pra mostrar um pouco das escolas, vou recorrer ao Google Maps pelo motivo que já coloquei logo no começo do artigo: privacidade das crianças e adolescentes.
E antes que me esqueça, aqui tem também uma bolsa pra cada filho melhor. É chamado barnbidrag e vem metade pro pai e metade pra mãe, mesmo sendo casados. Pra, no caso de separação e/ou divórcio, ficar tudo mais fácil. É como uma bolsa família mas com a diferença de que todo mundo recebe, independentemente se sua renda mensal precisa ou não. Não é um valor alto, por volta de 100 euros, mas é um dinheiro da criançada pra gastar com a criançada.
Bom... já descrevi da escola, mas onde está a parte relevante? Onde está a parte que a Suécia esconde?
Aqui é comum ver as crianças, quando mais jovens, irem vestidas... como quiserem. Meninos vão de vestido e meninas vão de vaqueiro. Não existe um "menino usa azul e menina usa rosa", que é até considerado sexismo por aqui. Tanto que uma loja de brinquedos viu-se no meio de uma discussão sobre sexismo ao separar os brinquedos em sessões "de meninos" e "de meninas".
E como foram as escolas durante a pandemia? Certo ou errado, a Suécia manteve a decisão de permanecer com escolas abertas até o 9° ano, pois o argumento foi de que era preciso manter as crianças na escola pros pais que trabalhavam na linha de frente pudessem continuar trabalhando. Apenas os jovens do colegial passaram a ter aulas remotas. E, claro, cada um recebeu um laptop pra poder participar das aulas. Nas universidades também as aulas passaram pro modo remoto, mas não sei dizer se receberam um laptop pra isso ou não.
Tá certo... mas isso não explica a reclamação dos pais moçambicanos, não é mesmo? O que acontece aqui é que a Suécia trata cada criança como uma cidadã desde pequena. Ao contrário de países como Brasil, onde os pais é que são seus guardiões, aqui o estado representa a criança e seus interesses. Inclusive contra os próprios pais, se for o caso. Então se um pai abusivo entrar em casa e bater numa criança, ou jovem, pode ser denunciado por esse à polícia. É uma noção bastante estranha pra nós brasileiros, mas com o tempo você acaba acostumando (ou então volta pro seu país).
Existe uma discussão sobre a Suécia estar criando pequenos ditadores nesse sentido. Mas eu diria que isso é um estigma de quem vem de fora e quer criar caso. Ninguém comenta do sistema de ensino, das escolas gratuitas e só olham pra esse lado. Claro que é mais fácil apontar pra um defeito, mas esquecer tudo mais que se ganha aqui?
Leva-se um tempo pra acostumar com as regras daqui e passar a tratar os filhos como... cidadãos. Então como qualquer outra pessoa, você tem de sempre negociar. Não vai tomar banho? Não tem Internet. Não vai pra escola? Não tem youtube. E por aí vai. Não, não é tarefa fácil, como atestam os moçambicanos.
Mas falar de brasileiros ou moçambicanos pode até dar a ideia errada sobre o que está por trás disso. O que acontece é que nem todo mundo compartilha os valores europeus. E pra estar aqui, deveriam adaptar-se. Então as famosas mutilações de genitais, praticadas em vários países do mundo, não é permitida aqui. A criança pode denunciar os pais por tentarem fazer isso e pode ser direcionada pra viver com sob guarda do estado.
Estranho uma criança tendo direitos, não é? Mas não devia ser. Aqui não é.
Então recebi assim, sem muita cerimônia, a reportagem da Record sobre a Suécia. Países considerados os mais felizes do mundo escondem problemas graves. O título já é um grande bait. A coisa toda vai de mau a pior. É um monte de bobagens juntas com vários estereótipos juntos.
Fonte: https://recordtv.r7.com/domingo-espetacular/videos/paises-considerados-os-mais-felizes-do-mundo-escondem-problemas-graves-16082021
Não é a primeira vez que recebo algo assim e provavelmente não será a última. Minha sensação é que pra esconder a atual situação, que eles ajudaram a chegar com várias reportagens de apoio ao atual presidente, ficam despejando esse conteúdo de vira-latas. Algo pra dizer "olha como não somos só nós que estamos mal, e eles escondem muitos problemas". Claro que existem problemas aqui, mas não como no Brasil.
Então vou escrever um pouco mais sobre a Suécia e tentar mostrar pelo menos outro tipo de visão da coisa.
E escolhi escreve sobre... propaganda! Sim, propaganda.
Aqui usa-se muito a imagem das pessoas como elas são, sem muita maquiagem, nem sensualização. Então não é um povo que vê sexo em tudo que vê, nem leva tudo pra um lado erótico. Pelo contrário.
Uns exemplos tirados de revistas. Nesse primeiro temos uma mulher... trocando o pneu. E duvido que seja apenas pra aparecer na capa da revista. Aqui a igualdade é levada ao extremo é comum ver mulheres trabalhando em empregos que tipicamente temos somente homens no Brasil, como construção civil.
Essa é a revista do sindicato. Eu sou sindicalizado aqui e faço parte do Unionen, um dos maiore sindicatos da Suécia. Na capa da revista é possível ver outra mulher e... nada de pose sensual. Pouco maquiagem. Apenas ela. E não é só nas revistas. Normalmente as mulheres aqui não usam muita maquiagem. Nem vestem-se de maneira muito chique.
Essa parte interna da revista tem uma propaganda com essa mulher acidentada. Novamente nada de maquiagem, ou ao menos que possa mostrar sensualidade. Se não me engano era um anúncio de seguro.
Um artigo sobre uma fazenda de animais ou algo assim. Notem novamente a pessoa na foto. Sem muita maquiagem e com roupas simples.
Agora uma reportagem interna sobre um treinador de futebol pra crianças. Novamente uma pessoa como ela é: sem roupas marcantes, sem ser aquele sueco super sensual e fortão. Apenas uma pessoa com cara de gente boa e fazendo algo que inspira outros a fazerem igual.
E essa bela foto dessa vovó simpática então? Novamente pouca produção e a pessoa mais próxima do que realmente é em seu cotidiano.
Aqui mais um homem, desses que a gente se identifica. Não aqueles deuses nórdicos que a mulheres acham que vão ver o tempo todo na Suécia. Esse sim um típico sueco.
E mais uma foto de propaganda na revista. Agora de um casal em posição de... nada. Apenas olhando o telefone juntos. Ambos usando roupas comuns e ela com um pouco de batom. E só. Bem simples, e bem cotidiano.
A última foto de mulher nesse artigo. Sem muita maquiagem e cabelos no mais natural possível.
E finalmente a capa da outra revista que mostrei a parte de dentro, o guia de saúde que recebemos a cada 3 meses. Novamente um homem sueco com cara de sueco mesmo. Simples, de óculos, com apenas uma blusa e nada estravagante.
Acho que até aqui já foi possível entender como as coisas funcionam aqui. O que existe no Brasil como normal em termos de propaganda com certeza seria tido como vulgar aqui. Eu mesmo depois desse tempo todo já vejo como vulgar, imagina então eles? E esse zen de vestir-se e viver de forma simples está em todo lugar. Algumas pessoas vão pra empresa de terno, mas maioria não vai. Aliás não existe um "dress code". Veste-se o que quer e como quer. Eu até acredito que é possível ir com a cueca por fora da calça que ninguém vai falar nada.
Então a Suécia figura como um dos países mais felizes do mundo. Um dos motivos é esse: simplicidade. Em tudo.
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!
Recebi essa semana um convite inusitado pra participar da Latinoware 2021. Conheço a Latinoware desde as épocas do FISL mas nunca participei ativamente.
Por quê não? Nos meus tempos de software livre no Brasil eu sempre tentei priorizar ao menos 1 viagem anual pra participar de eventos, que era dedicada ao FISL. Não que eu não pudesse participar de mais, mas eu não tinha cacife pra bancar mais do que uma viagem por ano em termos de custo financeiro e distância da família, e ainda conseguia negociar na empresa como sendo tempo de treinamento. Então era difícil participar de outro evento e acabava escolhendo o FISL. Outra coisa que me motivava mais em ir ao FISL era que o evento era gravado. Então apesar das muitas trilhas de palestras simultaneamente boas (e que às vezes acabava com sala cheia e não tinha como entrar), eu sempre podia recorrer posteriormente aos videos pra assistir o que tinha sido perdido. E isso não tinha em outros eventos.
Mas o tempo passou e as coisas mudaram. O FISL foi definhando ao ponto de praticamente estar morto hoje em dia. O grupo do evento ainda existe no telegram, mas já faz anos que o evento não acontece. Já o Latinoware só cresceu. Ocupou completamente o espaço de evento de software livre que era ocupado pelo FISL. E por causa da pandemia, assim como a maioria dos eventos, passou a ser online.
Então ano passado eu pude participar do Latinoware 2020 já remotamente. Como participante, claro.
E esse ano recebi o célebre convite pra participar mais ativamente. Não apenas uma honra como também não sei nem se tenho roupa pra participar. Ainda bem que será online e poderei estar de pijamas :D
O evento acontecerá em outubro nos dias 13, 14 e 15.
E terá a presença ilustre de outras caras do software livre no Brasil:
Essa é só uma prévia de nomes ainda não confirmada, mas já parece ser um ótimo evento com um time de muita gente boa pra palestrar. Como mencionou o próprio Marco Siriaco, um time de gente realmente phoda.
No meio de tanta gente boa assim, quem sou eu nessa fila do pão?
Eu ainda não pensei bem no que vou falar então aceito sugestões. Gostariam que eu fizesse uma palestra sobre algum assunto específico? Pode enviar sugestões ou pro meu e-mail: helio[AT]loureiro.eng.br, ou no twitter @helioloureiro ou no telegram @helioloureiro.
Tirei férias longas demais. Na verdade foram apenas 3 semanas, mas eu meio que deixei de postar aqui durante o período de férias. E a procrastinação voltou forte. Então aqui vamos nós com uma tentativa de voltar a escrever semanalmente.
Hoje, fazendo um troubleshooting the um serviço que não funcionava como devia (um scan de aplicação com o OWASP ZAP), descobri que meus containers em docker não estavam acessando a rede. O que mudou? Minha máquina de trabalho é um Ubuntu 18.04. O repositório bionic-update trouxe uma versão nova do docker que reiniciou o daemon, mas... a parte de rede não funcionando. E só percebi isso hoje.
root@dell-latitude-7480 /u/local# apt show docker.io Package: docker.io Version: 20.10.7-0ubuntu1~18.04.1 Built-Using: glibc (= 2.27-3ubuntu1.2), golang-1.13 (= 1.13.8-1ubuntu1~18.04.3) Priority: optional Section: universe/admin Origin: Ubuntu Maintainer: Ubuntu Developers <This email address is being protected from spambots. You need JavaScript enabled to view it. > Original-Maintainer: Paul Tagliamonte <This email address is being protected from spambots. You need JavaScript enabled to view it. > Bugs: https://bugs.launchpad.net/ubuntu/+filebug Installed-Size: 193 MB Depends: adduser, containerd (>= 1.2.6-0ubuntu1~), iptables, debconf (>= 0.5) | debconf-2.0, libc6 (>= 2.8), libdevmapper1.02.1 (>= 2:1.02.97), libsystemd0 (>= 209~) Recommends: ca-certificates, git, pigz, ubuntu-fan, xz-utils, apparmor Suggests: aufs-tools, btrfs-progs, cgroupfs-mount | cgroup-lite, debootstrap, docker-doc, rinse, zfs-fuse | zfsutils Breaks: docker (<< 1.5~) Replaces: docker (<< 1.5~) Homepage: https://www.docker.com/community-edition Download-Size: 36.9 MB APT-Manual-Installed: yes APT-Sources: mirror://mirrors.ubuntu.com/mirrors.txt bionic-updates/universe amd64 Packages Description: Linux container runtime Docker complements kernel namespacing with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers. . Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc. . This package contains the daemon and client. Using docker.io on non-amd64 hosts is not supported at this time. Please be careful when using it on anything besides amd64. . Also, note that kernel version 3.8 or above is required for proper operation of the daemon process, and that any lower versions may have subtle and/or glaring issues. N: There is 1 additional record. Please use the '-a' switch to see it
Primeira coisa que tentei foi reiniciar o docker mesmo.
root@dell-latitude-7480 /u/local# systemctl restart --no-block docker; journalctl -u docker -f [...] Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.203367946+02:00" level=info msg="Firewalld: docker zone already exists, returning" Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.549158535+02:00" level=warning msg="could not create bridge network for id 88bd200
b5bb27d3fd10d9e8bf86b1947b2190cf7be36cd7243eec55ac8089dc6 bridge name docker0 while booting up from persistent state: Failed to program NAT chain:
ZONE_CONFLICT: 'docker0' already bound to a zone" Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.596805407+02:00" level=info msg="stopping event stream following graceful shutdown"
error="" module=libcontainerd namespace=moby Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.596994440+02:00" level=info msg="stopping event stream following graceful shutdown"
error="context canceled" module=libcontainerd namespace=plugins.moby Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: failed to start daemon: Error initializing network controller: Error creating default "bridge" network:
Failed to program NAT chain: ZONE_CONFLICT: 'docker0' already bound to a zone Aug 12 10:29:25 dell-latitude-7480 systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE Aug 12 10:29:25 dell-latitude-7480 systemd[1]: docker.service: Failed with result 'exit-code'. Aug 12 10:29:25 dell-latitude-7480 systemd[1]: Failed to start Docker Application Container Engine. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Service hold-off time over, scheduling restart. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Scheduled restart job, restart counter is at 3. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: Stopped Docker Application Container Engine. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Start request repeated too quickly. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Failed with result 'exit-code'. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: Failed to start Docker Application Container Engine.
As linhas estão editadas pra facilitar a visualização uma vez que o systemd usa linhas bem maiores que 120 colunas. Mas o resultado foi... falha.
Parando o firewalld e somente reiniciando docker levava a uma condição em que o daemon iniciava, mas ao iniciar o container, novamente ficava sem acesso à rede.
root@dell-latitude-7480 /u/local# docker run -it --rm --init ubuntu:20.04 bash root@f45dcbb1ecaa:/# ping 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. ^C --- 1.1.1.1 ping statistics --- 6 packets transmitted, 0 received, 100% packet loss, time 5153ms root@f45dcbb1ecaa:/# exit
Olhando somente as regras do firewall eu pude ver que realmente o docker estava carregando a regra correta sem o firewalld:
root@dell-latitude-7480 /u/local# systemctl stop firewalld.service root@dell-latitude-7480 /u/local# iptables -L -n -t nat Chain PREROUTING (policy ACCEPT) target prot opt source destination Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain POSTROUTING (policy ACCEPT) target prot opt source destination root@dell-latitude-7480 /u/local# systemctl restart docker root@dell-latitude-7480 /u/local# systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2021-08-12 12:01:12 CEST; 4s ago Docs: https://docs.docker.com Main PID: 484649 (dockerd) Tasks: 27 CGroup: /system.slice/docker.service └─484649 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061383466+02:00" level=warning msg="Your kernel does not support swap memory limit" Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061414030+02:00" level=warning msg="Your kernel does not support CPU realtime scheduler" Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061421558+02:00" level=warning msg="Your kernel does not support cgroup blkio weight" Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061427194+02:00" level=warning msg="Your kernel does not support cgroup blkio weight_device" Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061796106+02:00" level=info msg="Loading containers: start." Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.531851162+02:00" level=info msg="Loading containers: done." Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.549979768+02:00" level=info msg="Docker daemon" commit="20.10.7-0ubuntu1~18.04.1" graphdriver(s)=overlay2 version=20.10.7 Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.550057275+02:00" level=info msg="Daemon has completed initialization" Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.558188106+02:00" level=info msg="API listen on /var/run/docker.sock" Aug 12 12:01:12 dell-latitude-7480 systemd[1]: Started Docker Application Container Engine. root@dell-latitude-7480 /u/local# iptables -L -n -t nat Chain PREROUTING (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- 172.16.0.0/24 0.0.0.0/0 Chain DOCKER (2 references) target prot opt source destination RETURN all -- 0.0.0.0/0 0.0.0.0/0
Claramente existia uma regra de MASQUERADE vinda da rede do docker (172.16.0.0/24). E o firewalld estava sumindo com essa regra ao ser ativado (pra ficar menos poluído com várias regras peguei só a saída da cadeia do POSTROUTING.
root@dell-latitude-7480 /u/local# systemctl start firewalld.service root@dell-latitude-7480 /u/local# iptables -L POSTROUTING -n -t nat Chain POSTROUTING (policy ACCEPT) target prot opt source destination POSTROUTING_direct all -- 0.0.0.0/0 0.0.0.0/0 POSTROUTING_ZONES_SOURCE all -- 0.0.0.0/0 0.0.0.0/0 POSTROUTING_ZONES all -- 0.0.0.0/0 0.0.0.0/0
A minha primeira ideia: inserir à força uma regra de MASQUERADE direto na cadeia de POSTROUTING.
root@dell-latitude-7480 /u/local# iptables -I POSTROUTING 1 -s 172.16.0.0/24 -j MASQUERADE -t nat root@dell-latitude-7480 /u/local# iptables -L POSTROUTING --line-numbers -t nat Chain POSTROUTING (policy ACCEPT) num target prot opt source destination 1 MASQUERADE all -- 172.16.0.0/24 anywhere 2 POSTROUTING_direct all -- anywhere anywhere 3 POSTROUTING_ZONES_SOURCE all -- anywhere anywhere 4 POSTROUTING_ZONES all -- anywhere anywhere
E, claro, não deu certo.
Depois de procurar na Internet sobre docker e firewalld, encontrei o próprio site do Docker explicando como fazer isso em https://docs.docker.com/network/iptables/ com o seguinte comando:
# Please substitute the appropriate zone and docker interface $ firewall-cmd --zone=trusted --remove-interface=docker0 --permanent $ firewall-cmd --reload
Beleza. Agora não teria como dar errado. E...
root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0 public root@dell-latitude-7480 /u/local# firewall-cmd --zone=public --remove-interface=docker0 --permanent The interface is under control of NetworkManager and already bound to the default zone The interface is under control of NetworkManager, setting zone to default. success root@dell-latitude-7480 /u/local# systemctl start docker Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details.
Caramba... algo de errado não estava certo. Bom... se tivesse funcionado de primeira, eu provavelmente não teria escrito esse artigo.
Então vamos rever em qual zona está a interface docker0, remover essa interface dessa zona e adicionar na zona do docker.
root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0 public root@dell-latitude-7480 /u/local# firewall-cmd --zone=public --remove-interface=docker0 --permanent The interface is under control of NetworkManager and already bound to the default zone The interface is under control of NetworkManager, setting zone to default. success root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0 public root@dell-latitude-7480 /u/local# firewall-cmd --reload success root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0 public
Mas que catzo... esse foi problema que encontrei. Por mais que eu removesse ou tentasse remover a interface docker0 da zone public, sempre voltava.
Foram algumas horas nesse vai e vem, procurando na Internet o que fazer, lendo documentação do firewalld, até que finalmente acertei.
root@dell-latitude-7480 /u/local# firewall-cmd --zone=docker --add-interface=docker0 --permanent The interface is under control of NetworkManager, setting zone to 'docker'. success root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0 docker root@dell-latitude-7480 /u/local# firewall-cmd --reload success root@dell-latitude-7480 /u/local# systemctl start docker
Então não precisava do comando pra remover. Apenas adicionar diretamente na zona desejada.
root@dell-latitude-7480 /u/local# docker run -it --rm --init ubuntu:20.04 bash root@e5d78d7f081b:/# ping -c 5 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=1.95 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=58 time=2.02 ms 64 bytes from 1.1.1.1: icmp_seq=3 ttl=58 time=1.68 ms 64 bytes from 1.1.1.1: icmp_seq=4 ttl=58 time=1.62 ms 64 bytes from 1.1.1.1: icmp_seq=5 ttl=58 time=1.76 ms --- 1.1.1.1 ping statistics --- 5 packets transmitted, 5 received, 0% packet loss, time 4003ms rtt min/avg/max/mdev = 1.621/1.808/2.026/0.162 ms root@e5d78d7f081b:/# exit
Eu estou no momento trabalhando num troubleshooting de uma rede 5G. Qual a novidade? Seria o 5G? Bom... não. A diferença é que pra acessar o ambiente cloud eu preciso fazer login numa máquina e depois fazer login em outra máquina.
Nada muito glamouroso, mas não é algo que eu possa escolher não fazer. Então a forma pra ajudar a ter isso feito da forma mais rápida possível foi através dos uso de expect.
Primeiramente uma visão da rede:
Um diagrama feito no libreoffice draw. Também nada glamouroso, mas deve dar conta do recado. Eu rodo o script, que pede somente minha senha de rede uma vez que é o mesmo usuário no server-gw1. E conecta via ssh. No server-gw2 é o mesmo usuário e senha, o que facilita as coisa. Dali pra rede k8s não tem mais nada porque eu acesso com um kube.conf e comand kubectl.
O script é esse aqui:
#! /usr/bin/expect
stty -echo
send_user -- "Entre a senha: "
expect_user -re "(.*)\n"
send_user "\n"
stty echo
set PASS $expect_out(1,string)
spawn ssh server-gw1
while {1} {
expect {
"ssword:" { send "$PASS\n" }
"(yes/no)?" { send "yes\n" }
"\$ " {break}
}
}
send "ssh server-gw2; exit\n"
while {1} {
expect {
"ssword:" { send "$PASS\n" }
"(yes/no)?" { send "yes\n" }
"\$ " {break}
}
}
interact
Note que é possível trocar pra receber o username como input ou como argumento do script. Na chamada pro segundo servidor existe um 'send "ssh server-gw2; exit\n"'. O motivo disso é pra quando eu digitar "exit" do servidor server-gw2 não precisar digitar novamente no server-gw1. Então ele faz o ssh pro server-gw2 e o próximo comando esperando já é um exit.
Espero que ajude e happy coding!
Esses dias, um pouco antes da BSD Day pra dize a verdade, eu aceitei o convite do prof. Juliano pra falar sobre shell scripts.
Eu não preparei muito antes, e fiz um tipo de live coding sobre shell scripts mostrando um pouco de cada coisa. Não espero que seja tido com um tipo de aprendizado justamente porque eu realmente não preparei nada como aula ou palestra, mas espero que sirva de inspiração pra quem quiser iniciar.
Boa diversão!
Chamada pro vídeo.
Vídeo completo.
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!
E minhas aventuras com LVM continuam. Um dos HDDs começou a chiar. Mas chiar mesmo, fazendo barulho de marteladas e resets. Olhando nos logs eu vi que já tinha dado o que tinha que dar. E justamente o HDD que fazia parte do mirroring em que estão os jogos da steam.
Então procedi com o comando pra terminar a montagem em mirroring.
lvconvert -m 0 diskspace/steam /dev/sdb3
Infelizmente eu dei um reboot em seguida e não salvei nada pra poder postar aqui. Mas o mais importante foi depois de removido HDD e instalado um novo (também de 2 TB) que tinha parado aqui. Como eu esqueci de remover o /dev/sdb3 do LVM, claro que subiu com vários erros. Particionei o novo HDD (/dev/sdb) com uma só partição LVM. E fui adicionar quando...
root@goosfraba ~# pvcreate /dev/sdb1
WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
Physical volume "/dev/sdb1" successfully created.
Epa! Que catzo de CvlXC4-não-sei-lá-o-que é esse? Sim, o disco que removi fisicamente e não tirei logicamente do LVM. Com "vgdisplay" pude ver que realmente o problema estava lá.
root@goosfraba ~# vgdisplay
WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
--- Volume group ---
VG Name diskspace
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 230
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 9
Open LV 2
Max PV 0
Cur PV 2
Act PV 1
VG Size 5.45 TiB
PE Size 4.00 MiB
Total PE 1429493
Alloc PE / Size 518912 /
Resolver não foi nada complicado. Bastou o seguinte comando:
root@goosfraba ~# vgreduce --removemissing diskspace
WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
Wrote out consistent volume group diskspace.
Pronto! Metadados do disco antigo removidos.
root@goosfraba ~# vgdisplay
--- Volume group ---
VG Name diskspace
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 231
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 9
Open LV 2
Max PV 0
Cur PV 1
Act PV 1
VG Size
Agora o trabalho seguinte foi adicionar o espaço novo dentro do VG (diskspace é o nome):
root@goosfraba ~# vgextend diskspace /dev/sdb1
Volume group "diskspace" successfully extended
root@goosfraba ~# vgdisplay
--- Volume group ---
VG Name diskspace
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 232
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 9
Open LV 2
Max PV 0
Cur PV 2
Act PV 2
VG Size
Em seguida colocar o disco novo como mirroring novamente:
root@goosfraba ~# lvconvert -m 1 /dev/diskspace/steam /dev/sdb1
Are you sure you want to convert linear LV diskspace/steam to raid1 with 2 images enhancing resilience? [y/n]: y
Logical volume diskspace/steam successfully converted.
E pronto. Acabou. Isso mesmo. Foi fácil assim. Agora é só ir monitorando o progresso da cópia no disco novo.
root@goosfraba ~# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
home diskspace -wi-a----- 500.00g
opt diskspace -wi-a----- 2.00g
root diskspace -wi-ao---- 10.00g
steam diskspace rwi-a-r--- 750.00g 0.88
swap diskspace -wi-a----- 15.00g
tmp diskspace -wi-a----- 5.00g
usr diskspace -wi-ao---- 95.00g
usrlocal diskspace -wi-a----- 600.00g
var diskspace -wi-a----- 50.00g
root@goosfraba ~# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
home diskspace -wi-a----- 500.00g
opt diskspace -wi-a----- 2.00g
root diskspace -wi-ao---- 10.00g
steam diskspace rwi-a-r--- 750.00g 1.10
swap diskspace -wi-a----- 15.00g
tmp diskspace -wi-a----- 5.00g
usr diskspace -wi-ao---- 95.00g
usrlocal diskspace -wi-a----- 600.00g
var diskspace -wi-a----- 50.00g
Como são 750 GB a coisa demora um pouco. Mas com LVM, tudo é muito fácil.
Happy hacking :)
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.
Page 8 of 33