Skip to content

Commit 109fadd

Browse files
committed
Initial commit (but mostly working!)
0 parents  commit 109fadd

6 files changed

+420
-0
lines changed

BOILERPLATE.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2011 Exavideo LLC.
2+
#
3+
# This file is part of Exaboard.
4+
#
5+
# Exaboard is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Exaboard is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Exaboard. If not, see <http://www.gnu.org/licenses/>.
17+

ffmpeg_control.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2011 Exavideo LLC.
2+
#
3+
# This file is part of Exaboard.
4+
#
5+
# Exaboard is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Exaboard is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Exaboard. If not, see <http://www.gnu.org/licenses/>.
17+
18+
require 'patchbay'
19+
require 'json'
20+
require_relative 'subprocess'
21+
22+
class FfmpegSubprocess < Subprocess
23+
stderr /frame=\s*(\d+)/ do |match|
24+
@frames_encoded = match[1]
25+
end
26+
27+
stderr /fps=\s*(\d+)/ do |match|
28+
@fps = match[1]
29+
end
30+
31+
stderr /size=\s*(\d+\w+)/ do |match|
32+
@size = match[1]
33+
end
34+
35+
stderr /time=\s*(\S+)/ do |match|
36+
@time = match[1]
37+
end
38+
39+
stderr /bitrate=\s*(\S+)/ do |match|
40+
@bitrate = match[1]
41+
end
42+
43+
attr_reader :frames, :fps, :size, :time, :bitrate
44+
45+
cmd 'ffmpeg -i /home/armena/openreplay2/test.mjpg -f rawvideo -s 1920x1080 - >/dev/null'
46+
47+
def json_status
48+
{
49+
:time => @time,
50+
:bitrate => @bitrate,
51+
:size => @size,
52+
:fps => @fps,
53+
:frames_encoded => @frames_encoded
54+
}.merge(super)
55+
end
56+
end
57+
58+
class ProcessControlApp < Patchbay
59+
get '/processes' do
60+
render :json => (@processes.map { |x| x.json_status }).to_json
61+
end
62+
63+
put '/process/:id/start' do
64+
pr = @processes[params[:id].to_i]
65+
pr.start
66+
67+
render :json => ''
68+
end
69+
70+
put '/process/:id/stop' do
71+
pr = @processes[params[:id].to_i]
72+
# send SIGTERM, if process not dead after 5 seconds send SIGKILL
73+
puts "sending TERM signal"
74+
pr.kill("-TERM")
75+
pr.wait(5)
76+
if pr.is_running
77+
puts "sending KILL signal"
78+
pr.kill("-KILL")
79+
pr.wait
80+
else
81+
puts "process died on its own"
82+
end
83+
84+
render :json => ''
85+
end
86+
87+
put '/process/:id' do
88+
data = incoming_json
89+
@processes[params[:id].to_i].cmd(data['cmd'])
90+
91+
render :json => ''
92+
end
93+
94+
attr_accessor :processes
95+
end
96+
97+
app = ProcessControlApp.new
98+
app.processes = [ FfmpegSubprocess.new ]
99+
app.run
100+

