SheepDog 0.1.0.20110705

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1,2 @@
1
+ = Muriel Salvan (murielsalvan@users.sourceforge.net)
2
+ * 0.1.0.20110705
data/ChangeLog ADDED
@@ -0,0 +1,5 @@
1
+ = StatsCollect Release History
2
+
3
+ == 0.1.0.20110705 (Alpha)
4
+
5
+ * Initial version
data/Credits ADDED
@@ -0,0 +1,13 @@
1
+ = Projects used by Sheep Dog
2
+
3
+ == Ruby
4
+ * Yukihiro « matz » Matsumoto (http://www.rubyist.net/~matz/)
5
+ * http://www.ruby-lang.org/
6
+ * Thanks a lot Matz for this truly wonderful language !
7
+
8
+ == rUtilAnts
9
+ * Muriel Salvan (http://murielsalvan.users.sourceforge.net)
10
+ * http://rutilants.sourceforge.net
11
+ * Used for plugins and logging handling.
12
+
13
+ = People that helped a lot in developing SheepDog
data/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+
2
+ The license stated herein is a copy of the BSD License (modified on July 1999).
3
+ The AUTHOR mentionned below refers to the list of people involved in the
4
+ creation and modification of any file included in the delivered package.
5
+ This list is found in the file named AUTHORS.
6
+ The AUTHORS and LICENSE files have to be included in any release of software
7
+ embedding source code of this package, or using it as a derivative software.
8
+
9
+ Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ 1. Redistributions of source code must retain the above copyright notice,
15
+ this list of conditions and the following disclaimer.
16
+ 2. Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+ 3. The name of the author may not be used to endorse or promote products
20
+ derived from this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
23
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
25
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
27
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
31
+ OF SUCH DAMAGE.
data/README ADDED
@@ -0,0 +1,18 @@
1
+ -- This file is best viewed when processed by rdoc.
2
+ ++
3
+
4
+ = Sheep Dog
5
+
6
+ Simple command line tool that monitors files and processes and sends notifications or take corrective actions when problems arise. Monitor log files for errors, processes CPU and memory consumption (can kill if exceeding), respawn dead processes.
7
+
8
+ == Where is the documentation ?
9
+
10
+ Check the website at http://sheepdogsys.sourceforge.net
11
+
12
+ == Who wrote it ?
13
+
14
+ Check the AUTHORS[link:files/AUTHORS.html] file.
15
+
16
+ == What is the license ?
17
+
18
+ You can find out in the LICENSE[link:files/LICENSE.html] file.
data/ReleaseInfo ADDED
@@ -0,0 +1,8 @@
1
+
2
+ # This file has been generated by RubyPackager during a delivery.
3
+ # More info about RubyPackager: http://rubypackager.sourceforge.net
4
+ {
5
+ :Version => '0.1.0.20110705',
6
+ :Tags => [ 'Alpha' ],
7
+ :DevStatus => 'Alpha'
8
+ }
data/bin/sheepdog.rb ADDED
@@ -0,0 +1,24 @@
1
+ #!/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
4
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
5
+ #++
6
+
7
+ require 'rUtilAnts/Logging'
8
+ RUtilAnts::Logging::initializeLogging('','')
9
+ require 'tmpdir'
10
+ lLogFile = "#{Dir.tmpdir}/SheepDog_#{Process.pid}.log"
11
+ setLogFile(lLogFile)
12
+ logInfo 'Starting SheepDog'
13
+ require 'sheepdog/Executor'
14
+
15
+ lConfFileName = ARGV[0]
16
+ if (lConfFileName == nil)
17
+ logErr "Usage: sheepdog.rb <ConfigFileName>"
18
+ elsif (File.exists?(lConfFileName))
19
+ SheepDog::Executor.new.execute(eval(File.read(lConfFileName)))
20
+ else
21
+ logErr "Missing file: #{lConfFileName}"
22
+ end
23
+
24
+ File.unlink(lLogFile)
@@ -0,0 +1,288 @@
1
+ #--
2
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ require 'time'
7
+ require 'fileutils'
8
+ require 'sheepdog/Report'
9
+
10
+ module SheepDog
11
+
12
+ class Executor
13
+
14
+ # Constructor
15
+ def initialize
16
+ # Parse plugins
17
+ require 'rUtilAnts/Plugins'
18
+ RUtilAnts::Plugins::initializePlugins
19
+ parsePluginsFromDir('Notifiers', "#{File.expand_path(File.dirname(__FILE__))}/Notifiers", 'SheepDog::Notifiers')
20
+ parsePluginsFromDir('Monitors', "#{File.expand_path(File.dirname(__FILE__))}/Monitors", 'SheepDog::Monitors')
21
+ end
22
+
23
+ # Execute a given configuration
24
+ #
25
+ # Parameters:
26
+ # * *iConf* (<em>map<Symbol,Object></em>): The sheep dog configuration
27
+ def execute(iConf)
28
+ # Get the local database, storing dates of last reports sent...
29
+ lDatabaseFileName = "#{iConf[:WorkingDir]}/Database"
30
+ lDatabase = nil
31
+ if (File.exists?(lDatabaseFileName))
32
+ lDatabase = Marshal.load(File.read(lDatabaseFileName))
33
+ else
34
+ lDatabase = {
35
+ # The time of last sent reports, per notifer and monitor name
36
+ # map< MonitorName, map< NotifierName, Time > >
37
+ :LastReportsSent => {}
38
+ }
39
+ end
40
+
41
+ # The list of monitor reports to be sent at the end of the process, per notifier, along with their respective configuration
42
+ # map< NotifierName, map< MonitorName, list < [ NotificationConf, list< ReportFileName > ] > > >
43
+ lGroupedMonitorReports = {}
44
+ # The map of monitor reports that are sent by our run
45
+ # map< ReportFileName >
46
+ lSentReports = {}
47
+ # The map of monitor reports that will be sent by later runs
48
+ # map< ReportFileName >
49
+ lDelayedReports = {}
50
+ # Loop through the objects to monitor
51
+ iConf[:Monitors].each do |iMonitorName, iMonitorInfo|
52
+ # Check that it is a known monitor, by accessing the plugin
53
+ lMonitorPluginInstance, lError = getPluginInstance('Monitors', iMonitorInfo[:Type])
54
+ if (lMonitorPluginInstance == nil)
55
+ # Unknown monitor
56
+ logErr "Unknown Monitor #{iMonitorInfo[:Type]}: #{lError}. Ignoring corresponding monitoring process. Please check configuration."
57
+ else
58
+ # Create the report to be filled by this process
59
+ lReport = Report.new
60
+ # Create the monitor configuration dir
61
+ lMonitorDir = "#{iConf[:WorkingDir]}/#{iMonitorName}"
62
+ FileUtils::mkdir_p(lMonitorDir)
63
+ # Set instance variables and methods for this monitor
64
+ lMonitorPluginInstance.instance_variable_set(:@SheepDogConf, iConf)
65
+ lMonitorPluginInstance.instance_variable_set(:@Report, lReport)
66
+ lMonitorPluginInstance.instance_variable_set(:@MonitorDir, lMonitorDir)
67
+ if (!lMonitorPluginInstance.respond_to?(:report))
68
+ # Report an entry
69
+ #
70
+ # Parameters:
71
+ # * *iEntry* (_String_): Entry to be reported
72
+ def lMonitorPluginInstance.report(iEntry)
73
+ @Report.addEntry(iEntry)
74
+ logInfo "Report: #{iEntry}"
75
+ end
76
+ end
77
+ # Call this monitor
78
+ begin
79
+ logInfo "Executing monitoring process #{iMonitorName} ..."
80
+ lMonitorPluginInstance.execute(iMonitorInfo)
81
+ rescue Exception
82
+ logErr "Exception while executing monitor #{iMonitorName}: #{$!}.\n#{$!.backtrace.join("\n")}"
83
+ report "!!! Exception while executing monitor #{iMonitorName}: #{$!}.\n#{$!.backtrace.join("\n")}"
84
+ end
85
+ # If this report is not empty, save it in a file
86
+ lCurrentReportFileName = nil
87
+ lCurrentReportTime = nil
88
+ if (!lReport.empty?)
89
+ lCurrentReportTime = Time.now.utc
90
+ lCurrentReportFileName = "#{lMonitorDir}/Report_#{lCurrentReportTime.strftime('%Y-%m-%d-%H-%M-%S')}"
91
+ File.open(lCurrentReportFileName, 'w') do |oFile|
92
+ oFile.write(Marshal.dump(lReport))
93
+ end
94
+ end
95
+ # Get the list of reports to send, per time
96
+ # map< Time, FileName >
97
+ lReportFiles = {}
98
+ Dir.glob("#{lMonitorDir}/Report_*").each do |iReportFile|
99
+ lMatch = File.basename(iReportFile).match(/^Report_(\d\d\d\d)-(\d\d)-(\d\d)-(\d\d)-(\d\d)-(\d\d)$/)
100
+ if (lMatch == nil)
101
+ logErr "Invalid file report name: #{iReportFile}. Ignoring it."
102
+ else
103
+ lReportFiles[Time.parse("#{lMatch[1]}-#{lMatch[2]}-#{lMatch[3]} #{lMatch[4]}:#{lMatch[5]}:#{lMatch[6]} UTC")] = iReportFile
104
+ end
105
+ end
106
+ # For each report file, compute the list of notifiers that will send it
107
+ if (!lReportFiles.empty?)
108
+ # There are some report files to be (maybe) sent.
109
+ # Loop through the notifiers.
110
+ iMonitorInfo[:Notifiers].each do |iNotifierName, iNotifierConf|
111
+ lNotifierID = iNotifierConf[:Type]
112
+ if (iNotifierConf[:GroupReports] == nil)
113
+ # Send the report now if it exists
114
+ if (lCurrentReportFileName != nil)
115
+ # Send [iMonitorInfo, [lCurrentReportFileName]] to iNotifierName
116
+ if (iNotifierConf[:GroupWithOtherMonitors] == true)
117
+ if (lGroupedMonitorReports[lNotifierID] == nil)
118
+ lGroupedMonitorReports[lNotifierID] = {}
119
+ end
120
+ if (lGroupedMonitorReports[lNotifierID][iMonitorName] == nil)
121
+ lGroupedMonitorReports[lNotifierID][iMonitorName] = []
122
+ end
123
+ lGroupedMonitorReports[lNotifierID][iMonitorName] << [ iNotifierConf, [ lCurrentReportFileName ] ]
124
+ else
125
+ notify(iConf, {lNotifierID => {iMonitorName => [ [ iNotifierConf, [lCurrentReportFileName] ] ] }}, lDelayedReports)
126
+ end
127
+ lSentReports[lCurrentReportFileName] = nil
128
+ # Remember last report sent
129
+ if (lDatabase[:LastReportsSent][iMonitorName] == nil)
130
+ lDatabase[:LastReportsSent][iMonitorName] = {}
131
+ end
132
+ lDatabase[:LastReportsSent][iMonitorName][iNotifierName] = lCurrentReportTime
133
+ end
134
+ else
135
+ # Get the interval in seconds
136
+ lSecsInterval = getSecsInterval(iNotifierConf[:GroupReports])
137
+ # Maybe we don't want to send reports now
138
+ # Get the last time we sent reports for this one
139
+ if ((lDatabase[:LastReportsSent][iMonitorName] != nil) and
140
+ (lDatabase[:LastReportsSent][iMonitorName][iNotifierName] != nil) and
141
+ ((Time.now.utc - lDatabase[:LastReportsSent][iMonitorName][iNotifierName]) < lSecsInterval))
142
+ # Reports from last one sent to the most recent one are marked to be sent later
143
+ lReportFiles.each do |iReportTime, iReportFileName|
144
+ if (iReportTime > lDatabase[:LastReportsSent][iMonitorName][iNotifierName])
145
+ # This report will be sent another time
146
+ lDelayedReports[iReportFileName] = nil
147
+ end
148
+ end
149
+ else
150
+ # Send all corresponding reports now
151
+ lLastReportSentDate = nil
152
+ if ((lDatabase[:LastReportsSent][iMonitorName] != nil) and
153
+ (lDatabase[:LastReportsSent][iMonitorName][iNotifierName] != nil))
154
+ lLastReportSentDate = lDatabase[:LastReportsSent][iMonitorName][iNotifierName]
155
+ else
156
+ lLastReportSentDate = Time.parse('1970-01-01 00:00:00 UTC')
157
+ end
158
+ lReportFilesToSend = []
159
+ lLastReportTime = Time.parse('1970-01-01 00:00:00 UTC')
160
+ lReportFiles.each do |iReportTime, iReportFileName|
161
+ if (iReportTime > lLastReportSentDate)
162
+ lReportFilesToSend << iReportFileName
163
+ lSentReports[iReportFileName] = nil
164
+ if (iReportTime > lLastReportTime)
165
+ lLastReportTime = iReportTime
166
+ end
167
+ end
168
+ end
169
+ if (!lReportFilesToSend.empty?)
170
+ # Send [iMonitorInfo, lReportFilesToSend] to iNotifierName
171
+ if (iNotifierConf[:GroupWithOtherMonitors] == true)
172
+ if (lGroupedMonitorReports[lNotifierID] == nil)
173
+ lGroupedMonitorReports[lNotifierID] = {}
174
+ end
175
+ if (lGroupedMonitorReports[lNotifierID][iMonitorName] == nil)
176
+ lGroupedMonitorReports[lNotifierID][iMonitorName] = []
177
+ end
178
+ lGroupedMonitorReports[lNotifierID][iMonitorName] << [ iNotifierConf, lReportFilesToSend ]
179
+ else
180
+ notify(iConf, {lNotifierID => {iMonitorName => [ [ iNotifierConf, lReportFilesToSend ] ]}}, lDelayedReports)
181
+ end
182
+ # Remember last report sent
183
+ if (lDatabase[:LastReportsSent][iMonitorName] == nil)
184
+ lDatabase[:LastReportsSent][iMonitorName] = {}
185
+ end
186
+ lDatabase[:LastReportsSent][iMonitorName][iNotifierName] = lLastReportTime
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ if (!lGroupedMonitorReports.empty?)
195
+ # Send all notifications that were grouped
196
+ notify(iConf, lGroupedMonitorReports, lDelayedReports)
197
+ end
198
+ # Now we can delete reports that were sent and are also not marked for delayed sending
199
+ lSentReports.each do |iReportFileName, iNil|
200
+ if (!lDelayedReports.has_key?(iReportFileName))
201
+ File.unlink(iReportFileName)
202
+ end
203
+ end
204
+ # Log reports to be sent delayed
205
+ lDelayedReports.keys.each do |iReportFileName|
206
+ logInfo "Report to be sent later: #{iReportFileName}"
207
+ end
208
+
209
+ # Write back database
210
+ File.open(lDatabaseFileName, 'w') do |oFile|
211
+ oFile.write(Marshal.dump(lDatabase))
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ # Process notifications to be sent.
218
+ #
219
+ # Parameters:
220
+ # * *iConf* (<em>map<Symbol,Object></em>): SheepDog config
221
+ # * *iNotificationsInfo* (<em>map<NotifierName,map<MonitorName,list<[NotifierConf,list<ReportFileName>]>>></em>): The list of report files to send along with their notifier config, per monitor name, per notifier name
222
+ # * *ioErrorReports* (<em>map<ReportFileName,nil></em>): The set of report file names that could not be sent through notifications
223
+ def notify(iConf, iNotificationsInfo, ioErrorReports)
224
+ iNotificationsInfo.each do |iNotifierName, iNotifierNotificationsInfo|
225
+ # Find this notifier
226
+ if (iConf[:Notifiers][iNotifierName] == nil)
227
+ logErr "Unknown notifier named #{iNotifierName}. Ignoring notifications to be sent there. Please check configuration."
228
+ else
229
+ accessPlugin('Notifiers', iConf[:Notifiers][iNotifierName][:Type]) do |iNotifierPlugin|
230
+ # List of reports to send through this notifier
231
+ # list< Report >
232
+ lLstReports = []
233
+ # Set of report files that will be sent through this call
234
+ # map< ReportFileName, nil >
235
+ lReportFilesSet = {}
236
+ iNotifierNotificationsInfo.each do |iMonitorName, iLstMonitorNotificationsInfo|
237
+ iLstMonitorNotificationsInfo.each do |iMonitorNotificationsInfo|
238
+ iNotifierConf, iLstReportFileNames = iMonitorNotificationsInfo
239
+ iLstReportFileNames.each do |iReportFileName|
240
+ lReport = nil
241
+ begin
242
+ lReport = Marshal.load(File.read(iReportFileName))
243
+ rescue Exception
244
+ logErr "Invalid report stored in file #{iReportFileName}: #{$!}.\n#{$!.backtrace.join("\n")}"
245
+ ioErrorReports[iReportFileName] = nil
246
+ lReport = nil
247
+ end
248
+ if (lReport != nil)
249
+ lReport.setReportFileName(iReportFileName)
250
+ if (iNotifierConf[:Title] != nil)
251
+ lReport.setTitle(iNotifierConf[:Title])
252
+ end
253
+ lLstReports << lReport
254
+ lReportFilesSet[iReportFileName] = nil
255
+ end
256
+ end
257
+ end
258
+ end
259
+ begin
260
+ logInfo "===== Send notification to #{iNotifierName} of #{lLstReports.size} reports..."
261
+ iNotifierPlugin.sendNotification(iConf[:Notifiers][iNotifierName], lLstReports)
262
+ rescue Exception
263
+ logErr "Exception while sending notification from #{iNotifierName} for reports #{lReportFilesSet.keys.join(', ')}: #{$!}.\n#{$!.backtrace.join("\n")}"
264
+ ioErrorReports.merge!(lReportFilesSet)
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ # Get the number of seconds defined in a configuration
272
+ #
273
+ # Parameters:
274
+ # * *iConf* (<em>map<Symbol,Object></em>): The configuration
275
+ # Return:
276
+ # * _Fixnum_: The number of seconds
277
+ def getSecsInterval(iConf)
278
+ if (iConf[:Interval_Secs] != nil)
279
+ return iConf[:Interval_Secs]
280
+ else
281
+ logErr "Unable to decode interval from #{iConf.inspect}"
282
+ return 0
283
+ end
284
+ end
285
+
286
+ end
287
+
288
+ end
@@ -0,0 +1,74 @@
1
+ #--
2
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module SheepDog
7
+
8
+ module Monitors
9
+
10
+ class LogFile
11
+
12
+ # Execute the monitoring process for a given configuration
13
+ #
14
+ # Parameters:
15
+ # * *iConf* (<em>map<Symbol,Object></em>): The monitor configuration
16
+ def execute(iConf)
17
+ # Get past values
18
+ lReadValuesFileName = "#{@MonitorDir}/ReadValues"
19
+ lReadValues = nil
20
+ if (File.exists?(lReadValuesFileName))
21
+ lReadValues = Marshal.load(File.read(lReadValuesFileName))
22
+ else
23
+ lReadValues = {
24
+ :LastPos => 0,
25
+ :LastUpdate => Time.parse('1970-01-01 00:00:00 UTC')
26
+ }
27
+ end
28
+ if (File.exists?(iConf[:FileName]))
29
+ lUpdateTime = File.stat(iConf[:FileName]).mtime
30
+ if (lUpdateTime != lReadValues[:LastUpdate])
31
+ # The file was modified
32
+ # If the size is smaller, read from the beginning
33
+ lStartPos = nil
34
+ if (File.size(iConf[:FileName]) <= lReadValues[:LastPos])
35
+ lStartPos = 0
36
+ else
37
+ lStartPos = lReadValues[:LastPos]
38
+ end
39
+ # Read file
40
+ File.open(iConf[:FileName], 'r') do |iFile|
41
+ iFile.seek(lStartPos)
42
+ iFile.read.split("\n").each do |iLine|
43
+ # Match the line against filters
44
+ lMatch = false
45
+ iConf[:Filters].each do |iFilter|
46
+ if (iLine.match(iFilter) != nil)
47
+ lMatch = true
48
+ break
49
+ end
50
+ end
51
+ if (lMatch)
52
+ # Report this line
53
+ report iLine
54
+ end
55
+ end
56
+ lReadValues[:LastPos] = iFile.pos
57
+ end
58
+ lReadValues[:LastUpdate] = lUpdateTime
59
+ end
60
+ else
61
+ report "!!! Missing file #{iConf[:FileName]}"
62
+ lReadValues[:LastPos] = -1
63
+ end
64
+ # Write back read values
65
+ File.open(lReadValuesFileName, 'w') do |oFile|
66
+ oFile.write(Marshal.dump(lReadValues))
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,131 @@
1
+ #--
2
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ require 'rUtilAnts/Misc'
7
+ RUtilAnts::Misc::initializeMisc
8
+
9
+ module SheepDog
10
+
11
+ module Monitors
12
+
13
+ class Process
14
+
15
+ # Execute the monitoring process for a given configuration
16
+ #
17
+ # Parameters:
18
+ # * *iConf* (<em>map<Symbol,Object></em>): The monitor configuration
19
+ def execute(iConf)
20
+ # Get the list of processes
21
+ # list< Integer >
22
+ lLstPIDs = []
23
+ `ps -Af`.split("\n")[1..-1].map { |iLine| iLine.strip }.each do |iLine|
24
+ lMatch = iLine.match(/^(\S+)\s+(\d+)\s+\d+\s+\d+\s+\S+\s+\S+\s+\S+\s+(.+)$/)
25
+ if (lMatch == nil)
26
+ report "Unable to decode ps output: \"#{iLine}\". Ignoring this line."
27
+ else
28
+ lUser, lPID, lCmd = lMatch[1..3]
29
+ iConf[:Processes].each do |iProcessFilterInfo|
30
+ if (iProcessFilterInfo.empty?)
31
+ report 'Process filter info is empty. ignoring it. Please check configuration.'
32
+ else
33
+ lOut = false
34
+ if (iProcessFilterInfo.has_key?(:UserFilter))
35
+ lOut = (lUser.match(iProcessFilterInfo[:UserFilter]) == nil)
36
+ end
37
+ if ((!lOut) and
38
+ (iProcessFilterInfo.has_key?(:NameFilter)))
39
+ lOut = (lCmd.match(iProcessFilterInfo[:NameFilter]) == nil)
40
+ end
41
+ if (!lOut)
42
+ # This PID is selected
43
+ lLstPIDs << lPID.to_i
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ if (lLstPIDs.empty?)
50
+ # Maybe we want to execute something
51
+ if (iConf.has_key?(:ExecuteIfMissing))
52
+ report "Missing process. Executing \"#{iConf[:ExecuteIfMissing][:CmdLine]}\" from \"#{iConf[:ExecuteIfMissing][:Pwd]}\":"
53
+ changeDir(iConf[:ExecuteIfMissing][:Pwd]) do
54
+ report `#{iConf[:ExecuteIfMissing][:CmdLine]}`
55
+ end
56
+ end
57
+ elsif (iConf.has_key?(:Limits))
58
+ # Monitor the processes
59
+ # Set of PIDs exceeding limits
60
+ # map< Integer, nil >
61
+ lAboveLimitsPIDs = {}
62
+ lLstPIDs.each do |iPID|
63
+ lCPUPercent, lMemPercent, lVirtualSize = getPIDMetrics(iPID)
64
+ # Challenge metrics against limits
65
+ if ((iConf[:Limits].has_key?(:CPUPercent)) and
66
+ (lCPUPercent > iConf[:Limits][:CPUPercent]))
67
+ report "PID #{iPID} exceeds CPU percent limit: #{lCPUPercent} > #{iConf[:Limits][:CPUPercent]}"
68
+ lAboveLimitsPIDs[iPID] = nil
69
+ end
70
+ if ((iConf[:Limits].has_key?(:MemPercent)) and
71
+ (lMemPercent > iConf[:Limits][:MemPercent]))
72
+ report "PID #{iPID} exceeds Mem percent limit: #{lMemPercent} > #{iConf[:Limits][:MemPercent]}"
73
+ lAboveLimitsPIDs[iPID] = nil
74
+ end
75
+ if ((iConf[:Limits].has_key?(:VirtualMemSize)) and
76
+ (lVirtualSize > iConf[:Limits][:VirtualMemSize]))
77
+ report "PID #{iPID} exceeds virtual mem size limit: #{lVirtualSize} > #{iConf[:Limits][:VirtualMemSize]}"
78
+ lAboveLimitsPIDs[iPID] = nil
79
+ end
80
+ end
81
+ if (!lAboveLimitsPIDs.empty?)
82
+ # What to do with PIDs exceeding limits ?
83
+ if (iConf[:ActionAboveLimits] == :Kill)
84
+ # Kill them
85
+ report "Killing PIDs #{lAboveLimitsPIDs.keys.join(' ')} ..."
86
+ report `kill -9 #{lAboveLimitsPIDs.keys.join(' ')}`
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ # Get metrics of a PID
95
+ #
96
+ # Parameters:
97
+ # * *iPID* (_Integer_): The PID to get metrics from
98
+ # Return:
99
+ # * _Float_: The CPU percentage
100
+ # * _Float_: The mem percentage
101
+ # * _Integer_: The total virtual memory size
102
+ def getPIDMetrics(iPID)
103
+ rCPUPercent = nil
104
+ rMemPercent = nil
105
+ rVS = nil
106
+
107
+ # From top
108
+ lTopOutput = `top -n1 -p#{iPID} -b | tail -2 | head -1`.strip
109
+ lMatch = lTopOutput.match(/^\d+\s+\S+\s+\d+\s+\d+\s+\S+\s+\S+\s+\d+\s+\S+\s+(\S+)\s+(\S+)\s+\S+\s+.+$/)
110
+ if (lMatch == nil)
111
+ report "Unable to decode top output for PID #{iPID}: \"#{lTopOutput}\""
112
+ else
113
+ rCPUPercent, rMemPercent = lMatch[1..2].map { |iStrValue| iStrValue.to_f }
114
+ end
115
+ # From proc/<PID>/stat
116
+ lStatOutput = `cat /proc/#{iPID}/stat`.strip
117
+ lMatch = lStatOutput.match(/^\d+\s+\(.+\)\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)\s+/)
118
+ if (lMatch == nil)
119
+ report "Unable to decode stat output for PID #{iPID}: \"#{lStatOutput}\""
120
+ else
121
+ rVS = lMatch[1].to_i
122
+ end
123
+
124
+ return rCPUPercent, rMemPercent, rVS
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+
131
+ end
@@ -0,0 +1,49 @@
1
+ #--
2
+ # Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module SheepDog
7
+
8
+ module Notifiers
9
+
10
+ class SendMail
11
+
12
+ # Send notifications for a given list of reports.
13
+ #
14
+ # Parameters:
15
+ # * *iConf* (<em>map<Symbol,Object></em>): The notifier config
16
+ # * *iLstReports* (<em>list<Report></em>): List of reports to notify
17
+ def sendNotification(iConf, iLstReports)
18
+ lTitle = nil
19
+ lMessage = nil
20
+ if (iLstReports.size > 1)
21
+ lTitle = "#{iLstReports.size} reports"
22
+ iLstReports.each_with_index do |iReport, iIdx|
23
+ lMessage << "===============================================\n"
24
+ lMessage << "========== Report #{iIdx+1} (#{iReport.CreationTime.utc.strftime('%Y-%m-%d %H:%M:%S')} UTC from #{iReport.ReportFileName}): #{iReport.Title}\n"
25
+ lMessage << iReport.getSimpleText
26
+ lMessage << "===============================================\n\n"
27
+ end
28
+ else
29
+ lReport = iLstReports.first
30
+ lTitle = "#{lReport.Title} (#{lReport.CreationTime.utc.strftime('%Y-%m-%d %H:%M:%S')} UTC from #{lReport.ReportFileName})"
31
+ lMessage = lReport.getSimpleText
32
+ end
33
+ require 'mail'
34
+ Mail.defaults do
35
+ delivery_method(:smtp, iConf[:SMTP])
36
+ end
37
+ Mail.deliver do
38
+ from iConf[:From]
39
+ to iConf[:To]
40
+ subject "SheepDog notification - #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')} UTC - #{lTitle}"
41
+ body lMessage
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module SheepDog
7
+
8
+ module Notifiers
9
+
10
+ class StdOut
11
+
12
+ # Send notifications for a given list of reports.
13
+ #
14
+ # Parameters:
15
+ # * *iConf* (<em>map<Symbol,Object></em>): The notifier config
16
+ # * *iLstReports* (<em>list<Report></em>): List of reports to notify
17
+ def sendNotification(iConf, iLstReports)
18
+ lTitle = nil
19
+ lMessage = nil
20
+ if (iLstReports.size > 1)
21
+ lTitle = "#{iLstReports.size} reports"
22
+ iLstReports.each_with_index do |iReport, iIdx|
23
+ lMessage << "===============================================\n"
24
+ lMessage << "========== Report #{iIdx+1} (#{iReport.CreationTime.utc.strftime('%Y-%m-%d %H:%M:%S')} UTC from #{iReport.ReportFileName}): #{iReport.Title}\n"
25
+ lMessage << iReport.getSimpleText
26
+ lMessage << "===============================================\n\n"
27
+ end
28
+ else
29
+ lReport = iLstReports.first
30
+ lTitle = "#{lReport.Title} (#{lReport.CreationTime.utc.strftime('%Y-%m-%d %H:%M:%S')} UTC from #{lReport.ReportFileName})"
31
+ lMessage = lReport.getSimpleText
32
+ end
33
+ puts lTitle
34
+ puts
35
+ puts lMessage
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,73 @@
1
+ #--
2
+ # Copyright (c) 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
3
+ # Licensed under the terms specified in LICENSE file. No warranty is provided.
4
+ #++
5
+
6
+ module SheepDog
7
+
8
+ # A report is a collection of entries, associated to a monitor run
9
+ class Report
10
+
11
+ # Title of the report
12
+ # String
13
+ attr_reader :Title
14
+
15
+ # File name of the report
16
+ # String
17
+ attr_reader :ReportFileName
18
+
19
+ # Time of this report's creation (UTC)
20
+ # Time
21
+ attr_reader :CreationTime
22
+
23
+ # Constructor
24
+ def initialize
25
+ @Entries = []
26
+ @CreationTime = Time.now
27
+ @ReportFileName = nil
28
+ @Title = nil
29
+ end
30
+
31
+ # Add an entry to the report
32
+ #
33
+ # Parameters:
34
+ # * *iEntry* (_String_): Entry to be added
35
+ def addEntry(iEntry)
36
+ @Entries << iEntry
37
+ end
38
+
39
+ # Set the report's title
40
+ #
41
+ # Parameters:
42
+ # * *iTitle* (_String_): Report's title
43
+ def setTitle(iTitle)
44
+ @Title = iTitle
45
+ end
46
+
47
+ # Set the report's file name
48
+ #
49
+ # Parameters:
50
+ # * *iFileName* (_String_): Report's file name
51
+ def setReportFileName(iFileName)
52
+ @ReportFileName = iFileName
53
+ end
54
+
55
+ # Get the report as simple text
56
+ #
57
+ # Return:
58
+ # * _String_: The report as simple text
59
+ def getSimpleText
60
+ return @Entries.join("\n")
61
+ end
62
+
63
+ # Is this report empty ?
64
+ #
65
+ # Return:
66
+ # * _Boolean_: Is this report empty ?
67
+ def empty?
68
+ return @Entries.empty?
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,98 @@
1
+ {
2
+
3
+ # The working directory, where SheepDog keeps reports waiting to be sent for notification and its private database
4
+ :WorkingDir => '/my/home/sheepdog_work',
5
+
6
+ # Define notifiers
7
+ :Notifiers => {
8
+
9
+ 'Mail' => {
10
+ :Type => 'SendMail',
11
+ :SMTP => {
12
+ :address => 'localhost',
13
+ :port => 25,
14
+ :domain => 'mail.domain.com',
15
+ :user_name => 'smtpuser',
16
+ :password => 'password',
17
+ :authentication => nil,
18
+ :enable_starttls_auto => false
19
+ },
20
+ :From => 'SheepDog@domain.com',
21
+ :To => 'Admin@domain.com'
22
+ }
23
+
24
+ },
25
+
26
+ # Monitor
27
+ :Monitors => {
28
+
29
+ # Monitor production log file of Rails
30
+ 'RailsLog' => {
31
+ :Type => 'LogFile',
32
+ :Notifiers => {
33
+ 'Mail' => {
34
+ :Type => 'Mail',
35
+ :GroupReports => {
36
+ :Interval_Secs => 60*60*24
37
+ },
38
+ :Title => 'Rails production log file',
39
+ :GroupWithOtherMonitors => true
40
+ }
41
+ },
42
+
43
+ :FileName => '/my/home/rails/log/production.log',
44
+ :Filters => [
45
+ /Error/
46
+ ]
47
+ },
48
+
49
+ # Monitor StatsCollect process
50
+ 'StatsCollect' => {
51
+ :Type => 'Process',
52
+ :Notifiers => {
53
+ 'Mail' => {
54
+ :Type => 'Mail',
55
+ :Title => 'StatsCollect process',
56
+ :GroupWithOtherMonitors => true
57
+ }
58
+ },
59
+
60
+ :Processes => [
61
+ {
62
+ :UserFilter => /username/,
63
+ :NameFilter => /StatsCollect\.rb/
64
+ }
65
+ ],
66
+ :Limits => {
67
+ :CPUPercent => 5,
68
+ :MemPercent => 5,
69
+ :VirtualMemSize => 16777216
70
+ },
71
+ :ActionAboveLimits => :Kill
72
+ },
73
+
74
+ # Monitor the mongrel server
75
+ 'Mongrel' => {
76
+ :Type => 'Process',
77
+ :Notifiers => {
78
+ 'Mail' => {
79
+ :Type => 'Mail',
80
+ :Title => 'Mongrel server',
81
+ :GroupWithOtherMonitors => true
82
+ }
83
+ },
84
+
85
+ :Processes => [
86
+ {
87
+ :UserFilter => /mongreluser/,
88
+ :NameFilter => /mongrel_rails start/
89
+ }
90
+ ],
91
+ :ExecuteIfMissing => {
92
+ :CmdLine => '/usr/bin/ruby /usr/bin/mongrel_rails start -p 12004 -d -e production -P log/mongrel.pid',
93
+ :Pwd => '/my/home/rails'
94
+ }
95
+ }
96
+
97
+ }
98
+ }
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: SheepDog
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ - 20110705
10
+ version: 0.1.0.20110705
11
+ platform: ruby
12
+ authors:
13
+ - Muriel Salvan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-05 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Simple command line tool that monitors files and processes and sends notifications or take corrective actions when problems arise. Monitor log files for errors, processes CPU and memory consumption (can kill if exceeding), respawn dead processes.
23
+ email: murielsalvan@users.sourceforge.net
24
+ executables:
25
+ - sheepdog.rb
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - AUTHORS
32
+ - bin/sheepdog.rb
33
+ - ChangeLog
34
+ - Credits
35
+ - lib/sheepdog/Executor.rb
36
+ - lib/sheepdog/Monitors/LogFile.rb
37
+ - lib/sheepdog/Monitors/Process.rb
38
+ - lib/sheepdog/Notifiers/SendMail.rb
39
+ - lib/sheepdog/Notifiers/StdOut.rb
40
+ - lib/sheepdog/Report.rb
41
+ - LICENSE
42
+ - README
43
+ - ReleaseInfo
44
+ - sheepdog.conf.rb.example
45
+ has_rdoc: true
46
+ homepage: http://sheepdogsys.sourceforge.net/
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project: sheepdogsys
73
+ rubygems_version: 1.3.7
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: System administration helper to monitor files and processes.
77
+ test_files: []
78
+