Lista de Conteúdos
Linux não é um SO opaco. Ele não ser opaco significa que é fácil ver o que acontece por trás dos processos. O que permite identificar um problema ou pelo menos saber se você realmente tem um problema.
Iniciei meu aprendizado em MlOps (Machine Learning Operations). Embora tenha pouca experiência foi fácil aceitar que MlOps envolve um workflow extremamente intricado com muitos possíveis pontos de falhas. Tais pontos podem não estar relacionados com os operadores. Portanto, saber identificar se existe uma falha e o que está causando ela é de suma importância. Isso vai desde compreender o comportamento de um processo criado pelos próprios operadores ou o que o pip/conda
e demais dependências externas estão aprontando debaixo dos panos.
O primeiro passo para entender um problema com um processo é analisar o output (a saída na sessão do seu terminal). Contudo, algumas vezes isso não te fornece a informação suficiente. Neste texto vou discorrer do básico sobre como debugar processos usando o strace
e lsof
. Iremos criar alguns exemplos patológicos usando python para simular problemas que podemos encontrar e como eles são dissecados pelo strace
e o lsof
.
Conceitos
“Everything is a file.” mantra UNIX
Quando você pensa em arquivo você talvez relacione com um CSV, uma planilha ou imagem. Mas na abordagem UNIX de fazer SO o conceito de arquivo aparece em todos os lugares. Por exemplo, até conexões de rede são associadas a um arquivo. Em casos que um elemento em si não é um arquivo tal elemento tem a ele associado um descritor de arquivo (file descriptor). Como isso se relaciona com debugar processos? **Se tudo é um arquivo analisar um processo pode ser feito com o mesmo conjunto de ferramentas e conceitos que usamos para listar, compreender e comunicar com arquivo inclusive com a mesma API. ** Aqui abordaremos uma ferramenta para listagem de arquivos, o lsof
.
LSOF
A ferramenta lsof é um comando que pode ser usado para listar os file descriptors abertos e os processos que foram responsáveis por tal ação. Desta maneira você pode listar os file descriptors de um usuário que estão associados a uma porta via conexão ou processo. O nome desse comando é um acrônimo para list open files.
O exemplo mais simples de uso jogando os resultados para um arquivo é esse
meuusuario:/$ lsof > lsof_tudo.txt
O comando acima irá criar uma tabela (imensa) dentro de lsof_tudo.txt
Essa tabela será mais ou menos assim
COMMAND PID TID TASKCMD USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root cwd unknown /proc/1/cwd (readlink: Permission denied)
systemd 1 root rtd unknown /proc/1/root (readlink: Permission denied)
systemd 1 root txt unknown /proc/1/exe (readlink: Permission denied)
Se você olhar com cuidado verá que aparecem linhas de diferentes usuários. As primeiras são do root
e uma das colunas mostra que você não tem permissão para ler os file descriptors desse usuário, ainda bem! Para pedir apenas a listagem do seu usuário faça
meuusuario:/$ lsof -u meuusuario > lsof_meu.txt
O arquivo ainda é enorme, mas abra ele com seu editor de texto. Tente procurar nomes de arquivos e processos que você esta usando agora.
Temos muitas colunas no output, você pode ver o significado detalhado de cada uma digitando man lsof
. Mas eu acho mais interessante você focar nas seguintes colunas:
- COMAND
- O nome do comando associado ao processo que abriu o arquivo
- PID
- Um número que identifica unicamente o processo. Você pode usar esse número para matar o processo usando
pkill
, usar ele nostrace
etc.
- Um número que identifica unicamente o processo. Você pode usar esse número para matar o processo usando
- TID
- Se o arquivo foi aberto por uma thread de um processo. Quando não tem nada nessa coluna significa que a ação foi feita por um processo.
- USER
- O usuário responsável pelo processo que efetuou a ação.
- TYPE
- Essa coluna é bem útil. Tal coluna te diz o tipo de nó associado ao arquivo. Por exemplo, se o arquivo for associado com protocolos você vera aqui coisas do tipo: IPV4, IPV6. Se for um arquivo normal haverá na coluna o identificador **REG. **Existem algumas dezenas de possibilidades de valores para essa coluna, eu nunca lembro o que elas significam, mas é fácil consultar online ou no
man
.
- Essa coluna é bem útil. Tal coluna te diz o tipo de nó associado ao arquivo. Por exemplo, se o arquivo for associado com protocolos você vera aqui coisas do tipo: IPV4, IPV6. Se for um arquivo normal haverá na coluna o identificador **REG. **Existem algumas dezenas de possibilidades de valores para essa coluna, eu nunca lembro o que elas significam, mas é fácil consultar online ou no
- NODE
- O identificador do nó do arquivo. No caso desse arquivo envolver protocolos de internet haverá coisas como TCP, UDP
- NAME
- Também bastante útil. Ele muda bastante dependendo do que o arquivo se refere. Pode ser o endereço do servidor ( www.google.com, localhost:5000) assim como o endereço do arquivo.
O lsof
tem muitos argumentos possíveis, veremos alguns utilizando alguns casos que eu acho interessante e que acontecem.
System Calls e strace
O system call é o mecanismo de comunicação entre processos e o kernel do seu SO.
Tal mecanismo permite que um processo requisite recursos do kernel disponibilizados pelo seu hardware. Para ler um arquivo armazenado em seu hardwre é necessário que ocorra antes um system call. Portanto, tendo uma maneira de interceptar essas chamadas entre um processo e o kernel temos como compreender o que tal processo está fazendo. Um comando que permite essa interceptação é o strace
.
$ man strace
Se o strace não estiver disponível instale
$ apt install strace
O strace
pode ser executado de duas formas. A primeira é
usando o comando a ser interceptado como argumento do strace
$ strace ARGS COMANDO_A_SER_INTERCEPTADO
a segunda, bastante útil, é interceptando um processo já iniciado usando o PID de tal processo,
$ strace ARGS -p PID_DO_PROCESSO
Para descobrir o PID de um processo use o htop
ou o seguinte comando ps aux | grep -i '[n]ome_do_processo'
.
Veja um exemplo simples do strace e seu output
$ strace -t ls
O resultado será algo do tipo
18:02:23 execve("/usr/bin/ls", ["ls"], 0x7fffa727a418 /* 54 vars */) = 0
18:02:23 brk(NULL) = 0x55ebef60c000
18:02:23 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
18:02:23 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
...
Cada linha representa uma system call e o seu respectivo resultado. O argumento -t
diz para imprimir na primeira coluna o instante de tempo que o system call foi chamado.
De forma resumida o formato das linhas segue o padrão:
Nome da SYS CALL(Argumentos usados na SYS CALL) = O resultado
O output é difícil se não humanamente impossível de compreender tudo sem um guia externo. Um guia possível é o comando man
. O comando abaixo mostra a documentação do sys call openat
$ man 2 openat
O openat
é o sys call que requisita a abertura de um arquivo, o resultado na última linha ( O_RDONLY|O_CLOEXEC) = 3
) significa que a chamada do sistema foi bem sucedida. Caso fosse -1 alguma coisa teria dado errado quando o processo requisitou o recurso.
Investigando problemas
Veremos aqui problemas e falhas relacionados a arquivos regulares e conexões de rede. Contudo podemos usar as mesmas tecnicas para outros tipos de problemas.
Identificando problemas de conexão
O conda está travado? O pip tá baixando os pacotes do servidor ou existe algum servidor engasgando? Para onde minhas requisições estão indo? Antes de tentar iniciar um modo verboso e ter que matar seu processo você pode usar o lsof para responder essas perguntas. Para começar nosso tutorial e realizar as simulações instale o flask e requests
$ python -m pip install requests flask
Crie o arquivo server_mlops.py
# server_mlops.py
import time
import flask
app = flask.Flask(__name__)
@app.route('/')
def hello_world():
sleep_time = flask.request.args.get('sleep', default=10, type=int)
print('sleep_time:', sleep_time)
time.sleep(sleep_time)
return 'Hello World!'
if __name__ == '__main__':
app.run()
Inicie duas sessões no terminal. Na primeira inicie o servidor
$ python server_mlops.py
Na segunda execute
$ ps aux | grep -i '[s]erver_mlops.py'
você vera um output do tipo
devmess+ 19321 18.0 0.3 29716 24792 pts/5 S+ 14:27 0:00 python server_mlops.py
O número na frente do seu username (19321) é o PID
do processo.
O meu serviço está on?
O argumento -a
pede que o lsof
use todos os argumentos de filtragem com o operador AND
isto é, todas as condições devem ser válidas. O argumento -i
pede para que ele filtre apenas arquivos associados a conexões e o argumento -p 19321
pede que use apena o processo com o PID 19321
.
$ lsof -a -i -p 19321
Você vera um output mais ou menos assim
COMMAND | PID | USER | FD | TYPE | DEVICE | SIZE/OFF | NODE | NAME |
---|---|---|---|---|---|---|---|---|
python | 19321 | devmessias | 4u | IPv4 | 16108218 | 0t0 | TCP | localhost:5000 (LISTEN) |
Está tudo ok com o seu serviço. Tente remover um dos argumentos (remova o -a
por exemplo) ou usar eles isolados, veja como o output muda.
O pip ou um cliente qualquer está engasgado esperando uma resposta de alguém?
Esse tipo de problema pode acontecer quando estamos gerenciando uma dependência, requisitando algum tipo de dado de um servidor e em inúmeros outros casos em que não temos acesso a máquina que executa o serviço. Portanto, precisamos analisar do nosso lado se o processo está travado por alguma falha nossa.
Crie o arquivo client_mlops.py
#!/usr/bin/env python
#client_mlops.py
import requests
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'--sleep', type=int, help='time to sleep', default=0)
args = parser.parse_args()
print('Ask for localhost:5000 to sleep for {} seconds'.format(args.sleep))
r = requests.get('http://localhost:5000', params={'sleep': int(args.sleep)})
print(r.text)
No código acima temos o argumento sleep
que pedira para o server_mlops.py
esperar alguns segundos antes de enviar a resposta.
Simularemos um problema de um servidor preguiçoso. Pedindo que ele durma por 20 segundos. Se você matou o processo do servidor inicie ele novamente.
Execute o client_mlops.py
com o strace
$ strace -e poll,select,connect,recvfrom,sendto python client_mlops.py --sleep=20
aqui estamos pedindo para que o strace
nos mostre apenas chamadas do tipo poll,select,connect,recvfrom
e sendto
.
O output será algo do tipo
connect(4, {sa_family=AF_INET, sin_port=htons(5000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
connect(4, {sa_family=AF_INET6, sin6_port=htons(5000), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = 0
connect(4, {sa_family=AF_INET6, sin6_port=htons(5000), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, 28) = -1 ECONNREFUSED (Connection refused)
connect(4, {sa_family=AF_INET, sin_port=htons(5000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
sendto(4, "GET /?sleep=10 HTTP/1.1\r\nHost: l"..., 154, 0, NULL, 0) = 154
recvfrom(4,
Note que temos uma SYS_CALL engasgada, recvfrom
(se você quiser obter mais informações sobre uma SYS_CALL digite man 2 recvfrom
) . Quem tá engasagando é o servidor e não o cliente.
Você pode também usar o lsof
para checar se você está com esse tipo de problema. Para isso, execute o cliente em uma sessão separada
$ python client_mlops.py --sleep=100
pegue o PID com ps aux | grep -i '[c]lient_mlops.py'
e execute o lsof
lsof -a -i -p 19321
O resultado será algo do tipo
COMMAND | PID | USER | FD | TYPE | DEVICE | SIZE/OFF | NODE | NAME |
---|---|---|---|---|---|---|---|---|
python | 31551 | devmessias | 4u | IPv4 | 16622065 | 0t0 | TCP | localhost:57314->localhost:5000 (ESTABLISHED) |
Note que uma conexão foi estabelecida (coluna NAME). Se o serviço estivesse enviado a resposta não teríamos obtido nada na saída do lsof
.
Problemas com arquivos
Vamos simular alguns problemas com arquivos regulares: csv, txt, bin, jpg etc. Copie um csv para pasta /tmp/, ou execute o comando abaixo para criar um txt dummy contendo o manual do comando strace.
$ man strace > /tmp/arquivo.csv
Quais processos estão usando esse arquivo?
O objetivo aqui é saber quais processos estão acessando um arquivo. Isto é útil quando queremos identificar processos que já deveriam ter “fechado” o arquivo ou inentificar acessos indenvidos. Também pode ser útil para descobrir qual processo está criando um arquivo gigantesco no seu sistema para que você possa dar um kill.
Crie o script a seguir em uma pasta.
#!/usr/bin/env python
# file_open.py
import time
f = open('/tmp/arquivo.csv', 'r')
input('Press Enter to continue...')
Depois abra duas sessões no terminal e rode o comando python file_open.py
. Agora basta listar os processos que estão com arquivo.csv abertos
$ lsof /tmp/arquivo.csv
O output será algo do tipo
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python 15411 devmessias 3r REG 8,2 0 2911031 /tmp/arquivo.csv
python 20777 devmessias 3r REG 8,2 0 2911031 /tmp/arquivo.csv
Temos dois processos distintos (dois PID) utilizando nosso arquivo.
Deletei o csv e agora?
Suponha uma situação em que acidentalmente um arquivo foi apagado. Contudo, existe um processo que ainda está fazendo uso de tal recurso.
Crie um arquivo qualquer, aqui vou chamar de acidente.txt
Abra uma sessão no terminal e execute o comando a seguir. Não feche a sessão!
$ python -c 'f=open("acidente.txt", "r");input("...")'
Simularemos o acidente que outro processo remove o arquivo. Execute os comandos abaixo
$ rm acidente.txt
$ ls acidente.txt
Nosso arquivo foi embora :(
ls: cannot access 'acidente.txt': No such file or directory
Mas não se preocupe! Uma coisa legal do linux: todos os processos do sistema tem a eles associados um diretório dentro da pasta /proc
(everthing is a file). E o que tem nesses diretórios ? Muitas coisas, incluindo o file descriptor
do acidente.txt
. Utilizado pelo nosso processo python. Para encontrar esse file descriptor
usaremos o lsof
$ lsof -u nomedeusuario | grep 'acidente.txt'
No meu caso obtive o seguinte output
python 22465 devmessias 3r REG 8,2 37599 14288174 caminho/acidente.txt (deleted)
Então o PID é 22465
e o número que descreve o arquivo (file descriptor) é 3
(o que vem antes do r
no output acima). Para obter uma cópia do acidente.txt deletado basta chamar um simples cp
$ cp /proc/22465/fd/3 recuperado.txt
Abra o arquivo recuperado.txt
e veja que tudo está no seu devido lugar. Não é mágica, procure por process pseudo-filesystem na web ou digite man proc
.
Erros silenciosos: arquivo não existente ou permissão
Em alguns casos você pode ter um processo criado por uma dependência externa que tenta acessar um arquivo com permissão errada ou mesmo não existente. Criaremos essas duas situações com o script file_404.py
.
#!/usr/bin/env python
# file_404.py
import time
try:
f = open('/tmp/arquivo_404.csv', 'r')
except FileNotFoundError:
pass
try:
# um arquivo que vc nao tem permissao, crie como sudo e mude com chmod 700
f = open('/tmp/arquivo_permission.csv', 'r')
except PermissionError:
pass
input('Press Enter to continue...')
Execute ele com python file_404.py
veja que nenhum problema é informado.
Para traquear as chamadas do sistema do tipo arquivo feitas por python file_404.py
basta digitar o comando abaixo no terminal
$ strace -f -e trace=file python file_404.py
o argumento -f
diz para o strace monitorar também qualquer processo filho criado. Em python, isso seria por exemplo os processos criados por os.fork
.
A saída do exemplo será algo do tipo
lstat("SEU DIRETORIO/file_404.py", {st_mode=S_IFREG|0644, st_size=242, ...}) = 0
openat(AT_FDCWD, "file_404.py", O_RDONLY) = 3
openat(AT_FDCWD, "/tmp/arquivo_404.csv", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/tmp/arquivo_permission.csv", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
Note que temos no output informações que não queremos investigar, mas nas últimas linhas os erros de permissão e ausência de arquivo apareceram.
Uma maneira de filtrar o resultado e tornar sua vida mais fácil é usar o awk
redirecionado a saída do strace com o pipe |
.
$ strace -f -e trace=file python file_404.py 2>&1 | awk '/^open/ && /= -1/ {print}'
O comando acima diz para mostrar apenas as linhas que começam com a string open
e em alguma parte da linha tenha o padrão = -1
.
O comando com awk
concatenado produzirá um output mais limpo, veja só
openat(AT_FDCWD, "/home/devmessias/anaconda3/pyvenv.cfg", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/tmp/arquivo_404.csv", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/tmp/arquivo_permission.csv", O_RDONLY|O_CLOEXEC) = -1 EACCES (Permission denied)
Esse processo está salvando algo que não deveria? Onde?
Talvez você queira monitorar o que uma dependência externa anda fazendo no seu sistema de arquivos. Outro problema que pode ocorrer é caso você delete um arquivo usado por uma dependência, contudo tal dependência fez um cache em algum lugar antes de você efetuar a remoção. O que te impede de ressetar a dependência.
Usando o mesmo comando anterior é possível buscar onde esses caches e arquivos estão
$ strace -f -e trace=file comando 2>&1 | awk '/^open/{print}'
se você quiser pegar apenas as chamadas que não retornaram em falha digite
$ strace -f -e trace=file comando 2>&1 | awk '/^open/ && !/= -1/ {print}'
Extras envolvendo arquivos (/proc/
) e strace
Usando problemas comuns envolvendo arquivos e conexões conversamos um pouco sobre o strace
e lsof
. Conceitos como SYS CALL e a pasta /proc/
também foram mencioandos. Darei alguns exemplos de algumas outras questões que podemos responder usando esses outros elementos.
Gerando um sumário de SYS CALL
Você pode sumarizar todas as sys call feitas por um processo usando o argumento -c
. Isso pode te ajudar a economizar tempo numa pre-análise.
O comando abaixo retorna as sys calls efetuadas pelo comando make sync-env
$ strace -c -e trace=!\wait4 make sync-env
outro argumento que foi alterado aqui é o operador !\
que diz para o strace ignorar as sys call do tipo wait4
. O ouput será algo do tipo:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
14,54 0,000209 6 33 13 openat
13,01 0,000187 17 11 vfork
12,32 0,000177 7 25 mmap
8,49 0,000122 3 31 close
8,42 0,000121 5 21 rt_sigprocmask
8,14 0,000117 6 17 read
6,89 0,000099 5 19 11 stat
5,85 0,000084 3 23 fstat
2,85 0,000041 8 5 mprotect
2,64 0,000038 9 4 write
2,51 0,000036 2 16 fcntl
2,02 0,000029 3 9 rt_sigaction
1,95 0,000028 14 2 readlink
1,95 0,000028 14 2 getdents64
1,25 0,000018 4 4 brk
1,25 0,000018 18 1 1 access
1,25 0,000018 3 5 pipe
1,11 0,000016 4 4 ioctl
0,84 0,000012 6 2 getcwd
0,70 0,000010 10 1 munmap
0,49 0,000007 7 1 lstat
0,49 0,000007 7 1 execve
0,49 0,000007 3 2 prlimit64
0,35 0,000005 5 1 chdir
0,21 0,000003 3 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0,001437 241 25 total
A coluna time diz que make sync-env
gastou $14$% do tempo (com exceção do wait4
) em sys calls do tipo openat
e $13$ das $33$ chamadas não foram bem sucedidas.
O processo foi iniciado com as variáveis de ambiente corretas?
Os próximos exemplos envolvem situações em que um processo foi iniciado, mas você quer verificar algumas informações sobre o mesmo sem que seja necessário matar e reiniciar processo. Imagine fazer isso em produção? Ou com um modelo de ML que já gastou muitos R$ para chegar no estágio atual.
Vamos continuar com o nosso server_mlops.py
. Suponha que o processo foi iniciado usando uma variável de ambiente extra, ANSWER
.
$ ANSWER=42 python server_mlops.py
Após o inicio do processo como saber com quais variáveis de ambiente ele está usando? Essa variáveis setam por exemplo bibliotecas de otimização(BLAS, LAPACK), env’s python etc.
Como dito em um exemplo anterior, a pasta /proc
contêm arquivos representado o estado dos processos em execução. Supondo que o PID do processo é 4031
você pode acessar as variáveis de ambiente do mesmo através de cat /proc/4031/environ
. Mas o output é meio feio, vamos usar tr
para trocar os caracteres nulos \0
por quebras de linhas, \n
.
$ tr '\0' '\n' < /proc/4031/environ
Você terá um output do tipo
ANSWER=42
SHELL=/bin/bash
LANGUAGE=en_US
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/bin/java
...more stuff
Se você quiser filtrar apenas linhas que comecem com a string CONDA faça
$ tr '\0' '\n' < /proc/4031/environ 2>&1 | awk '/^CONDA/ {print}'
o output no meu caso foi algo do tipo
CONDA_EXE=/home/devmessias/anaconda3/bin/conda
CONDA_PREFIX=/home/devmessias/anaconda3
CONDA_PROMPT_MODIFIER=(base)
CONDA_SHLVL=1
CONDA_PYTHON_EXE=/home/devmessias/anaconda3/bin/python
CONDA_DEFAULT_ENV=base
Esqueci de redirecionar os outputs do processo para um arquivo. O que fazer?
Suponha que você iniciou um processo e não redirecionou os outputs para um arquivo de texto por esquecimento ou por subestimar problemas. Se reiniciar o processo não é uma opção você está com problemas. Felizmente é possível usar o strace
para interceptar os outputs e salva-los em um arquivo externo.
A SYS CALL responsável por requisitar a escrita no stdin, stdout e stderr é a write
. Veja o manual dessa chamada
$ man 2 write
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
O primeiro argumento é um inteiro que representa o file descriptor. Sendo que fd=1 implica que a chamada escreverá no stdout e fd=2 no stderr . Portanto, não existe nenhum segredo aqui. Se você quiser capturar os outputs basta filtrar as SYS CALL do tipo write e file descriptor 1 ou 2 e envia-las para o arquivo desejado. Temos que tomar cuidado só com as algumas coisas aqui. No manual do strace (man strace
) você vera que por padrão ele printa apenas $32$ caracteres em uma string. Portanto, precisamos aumentar o limite com o argumento -s
. Também é interessante traquear os forks. No caso do server_mlops.py
por exemplo, qualquer print dentro de um método não será executado na main, então o -f
é obrigatório.
O comando para redirecionar as saidas do stdout e stderr no arquivo out.txt
pode ser colocado da seguinte maneira com o log dos tempos (-t) opicional.
$ strace -f -t -etrace=write -s 666 -p PID_DO_PROCESSO 2>&1 | grep --line-buffered -e 'write(2, ' -e 'write(1, ' >> out.txt
O código abaixo tem uma alteração no server_mlops.py
, e execute ele assim como o client_mlops.py
. Pegando o PID do serve_mlops
você conseguirá explorar esse exemplo
# server_mlops.py
import time
import flask
import sys
app = flask.Flask(__name__)
@app.route('/')
def hello_world():
sleep_time = flask.request.args.get('sleep', default=10, type=int)
print('sleep_time:', sleep_time)
for i in range(sleep_time):
print(f'INFO: {i} of sleep_time \n asdf \t ')
print(f'ERROR: Example msg {i}', file=sys.stderr)
time.sleep(1)
return 'Hello World!'
if __name__ == '__main__':
app.run()
Qual comando gerou o processo e onde é o seu working dir?
Essa pergunta talvez não seja tão difícil de responder se você tem o htop
instalado. Mas supondo que você não lembra as informações sobre o comando que gerou o processo execute o comando abaixo
$ tr '\0' '\t' < /proc/PID_CLIENT_MLOPS/cmdline
o output será
python client_mlops.py --sleep 1000
Para descobrir o diretório do client_mlops.py
basta executar
$ readlink /proc/PID_CLIENT_MLOPS/cwd
Agradecimentos & Sugestões
Achou um erro? Tem alguma sugestão ou dica? mande um email para devmessias@gmail.com.
- Obrigado Elisa Ribeiro por ter corrigido os typos da primeira versão do post.
- Reynaldo Allan Fulin pelas discussões sempre úteis sobre linux.