process_tail 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 85d86bdf54735a348134ab9abceec3610882fe23
4
- data.tar.gz: 9f1e65346bd355e4fb1b9f9c83fdf8c45a16a224
3
+ metadata.gz: 3adc23055f8977b049a04d0b94ddb2f9a5740cf2
4
+ data.tar.gz: 680424fb0373ed7d1e429044920ced1033151278
5
5
  SHA512:
6
- metadata.gz: e3da11ae5cece8697449bcea8307bc34e1af5b6cb3a39834e96d56f2b32d6306e4ac9070909c79bab0f2486f319a15de408d13df6982d9d3fa367163920c8387
7
- data.tar.gz: 30b6556d39cf4b06169e518a69fe7a8c1b0b03a05e0f0db97bcd6abafee42625622048885fbc7452dfbf48cec4915983c2feb10aa6aae5ea132a7a7b8c770b56
6
+ metadata.gz: 90c7886af47819ac7ae13281bec9a157cb051569f1799dfa11d7edaa23ecdc1e3a1298febbb59ef6202fba679a32e28dbf87ecd34e0c23623c522406da51d846
7
+ data.tar.gz: ed2d69a06283896f786f36aa5a960ec9fc1e1b9d059840f15686c49a88d0611eb140e256b81599c0e66669122f2f579445c55015d57613e445f2add89594cd71
data/README.md CHANGED
@@ -11,7 +11,6 @@ So you can get other process outputs.
11
11
 
12
12
  * Mac OSX and Windows are not supported at the moment
13
13
  * SEGV occures occasionally
14
- * Multithreaded process support
15
14
 
16
15
  ## Installation
17
16
 
@@ -31,12 +30,20 @@ Or install it yourself as:
31
30
 
32
31
  ## Usage
33
32
 
33
+ ### Get outputs as an IO object
34
+
34
35
  ```ruby
35
36
  ProcessTail.open pid, :stdout do |io|
36
37
  puts "Recent stdout of #{pid}: #{io.gets}"
37
38
  end
38
39
  ```
39
40
 
41
+ ### Show outputs instantly
42
+
43
+ ```bash
44
+ $ process_tail $(pgrep PROCESS_NAME)
45
+ ```
46
+
40
47
  ## Contributing
41
48
 
