+-------------------------------------------------------------+
|=---------------=[ S O L A R I S  10 ]=---------------------=|
|=-----------------------------------------------------------=|
|=----=[ZFS, GNOME-Terminal, SSH, Rsync, XDMCP e DTrace]=----=|
+-------------------------------------------------------------+
|=---------------------=[ byLeal ]=--------------------------=|
+-------------------------------------------------------------+

                         CONTEÚDO:

             1. INTRODUÇÃO................  1
             2. A MÁQUINA.................  1
             3. A TAREFA..................  2
             4. O PROBLEMA................  2
             5. A INVESTIGAÇÃO............  3
                5.1 Provider SYSCALL......  3
                5.2 ERRINFO...............  5
                5.3 Provider SYSINFO......  6
                5.4 Breve.................  7
                5.5 SYSCALL: read(2)......  12
                5.6 PROCSYSTIME.............18
             6. CONCLUSÃO................. 22  

1. INTRODUÇÃO:
 O presente documento visa relatar  os  procedimentos  efetua-
dos, bem como apresentar os resultados obtidos, na  tarefa  de
identificação da causa do atraso anormal em uma tarefa de sin-
cronismo de arquivos. Utilizando a ferramenta DTrace, disponí-
vel no sistema operacional Solaris 10 (S10).

2. A MÁQUINA:

 Vamos a configuração do servidor S10:

+-----------------------------------------------------------+
| Memoria: 4096 Megabytes                                   |
|                                                           |
| Processador (psrinfo -v):                                 |
| Status of virtual processor 0 as of: 12/07/2006 14:16:23  |
|  on-line since 11/28/2006 10:59:44.                       |
|  The i386 processor operates at 3000 MHz,                 |
|      and has an i387 compatible floating point processor. |
| Status of virtual processor 1 as of: 12/07/2006 14:16:23  |
|  on-line since 11/28/2006 10:59:53.                       |
|  The i386 processor operates at 3000 MHz,                 |
|      and has an i387 compatible floating point processor. |
+-----------------------------------------------------------+

 A tarefa foi executada logo após a instalação e  atualização
do S10, apenas com os serviços que estão disponibilizados por
padrão. A seguir temos uma visão do estado do servidor segun-
dos antes da execução:

 Comando: vmstat 1

 kthr      memory            page            disk
 r b w   swap  free  re  mf pi po fr de sr cd cd s0 s1
 0 0 0 4780200 3230076 5 30  3 10 10  0  3  6  6  2  2
 0 0 0 2019116 527296 0  51  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   0  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   0  0  0  0  0  0 129 132 0 0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   1  0  0  0  0  0  0  0  0  0
 0 0 0 2019108 527288 0   0  0  0  0  0  0 129 120 0 0
 0 0 0 2019108 527288 0   0  0  0  0  0  0  0  0  0  0 

continuação...
     faults      cpu
    in   sy   cs us sy id
 1056 1154  499  1  1 98
 1158 1206  523  1  2 97
 1574 1599  696  1  1 98
 1230 1315  541  1  1 98
 1414 1477  638  1  1 98
 2065 1334 1467  0  3 97
 1154 1170  518  1  0 99
 1325 1375  558  0  1 98
 1336 1377  569  1  1 98
 1440 1450  621  1  1 98
 1890 1189 1417  1  2 97
 1260 1372  555  1  1 98

3. A TAREFA:

 A tarefa a ser feita era simples:  Efetuar o  sincronismo en-
tre dois diretórios. O diretório de ORIGEM estava em um servi-
dor Linux, e o diretório de DESTINO encontrava-se  na  máquina
S10.
 O utilitário executado foi o rsync(1), que será utilizado fu-
turamente para sincronismo deste mesmo diretório. Mas  esta  é
a primeira execução, portanto todo conteúdo do diretório DirB,
precisará ser transferido (13GB de dados). 

 Eis o comando: rsync -av -e ssh user@IP:/DirA/DirB .

 O comando acima foi executado dentro de um gnome-terminal(1),
em uma sessão JDS, iniciada a partir de um desktop Linux (Ubun
tu - 6.10), utilizando o protocolo XDMCP. O diretório "." é um
sistema de arquivos ZFS.

4. O PROBLEMA:
 Após alguns minutos (na realidade ~1hora), fui conferir a ve-
locidade de gravação, e quanto restava para ser transferido.

 O comando que executei foi: zpool iostat colorado 3

                capacity    operations    bandwidth
   pool       used  avail  read  write   read  write
   --------- -----  ----- -----  -----  -----  -----
   colorado  606M   147G     0     84    329   384K
   colorado  606M   147G     0     71      0   149K
   colorado  606M   147G     0      0      0      0
   colorado  606M   147G     0     81      0   164K
   colorado  606M   147G     0      0      0      0
   colorado  606M   147G     0     74      0   121K
   colorado  606M   147G     0     75      0   147K
   colorado  606M   147G     0      0      0      0
   colorado  606M   147G     0     57      0   121K
   colorado  606M   147G     0      0      0      0
   colorado  607M   147G     0     64      0   137K
   colorado  607M   147G     0     59      0   149K
   colorado  607M   147G     0      0      0      0
   colorado  607M   147G     0     63      0   125K
   colorado  607M   147G     0      0      0      0
   colorado  607M   147G     0     56      0   140K
   colorado  607M   147G     0     64      0   141K
   colorado  607M   147G     0      0      0      0
   colorado  607M   147G     0     65      0   133K

 No decorrer  de quase uma hora de transferência, este não era
