Surely can...
I have eliminated the rufus scheduler by adding the same lines over and over again a dozen or so times so its effectively running manually. So its definitely the rules engine part.... this might be a bit complex but let me try:
So in the initialise I created everything:
require 'rufus/scheduler'
require 'RulesEngineCaller'
debug = true
scheduler = Rufus::Scheduler.start_new(:frequency => 1.0)
Rails.logger = Logger.new("rule-log.txt", 'daily')
Rails.logger.debug "#{Time.now.utc} - Created Notification Object"
MSG_NOTIFY = Notification.new()
rule_engine = Ruleby::Core::Engine.new
Rails.logger.debug "#{Time.now.utc} - Created New Rules Engine"
RULES_ENG = RulesEngineCaller.new(rule_engine)
unless ( File.basename($0) == "rake" && ARGV.include?("db:migrate") )
EngineRulebook.new(rule_engine).rules
if debug == true
sleep 30
RULES_ENG.rebuild_engine
RULES_ENG.run_engine
else
#initial data load in case no rules are changed at start
Rails.logger.debug "#{Time.now.utc} - Added Rules & Facts to New Rules Engine"
ActiveRecord::Base.connection_pool.with_connection do
RULES_ENG.rebuild_engine
end
end
#run re-occuring per schedule in config
scheduler.every(CONFIG[:core_settings][:run_engine_time], :allow_overlapping => false) do
ActiveRecord::Base.connection_pool.with_connection do
RULES_ENG.run_engine
end
end
scheduler.every(CONFIG[:core_settings][:forced_engine_restart], :allow_overlapping => false) do
ActiveRecord::Base.connection_pool.with_connection do
RULES_ENG.rebuild_engine
end
end
end
end
def scheduler.handle_exception(job, exception)
Rails.logger.debug "#{Time.now.utc} - job #{job.job_id} caught exception '#{exception}'"
end
Then I have my rules engine caller where I build the engine and do the running:
def rebuild_engine
Rails.logger.debug "#{Time.now.utc} - Acquiring Mutex"
@rules_mutex.synchronize {
Rails.logger.debug "#{Time.now.utc} - Mutex Acquired"
#get rid of all terminated events on a reload
@terminated_event = Event.find_all_by_terminate_flag('1')
@terminated_event.each do |ended_ticket|
Event.where(:ticket_id => ended_ticket.ticket_id, :source => ended_ticket.source).destroy_all
end
#get rid of all 'records' older than the retention date
@old_record = Record.where("updated_at < ?", CONFIG[:core_settings][:event_history_length].to_i.days.ago)
@old_record.each do |old_ticket|
Record.destroy(old_ticket)
end
#get rid of all events that arent used by any rules
#@invalid_source_events = Event.where(:source != Rule.select(:source).uniq.source)
#@invalid_source_events.each do |invalid_source_ticket|
#
Rails.logger.info "No Rule Source Match, Deleting Record: ticket_id - #{invalid_source_ticket.ticket_id} | source - #{invalid_source_ticket.source} | cust region - #{invalid_source_ticket.cust_region} | description - #{invalid_source_ticket.description} | created at - #{invalid_source_ticket.created_at}"
#Event.destroy(invalid_source_ticket)
#end
@rule_engine = Ruleby::Core::Engine.new
EngineRulebook.new(@rule_engine).rules
@events = Event.all
Rails.logger.debug "#{Time.now.utc} - Started loading Events"
@events.each do |event|
@rule_engine.assert event
end
Rails.logger.debug "#{Time.now.utc} - Finished loading Events"
Rails.logger.debug "#{Time.now.utc} - Releasing Mutex"
}
Rails.logger.debug "#{Time.now.utc} - Mutex Released"
end
def run_engine
Rails.logger.debug "#{Time.now.utc} - Run Engine"
d = Time.now.utc
puts "#{Time.now.utc} - Run Engine"
Rails.logger.debug "#{Time.now.utc} - Acquiring Mutex"
@rules_mutex.synchronize {
Rails.logger.debug "#{Time.now.utc} - Mutex Acquired"
@rule_engine.match
#hard coded alert condition if running engine takes too long
if (Time.now.utc - d) > CONFIG[:core_settings][:threshold].to_i
@fail_count = @fail_count + 1
Rails.logger.debug "#{Time.now.utc} - Processing above set threshold #{Time.now.utc - d} seconds, fail count #{@fail_count}"
if @fail_count == 5
MSG_NOTIFY.notify_group(1, "Alert Processing above set threshold x5, last processing #{Time.now.utc - d} seconds")
elsif @fail_count == 30
MSG_NOTIFY.notify_group(1, "Alert Processing above set threshold x30, last processing #{Time.now.utc - d} seconds")
elsif @fail_count == 60
MSG_NOTIFY.notify_group(1, "Alert Processing above set threshold x60, last processing #{Time.now.utc - d} seconds")
@fail_count = 0
end
else
@fail_count = 0
end
Rails.logger.debug "#{Time.now.utc} - Releasing Mutex"
}
Rails.logger.debug "#{Time.now.utc} - Mutex Released"
Rails.logger.debug "#{Time.now.utc} - Finished Engine Match #{Time.now.utc - d} "
@last_processing_timestamp = Time.now.utc
@last_processing_duration = Time.now.utc - d
#add values to an array for use with tracking history
if @last_processing_duration_history.length > 20
@last_processing_duration_history.shift
@last_processing_duration_history << @last_processing_duration
else
@last_processing_duration_history << @last_processing_duration
end
end
Ok so now you have the bits that explain (wtf) im doing in some of my code here is the rules engine part where I load in the rules programatically....
require_relative 'notification'
require 'ruleby'
include Ruleby
class EngineRulebook < Rulebook
def rules
Rails.logger.debug "#{Time.now.utc} - Loading Rules into Rulebook"
#msg = Notification.new()
@rules = Rule.all
@rules.each do |rule_name|
case
##### Rules for no other text & no ctc #####
# milestone 1 =, other text na, ctc na, denomination not used
when rule_name.milestone1_operator == "=" && rule_name.other_text_operator == "" && rule_name.ctc_id_operator == "" && rule_name.milestone1_time_value_denomination == ""
rule [Event, :m, m.milestone == rule_name.milestone1_value, m.source =~ /#{rule_name.source}|^$/, m.cust_no =~ /#{rule_name.cust_no}|^$/, m.call_type =~ /#{rule_name.call_type}|^$/, m.priority =~ /#{rule_name.priority}|^$/, m.group_owner =~ /#{rule_name.group_owner}|^$/, m.entitlement_code =~ /#{rule_name.entitlement_code}|^$/ ] do |v|
#check if rule has already fired
if v[:m].rules.include?(rule_name) == false
#output matched rule to console & logfile
#do actual alert
#add reference so rule doesn't fire again
@event = Event.find_by_id(v[:m].id)
@event.rules << rule_name
v[:m].rules << rule_name
modify v[:m]
end
#if updating a bunch of records last updated, then do it here
end
#milestone1 =, no other text, no ctc, duration based ###### THE SECTION BELOW IS THE PROBLEM#######
when rule_name.milestone1_operator == "=" && rule_name.other_text_operator == "" && rule_name.ctc_id_operator == "" && (rule_name.milestone1_time_value_denomination == "H" || rule_name.milestone1_time_value_denomination == "D")
rule [Event, :m, m.milestone == rule_name.milestone1_value, m.milestone_type =~ /^D|^S/,m.source =~ /#{rule_name.source}|^$/, m.cust_no =~ /#{rule_name.cust_no}|^$/, m.call_type =~ /#{rule_name.call_type}|^$/, m.priority =~ /#{rule_name.priority}|^$/, m.group_owner =~ /#{rule_name.group_owner}|^$/, m.entitlement_code =~ /#{rule_name.entitlement_code}|^$/ ] do |v|
#check if rule has already fired
if v[:m].rules.include?(rule_name) == false
#find the last event
@last_event = Event.where(:ticket_id => v[:m].ticket_id, :source => v[:m].source, :milestone_type => ['D', 'S']).order(:time_stamp).select(:id).last
#check current event against the last duration type event
if rule_name.milestone1_time_value_denomination == "H"
#convert hours to minutes
target_minutes = (rule_name.milestone1_time_value * 60).to_int
milestone1_target_time = (v[:m].time_stamp + target_minutes.minutes)
elsif rule_name.milestone1_time_value_denomination == "D"
#convert days to minutes
target_minutes = (rule_name.milestone1_time_value * 60 * 24).to_int
milestone1_target_time = (v[:m].time_stamp + target_minutes.minutes)
else
#erroneous data error, do not trigger but record in log
milestone1_target_time = Time.now.utc - 5.years
Rails.logger.warn "#{Time.now.utc} - DATA ERROR in #{
rule_name.id} #{rule_name.title} - #{v[:m].ticket_id} - #{v[:m].description}"
end
#check time now against the target specified
if Time.now.utc >= milestone1_target_time
#matches condition
#output matched rule to console & logfile
#do actual alert
#add reference so rule doesn't fire again
@event = Event.find_by_id(v[:m].id)
@event.rules << rule_name
v[:m].rules << rule_name
modify v[:m]
else
#do nothing, time hasn't expired yet - allows for check on next cycle
end
else
#not the last event for duration calculation
#add reference so rule doesn't fire again
@event = Event.find_by_id(v[:m].id)
@event.rules << rule_name
v[:m].rules << rule_name
modify v[:m]
end
end
end
So I build rules in a form, then i use a big case statement to load them in programatically. I try and make the rules engine do most of the heavy lifting but I have to draw the line somewhere so some extra checking is done to see if everything is true after the rules engine says its right. Then I add some values towards the end to allow the fact not to be re-run on the same rule but allow the fact to match other rules which it might use. I'm thinking I'm going to add some sort of timestamp value to facts that are used for assessment so i can delete facts that aren't used and maybe speed it up some more. Currently I'm holding about 150 rules and using 56000 facts from a live feed. This is super cool stuff and it was working awesomely until I did something (im assuming) I did discard some records which aren't referred to in any of the above code as part of a a scheduled cleanup (Records) is something I keep to tell me who recieved what notifications and had what matches per users who want to know particular rules.
Its a little complicated... if the ruleby gave me more debugging info I could figure it out, currently without the rufus scheduler the error doesnt come up but it definitely resets and tries to start again so its in sort of a loop. Pulling out the bit I mention makes it work but then I dont have a section I need.