Please disable Adblockers and enable JavaScript for domain CEWebS.cs.univie.ac.at! We have NO ADS, but they may interfere with some of our course material.
Name: lib/remar.rb
1: | # runtime event monitoring, analysis and response |
2: | require 'thread' |
3: | |
4: | class Symbol# {{{ |
5: | def <=>(s) |
6: | self.to_s <=> s.to_s |
7: | end |
8: | end# }}} |
9: | class Array# {{{ |
10: | def subset?(other) |
11: | self.each do |x| |
12: | return false unless other.include?(x) |
13: | end |
14: | true |
15: | end |
16: | |
17: | def superset?(other) |
18: | other.subset?(self) |
19: | end |
20: | end# }}} |
21: | |
22: | class REMAR |
23: | class RuleBailout < ::RuntimeError; end |
24: | class RuleError < ::RuntimeError; end |
25: | class RulesetError < ::StandardError; end |
26: | class BlockError < ::StandardError; end |
27: | |
28: | class Context# {{{ |
29: | def initialize(context,dirty) |
30: | @context = context |
31: | @dirty = dirty |
32: | end |
33: | |
34: | def method_missing(name,*args,&blk) |
35: | return [:context,name,blk] if block_given? && args.empty? |
36: | if !block_given? && args.empty? |
37: | unless @dirty.has_key?(name) |
38: | @dirty[name] = Marshal.load(Marshal.dump(@context[name])) |
39: | end |
40: | return Realization.new(@context[name]) |
41: | end |
42: | end |
43: | end# }}} |
44: | class Events# {{{ |
45: | def initialize(events) |
46: | @events = events |
47: | end |
48: | |
49: | def method_missing(name,*args,&blk) |
50: | if block_given? && args.empty? |
51: | return [:event,name,blk] |
52: | elsif !block_given? && args.empty? |
53: | return [:event,name,Proc.new{true}] |
54: | end |
55: | end |
56: | end# }}} |
57: | |
58: | class Realization# {{{ |
59: | def initialize(values) |
60: | @values = values |
61: | end |
62: | def method_missing(name,*args) |
63: | temp = nil |
64: | if args.empty? && @values.has_key?(name) |
65: | @values[name] |
66: | elsif @values.keys.find{|f| temp = f.to_s; temp == name.to_s[0..(temp.length-1)] } |
67: | op = name.to_s[temp.length..-1] |
68: | case op |
69: | when "=" |
70: | @values[temp.to_sym] = args[0] |
71: | end |
72: | else |
73: | super |
74: | end |
75: | end |
76: | end# }}} |
77: | class RuleRealization# {{{ |
78: | def initialize(event,context,calls,timers,default) |
79: | @event = event |
80: | @context = context |
81: | @calls = calls |
82: | @default = default |
83: | @timers = timers |
84: | end |
85: | def event |
86: | Realization.new(@event) |
87: | end |
88: | def context |
89: | @context |
90: | end |
91: | def timed(seconds,&what) |
92: | @timers << [seconds,what] |
93: | end |
94: | def method_missing(name,*args) |
95: | if @calls.has_key?(name) |
96: | if @calls[name].nil? |
97: | unless @default.nil? |
98: | @default.call(name,args) |
99: | end |
100: | else |
101: | @calls[name].call(*args) |
102: | end |
103: | end |
104: | end |
105: | end# }}} |
106: | class RulesetRealization# {{{ |
107: | def initialize(main,values,events,context,calls,rules_context,rules_event,c_events,c_context) |
108: | @main = main |
109: | @values = values |
110: | @events = events |
111: | @context = context |
112: | @calls = calls |
113: | @rules_context = rules_context |
114: | @rules_event = rules_event |
115: | @c_events = c_events |
116: | @c_context = c_context |
117: | end |
118: | |
119: | def value(name, identifier, value=nil)# {{{ |
120: | if name.is_a?(Symbol) |
121: | @values[name] ||= {} |
122: | if value.nil? |
123: | @values[name][identifier] = identifier |
124: | elsif value.is_a?(Symbol) |
125: | @values[name][identifier] = value |
126: | end |
127: | end |
128: | end# }}} |
129: | def event(*para)# {{{ |
130: | if !para.empty? && para[0].is_a?(Symbol) |
131: | @events[para[0]] ||= [] |
132: | para[1..-1].each do |value| |
133: | @events[para[0]] << value if value.is_a?(Symbol) |
134: | end |
135: | @events[para[0]].sort! |
136: | end |
137: | return @c_events if para.empty? |
138: | nil |
139: | end# }}} |
140: | def context(*para)# {{{ |
141: | if !para.empty? && para[0].is_a?(Symbol) |
142: | @context[para[0]] ||= {} |
143: | para[1..-1].each do |value| |
144: | if value.is_a?(Hash) |
145: | value.each do |k,v| |
146: | @context[para[0]][k] = v if k.is_a?(Symbol) |
147: | end |
148: | end |
149: | end |
150: | end |
151: | return @c_context if para.empty? |
152: | nil |
153: | end# }}} |
154: | def call(name)# {{{ |
155: | if name.is_a?(Symbol) && !@calls.include?(name) |
156: | @calls[name] = nil |
157: | (class << @main; self; end).class_eval do |
158: | define_method "on_#{name}".to_sym do |&blk| |
159: | @calls[name] = blk |
160: | end |
161: | end |
162: | end |
163: | end# }}} |
164: | def rule(*prop,&blk)# {{{ |
165: | estate = nil |
166: | prop.each do |p| |
167: | if p[0] == :event && estate.nil? |
168: | estate = prop.delete(p) |
169: | elsif p[0] == :event && !estate.nil? |
170: | raise RuleError, "rule with more than 1 event fount" |
171: | end |
172: | end |
173: | if estate |
174: | @rules_event[estate[1]] ||= [] |
175: | @rules_event[estate[1]] << [estate[2],prop,blk] |
176: | else |
177: | prop.each do |p| |
178: | @rules_context[p[1]] ||= [] |
179: | @rules_context[p[1]] << [prop,blk] |
180: | end |
181: | end |
182: | end# }}} |
183: | end# }}} |
184: | |
185: | def initialize# {{{ |
186: | @before_push = nil |
187: | @after_push = nil |
188: | @default = nil |
189: | @dirty = {} |
190: | @values = {} |
191: | @events = {} |
192: | @context = {} |
193: | @calls = {} |
194: | @rules_event = {} |
195: | @rules_context = {} |
196: | @c_events = Events.new(@events) |
197: | @c_context = Context.new(@context,@dirty) |
198: | @mutex = Mutex.new |
199: | @timers = [] |
200: | @rulesetrealization = RulesetRealization.new(self,@values,@events,@context,@calls,@rules_context,@rules_event,@c_events,@c_context) |
201: | end# }}} |
202: | |
203: | private |
204: | |
205: | def __dirty_recursive# {{{ |
206: | return if @dirty.empty? |
207: | confirmation = [] |
208: | @dirty.each do |d,c| |
209: | c.each do |k,v| |
210: | if @context[d][k] != v |
211: | confirmation << d |
212: | end |
213: | end |
214: | end |
215: | @dirty.clear |
216: | confirmation.each do |conf| |
217: | if @rules_context.has_key?(conf) |
218: | @rules_context[conf].each do |e| |
219: | begin |
220: | e[0].each do |c| |
221: | Realization.new(@context[c[1]]).instance_eval(&c[2]) || raise(RuleBailout) |
222: | end |
223: | RuleRealization.new({},@c_context,@calls,@timers,@default).instance_eval(&e[1]) |
224: | rescue RuleBailout |
225: | end |
226: | end |
227: | end |
228: | end |
229: | __dirty_recursive |
230: | end # }}} |
231: | def __cleanup# {{{ |
232: | @values.clear |
233: | @events.clear |
234: | @context.clear |
235: | @calls.clear |
236: | @rules_event.clear |
237: | @rules_context.clear |
238: | end# }}} |
239: | def __set_timer(data,seconds,what)# {{{ |
240: | Thread.new do |
241: | sleep seconds |
242: | @mutex.synchronize do |
243: | begin |
244: | RuleRealization.new(data,@c_context,@calls,@timers,@default).instance_eval(&what) |
245: | rescue RuleBailout |
246: | end |
247: | __dirty_recursive |
248: | @after_push.call(@context) unless @after_push.nil? |
249: | @timers.delete_if do |seconds,what| |
250: | __set_timer(data,seconds,what) |
251: | true |
252: | end |
253: | end |
254: | end |
255: | end# }}} |
256: | |
257: | public |
258: | attr_reader :context |
259: | |
260: | def clear(what=nil)# {{{ |
261: | case what |
262: | when nil, :context |
263: | @context.clear |
264: | when nil, :events |
265: | @events.clear |
266: | when nil, :values |
267: | @values.clear |
268: | when nil, :rules |
269: | @rules_event.clear |
270: | @rules_context.clear |
271: | when nil, :calls |
272: | @calls.clear |
273: | end |
274: | true |
275: | end# }}} |
276: | |
277: | def default(&blk)# {{{ |
278: | @default = blk if block_given? |
279: | end# }}} |
280: | |
281: | def ruleset(code = nil,&blk)# {{{ |
282: | if code.nil? && !block_given? |
283: | @rulesetrealization |
284: | else |
285: | unless block_given? |
286: | blk = Proc.new do |
287: | begin |
288: | eval("#{code}") |
289: | rescue SyntaxError => err |
290: | raise RulesetError, err.message, err.backtrace |
291: | end |
292: | end |
293: | end |
294: | begin |
295: | __cleanup |
296: | @rulesetrealization.instance_eval(&blk) |
297: | rescue => err |
298: | raise RulesetError, err.message, err.backtrace |
299: | end |
300: | self |
301: | end |
302: | end# }}} |
303: | |
304: | def test_push(name,data)# {{{ |
305: | evt_data = data.keys.sort |
306: | evt_name = @events.find{|k,v| v.subset?(evt_data) && k == name } |
307: | |
308: | if evt_name.nil? |
309: | false |
310: | else |
311: | evt_name = evt_name[0] |
312: | # map values |
313: | data.each do |d,i| |
314: | if @values.has_key?(d) && @values[d].has_key?(i) |
315: | data[d] = @values[d][i] |
316: | end |
317: | end |
318: | |
319: | # when values are defined, accept only this values |
320: | @events[evt_name].each do |ee| |
321: | unless @values[ee].nil? |
322: | unless @values[ee].values.include?(data[ee]) |
323: | return false |
324: | end |
325: | end |
326: | end |
327: | |
328: | true |
329: | end |
330: | end# }}} |
331: | |
332: | def push(name,data)# {{{ |
333: | evt_data = data.keys.sort |
334: | evt_name = @events.find{|k,v| v.subset?(evt_data) && k == name } |
335: | |
336: | if evt_name.nil? |
337: | false |
338: | else |
339: | evt_name = evt_name[0] |
340: | # map values |
341: | data.each do |d,i| |
342: | if @values.has_key?(d) && @values[d].has_key?(i) |
343: | data[d] = @values[d][i] |
344: | end |
345: | end |
346: | |
347: | # when values are defined, accept only these values |
348: | @events[evt_name].each do |ee| |
349: | unless @values[ee].nil? |
350: | unless @values[ee].values.include?(data[ee]) |
351: | return false |
352: | end |
353: | end |
354: | end |
355: | |
356: | return false if @rules_event[evt_name].nil? |
357: | |
358: | @mutex.synchronize do |
359: | @before_push.call(name,data,@context) unless @before_push.nil? |
360: | @rules_event[evt_name].each do |e| |
361: | begin |
362: | Realization.new(data).instance_eval(&e[0]) || raise(RuleBailout) |
363: | e[1].each do |c| |
364: | Realization.new(@context[c[1]]).instance_eval(&c[2]) || raise(RuleBailout) |
365: | end |
366: | RuleRealization.new(data,@c_context,@calls,@timers,@default).instance_eval(&e[2]) |
367: | rescue RuleBailout |
368: | p 'aaa' |
369: | end |
370: | end |
371: | __dirty_recursive |
372: | @after_push.call(@context) unless @after_push.nil? |
373: | @timers.delete_if do |seconds,what| |
374: | __set_timer(data,seconds,what) |
375: | true |
376: | end |
377: | end |
378: | |
379: | true |
380: | end |
381: | end# }}} |
382: | |
383: | def before_push(&blk)# {{{ |
384: | @before_push = blk |
385: | end# }}} |
386: | def after_push(&blk)# {{{ |
387: | @after_push = blk |
388: | end# }}} |
389: | end |