o retorno que esperava. As duas máquinas  envolvidas estão  na
mesma rede, com uma conexão ethernet giga, sem erros, e a mes-
ma transferência para um terceiro servidor Linux na  mesma re-
de, terminava em minutos. Sei que a conexão deste servidor  em
particular, poderia estar causando o problema. Mas com  o fato
desta mesma máquina dias atrás ter rodado Linux sem apresentar
problemas, utilizando  a mesma  estrutura de rede, me fez sus-
peitar do ZFS.

5. A INVESTIGAÇÃO
 Bom, termos um ambiente sem problemas é o objetivo, mas  isto
é uma tarefa bem difícil. O ideal é que quando apareçam proble
mas: 1) tenhamos ferramentas para identificar  suas  causas, e
2) quando possível, resolvê-los.

 obs.: Os comandos a seguir foram executados durante a  tarefa
de sincronismo.

 5.1 Provider SYSCALL

 Solaris tem inúmeras ferramentas para executar tarefas de de-
puração, e identificação de erros ou problemas de performance.
Mas a novidade fica por conta da linguagem D. Então, vamos ver
até onde conseguimos chegar com alguns scripts:

 script: dtrace -n 'syscall:::entry {@[execname]=count()}'

 O comando acima ficou executando por ~5 minutos, e o objetivo
era contar (count()) o número de "system calls" (syscall:::en-
try), executada por processo (execname). O resultado é mostra-
do a seguir:

  snmpdx                                                2
  xfs                                                   4
  sac                                                   6
  ttymon                                                9
  wnck-applet                                          10
  dsdm                                                 14
  dtlogin                                              16
  syslogd                                              16
  metacity                                             21
  init                                                 27
  automountd                                           29
  fmd                                                  30
  inetd                                                30
  svc.configd                                          30
  svc.startd                                           30
  gnome-smproxy                                        32
  snmpd                                                32
  utmpd                                                51
  mapping-daemon                                       60
  clock-applet                                         80
  screen-4.0.2                                        148
  nautilus                                            248
  zpool                                               297
  gnome-vfs-daemon                                    300
  nfsmapid                                            510
  nscd                                                512
  sendmail                                            605
  xscreensaver                                        761
  sshd                                                805
  Xsun                                               1186
  ssh                                                1606
  dtgreet                                            2969
  gnome-netstatus-                                   2988
  dtrace                                             7547
  gnome-panel                                        8400
  gconfd-2                                           9583
  mixer_applet2                                     15026
  rsync                                             60671
  gnome-terminal                                   267771

 Como podemos observar, o gnome-terminal(1) que deveria ser a-
penas um coadjuvante, está com o papel principal no  nosso am-
biente. Executando quase 5 vezes mais chamadas  de  sistema do
que o aplicativo rsync(1).
 A pergunta que fica é: 

  "O que o gnome-terminal(1) está fazendo"?

 Para começar a responder esta pergunta, vamos para mais um pe
queno programa em D:

 script: dtrace -n syscall:::entry'/execname == "gnome-termin
al"/ {@[probefunc] = count()}'

 Executando o script acima por mais cinco minutos, conseguimos
agrupar a contagem de  chamadas  de sistema (syscall), somente
para o gnome-terminal(1).

 obs.: Apenas eu estou logado no sistema, e apenas  um  gnome-
terminal está em execução. 

 A seguir temos o retorno do comando:

  ioctl                                              6821
  writev                                            11921
  systeminfo                                        43661
  write                                             64200
  pollsys                                          105918
  read                                             121687
  readv                                            133679

 Podemos identificar uma quantidade bastante grande de "leitu-
ras", ou "tentativas de leitura". Observe a presença  da  sys-
call pollsys(2), também apresentando um número bem elevado  de
requisições.

 obs.: Note que a falta da syscall lseek, nos leva a crer  que
os dados são sequenciais. Portanto: 1) Estamos  lendo  sequen-
cialmente um arquivo, ou 2) Estamos lendo um "file descriptor"
(FD) que não suporta "seek" (ex.: um terminal ou socket).

 Com uma quantidade tão grande de chamadas de sistema, uma in-
formação interessante seria a quantidade de erros  no  retorno
destas requisições.
 Utilizando o  script D "errinfo", disponibilizado no  DTrace-
Toolkit, podemos responder esta pergunta (também executado por
 ~5 minutos).

 comando: errinfo -cn gnome-terminal

 Tracing... Hit Ctrl-C to end.
 ^C
 EXEC        SYSCALL ERR COUNT  DESC
gnome-terminal readv 11 43197 Resource temporarily unavailable
gnome-terminal read  11 74286 Resource temporarily unavailable

 Praticamente metade das chamadas para "read" and "readv"  re-