subprocess.rb

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# Copyright 2011 Exavideo LLC.
2+
#
3+
# This file is part of Exaboard.
4+
#
5+
# Exaboard is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Exaboard is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with Exaboard. If not, see <http://www.gnu.org/licenses/>.
17+
18+
class Subprocess
19+
def start
20+
# check if the process is already running
21+
if @pid
22+
update_status
23+
if @pid
24+
fail "process was already running"
25+
end
26+
end
27+
28+
pread_stdout, pwrite_stdout = IO.pipe
29+
pread_stderr, pwrite_stderr = IO.pipe
30+
pid = fork
31+
32+
if pid == nil
33+
# child process
34+
Process.setsid
35+
$stdin.close
36+
$stdout.reopen pwrite_stdout
37+
$stderr.reopen pwrite_stderr
38+
39+
exec cmd
40+
puts "exec failed???"
41+
exit! 1
42+
else
43+
@pid = pid
44+
@exited = nil
45+
Thread.new { process_input(pread_stdout, stdout_filters) }
46+
Thread.new { process_input(pread_stderr, stderr_filters) }
47+
end
48+
end
49+
50+
def process_line(filters, line)
51+
filters.each do |filter|
52+
match = filter[0].match(line)
53+
if match
54+
if (filter[1].arity == 1)
55+
self.instance_exec(match, &filter[1])
56+
elsif (filter[1].arity == 0)
57+
self.instance_exec(&filter[1])
58+
end
59+
end
60+
end
61+
end
62+
63+
def process_input(pipe, filters)
64+
begin
65+
line = ''
66+
while true
67+
ch = pipe.read(1)
68+
69+
if ch == nil
70+
break
71+
elsif ch.ord < 32
72+
process_line(filters, line)
73+
line = ''
74+
else
75+
line += ch
76+
end
77+
end
78+
rescue EOFError
79+
# do nothing
80+
rescue Exception => e
81+
p e
82+
end
83+
end
84+
85+
def update_status
86+
check_process
87+
@status ||= Process.waitpid(@pid, Process::WNOHANG)
88+
if @status
89+
@pid = nil
90+
@exited = true
91+
end
92+
end
93+
94+
def is_running
95+
if @pid.nil?
96+
false
97+
else
98+
update_status
99+
if @status.nil?
100+
true
101+
else
102+
false
103+
end
104+
end
105+
end
106+
107+
def exit_status
108+
fail 'process has not exited yet' unless @exited
109+
update_status
110+
@status.to_i
111+
end
112+
113+
def wait_with_timeout(timeout)
114+
# use this hack because Ruby doesn't support various things
115+
# that would be needed to do it right (sigtimedwait etc)
116+
start_time = Time.now
117+
118+
check_process
119+
120+
while Time.now - start_time < timeout
121+
if @status.nil?
122+
break
123+
else
124+
update_status
125+
end
126+
sleep 0.1
127+
end
128+
end
129+
130+
def wait(timeout = nil)
131+
if timeout
132+
wait_with_timeout(timeout)
133+
else
134+
check_process
135+
if @status.nil?
136+
@status = Process.waitpid(@pid)
137+
end
138+
end
139+
end
140+
141+
def kill(signal)
142+
check_process
143+
Process.kill(signal, @pid)
144+
end
145+
146+
def cmd
147+
@cmd ||= self.class.cmd
148+
@cmd
149+
end
150+
151+
def cmd=(str)
152+
@cmd = str
153+
end
154+
155+
def stderr_filters
156+
self.class.stderr_filters
157+
end
158+
159+
def stdout_filters
160+
self.class.stdout_filters
161+
end
162+
163+
164+
def self.cmd(cmd=nil)
165+
@cmd ||= ''
166+
if cmd
167+
@cmd = cmd
168+
end
169+
@cmd
170+
end
171+
172+
def self.stderr(regex, &block)
173+
stderr_filters << [ regex, block ]
174+
end
175+
176+
def self.stdout(regex, &block)
177+
stdout_filters << [ regex, block ]
178+
end
179+
180+
def self.stdout_filters
181+
@stdout_filters ||= []
182+
@stdout_filters
183+
end
184+
185+
def self.stderr_filters
186+
@stderr_filters ||= []
187+
@stderr_filters
188+
end
189+
190+
def cpu_usage_sample
191+
# this doesn't work!!
192+
193+
# do cool shit with /proc eventually
194+
myproc = IO.read("/proc/#{@pid}/stat")
195+
global_stats = IO.readlines('/proc/stat')
196+
197+
myproc_fields = myproc.split(/\s+/)
198+
p myproc_fields
199+
200+
myproc_total_cpu = myproc_fields[15].to_i + myproc_fields[16].to_i
201+
puts "cpu this process=#{myproc_total_cpu}"
202+
cpuline = ''
203+
global_stats.each do |line|
204+
if line =~ /cpu\s/
205+
cpuline = line
206+
end
207+
end
208+
209+
total_cpu = 0
210+
cpufields = cpuline.split(/\s+/)
211+
p cpufields
212+
213+
cpufields.drop(1).each do |field|
214+
total_cpu += field.to_i
215+
end
216+
217+
return [myproc_total_cpu, total_cpu]
218+
end
219+
220+
def cpu_usage
221+
p1, t1 = cpu_usage_sample
222+
sleep 0.1
223+
p2, t2 = cpu_usage_sample
224+
225+
(p2 - p1).to_f / (t2 - t1).to_f
226+
end
227+
228+
def json_status
229+
if is_running
230+
{
231+
:is_running => true,
232+
:pid => @pid,
233+
:cpu => cpu_usage,
234+
:cmd => cmd
235+
}
236+
else
237+
{
238+
:is_running => false,
239+
:cmd => cmd
240+
}
241+
end
242+
end
243+
244+
protected
245+
def check_process
246+
if @pid == nil
247+
fail "process was never started"
248+
end
249+
end
250+
end

0 commit comments

Comments
 (0)