Class: REC::State

Inherits:
Object
  • Object
show all
Defined in:
lib/rec/state.rb

Overview

A State is an object that represents the memory of something having happened. For example, “server terra is down”. It also remembers useful statistics about what caused this state to be (the original log entries and the rule they matched), for how long it should remain in memory, what it pertains to (eg. the server called ‘terra’).

A state is also useful for other rules to refer to. For example, a second rule matching “host terra is up” can check if the server is currently down by reference to the state with a title of “host terra is down”.

This is much more useful than matching log entries one by one without any memory of what has gone before. You cannot correlate events without keeping State.

Constant Summary collapse

Generate =

shortcut action to generate a message on each event

Proc.new { |state|
	state.generate()
}
Generate_and_release =

shortcut action to generate a message and release the state immediately

Proc.new { |state|
	state.generate()
	state.release()
}
Generate_first_only =

shortcut action to generate a message on first event only

Proc.new { |state|
	state.generate_first_only()
}
@@timeouts =

A array of Timeouts. A Timeout struct has two elements:

  • timestamp at which to expire

  • key of the state to be expired

[]
@@states =

A hash of states, keyed on state title

{}
@@eventsOut =

A count of new events sent to output

0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title, lifespan, params = {}) ⇒ State

Creates a new state with the given (unique) title, the lifespan before the state is forgotten, and a hash of parameters.

Note that the time is not necessarily ‘now’ because REC can be executed against historical log files. It uses the timestamp of the original log entry, not the current clock time.



135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rec/state.rb', line 135

def initialize(title, lifespan, params={})
	@created = @updated = Correlator.now()
	@title = title
	@lifespan = lifespan.to_f
	@params = params
	@count = 0
	@rid = 0
	@logs = []		# array of remembered logLines
	@details = {}	# hash of remembered details
	@@states[title] = self
	State.timeout_at(@created + @lifespan, @title)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

Allow access to any parameter by a convenience method state.capture is more succinct than state.params



229
230
231
# File 'lib/rec/state.rb', line 229

def method_missing(symbol, *args)
	@params[symbol]
end

Instance Attribute Details

#alertObject (readonly)

An alert message to be sent if subsequent events warrant it



113
114
115
# File 'lib/rec/state.rb', line 113

def alert
  @alert
end

#countObject (readonly)

Number of times this state has been matched



119
120
121
# File 'lib/rec/state.rb', line 119

def count
  @count
end

#createdObject (readonly)

time when this state was created



121
122
123
# File 'lib/rec/state.rb', line 121

def created
  @created
end

#detailsObject (readonly)

Hash of custom fields to be remembered



125
126
127
# File 'lib/rec/state.rb', line 125

def details
  @details
end

#finalObject (readonly)

An alert message to be sent when the state expires



115
116
117
# File 'lib/rec/state.rb', line 115

def final
  @final
end

#lifespanObject (readonly)

How long this state shoudl live before being automatically forgotten



111
112
113
# File 'lib/rec/state.rb', line 111

def lifespan
  @lifespan
end

#logsObject (readonly)

Array of original log entries matching this state



127
128
129
# File 'lib/rec/state.rb', line 127

def logs
  @logs
end

#paramsObject (readonly)

Hash of parameters of the rule that created this state



117
118
119
# File 'lib/rec/state.rb', line 117

def params
  @params
end

#ridObject (readonly)

unique ID of the rule which gave rise to this state



107
108
109
# File 'lib/rec/state.rb', line 107

def rid
  @rid
end

#titleObject (readonly)

The unique title for this state (eg. “server earth is down”)



109
110
111
# File 'lib/rec/state.rb', line 109

def title
  @title
end

#updatedObject (readonly)

last time this state was updated



123
124
125
# File 'lib/rec/state.rb', line 123

def updated
  @updated
end

Class Method Details

.[](key) ⇒ Object

Returns the state matching the given key (title)



50
51
52
# File 'lib/rec/state.rb', line 50

def self.[](key)
	@@states[key]
end

.expire_statesObject

Deletes all expired states, executing :onexpiry blocks before deletion



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/rec/state.rb', line 69

def self.expire_states()
	timeout = @@timeouts.first()
	while @@timeouts.length > 0 and timeout.expiry < Correlator.now() do
		state = State[timeout.key]
		if state.nil?
			@@timeouts.shift
			timeout = @@timeouts.first()
			next
		end
		#$stderr.puts("Releasing state #{state.title} with count of #{state.count}")
		final = Rule[state.rid].params[:final]
		final.call(state) if final
		@@states.delete(@@timeouts.shift().key)
		timeout = @@timeouts.first()
	end