tornam erro de "recurso temporariamente  indisponível".  Sendo
que estas duas chamadas de sistema são as mais executadas pelo
gnome-terminal(1). Mas nós faremos a análise mais  aprofundada
deste erro mais adiante. Por enquanto, vale lembrar apenas que
ERR 11, conforme o arquivo /usr/include/sys/errno.h:

 #define EAGAIN  11   /* Resource temporarily unavailable */

 E conforme a página manual da syscall read(2):

 EAGAIN         Mandatory  file/record  locking   was   set,
                O_NDELAY  or  O_NONBLOCK  was set, and there
                was a blocking record lock; total amount  of
                system  memory  available when reading using
                raw I/O is temporarily insufficient; no data
                is  waiting  to be read on a file associated
                with a tty device and O_NONBLOCK was set; or
                no message is waiting to be read on a stream
                and O_NDELAY or O_NONBLOCK was set.

 5.2 Provider SYSINFO 

 Podemos deduzir que por estar  executando bem  mais  chamadas
que os outros processos, o gnome-terminal(1) esteja  recebendo
mais parcelas de CPU. Mas para confirmar isto, podemos  execu-
tar um outro pequeno script D:

 script : 

   dtrace -n 'sysinfo:::pswitch {@[execname] = count();}'

  ttymon                                                1
  dtlogin                                               2
  sac                                                   2
  init                                                  3
  snmpdx                                                3
  xfs                                                   4
  metacity                                              5
  wnck-applet                                           5
  syslogd                                               8
  automountd                                           11
  gnome-smproxy                                        14
  utmpd                                                16
  screen-4.0.2                                         22
  clock-applet                                         25
  snmpd                                                45
  nfsmapid                                             88
  fmd                                                  90
  svc.startd                                           91
  inetd                                                92
  svc.configd                                          94
  sshd                                                103
  xscreensaver                                        140
  mapping-daemon                                      177
  sendmail                                            181
  nautilus                                            229
  gnome-vfs-daemon                                    298
  fsflush                                             300
  zpool                                               305
  nscd                                                579
  Xsun                                                592
  dtrace                                              602
  gconfd-2                                           1266
  gnome-netstatus-                                   1520
  gnome-panel                                        1555
  dtgreet                                            1730
  mixer_applet2                                      3450
  rsync                                              4091
  ssh                                                5377
  gnome-terminal                                   163121
  sched                                            305042

 Como o script que executamos sinaliza cada vez que um proces-
so ganha acesso ao  processador, o  resultado  acima  comprova
nossa suspeita. Mais uma vez o tempo de execuçao do script foi
~5 minutos.

 Na tentativa de isolar o problema, vamos executar o mesmo sin
cronismo dentro de uma conexão SSH simples, sem utilizar o gno
me-termina, nem o XDMCP.

 comando: time rsync -av -e ssh user@IP:/DirA/DirB .

 sent 9871968 bytes  received 11948679206 bytes  3621060.16 by
tes/sec total size is 11908868051  speedup is 1.00

real    55m2.148s
user    6m38.540s
sys     4m55.741s

 Totalmente dentro do esperado. Na realidade, o sistema de ar-
quivos no servidor Linux aponta 13GB (XFS), no  ZFS os  mesmos
dados são apresentados como: 12GB ("du -h /DirA/DirB" em ambos
os casos).
 Não temos culpados, mas já temos dois inocentes:  Rsync(1), e
o ZFS.

 5.3 Breve

 Para continuarmos nossa investigação na performance do acesso
remoto ao ambiente gráfico, e mais especificamente a  execução
de um processo dentro do gnome-terminal (com atualização de te
la), vamos escrever um pequeno programa em C:

+------------------------------------------------------------+
| PROGRAMA: breve.c                                          |
+------------------------------------------------------------+
| #include                                                   |
|                                                            |
| int main (int argc, char **argv)                           |
| {                                                          |
|  char path[32]=”/home/leal/p/a/teste”;                     |
|  int c = 0;                                                |
|  FILE *fd;                                                 |
|                                                            |
|  while ( c < 10000 ) {                                     |
|   if (!(fd = fopen(path, "w"))) {                          |
|    printf ("Error!n"); exit (1);                          |
|   }                                                        |
|   fclose(fd);                                              |
|   printf ("Br %in", c);                                   |
|   c++;                                                     |
|  }                                                         |
|  exit (0);                                                 |
| }                                                          |
+------------------------------------------------------------+

 A idéia do "loop" e  das  chamadas open(2)/close(2)/write(2),
no programa breve, é visualizar o atraso na execução  de  todo
o processo, em  decorrência do  "bloqueio"  na  atualização do
terminal. Isto é,  a criação e atualização  dos  arquivos pelo
rsync(1), tarefa principal, ficar esperando o retorno do  gno-
me-terminal, tarefa secundária.

 obs.: A partir deste ponto, nossos testes serão efetuados so-