42
49
  1. Fork it ( https://github.com/hibariya/process_tail/fork )
data/bin/process_tail CHANGED
@@ -5,7 +5,11 @@ require 'process_tail'
5
5
  $stdout.sync = true
6
6
 
7
7
  ProcessTail.open Integer(ARGV[0]) do |io|
8
- while c = io.getc
9
- print c
8
+ begin
9
+ while c = io.getc
10
+ print c
11
+ end
12
+ rescue IOError
13
+ # NOP
10
14
  end
11
15
  end
@@ -8,7 +8,10 @@ end
8
8
  abort "missing #{m}" unless have_struct_member('struct user_regs_struct', m, 'sys/user.h')
9
9
  end
10
10
 
11
- abort 'missing __WALL' unless have_const('__WALL', 'sys/wait.h')
12
- abort 'missing PTRACE_O_TRACESYSGOOD' unless have_const('PTRACE_O_TRACESYSGOOD', 'sys/ptrace.h')
11
+ %w(PTRACE_O_TRACESYSGOOD PTRACE_O_TRACEFORK PTRACE_O_TRACEVFORK PTRACE_O_TRACECLONE).each do |c|
12
+ abort "missing #{c}" unless have_const(c, 'sys/ptrace.h')
13
+ end
14
+
15
+ abort 'missing __WALL' unless have_const('__WALL', 'sys/wait.h')
13
16
 
14
17
  create_makefile 'process_tail/process_tail'
@@ -15,11 +15,26 @@ typedef struct pt_tracee {
15
15
  struct pt_tracee *next;
16
16
  } pt_tracee_t;
17
17
 
18
- typedef struct pt_wait_args {
18
+ typedef struct {
19
19
  int *status;
20
20
  pid_t pid;
21
21
  } pt_wait_args_t;
22
22
 
23
+ typedef struct {
24
+ unsigned int fd;
25
+ pt_tracee_t *tracee;
26
+ VALUE *wait_queue;
27
+ } pt_loop_args_t;
28
+
29
+ static void
30
+ pt_tracee_initialize(pt_tracee_t *tracee)
31
+ {
32
+ tracee->pid = 0;
33
+ tracee->activated = 0;
34
+ tracee->dead = 0;
35
+ tracee->next = NULL;
36
+ }
37
+
23
38
  static pt_tracee_t *
24
39
  pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
25
40
  {
@@ -29,10 +44,9 @@ pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
29
44
  exit(EXIT_FAILURE);
30
45
  }
31
46
 
32
- tracee->pid = pid;
33
- tracee->activated = 0;
34
- tracee->dead = 0;
35
- tracee->next = (struct pt_tracee *)*headpp;
47
+ pt_tracee_initialize(tracee);
48
+ tracee->pid = pid;
49
+ tracee->next = (struct pt_tracee *)*headpp;
36
50
 
37
51
  (*headpp) = tracee;
38
52
 
@@ -42,14 +56,15 @@ pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
42
56
  static pt_tracee_t *
43
57
  pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid)
44
58
  {
45
- pt_tracee_t *tracee = *headpp;
59
+ pt_tracee_t *headp = *headpp;
60
+ pt_tracee_t *current = headp;
46
61
 
47
- while (tracee != NULL) {
48
- if (tracee->pid == pid) {
49
- return tracee;
62
+ while (current) {
63
+ if (current->pid == pid) {
64
+ return current;
50
65
  }
51
66
 
52
- tracee = tracee->next;
67
+ current = current->next;
53
68
  }
54
69
 
55
70
  return pt_tracee_add(headpp, pid);
@@ -58,14 +73,14 @@ pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid)
58
73
  static int
59
74
  pt_tracee_wipedoutq(pt_tracee_t *headp)
60
75
  {
61
- pt_tracee_t *tracee = headp;
76
+ pt_tracee_t *current = headp;
62
77
 
63
- while (tracee != NULL) {
64
- if (tracee->dead == 0) {
78
+ while (current) {
79
+ if (current->dead == 0) {
65
80
  return 0;
66
81
  }
67
82
 
68
- tracee = tracee->next;
83
+ current = current->next;
69
84
  }
70
85
 
71
86
  return 1;
@@ -74,20 +89,14 @@ pt_tracee_wipedoutq(pt_tracee_t *headp)
74
89
  static void
75
90
  pt_tracee_free(pt_tracee_t **headpp)
76
91
  {
77
- pt_tracee_t *tracee = *headpp;
92
+ pt_tracee_t *tracee = *headpp;
78
93
 
79
- if (tracee->next) {
94
+ if (tracee) {
80
95
  pt_tracee_free(&tracee->next);
81
- }
82
-
83
- free(tracee);
84
- (*headpp) = NULL;
85
- }
86
96
 
87
- static int
88
- pt_io_closedq(VALUE io)
89
- {
90
- return rb_funcall(io, rb_intern("closed?"), 0) == Qtrue;
97
+ free(tracee);
98
+ tracee = NULL;
99
+ }
91
100
  }
92
101
 
93
102
  static void *
@@ -101,7 +110,7 @@ pt_wait(void *argp)
101
110
  }
102
111
 
103
112
  static void
104
- pt_get_data(pid_t pid, long addr, char *string, int length)
113
+ pt_read_data(pid_t pid, long addr, char *string, int length)
105
114
  {
106
115
  int i;
107
116
  long data;
@@ -123,39 +132,33 @@ pt_ptrace_syscall(pid_t pid, long signal)
123
132
  rb_thread_schedule();
124
133
  }
125
134
 
126
- static void
127
- pt_loop(unsigned int fd, VALUE write_io, VALUE read_io, VALUE wait_queue, pt_tracee_t *tracee_headp)
135
+ static VALUE
136
+ pt_loop(VALUE loop_args)
128
137
  {
138
+ pt_loop_args_t *args = (void *)loop_args;
139
+
129
140
  char *string = NULL;
130
141
  int syscall = 0;
131
142
  int status;
132
- long signal;
133
143
  pid_t pid;
134
144
  pt_tracee_t *tracee;
135
145
  pt_wait_args_t pt_wait_args = {&status};
136
146
 
137
- // NOTE: system call: eax, arguments: rdi, rsi, rdx, r10, r8, r9
147
+ // NOTE: system call: orig_rax, arguments: rdi, rsi, rdx, r10, r8, r9
138
148
  struct user_regs_struct regs;
139
149
 
140
150
  while (1) {
141
151
  rb_thread_call_without_gvl(pt_wait, &pt_wait_args, RUBY_UBF_PROCESS, NULL);
142
152
 
143
- signal = 0;
144
153
  pid = pt_wait_args.pid;
145
- tracee = pt_tracee_find_or_add(&tracee_headp, pid);
146
-
147
- if (pt_io_closedq(read_io)) {
148
- pt_tracee_free(&tracee_headp);
149
-
150
- return;
151
- }
154
+ tracee = pt_tracee_find_or_add(&args->tracee, pid);
152
155
 
153
156
  if (tracee->activated == 0) {
154
- ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);
155
157
  tracee->activated = 1;
158
+ rb_funcall(*args->wait_queue, rb_intern("enq"), 1, INT2FIX((int)pid));
156
159
 
157
- rb_funcall(wait_queue, rb_intern("enq"), 1, INT2FIX((int)pid));
158
- pt_ptrace_syscall(pid, signal);
160
+ ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE);
161
+ pt_ptrace_syscall(pid, 0);
159
162
 
160
163
  continue;
161
164
  }
@@ -163,18 +166,15 @@ pt_loop(unsigned int fd, VALUE write_io, VALUE read_io, VALUE wait_queue, pt_tra
163
166
  if (WIFEXITED(status) || WIFSIGNALED(status)) {
164
167
  tracee->dead = 1;
165
168
 
166
- if (pt_tracee_wipedoutq(tracee_headp)) {
167
- pt_tracee_free(&tracee_headp);
168
-
169
- return;
169
+ if (pt_tracee_wipedoutq(args->tracee)) {
170
+ return Qnil;
170
171
  }
171
172
 
172
173
  continue;
173
174
  }
174
175
 
175
176
  if (!(WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)) {
176
- signal = WSTOPSIG(status);
177
- pt_ptrace_syscall(pid, signal);
177
+ pt_ptrace_syscall(pid, WSTOPSIG(status));
178
178
 
179
179
  continue;
180
180
  }
@@ -182,35 +182,30 @@ pt_loop(unsigned int fd, VALUE write_io, VALUE read_io, VALUE wait_queue, pt_tra
182
182
  ptrace(PTRACE_GETREGS, pid, 0, &regs);
183
183
 
184
184
  if (regs.orig_rax != SYS_write) {
185
- pt_ptrace_syscall(pid, signal);
185
+ pt_ptrace_syscall(pid, 0);
186
186
 
187
187
  continue;
188
188
  }
189
189
 
190
190
  syscall = 1 - syscall;
191
- if (syscall && (fd == 0 || regs.rdi == fd)) {
191
+ if (syscall && (args->fd == 0 || regs.rdi == args->fd)) {
192
192
  string = malloc(regs.rdx + sizeof(long));
193
193
 
194
194
  if (string == NULL) {
195
195
  exit(EXIT_FAILURE);
196
196
  }
197
197
 
198
- pt_get_data(pid, regs.rsi, string, regs.rdx);
199
-
200
- if (pt_io_closedq(read_io)) {
201
- free(string);
202
- pt_tracee_free(&tracee_headp);
203
-
204
- return;
205
- }
198
+ pt_read_data(pid, regs.rsi, string, regs.rdx);
206
199
 
207
- rb_io_write(write_io, rb_str_new_cstr(string));
200
+ rb_yield(rb_ary_new_from_args(3, rb_str_new_cstr(string), INT2FIX((int)pid), INT2FIX(args->fd)));
208
201
 
209
202
  free(string);
210
203
  }
211
204
 
212
- pt_ptrace_syscall(pid, signal);
205
+ pt_ptrace_syscall(pid, 0);
213
206
  }
207
+
208
+ return Qnil;
214
209
  }
215
210
 
216
211
  static VALUE
@@ -240,15 +235,24 @@ pt_detach(VALUE klass, VALUE pidv)
240
235
  }
241
236
 
242
237
  static VALUE
243
- pt_trace(VALUE klass, VALUE fdp, VALUE write_io, VALUE read_io, VALUE wait_queue)
238
+ pt_finalize(VALUE traceev)
239
+ {
240
+ pt_tracee_t *tracee = (void *)traceev;
241
+
242
+ pt_tracee_free(&tracee);
243
+
244
+ return Qnil;
245
+ }
246
+
247
+ static VALUE
248
+ pt_trace(VALUE klass, VALUE fdv, VALUE wait_queue)
244
249
  {
245
- pt_tracee_t *tracee_headp = NULL;
250
+ pt_tracee_t *tracee = NULL;
251
+ pt_loop_args_t loop_args = {FIX2INT(fdv), tracee, &wait_queue};
246
252
 
247
- Check_Type(fdp, T_FIXNUM);
248
- Check_Type(write_io, T_FILE);
249
- Check_Type(read_io, T_FILE);
253
+ Check_Type(fdv, T_FIXNUM);
250
254
 
251
- pt_loop((unsigned int)FIX2INT(fdp), write_io, read_io, wait_queue, tracee_headp);
255
+ rb_ensure(pt_loop, (VALUE)&loop_args, pt_finalize, (VALUE)tracee);
252
256
 
253
257
  return Qnil;
254
258
  }
@@ -258,7 +262,7 @@ Init_process_tail()
258
262
  {
259
263
  VALUE ProcessTail = rb_define_module("ProcessTail");
260
264
 
261
- rb_define_singleton_method(ProcessTail, "attach", pt_attach, 1);
262
- rb_define_singleton_method(ProcessTail, "do_trace", pt_trace, 4);
263
- rb_define_singleton_method(ProcessTail, "detach", pt_detach, 1);
265
+ rb_define_private_method(rb_singleton_class(ProcessTail), "attach", pt_attach, 1);
266
+ rb_define_private_method(rb_singleton_class(ProcessTail), "do_trace", pt_trace, 2);
267
+ rb_define_private_method(rb_singleton_class(ProcessTail), "detach", pt_detach, 1);
264
268
  }
@@ -1,3 +1,3 @@
1
1
  module ProcessTail
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/lib/process_tail.rb CHANGED
@@ -7,50 +7,69 @@ module ProcessTail
7
7
 
8
8
  class << self
9
9
  def open(pid, fd = :stdout)
10
- io = trace(pid, fd)
10
+ read_io, write_io = IO.pipe
11
+ trace_thread = trace(pid, fd) {|str, *|
12
+ unless read_io.closed?
13
+ write_io.write str
14
+ else
15
+ trace_thread.kill
16
+ end
17
+ }
18
+
19
+ wait_thread = fork_wait_thread(trace_thread, [read_io, write_io])
20
+
21
+ if block_given?
22
+ value = yield(read_io)
23
+
24
+ trace_thread.kill
25
+ wait_thread.join
11
26
 
12
- block_given? ? yield(io) : io
13
- ensure
14
- io.close if block_given? && !io.closed?
27
+ value
28
+ else
29
+ read_io
30
+ end
15
31
  end
16
32
 
17
- def trace(pid, fd = :stdout)
33
+ def trace(pid, fd = :stdout, &block)
18
34
  TRACE_LOCK.synchronize {
19
- trace_without_lock(pid, fd)
35
+ trace_without_lock(pid, fd, &block)
20
36
  }
21
37
  end
22
38
 
23
39
  private
24
40
 
25
- def trace_without_lock(pid, fd)
26
- read_io, write_io = IO.pipe
27
- task_ids = extract_task_ids(pid)
28
- wait_queue = Queue.new
29
-
30
- thread = Thread.fork {
41
+ def trace_without_lock(pid, fd, &block)
42
+ task_ids = extract_task_ids(pid)
43
+ wait_queue = Queue.new
44
+ trace_thread = Thread.fork {
31
45
  begin
32
- extract_task_ids(pid).each do |tid|
46
+ task_ids.each do |tid|
33
47
  attach tid
34
48
  end
35
49
 
36
- do_trace extract_fd(fd), write_io, read_io, wait_queue
50
+ do_trace extract_fd(fd), wait_queue, &block
37
51
  ensure
38
- write_io.close unless write_io.closed?
39
-
40
52
  task_ids.each do |tid|
41
53
  detach tid
42
54
  end
43
55
  end
44
56
  }
45
57
 
46
- at_exit do
47
- thread.kill
48
- thread.join
49
- end
50
-
51
58
  wait_all_attach task_ids, wait_queue
52
59
 
53
- read_io
60
+ trace_thread
61
+ end
62
+
63
+ def fork_wait_thread(trace_thread, pipe)
64
+ Thread.fork {
65
+ begin
66
+ trace_thread.join
67
+ ensure
68
+ pipe.each do |io|
69
+ io.close unless io.closed?
70
+ end
71
+ end
72
+ }
54
73
  end
55
74
 
56
75
  def wait_all_attach(task_ids, waitq)
@@ -21,15 +21,14 @@ describe ProcessTail do
21
21
  def expect_same_output_and_no_error(fd, method, string)
22
22
  greeting = process_maker { send(method, string) }
23
23
  pid = greeting.call
24
- io = ProcessTail.trace(pid, fd)
25
24
 
26
- Process.kill :USR1, pid # resume child
25
+ ProcessTail.open pid, fd do |io|
26
+ Process.kill :USR1, pid # resume child
27
27
 
28
- "#{string}\n".each_line do |expected|
29
- expect(io.gets).to eq expected
28
+ "#{string}\n".each_line do |expected|
29
+ expect(io.gets).to eq expected
30
+ end
30
31
  end
31
-
32
- io.close
33
32
  end
34
33
 
35
34
  around do |example|
@@ -40,11 +39,12 @@ describe ProcessTail do
40
39
  timeout 5 do
41
40
  example.run
42
41
  end
43
-
44
42
  ensure
45
43
  $recorded_children.each do |child|
46
44
  Process.kill :TERM, child
47
45
  end
46
+
47
+ sleep 1 # FIXME :(
48
48
  end
49
49
  end
50
50
 
@@ -71,9 +71,7 @@ describe ProcessTail do
71
71
 
72
72
  expect(read_io).to be_closed
73
73
  end
74
- end
75
74
 
76
- describe '.trace' do
77
75
  specify 'simple stdout' do
78
76
  expect_same_output_and_no_error :stdout, :puts, 'HELLO'
79
77
  end
@@ -82,6 +80,10 @@ describe ProcessTail do
82
80
  expect_same_output_and_no_error :stderr, :warn, 'HELLO'
83
81
  end
84
82
 
83
+ specify 'multiline' do
84
+ expect_same_output_and_no_error :stdout, :puts, "HELLO\nHELLO"
85
+ end
86
+
85
87
  specify 'multithreaded' do
86
88
  pid = Process.fork {
87
89
  trap :USR1 do
@@ -90,7 +92,7 @@ describe ProcessTail do
90
92
 
91
93
  Thread.fork do
92
94
  trap :USR2 do
93
- puts 'HELLO'
95
+ puts 'LOVE AND KISSES'
94
96
  end
95
97
 
96
98
  sleep
@@ -100,19 +102,15 @@ describe ProcessTail do
100
102
  }
101
103
 
102
104
  $recorded_children << pid
103
- io = ProcessTail.trace(pid, :stdout)
105
+ io = ProcessTail.open(pid, :stdout)
104
106
 
105
107
  Process.kill :USR1, pid
106
108
  Process.kill :USR2, pid
107
109
 
108
110
  expect(io.gets).to eq "HELLO\n"
109
- expect(io.gets).to eq "HELLO\n"
111
+ expect(io.gets).to eq "LOVE AND KISSES\n"
110
112
 
111
113
  io.close
112
114
  end
113
-
114
- specify 'multiline' do
115
- expect_same_output_and_no_error :stdout, :puts, "HELLO\nLOVE AND KISSES"
116
- end
117
115
  end
118
116
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_tail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hika Hibariya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-08 00:00:00.000000000 Z
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler