]> git.immae.eu Git - perso/Immae/Projets/Ruby/Monitor.git/blame - monitor.rb
- Added possibility to mark a line in red, using escape sequence `a la xterm`
[perso/Immae/Projets/Ruby/Monitor.git] / monitor.rb
CommitLineData
8b6154e9
I
1#!/usr/bin/env ruby
2
3require "time"
4require "ncurses"
5require "inifile"
6require "open3"
7
8class IO
9 def readline_nonblock
10 buffer = ""
11 buffer << read_nonblock(1) while buffer[-1] != "\n"
12 buffer
13 rescue IO::WaitReadable => blocking
14 if (not buffer.empty?)
15 ungetc(buffer)
16 end
17 raise blocking
18 end
19end
20
21class List_Win
22 def initialize(inifile)
23 @params = inifile
24 @win = Ncurses::WINDOW.new(0, Ncurses.COLS()/4, 0, 0)
25 @win.border(*([0]*8))
26 @win.keypad(true)
27 @win.timeout(1000)
28 @win.refresh()
29 @entry = 0
30 end
31
32 def getch()
33 @win.getch()
34 end
35 def clear()
36 @win.clear()
37 end
38 def resize(x,y)
39 @win.resize(x,y)
40 end
41 def move(x,y)
42 @win.move(x,y)
43 end
44 def refresh()
45 @win.refresh()
46 end
47
48 def print_list(entry=nil)
49 if(not entry.nil?)
50 @entry = entry
51 end
52 i = 0
53 @params.each_section do |section|
54 if(@entry == i)
55 @win.attron(Ncurses::A_REVERSE)
56 end
57 @win.move(i+1,1)
58 print_line(@win,@params[section]['Name'])
59 if(@entry == i)
60 @win.attroff(Ncurses::A_REVERSE)
61 end
62 i = i+1
63 end
64 @win.border(*([0]*8))
65 @win.move(0,3)
66 @win.addstr("Menu")
67 @win.refresh()
68 end
69end
70
71class Buff_Win
72 def initialize(winsize,winpos,params)
73 @params = params
74 @win = Ncurses::WINDOW.new(0, winsize, 0, winpos)
75 @panel = Ncurses::Panel::PANEL.new(@win)
76 if(@params['Type'] == 'oneshot')
77 @buffer = Buffer.new(0)
78 else
79 @buffer = Buffer.new(@params['Buffer'].to_i)
80 end
81 @proc = nil
82 @curr_offset = 0
83 @hscroll = 0
84 update_date()
85
86 spawn_proc()
87 print_buffer()
88 end
89
90 def move_resize(winsize,winpos)
91 newwin = Ncurses::WINDOW.new(0, winsize, 0, winpos)
92 Ncurses::Panel.replace_panel(@panel, newwin)
93 Ncurses.delwin(@win)
94 @win = newwin
95 print_buffer()
96 end
97 def update_date()
98 @last_update = Time.now
99 @date = @last_update.strftime("%F %R:%S")
100 end
101
102 def win_border()
103 @win.border(*([0]*8))
104 win_header()
105 end
106 def win_header()
107 @win.move(0,3)
108 @win.addnstr(@params['Name'],@win.getmaxx-@date.length-10)
109 @win.move(0,@win.getmaxx-@date.length-2)
110 @win.addstr(@date)
111 end
112 def refresh()
113 @win.refresh()
114 end
115 def clear()
116 @win.clear()
117 end
118
119 def show_win()
120 Ncurses::Panel.top_panel(@panel)
121 Ncurses::Panel.update_panels
122 end
123
124 def hscroll(scroll=0)
125 @hscroll += scroll
126 if(@hscroll < 0)
127 @hscroll = 0
128 end
129 if(@hscroll > @buffer.maxlen()-@win.getmaxx+3)
130 @hscroll = @buffer.maxlen()-@win.getmaxx+3
131 end
132 print_buffer()
133 refresh()
134 end
135 def scroll(scroll=0,goto=nil,fact=nil)
136 if (not fact.nil?)
137 scroll = (fact * @win.getmaxy.to_f).to_i
138 elsif (not goto.nil?)
139 @curr_offset = (goto * @buffer.size()).to_i
140 scroll = 0
141 end
142 #@curr_offset -= @win.getmaxy/2
143 @curr_offset += scroll
144 if(@curr_offset < 0)
145 @curr_offset = 0
146 end
147 if(@curr_offset > @buffer.size()-@win.getmaxy+2)
148 @curr_offset = @buffer.size()-@win.getmaxy+2
149 end
150 print_buffer()
151 refresh()
152 end
153
154 def print_buffer()
9b96f7cb 155 #clear()
8b6154e9
I
156 win_border()
157 j = 1
158 @buffer.yield(@win.getmaxy-2,@curr_offset) { |l,type|
159 @win.move(j,1)
160 if(type == 1) then @win.attron(Ncurses.COLOR_PAIR(1)) end
161 print_line(@win,l,hscroll=@hscroll)
162 if(type == 1) then @win.attroff(Ncurses.COLOR_PAIR(1)) end
163 j = j+1
164 }
165 if(@buffer.has_before?)
166 @win.move(2,@win.getmaxx-1)
167 @win.attron(Ncurses::A_REVERSE)
168 @win.addstr("↑")
169 @win.attroff(Ncurses::A_REVERSE)
170 end
171 if(@buffer.has_after?)
172 @win.move(@win.getmaxy-2,@win.getmaxx-1)
173 @win.attron(Ncurses::A_REVERSE)
174 @win.addstr("↓")
175 @win.attroff(Ncurses::A_REVERSE)
176 end
177 end
178
179 def proc_readlines()
180 begin
181 while true
182 @buffer.push(@proc.readline_nonblock)
183 update_date()
184 end
185 rescue IO::WaitReadable
186 end
187 end
188
9b96f7cb 189 def update(force=false)
8b6154e9
I
190 if(@params['Type'] == 'continuous')
191 proc_readlines()
192 end
193 if(@params['Type'] == 'oneshot')
9b96f7cb 194 if(force or (Time.now - @last_update > @params['Periodic'].to_i))
8b6154e9
I
195 @buffer.clear()
196 spawn_proc()
197 end
198 end
199 print_buffer()
200 end
201
202 def spawn_proc()
203 if(@params['Type'] == 'oneshot')
204 update_date()
205 Open3.popen3(@params["Command"]) { |i,o,e,t|
206 while ((not o.eof?) or (not e.eof?))
207 rs = IO.select([o,e],nil)[0]
208 r = (rs[0].eof?)? rs[1] : rs[0]
209
210 if r.fileno == o.fileno
211 @buffer.push(r.readline)
212 elsif r.fileno == e.fileno
213 @buffer.push(r.readline,type=1)
214 end
215 end
216 }
217 elsif(@params['Type'] == 'continuous')
218 @proc = IO.popen(@params["Command"])
219 proc_readlines()
220 end
221 end
222end
223
224class Buffer
225 def initialize(size)
226 @size = size
227 @buff = []
228 @buff_type = []
229 @current = 0
230 @wrap = false
231 @before = false
232 @after = false
233 end
234 def size()
235 return @buff.length
236 end
237 def maxlen()
238 maxlen = 0
239 @buff.each do |string|
240 if string.length > maxlen
241 maxlen = string.length
242 end
243 end
244 return maxlen
245 end
246
247 def push(string,type=0)
248 if(string.chomp.empty?) then string = " " end
249 string.split( /\r?\n/ ).each do |line|
250 @buff[@current] = line
251 @buff_type[@current] = type
252 if(@size > 0)
253 @current = (1+@current) % @size
254 else
255 @current = 1+@current
256 end
257 if(@current == 0) then @wrap = true end
258 end
259 end
260 def yield(size,offset=0,&block)
9b96f7cb 261 if(size < 0) then size = 0 end
8b6154e9
I
262 range = Range.new(0,@current-1).to_a
263 if(@wrap)
264 range = Range.new(@current,@size-1).to_a + range
265 end
266 range = range.last(size+offset)[0,size]
267 @before = (size+offset < @buff.length)
268 @after = (offset != 0 and size < @buff.length)
269 if(block)
270 range.each do |i|
271 yield [@buff[i],@buff_type[i]]
272 end
273 else
274 return range.collect{|r| [@buff[r],@buff_type[r]]}
275 end
276 end
277 def has_after?()
278 return @after
279 end
280 def has_before?()
281 return @before
282 end
283 def clear()
284 @current = 0
285 @buff = []
286 @buff_type = []
287 end
288end
289
290def read_ini()
291 inifile = IniFile.load('monitorrc')
292 return inifile
293end
294
295def print_line(win, str, hscroll=0)
9b96f7cb
I
296 revert_color = false
297 str[0,5].match(/\033\[3(.)m/) { |c| #Line starts with an escape sequence. We handle that `a la xterm`
298 Ncurses.init_pair(10, c[1].to_i, Ncurses::COLOR_BLACK)
299 win.attron(Ncurses.COLOR_PAIR(10))
300 revert_color = true
301 str = str[5,str.length]
302 }
303 str = str.gsub("\011"," ")
304 #Any other control char is ignored and escaped
305 str = str.gsub(/[[:cntrl:]]/) { |m|
306 "^"+(m.ord + 64).chr
307 }
8b6154e9
I
308 if(hscroll > 0)
309 strcut = str[hscroll,str.length]
310 if(strcut.nil? or strcut.empty?)
311 str = ""
312 else
313 str = "…"+strcut
314 end
315 end
316 strlen = str.length
317 winlen = win.getmaxx-win.getcurx-1
318 if(strlen <= winlen)
319 win.addstr(str + " "*(winlen-strlen))
320 else
321 win.addstr(str[0,winlen-1]+"…")
322 end
9b96f7cb
I
323 if(revert_color)
324 win.attroff(Ncurses.COLOR_PAIR(10))
325 end
8b6154e9
I
326end
327
328def make_bufwins(inifile)
329 bufwins = []
330 inifile.each_section do |section|
331 bufwin = Buff_Win.new(Ncurses.COLS()-Ncurses.COLS()/4,
332 Ncurses.COLS()/4,
333 inifile[section])
334 bufwins.push(bufwin)
335 end
336 return bufwins
337end
338
339def update_buffers(bufwins)
340 bufwins.each do |bufwin|
341 bufwin.update()
342 end
343end
344
345def redraw_all(list,bufwins,curr_bufwin)
346 bufwins.each do |bufwin|
347 bufwin.move_resize(Ncurses.COLS()-Ncurses.COLS()/4,Ncurses.COLS()/4)
348 end
349 list.resize(Ncurses.LINES(), Ncurses.COLS()/4)
350 list.clear()
351 list.print_list()
352 list.refresh()
353 curr_bufwin.refresh()
354end
355begin
356 # initialize ncurses
357 Ncurses.initscr
358 Ncurses.start_color
359 Ncurses.cbreak # provide unbuffered input
360 Ncurses.noecho # turn off input echoing
361 #Ncurses.nonl # turn off newline translation
362 #Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
363 Ncurses.stdscr.keypad(true) # turn on keypad mode
364 Ncurses.init_pair(1, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK)
9b96f7cb 365 Ncurses.init_pair(10, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK)
8b6154e9
I
366
367
368 inifile = read_ini()
369
370 list = List_Win.new(inifile)
371 bufwins = make_bufwins(inifile)
372 entry = 0
373 cur_bufwin = bufwins[entry]
374 cur_bufwin.show_win()
375 list.print_list()
376 while(ch = list.getch()) do
377 case(ch)
378 when "n".ord
379 entry = (entry +1) % bufwins.length
380 cur_bufwin = bufwins[entry]
381 cur_bufwin.show_win()
382 list.print_list(entry=entry)
383 when "p".ord
384 entry = (entry -1) % bufwins.length
385 cur_bufwin = bufwins[entry]
386 cur_bufwin.show_win()
387 list.print_list(entry=entry)
388 when 12 #ctrl+L
389 redraw_all(list,bufwins,cur_bufwin)
9b96f7cb
I
390 when 18 #ctrl+R
391 cur_bufwin.update(force=true)
8b6154e9
I
392 when Ncurses::KEY_RESIZE
393 redraw_all(list,bufwins,cur_bufwin)
394 when Ncurses::KEY_LEFT
395 cur_bufwin.hscroll(scroll=-1)
396 when Ncurses::KEY_RIGHT
397 cur_bufwin.hscroll(scroll=1)
398 when Ncurses::KEY_DOWN
399 cur_bufwin.scroll(scroll=-1)
400 when Ncurses::KEY_UP
401 cur_bufwin.scroll(scroll=1)
402 when Ncurses::KEY_NPAGE
403 cur_bufwin.scroll(scroll=0,goto=nil,fact=-0.75)
404 when Ncurses::KEY_PPAGE
405 cur_bufwin.scroll(scroll=0,goto=nil,fact=0.75)
406 when Ncurses::KEY_HOME
407 cur_bufwin.scroll(scroll=0,goto=1.0)
408 when Ncurses::KEY_END
409 cur_bufwin.scroll(scroll=0,goto=0.0)
410 when Ncurses::ERR
411 update_buffers(bufwins)
412 cur_bufwin.show_win()
413 when "q".ord
414 break
415 else
416 next
417 end
418 end
419
420ensure
421 Ncurses.echo
422 Ncurses.nocbreak
423 Ncurses.nl
424 Ncurses.endwin
425end