bre a execução do programa "breve". A  tarefa  de  sincronismo
foi cancelada.

 Após a compilação, executamos o programa em uma conexão SSH:

 comando: time ./breve

   real   0m0.342s
   user   0m0.032s
   sys    0m0.309s

 O programa executou em menos de um segundo (342  milisegundos
para ser exato). Logo em seguida, testamos a execução  através
da interface gráfica(XDMCP), dentro de um gnome-terminal(1):

 comando: time ./breve

   real   0m56.058s
   user   0m0.032s
   sys    0m0.286s

 Nada bom... 56 segundos! Vamos visualizar as chamadas de sis-
tema que nosso programa está executando. Para  isto,  primeiro
rodamos o script D e em seguida executamos o programa "breve".

 script: dtrace -n 'syscall:::entry /execname == "breve"/
{@[probefunc] = count()}'

  fstat64                                               1
  getcwd                                                1
  getpid                                                1
  getrlimit                                             1
  ioctl                                                 1
  memcntl                                               1
  rexit                                                 1
  sysconfig                                             1
  sysi86                                                1
  munmap                                                2
  setcontext                                            2
  resolvepat                                            3
  xstat                                                 4
  mmap                                                  7
  write                                             10000
  close                                             10001
  open                                              10002

 Perfeito, basicamente as iterações de  abertura e  fechamento
do arquivo que criamos, e 10000 chamadas a sycall "write", exe
cutadas  através do "printf()".
 Precisamos calcular a latência de cada operação, para  saber-
mos qual chamada  exatamente  está causando atraso.  Para isso
executaremos o seguinte programa em D:

+------------------------------------------------------------+
| PROGRAMA: latencia.d                                       |
+------------------------------------------------------------+
| #!/usr/sbin/dtrace -s                                      |
| #pragma D option quiet                                     |
| int x;                                                     |
|                                                            |
| syscall::$2:entry                                          |
| /execname == $$1/                                          |
| {                                                          |
|  self->ENTRADA = timestamp;                                |
|  x = x + 1;                                                |
| }                                                          |
|                                                            |
| syscall::$2:return                                         |
| /self->ENTRADA/                                            |
| {                                                          |
|  this->SAIDA = timestamp - self->ENTRADA;                  |
|  this->TIME = ( this->SAIDA / 1000000 );                   |
|  printf(”%10d : %10d millisecondsn”, x, this->TIME);      |
|  self->ENTRADA = 0;                                        |
| }                                                          |
+------------------------------------------------------------+

 O script latencia.d irá contabilizar o tempo de execução  pa-
ra cada chamada de sistema que for solicitada através da linha
de comando ( 2º argumento), para o programa em questão (1º ar-
gumento). Iremos utilizar este script para verificar os tempos
de execução de cada uma das três principais chamadas de siste-
ma que nosso programa breve executa: open(2), close(2)  e wri-
te(2).

 script: ./latencia.d breve open >/tmp/latencia.log
 comando: tail /tmp/latencia.log
      9995 :          0 milliseconds
      9996 :          0 milliseconds
      9997 :          0 milliseconds
      9998 :          0 milliseconds
      9999 :          0 milliseconds
     10000 :          0 milliseconds
     10001 :          0 milliseconds
     10002 :          0 milliseconds

 script: awk ‘{print $3}’ /tmp/latencia.log | sort -u

0

 Obs.: utilizaremos o script awk acima para nos certificar que
todos os valores são zero. Caso contrário, o comando “sort -u”
nos informará as diferenças.

 No caso das chamadas “open”, todas executaram sem problemas e
com tempos normais.

 script: ./latencia.d breve close >/tmp/latencia.log
 comando: tail /tmp/latencia.log
      9953 :          0 milliseconds
      9954 :          0 milliseconds
      9955 :          0 milliseconds
      9956 :          0 milliseconds
      9957 :          0 milliseconds
      9958 :          0 milliseconds
      9959 :          0 milliseconds
      9960 :          0 milliseconds

 script: awk ‘{print $3}’ /tmp/latencia.log | sort -u

0

 O mesmo aconteceu com a chamada close(2)...

 script: ./latencia.d breve write >/tmp/latencia.log
 comando: tail /tmp/latencia.log
      9993 :          0 milliseconds
      9994 :          0 milliseconds
      9995 :          0 milliseconds
      9996 :          0 milliseconds
      9997 :          0 milliseconds
      9998 :          0 milliseconds
      9999 :          0 milliseconds
     10000 :          0 milliseconds

 script: awk ‘{print $3}’ /tmp/latencia.log | sort -u | tail
354
355
357
359
362
375
380
383
549
737

 Aí está! Agora vamos utilizar outro script, desta vez escrito
em perl, para podermos obter  algumas  informações  importantes
sobre os tempos de execução da chamada write(2):

 a) média
 b) desvio-padrão
 c) total
 d) etc..

comando: awk ‘{print $3}’ /tmp/latencia.log | /home/leal/p/to
ols/report.pl

ops: 10000
max: 737
min: 0
med: 317
avg: 5.3684
sum: 53684                     <===== 53 segundos!
dev: 41.6126596456448

 O programa rodando em um gnome-terminal(1), através do proto-
colo XDMCP, demorou 56 segundos para  praticamente contar  até
10000! A  interação com o usuário, o  "-v" do  nosso programa,
levou 53 segundos para imprimir cerca de 10 dígitos por linha.
 Mas nosso problema com o rsync(1) ainda é  pior, pois  os ca-
