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 +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
|