파이프 버퍼가 가득 찼는지 어떻게 알 수 있나요?

파이프 버퍼가 가득 찼는지 어떻게 알 수 있나요?

나는 내가 작성한 일부 Perl에 프로그램의 출력을 파이프로 연결하고 있습니다. 이는 오랜 과정이고 때로는 며칠이 걸리기 때문에 병목 현상이 있는 위치를 찾아 해결하려고 노력하고 싶습니다. 내 스크립트가 처리할 수 있는 것보다 더 빠르게 데이터가 내 스크립트로 파이프되고 있는지 궁금합니다. 이런 경우에는 스크립트를 조정하려고 노력하지만 필요하지 않으면 조정하지 않을 것입니다. 버퍼가 가득 찼을 때 추가 쓰기를 방지하기 위해 플래그를 설정하는 방법에 대한 논의가 있지만 플래그가 설정되었는지 또는 얼마나 자주 확인하려면 어떻게 해야 합니까? 어떤 아이디어가 있나요?

답변1

strace나는 Perl 스크립트 (Linux), dtruss(OS X), ktrace(FreeBSD), truss(Solaris) 등을 추적하기 위해 시스템 호출 추적 도구를 사용할 것입니다. 목표는 Perl 스크립트가 표준 입력에서 읽기를 기다리는 데 소비하는 시간과 다른 프로그램이 표준 출력에 쓰기를 기다리는 데 소비하는 시간을 확인하는 것입니다.

여기서는 작가를 병목 현상으로 테스트하고 있습니다.

terminal 1$ gzip -c < /dev/urandom | cat > /dev/null

terminal 2$ ps auxw | egrep 'gzip|cat'
slamb    25311 96.0  0.0  2852  516 pts/0    R+   23:35   3:40 gzip -c
slamb    25312  0.8  0.0  2624  336 pts/0    S+   23:35   0:01 cat

terminal 2$ strace -p 25312 -s 0 -rT -e trace=read
Process 25312 attached - interrupt to quit
     0.000000 read(0, ""..., 4096) = 4096 <0.005207>
     0.005531 read(0, ""..., 4096) = 4096 <0.000051>

여기서 첫 번째 숫자는 마지막 시스템 호출이 시작된 이후의 시간이고 마지막 숫자는 시스템 호출에 걸린 시간입니다. 따라서 Perl을 사용하여 후처리를 수행하여 이를 집계할 수 있습니다... [*]

terminal 2$ strace -p 25312 -s 0 -rT -e trace=read 2>&1 | perl -nle 'm{^\s*([\d.]+) read\(0, .*<([\d.]+)>} or next; $total_work += $1 - $last_wait; $total_wait += $2; $last_wait = $2; print "working for $total_work sec, waiting for $total_wait sec"; $last_wait = $2;'
working for 0 sec, waiting for 0.005592 sec
...
working for 0.305356 sec, waiting for 2.28624900000002 sec
...

terminal 2$ strace -p 25311 -s 0 -rT -e trace=write 2>&1 | perl -nle 'm{^\s*([\d.]+) write\(1, .*<([\d.]+)>} or next; $total_work += $1 - $last_wait; $total_wait += $2; $last_wait = $2; print "working for $total_work sec, waiting for $total_wait sec"; $last_wait = $2;'
...
working for 5.15862000000001 sec, waiting for 0.0555740000000007 sec
...

좀 더 화려하게 만들어 양쪽, 올바른 파일 설명자만 추적하고 각 쪽이 다른 쪽을 기다린 시간의 백분율과 함께 매초마다 멋진 상태 업데이트를 인쇄하는 SystemTap 또는 DTrace 스크립트를 만들 수 있습니다.

[*] - 경고: 다른 파일 설명자에서 읽기/쓰기가 호출되면 대략적인 집계가 덜 정확해집니다. 이 경우 작업 시간이 과소평가됩니다.


dtrace 버전은 실제로 매우 간결합니다.

terminal 1$ gzip -c < /dev/urandom | cat > /dev/null