minhos de cada  arquivo são informados na tela, o que  normal-
mente contém bem mais que  10  dígitos. Para  chegarmos  mais
próximos deste problema, vamos  alterar nossa linha de: 

 printf ("Br %in", c);

     para:

 printf ("Brrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
%in", c);

 Vamos efetuar o teste com esta alteração, aumentando a string
do nosso comando "printf" para aproximadamente 60 caracteres..

 comando: time ./breve

    real   3m46.918s           <===== 3 minutos!
    user   0m0.043s
    sys    0m0.325s

 Com nosso pequeno programa "breve",  conseguimos  definitiva-
mente inocentar a tarefa de sincronismo, e o sistema de arqui-
vos ZFS. Portanto, nossa investigação fica restrita ao  gnome-
terminal(1), e a conexão XDMCP.
 Podemos  começar  executando  nosso  programa  em  um  gnome-
terminal remotamente, sem a utilização do XDMCP. No caso, uti-
lizaremos um túnel ssh como transporte.

 comando: time ./breve 

     real    0m0.371s
     user    0m0.031s
     sys     0m0.315s

 Valores muito próximos dos que visualizamos  em  uma  conexão
ssh simples (caracter). Neste caso foram 371 milisegundos, con
tra 342 milisegundos da execução diretamente no terminal ssh.

 5.4 SYSCALL: read(2)
 Ainda estamos devendo uma análise um pouco mais apurada do re
torno das chamadas read(2). Constatamos durante  a  tarefa  de
sincronismo, que metade das requisições retornavam erro  "11".
Vamos utilizar um outro script D para visualizarmos  melhor  o
retorno destas chamadas:

 obs.: Conforme a página manual, a chamada  read(2)  tem  como
código de retorno a  quantidade de  bytes lidos, ou -1 sinali-
zando erro. No caso de final de arquivo, o retorno é 0.  Então
só temos um argumento no retorno: arg0.

 script: dtrace -n 'syscall::read:return /execname == "gnome-
terminal"/ { @Size = quantize(arg0); }'

dtrace: description 'syscall::read:return ' matched 1 probe
^C

      value  ------------- Distribution ------------- count
         -2 |                                         0
         -1 |@@@@@@@@@@@@@@@@@@@@                     48793
          0 |                                         0
          1 |                                         0
          2 |                                         1
          4 |                                         1
          8 |                                         0
         16 |                                         0
         32 |@@@@@@@@@@@@@@@@@@@@                     50119
         64 |                                         3
        128 |                                         1
        256 |                                         0
        512 |                                         968
       1024 |                                         0

 Realmente confirmado. Metade das chamadas read(2)  retornaram
-1. Isto foi o que constatamos ao executar o script errinfo du
rante nossa tarefa de sincronismo. A maioria das chamadas  que
retornam OK, são requisições de até 32 bytes (16-32).

 Conforme a página manual da system call read(2):

 ...
 When attempting to read a file associated  with  a  terminal
 that has no data currently available:

  o  If O_NDELAY is set, read() returns 0.

  o  If O_NONBLOCK is set, read() returns -1 and sets  errno
     to EAGAIN.

 e ainda...

 When attempting to read a file associated with a socket or a
 stream  that  is not a pipe, a FIFO, or a terminal,  and the
 file has no data currently available:

   o  If O_NDELAY or O_NONBLOCK is set, read() returns -1 and
      sets errno to EAGAIN.
 ...

 Portanto, vamos  agrupar  as  chamadas read(2)  por file des-
criptor, para que depois possamos  conferir se o que  diz  na
página manual se encaixa nesse caso.

 Conforme o protótipo da função read(2): 

    ssize_t read(int fildes, void *buf, size_t nbyte);
                    /
                    ||
                     ====== 1° argumento

 script: dtrace -n 'syscall::read:entry /execname == "gnome-t
erminal"/ { @[arg0] = count(); }'

dtrace: description 'syscall::read:entry ' matched 1 probe
^C

               21             1282
                4            98611

 Agora precisamos listar os FDs que estão sendo utilizados pe-
lo gnome-terminal(1), para identificar os de  número 21 e 4. O
que conseguimos com o comando:

 pfiles `pgrep gnome-terminal`

 ------------------------ corte aqui ------------------------
3243:   gnome-terminal
 Current rlimit: 256 file descriptors
  0: S_IFCHR mode:0666 dev:270,0 ino:6815752 uid:0 gid:3
rdev:13,2
     O_RDONLY|O_LARGEFILE
     /devices/pseudo/mm@0:null
  1: S_IFCHR mode:0666 dev:270,0 ino:6815752 uid:0 gid:3
rdev:13,2
     O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE
     /devices/pseudo/mm@0:null
  2: S_IFCHR mode:0666 dev:270,0 ino:6815752 uid:0 gid:3
rdev:13,2
     O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE
     /devices/pseudo/mm@0:null
  3: S_IFDOOR mode:0444 dev:279,0 ino:63 uid:0 gid:0 size:0
     O_RDONLY|O_LARGEFILE FD_CLOEXEC  door to nscd[111]
     /var/run/name_service_door
  4: S_IFSOCK mode:0666 dev:276,0 ino:32241 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_KEEPALIVE,SO_SNDBUF(49152),SO_RCVBUF(49640),
IP_NEXTHOP(232.193.0.0)
       sockname: AF_INET6 ::ffff:10.X.X.y  port: 33130
       peername: AF_INET6 ::ffff:192.X.X.y  port: 6020
  5: S_IFIFO mode:0000 dev:278,0 ino:2095 uid:100 gid:1 size:0
     O_RDWR
  6: S_IFIFO mode:0000 dev:278,0 ino:2095 uid:100 gid:1 size:0
     O_RDWR
  7: S_IFIFO mode:0000 dev:278,0 ino:2096 uid:100 gid:1 size:0
     O_RDWR
  8: S_IFIFO mode:0000 dev:278,0 ino:2096 uid:100 gid:1 size:0
     O_RDWR
  9: S_IFIFO mode:0000 dev:278,0 ino:2097 uid:100 gid:1 size:0
     O_RDWR
 10: S_IFIFO mode:0000 dev:278,0 ino:2097 uid:100 gid:1 size:0
     O_RDWR|O_NONBLOCK
 11: S_IFCHR mode:0644 dev:270,0 ino:78118918 uid:0 gid:3
rdev:149,1
     O_RDONLY
     /devices/pseudo/random@0:urandom
 12: S_IFIFO mode:0000 dev:278,0 ino:2098 uid:100 gid:1 size:0
     O_RDWR
 13: S_IFIFO mode:0000 dev:278,0 ino:2098 uid:100 gid:1 size:0
     O_RDWR
 14: S_IFSOCK mode:0666 dev:276,0 ino:34603 uid:0 gid:0 size:0
     O_RDWR FD_CLOEXEC
       SOCK_STREAM
       SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX
       peername: AF_UNIX /tmp/.ICE-unix/3185
 15: S_IFSOCK mode:0666 dev:276,0 ino:36821 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX
       peername: AF_UNIX /var/tmp/orbit-leal/linc-c76-0-45890
0e8838cd
 16: S_IFSOCK mode:0666 dev:276,0 ino:38208 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_REUSEADDR,SO_SNDBUF(49152),SO_RCVBUF(49152),
IP_NEXTHOP(0.192.0.0)
       sockname: AF_INET 0.0.0.0  port: 33131
 17: S_IFSOCK mode:0666 dev:276,0 ino:38012 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_REUSEADDR,SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX /var/tmp/orbit-leal/linc-cab-0-45894
36c4fcba
 18: S_IFSOCK mode:0666 dev:276,0 ino:38649 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_REUSEADDR,SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX /var/tmp/orbit-leal/linc-cab-0-45894
36c4fcba
       peername: AF_UNIX
 19: S_IFSOCK mode:0666 dev:276,0 ino:34604 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_REUSEADDR,SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX /var/tmp/orbit-leal/linc-cab-0-45894
36c4fcba
       peername: AF_UNIX
 20: S_IFSOCK mode:0666 dev:276,0 ino:5048 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX
       peername: AF_UNIX /var/tmp/orbit-leal/linc-c7d-0-4589
05d5d058b
 21: S_IFCHR mode:0000 dev:270,0 ino:41498 uid:0 gid:0
rdev:23,5
     O_RDWR|O_NONBLOCK
     /devices/pseudo/clone@0:ptm
 22: S_IFIFO mode:0000 dev:278,0 ino:2099 uid:100 gid:1 size:0
     O_RDWR
 23: S_IFIFO mode:0000 dev:278,0 ino:2099 uid:100 gid:1 size:0
     O_RDWR
 24: S_IFIFO mode:0000 dev:278,0 ino:2100 uid:100 gid:1 size:0
     O_RDWR
 25: S_IFSOCK mode:0666 dev:276,0 ino:36545 uid:0 gid:0 size:0
     O_RDWR
       SOCK_STREAM
       SO_SNDBUF(16384),SO_RCVBUF(5120)
       sockname: AF_UNIX
 26: S_IFIFO mode:0000 dev:278,0 ino:2100 uid:100 gid:1 size:0
     O_RDWR|O_NONBLOCK
 ------------------------ corte aqui ------------------------

 Vamos exibi-los novamente para facilitar a identificação:

 ------------------------ corte aqui ------------------------
  4: S_IFSOCK mode:0666 dev:276,0 ino:32241 uid:0 gid:0 size:0
     O_RDWR|O_NONBLOCK FD_CLOEXEC
       SOCK_STREAM
       SO_KEEPALIVE,SO_SNDBUF(49152),SO_RCVBUF(49640),
IP_NEXTHOP(232.193.0.0)
       sockname: AF_INET6 ::ffff:10.X.X.y  port: 33130
       peername: AF_INET6 ::ffff:192.X.X.y  port: 6020
 ------------------------ corte aqui ------------------------

 Este é o socket que representa a conexão  entre o  servidor e
a minha estação de trabalho (os IP's são fictícios). Note  que
conforme a página manual, este FD está com a flag "O_NONBLOCK"
habilitada. Portanto, gnome-terminal(1)  receberá erros EAGAIN
no acesso a este FD, caso os dados não estejam prontos.

 ------------------------ corte aqui ------------------------
 21: S_IFCHR mode:0000 dev:270,0 ino:41498 uid:0 gid:0
rdev:23,5
     O_RDWR|O_NONBLOCK
     /devices/pseudo/clone@0:ptm
 ------------------------ corte aqui ------------------------

 O FD acima é referente ao nosso terminal, e seria interessan-
te sabermos se temos um número grande de erros "EAGAIN"  neste
FD, pois temos a flag "O_NONBLOCK" habilitada também.
 Então vamos discriminar os FDs e identificar quantos erros 11
estão sendo gerados para cada um... Vamos começar com o FD 4:

 script: dtrace -n 'syscall::read:entry /execname == "gnome-t
erminal" && arg0 == 4/ {self->FLAG = 1;}  syscall::read:retur
n /self-> FLAG / {@[arg0] = count(); self->FLAG =0;}'

dtrace: description 'syscall::read:entry ' matched 2 probes
^C

               -1            48827
               32            49823

 Agora vamos para o FD 21...

 script: dtrace -n 'syscall::read:entry /execname == "gnome-t
erminal" && arg0 == 21/ {self->FLAG = 1;} syscall::read:retur
n /self-> FLAG / {@[arg0] = count(); self->FLAG =0;}'

dtrace: description 'syscall::read:entry ' matched 2 probes
^C

                2                1
              106                1
              293                1
              318                1
              555                1
               53                2
              550               69
               54               90
               55              205
              560              899

 Perfeito, os erros EAGAIN estão aparentemente no acesso ao FD
que representa nossa conexão. Mas isto é apenas uma  constata-
ção para entendermos as informações que  estamos  verificando.
Não necessariamente esta seja a razão do  atrazo na  execução.
Talvez seja apenas uma característica da  programação,  e  não
esteja gerando problemas. Vamos calcular a latência das chama-
das read(2)/readv(2), para sabermos se são problema ou não.

 script: ./latencia.d gnome-terminal read > /tmp/latencia.log

 script: awk '{print $3}' /tmp/latencia.log | sort -u

0

 script: ./latencia.d gnome-terminal readv > /tmp/latencia.log

 script: awk '{print $3}' /tmp/latencia.log | sort -u

0

 Conforme vimos nos resultados acima, nosso problema  não está
nas chamadas read(2), e nem nas chamadas readv(2). 

 obs.: O que me intriga é que temos a  utilização  da  chamada
pollsys(2), e mesmo assim temos chamadas read(2)  sendo execu-
tadas sobre FD que não estão prontos para leitura. Conforme  a
página manual da chamada poll(2):

 DESCRIPTION
  The poll() function provides applications with  a  mechanism
  for  multiplexing  input/output  over a set of file descrip-
  tors.  For each member of  the  array  pointed  to  by  fds,
  poll()  examines  the given file descriptor for the event(s)
  specified in events. The number of pollfd structures in  the
  fds  array is specified by nfds. The poll() function identi-
  fies those file descriptors on which an application can read
  or write data, or on which certain events have occurred.

 Ainda precisamos identificar no gnome-terminal(1), onde estão
os quase três minutos de latencia. No DTraceToolkit  temos  um
script D muito útil para nos fornecer esta informação:
 procsystime.

 Vamos executá-lo e observar os resultados:

 script: ./procsystime -a -n gnome-terminal
Hit Ctrl-C to stop sampling...
^C

Elapsed Times for processes gnome-terminal,

         SYSCALL          TIME (ns)
           fcntl              15805
         fstat64              32766
             brk           11092009
           ioctl           17873960
      systeminfo           67372551
            read          475826059
           readv          852809922
           write         1566182042
         pollsys       216148192460 <===== 3,6 minutos
          TOTAL:       219139397574

CPU Times for processes gnome-terminal,

         SYSCALL          TIME (ns)
           fcntl               4389
         fstat64              19034
           ioctl            8044618
             brk            8088614
      systeminfo           15097287
            read          263802526
           readv          584568168
         pollsys          957183040
           write         1428503773
          TOTAL:         3265311449

Syscall Counts for processes gnome-terminal,

         SYSCALL              COUNT
           fcntl                  5
         fstat64                  5
             brk               1342
           ioctl               4156
      systeminfo              25402
           write              52097
         pollsys              93410
            read             100364
           readv             126316
          TOTAL:             403097

 Os resultados acima foram  bastante  interessantes. Temos  os
tempos de CPU (execução), e o tempo decorrido de  cada  chama-
da. O que nos faz direcionarmos nossa atenção para  a  chamada
pollsys(2). Esta chamada de sistema teve uma latência total de
mais de três minutos! Novamente vamos utilizar nosso script de
cálculo de latência para acompanhar a execução de cada  requi-
sição efetuada a syscall pollsys(2).

 script: ./latencia.d gnome-terminal pollsys > /tmp/latencia.
log

 script: awk '{print $3}' /tmp/latencia.log | sort -u | tail
51
54
55
58
6
301
65
7
8
9

 script: awk '{print $3}' /tmp/latencia.log | /home/leal/p/to
ols/report.pl
ops: 86652
max: 343
min: 0
med: 47
avg: 1.87045884688178
sum: 162079                         <===== 2,7 minutos
dev: 4.86219237692499

 Alcançamos mais um ponto em nossa investigação.

 Vamos tentar ir um pouco mais longe, e discriminar as rotinas
e funções que estão sendo executadas, cada vez  que a  chamada
pollsys é invocada.

 script: dtrace -n 'syscall::pollsys:entry /execname == "gnom
e-terminal "/ { @[ustack()] = count(); }'
dtrace: description 'syscall::pollsys:entry ' matched 1 probe
^C

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XEventsQueued+0x1ad
              libX11.so.4`XPending+0x42
              libgdk-x11-2.0.so.0.400.9`gdk_check_xpending+0x1f
               14

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XSync+0x65
              libgdk-x11-2.0.so.0.400.9`gdk_window_queue+0x72
               61

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XSync+0x65
              libgdk-x11-2.0.so.0.400.9`gdk_flush+0x2c
              211

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XGetGeometry+0x6b
              libXft.so.2`XftDrawDepth+0x43
              libXft.so.2`XftDrawBitsPerPixel+0x24
              libXft.so.2`_XftSmoothGlyphFind+0x4e
              libXft.so.2`XftGlyphFontSpecCore+0x356
              libXft.so.2`XftDrawGlyphFontSpec+0x124
              libXft.so.2`XftDrawCharFontSpec+0xb1
              libvte.so.4.4.0`_vte_xft_draw_text+0x238
              966

              libc.so.1`__pollsys+0x15
              libc.so.1`poll+0x52
             4837

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XGetImage+0x9f
              libX11.so.4`XGetSubImage+0x32
              libgdk-x11-2.0.so.0.400.9`_gdk_x11_copy_to_image+0x58d
            11593

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XGetImage+0x9f
              libX11.so.4`XGetSubImage+0x32
            13532

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XRead+0xa9
              libX11.so.4`_XReply+0xce
              libX11.so.4`XGetImage+0x9f
              libXft.so.2`XftGlyphFontSpecCore+0x45b
              libXft.so.2`XftDrawGlyphFontSpec+0x124
              libXft.so.2`XftDrawCharFontSpec+0xb1
              libvte.so.4.4.0`_vte_xft_draw_text+0x238
            22236

              libc.so.1`__pollsys+0x15
              libc.so.1`pselect+0x18e
              libc.so.1`select+0x82
              libX11.so.4`_XWaitForReadable+0xcf
              libX11.so.4`_XReadPad+0xea
              libX11.so.4`XGetImage+0xea
              libXft.so.2`XftGlyphFontSpecCore+0x45b
              libXft.so.2`XftDrawGlyphFontSpec+0x124
              libXft.so.2`XftDrawCharFontSpec+0xb1
              libvte.so.4.4.0`_vte_xft_draw_text+0x238
            34983

 Perfeito, agora sabemos como estamos  chegando  nas  chamadas
pollsys, e temos esta informação agrupada por  "ustack"  (user
stack). Agora temos a  oportunidade de  identificar  as  fron-
teiras entre bibliotecas, e fica mais fácil isolar o problema.
 Com base no exposto acima, constatamos que quase totalidade das chamadas
pollsys(2), são originadas na biblioteca libX11.so.4, na função
_XWaitForReadable. Para comprovarmos esta teoria, precisamos de um
script D que explore o provider PID.

+------------------------------------------------------------+
| PROGRAMA: pid.d                                            |
+------------------------------------------------------------+
| #!/usr/sbin/dtrace -s                                      |
| #pragma D option quiet                                     |
| int x;                                                     |
|                                                            |
| pid$1:libX11:_XWaitForReadable:entry                       |
| {                                                          |
|  self->ENTRADA = timestamp;                                |
|  x = x + 1;                                                |
| }                                                          |
|                                                            |
| pid$1:libX11:_XWaitForReadable:return                      |
| /self->ENTRADA/                                            |
| {                                                          |
|  this->SAIDA = timestamp - self->ENTRADA;                  |
|  this->TIME = ( this->SAIDA / 1000000 );                   |
|  printf("%10d : %10d millisecondsn", x, this->TIME);      |
|  self->ENTRADA = 0;                                        |
| }                                                          |
+------------------------------------------------------------+

./pid.d `pgrep gnome-terminal` > /tmp/latencia.log

awk '{print $3}' /tmp/latencia.log | /home/leal/p/tools/report.pl
ops: 80577
max: 220
min: 0
med: 29
avg: 2.46742867071249
sum: 198818                    <===== 3,3 minutos
dev: 5.02238185341986

 6. CONCLUSÃO

 Poderíamos continuar esta investigação  por  bastante  tempo,
mas já conseguimos identificar a causa do atraso na nossa  ta-
refa de sincronismo, e também pudemos perceber o  poder  desta
ferramenta DTrace. Com scripts realmente simples, e uma  noção
básica de como os sistemas POSIX funcionam, fica relativamente
fácil iniciarmos a utilização desta ferramenta e  como  conse-
quência, identificar a causa de problemas.