process_tail 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/bin/process_tail +6 -2
- data/ext/process_tail/extconf.rb +5 -2
- data/ext/process_tail/process_tail.c +72 -68
- data/lib/process_tail/version.rb +1 -1
- data/lib/process_tail.rb +41 -22
- data/spec/process_tail_spec.rb +14 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3adc23055f8977b049a04d0b94ddb2f9a5740cf2
|
4
|
+
data.tar.gz: 680424fb0373ed7d1e429044920ced1033151278
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/ext/process_tail/extconf.rb
CHANGED
@@ -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
|
-
|
12
|
-
abort
|
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
|
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
|
33
|
-
tracee->
|
34
|
-
tracee->
|
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 *
|
59
|
+
pt_tracee_t *headp = *headpp;
|
60
|
+
pt_tracee_t *current = headp;
|
46
61
|
|
47
|
-
while (
|
48
|
-
if (
|
49
|
-
return
|
62
|
+
while (current) {
|
63
|
+
if (current->pid == pid) {
|
64
|
+
return current;
|
50
65
|
}
|
51
66
|
|
52
|
-
|
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 *
|
76
|
+
pt_tracee_t *current = headp;
|
62
77
|
|
63
|
-
while (
|
64
|
-
if (
|
78
|
+
while (current) {
|
79
|
+
if (current->dead == 0) {
|
65
80
|
return 0;
|
66
81
|
}
|
67
82
|
|
68
|
-
|
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
|
92
|
+
pt_tracee_t *tracee = *headpp;
|
78
93
|
|
79
|
-
if (tracee
|
94
|
+
if (tracee) {
|
80
95
|
pt_tracee_free(&tracee->next);
|
81
|
-
}
|
82
|
-
|
83
|
-
free(tracee);
|
84
|
-
(*headpp) = NULL;
|
85
|
-
}
|
86
96
|
|
87
|
-
|
88
|
-
|
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
|
-
|
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
|
127
|
-
pt_loop(
|
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:
|
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(&
|
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
|
-
|
158
|
-
pt_ptrace_syscall(pid,
|
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(
|
167
|
-
|
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
|
-
|
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, ®s);
|
183
183
|
|
184
184
|
if (regs.orig_rax != SYS_write) {
|
185
|
-
pt_ptrace_syscall(pid,
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
250
|
+
pt_tracee_t *tracee = NULL;
|
251
|
+
pt_loop_args_t loop_args = {FIX2INT(fdv), tracee, &wait_queue};
|
246
252
|
|
247
|
-
Check_Type(
|
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
|
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
|
-
|
262
|
-
|
263
|
-
|
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
|
}
|
data/lib/process_tail/version.rb
CHANGED
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
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
46
|
+
task_ids.each do |tid|
|
33
47
|
attach tid
|
34
48
|
end
|
35
49
|
|
36
|
-
do_trace extract_fd(fd),
|
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
|
-
|
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)
|
data/spec/process_tail_spec.rb
CHANGED
@@ -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
|
-
|
25
|
+
ProcessTail.open pid, fd do |io|
|
26
|
+
Process.kill :USR1, pid # resume child
|
27
27
|
|
28
|
-
|
29
|
-
|
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 '
|
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.
|
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 "
|
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.
|
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-
|
11
|
+
date: 2015-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|