end

.find(template, state) ⇒ Object

Returns a matching state or nil.

This is used to locate a state, typically the other state created by a pair of rules. For example, in a rule handling “Server earth is up” we want to find the state corresponding to “Server earth is down”

  • template is a string like “Server %host$s is down”

  • state is the current state



101
102
103
104
# File 'lib/rec/state.rb', line 101

def self.find(template, state)
	title = template.sprinth(state.params)
	@@states[title]
end

.statsObject

Returns a 2-element array containing:

  • the number of states

  • the number of new events sent to output



89
90
91
92
# File 'lib/rec/state.rb', line 89

def self.stats()
	statesCount = @@states.keys.length
	[statesCount, @@eventsOut]
end

.timeout_at(time, title) ⇒ Object

Add a Timeout for the given time and specified state. This timeout is sorted into the correct sequence of timeouts to make State::expire_states more efficient.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rec/state.rb', line 56

def self.timeout_at(time, title)
	tnew = Struct::Timeout.new(time, title)
	n = @@timeouts.find_index { |to|
		to.expiry > time
	}
	if n.nil?
		@@timeouts = @@timeouts << tnew
	else
		@@timeouts[n..n] = [tnew, @@timeouts[n]]
	end
end

Instance Method Details

#ageObject

Returns the age of this state



166
167
168
# File 'lib/rec/state.rb', line 166

def age
	Correlator.now() - @created
end

#extend_for(dur) ⇒ Object

Resets the expiry time to be dur seconds after current time (this may be shorter than the original lifetime)



186
187
188
189
190
191
192
193
# File 'lib/rec/state.rb', line 186

def extend_for(dur)
	n = @@timeouts.find_index { |to|
		to.title == @title
	}
	@@timeouts[n..n] = [] unless n.nil?
	expiry = Correlator.now() + dur
	self.timeout_at(expiry, @title)
end

#generate(sym = :alert) ⇒ Object

Creates a new event, writes it to the output log, and returns the event. An event (or ‘log entry’) is a timestamp followed by a message

Returns the message only (without the timestamp).



209
210
211
212
213
214
215
216
217
218
# File 'lib/rec/state.rb', line 209

def generate(sym = :alert)
	message = @params[sym].sprinth(stats())
	if message.length > 0
		event = "%s %s" % [@created.iso8601, message] + @logs.join("\n")
		print("> ") if $debug
		puts(event)
		@@eventsOut = @@eventsOut + 1
	end
	message
end

#generate_first_only(sym = :alert) ⇒ Object

Creates a new event when this state is created, but ignores later occurrences



221
222
223
# File 'lib/rec/state.rb', line 221

def generate_first_only(sym = :alert)
	generate(sym) if @count == 1
end

#release(pattern = nil) ⇒ Object

Forget a state. - if no pattern is provided, forget this state. - if a pattern is provided, use the stats for this state to determine the title of the other state, and remove that from memory. For example, if the server is back up again, we no longer need to remember that the server was down.



176
177
178
179
180
181
182
# File 'lib/rec/state.rb', line 176

def release(pattern=nil)
	if pattern.nil?
		@@states.delete(@title)
	else
		@@states.delete(pattern.sprinth(stats()))
	end
end

#statsObject

Returns the details of the state (ie. whatever custom fields were defined, eg. userid) merged with the standard statistics:

  • count: number of matches so far

  • age: age of the state

  • created: time when this state was created

  • updated: time last updated (eg. when the latest event matched this state)



201
202
203
# File 'lib/rec/state.rb', line 201

def stats()
	@details.merge({"count"=>@count, "age"=>age(), "created"=>@created, "updated"=>@updated})
end

#update(rid, matches, logLine = nil) ⇒ Object

Updates the statistics of this state (following a match). Remembers lots of useful things: - the number of matches so far

  • time last updated

  • age of this state

  • the ID of the rule which last matched

  • more details (custom fields) may be added to memory

  • message templates (for values to be interpolated into)

  • a list of the original log entries pertaining to this state



157
158
159
160
161
162
163
# File 'lib/rec/state.rb', line 157

def update(rid, matches, logLine=nil)
	@count = @count.succ
	@updated = Correlator.now()
	@rid = rid
	@details.merge!(matches)
	@logs << logLine if @params[:capture]
end