terminal 2$ ps aux | egrep 'gzip| cat'
slamb    54189  95.8  0.0   591796    584 s006  R+   12:49AM  22:49.55 gzip -c
slamb    54190   0.4  0.0   599828    392 s006  S+   12:49AM   0:06.08 cat

terminal 2$ cat > pipe.d <<'EOF'
#!/usr/sbin/dtrace -qs

BEGIN
{
  start = timestamp;
  writer_blocked = 0;
  reader_blocked = 0;
}

tick-1s, END
{
  this->elapsed = timestamp - start;
  printf("since startup, writer blocked %3d%% of time, reader %3d%% of time\n",
         100 * writer_blocked / this->elapsed,
         100 * reader_blocked / this->elapsed);
}

syscall::write:entry
/pid == $1 && arg0 == 1/
{
  self->entry = timestamp;
}

syscall::write:return
/pid == $1 && self->entry != 0/
{
  writer_blocked += timestamp - self->entry;
  self->entry = 0;
}

syscall::read:entry
/pid == $2 && arg0 == 0/
{
  self->entry = timestamp;
}

syscall::read:return
/pid == $2 && self->entry != 0/
{
  reader_blocked += timestamp - self->entry;
  self->entry = 0;
}
EOF

terminal 2$ chmod u+x pipe.d
terminal 2$ sudo ./pipe.d 54189 54190
since startup, writer blocked   0% of time, reader  98% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
since startup, writer blocked   0% of time, reader  99% of time
^C
since startup, writer blocked   0% of time, reader  99% of time

SystemTap 버전:

terminal 1$ gzip -c /dev/urandom | cat > /dev/null

terminal 2$ ps auxw | egrep 'gzip| cat'
slamb     3405  109  0.0   4356   584 pts/1    R+   02:57   0:04 gzip -c /dev/urandom
slamb     3406  0.2  0.0  10848   588 pts/1    S+   02:57   0:00 cat

terminal 2$ cat > probes.stp <<'EOF'
#!/usr/bin/env stap

global start
global writer_pid
global writes
global reader_pid
global reads

probe begin {
  start = gettimeofday_us()
  writer_pid = strtol(argv[1], 10)
  reader_pid = strtol(argv[2], 10)
}

probe timer.s(1), end {
  elapsed = gettimeofday_us() - start
  printf("since startup, writer blocked %3d%% of time, reader %3d%% of time\n",
         100 * @sum(writes) / elapsed,
         100 * @sum(reads) / elapsed)
}

probe syscall.write.return {
  if (pid() == writer_pid && $fd == 1)
    writes <<< gettimeofday_us() - @entry(gettimeofday_us())
}

probe syscall.read.return {
  if (pid() == reader_pid && $fd == 0)
    reads <<< gettimeofday_us() - @entry(gettimeofday_us())
}
EOF

terminal 2$ chmod a+x probes.stp
terminal 2$ sudo ./pipe.stp 3405 3406
since startup, writer blocked   0% of time, reader  99% of time
...

답변2

pv -TC파이프라인에 명령을 삽입 할 수 있습니다 .

cmd1 | pv -TC | cmd2

pv자체 버퍼를 사용하여 -T1초 동안의 평균 채우기를 보고하도록 합니다(기본값).

항상 100%이면 출력이 cmd1소비되는 것보다 출력이 더 빠르게 생성된다는 의미입니다. cmd2그렇지 않다면 그 반대입니다. 파이프 자체는 64kB를 저장할 수 있습니다.

-B지정된 pv버퍼 크기 도 참조하세요 . 다음과 같이 여러 s를 사용할 수 있습니다 pv.

$ cmd1 | pv -cCTrbN 'cmd1 -> cmd2' | cmd2 | pv -cCTrbN 'cmd2 -> cmd3' | cmd3
cmd1 -> cmd2: 1.92GiB { 53%} [ 387MiB/s]
cmd2 -> cmd3: 1.92GiB {  0%} [ 387MiB/s]

관련 정보