perus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +16 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/exe/perus-pinger +23 -0
  12. data/exe/perus-server +23 -0
  13. data/lib/perus/options.rb +30 -0
  14. data/lib/perus/pinger/chrome_command.rb +54 -0
  15. data/lib/perus/pinger/command.rb +123 -0
  16. data/lib/perus/pinger/commands/chrome_execute.rb +26 -0
  17. data/lib/perus/pinger/commands/chrome_navigate.rb +25 -0
  18. data/lib/perus/pinger/commands/chrome_reload.rb +29 -0
  19. data/lib/perus/pinger/commands/kill_process.rb +13 -0
  20. data/lib/perus/pinger/commands/remove_path.rb +15 -0
  21. data/lib/perus/pinger/commands/replace.rb +18 -0
  22. data/lib/perus/pinger/commands/restart.rb +9 -0
  23. data/lib/perus/pinger/commands/script.rb +59 -0
  24. data/lib/perus/pinger/commands/upgrade.rb +16 -0
  25. data/lib/perus/pinger/commands/upload.rb +15 -0
  26. data/lib/perus/pinger/metrics/chrome.rb +33 -0
  27. data/lib/perus/pinger/metrics/cpu.rb +17 -0
  28. data/lib/perus/pinger/metrics/hd.rb +14 -0
  29. data/lib/perus/pinger/metrics/mem.rb +11 -0
  30. data/lib/perus/pinger/metrics/process.rb +27 -0
  31. data/lib/perus/pinger/metrics/screenshot.rb +36 -0
  32. data/lib/perus/pinger/metrics/temp.rb +18 -0
  33. data/lib/perus/pinger/metrics/uptime.rb +10 -0
  34. data/lib/perus/pinger/metrics/value.rb +18 -0
  35. data/lib/perus/pinger/pinger.rb +218 -0
  36. data/lib/perus/pinger.rb +38 -0
  37. data/lib/perus/server/admin.rb +83 -0
  38. data/lib/perus/server/app.rb +304 -0
  39. data/lib/perus/server/db.rb +29 -0
  40. data/lib/perus/server/form.rb +73 -0
  41. data/lib/perus/server/helpers.rb +36 -0
  42. data/lib/perus/server/migrations/001_create_systems.rb +20 -0
  43. data/lib/perus/server/migrations/002_create_configs.rb +12 -0
  44. data/lib/perus/server/migrations/003_create_values.rb +23 -0
  45. data/lib/perus/server/migrations/004_create_groups.rb +12 -0
  46. data/lib/perus/server/migrations/005_create_errors.rb +15 -0
  47. data/lib/perus/server/migrations/006_create_alerts.rb +14 -0
  48. data/lib/perus/server/migrations/007_create_actions.rb +18 -0
  49. data/lib/perus/server/migrations/008_create_metrics.rb +16 -0
  50. data/lib/perus/server/migrations/009_create_command_config.rb +13 -0
  51. data/lib/perus/server/migrations/010_create_scripts.rb +13 -0
  52. data/lib/perus/server/migrations/011_create_script_commands.rb +14 -0
  53. data/lib/perus/server/migrations/012_create_config_metrics.rb +14 -0
  54. data/lib/perus/server/models/action.rb +71 -0
  55. data/lib/perus/server/models/alert.rb +9 -0
  56. data/lib/perus/server/models/command_config.rb +58 -0
  57. data/lib/perus/server/models/config.rb +30 -0
  58. data/lib/perus/server/models/config_metric.rb +15 -0
  59. data/lib/perus/server/models/error.rb +5 -0
  60. data/lib/perus/server/models/group.rb +12 -0
  61. data/lib/perus/server/models/metric.rb +53 -0
  62. data/lib/perus/server/models/script.rb +39 -0
  63. data/lib/perus/server/models/script_command.rb +15 -0
  64. data/lib/perus/server/models/system.rb +160 -0
  65. data/lib/perus/server/models/value.rb +11 -0
  66. data/lib/perus/server/public/css/reset.css +102 -0
  67. data/lib/perus/server/public/css/style.css +461 -0
  68. data/lib/perus/server/public/fonts/opensans/OpenSans-Italic.ttf +0 -0
  69. data/lib/perus/server/public/fonts/opensans/OpenSans-Light.ttf +0 -0
  70. data/lib/perus/server/public/fonts/opensans/OpenSans-LightItalic.ttf +0 -0
  71. data/lib/perus/server/public/fonts/opensans/OpenSans-Regular.ttf +0 -0
  72. data/lib/perus/server/public/fonts/opensans/OpenSans-Semibold.ttf +0 -0
  73. data/lib/perus/server/public/fonts/opensans/OpenSans-SemiboldItalic.ttf +0 -0
  74. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.eot +0 -0
  75. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.svg +1824 -0
  76. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.ttf +0 -0
  77. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.woff +0 -0
  78. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.woff2 +0 -0
  79. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.eot +0 -0
  80. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.svg +1824 -0
  81. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.ttf +0 -0
  82. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.woff +0 -0
  83. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.woff2 +0 -0
  84. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.eot +0 -0
  85. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.svg +1824 -0
  86. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.ttf +0 -0
  87. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.woff +0 -0
  88. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.woff2 +0 -0
  89. data/lib/perus/server/public/fonts/opensans/stylesheet.css +35 -0
  90. data/lib/perus/server/public/js/dygraph-combined.js +6 -0
  91. data/lib/perus/server/public/js/jquery.js +4 -0
  92. data/lib/perus/server/server.rb +32 -0
  93. data/lib/perus/server/views/admin/edit.erb +9 -0
  94. data/lib/perus/server/views/admin/index.erb +18 -0
  95. data/lib/perus/server/views/admin/new.erb +5 -0
  96. data/lib/perus/server/views/alerts/form.erb +3 -0
  97. data/lib/perus/server/views/command_config.erb +37 -0
  98. data/lib/perus/server/views/configs/edit.erb +36 -0
  99. data/lib/perus/server/views/configs/form.erb +1 -0
  100. data/lib/perus/server/views/errors.erb +6 -0
  101. data/lib/perus/server/views/groups/form.erb +1 -0
  102. data/lib/perus/server/views/index.erb +21 -0
  103. data/lib/perus/server/views/layout.erb +44 -0
  104. data/lib/perus/server/views/scripts/edit.erb +36 -0
  105. data/lib/perus/server/views/scripts/form.erb +2 -0
  106. data/lib/perus/server/views/system.erb +202 -0
  107. data/lib/perus/server/views/systems/form.erb +6 -0
  108. data/lib/perus/server/views/systems.erb +17 -0
  109. data/lib/perus/server.rb +24 -0
  110. data/lib/perus/version.rb +3 -0
  111. data/lib/perus.rb +10 -0
  112. data/perus.gemspec +36 -0
  113. metadata +354 -0
@@ -0,0 +1,11 @@
1
+ module Perus::Pinger
2
+ class Mem < Command
3
+ description 'Measures overall RAM usage as a percentage on the client.'
4
+ metric!
5
+
6
+ def run
7
+ percent = `cat /proc/meminfo | awk '{if ($1=="MemTotal:") total = $2; if ($1 == "MemFree:") free = $2;} END {print (1 - (free / total))*100}'`
8
+ {mem_all: percent.to_f}
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ module Perus::Pinger
2
+ class Process < Command
3
+ description 'Measures percentage of RAM and CPU used by the process
4
+ specified by "process_path". The results are returned to
5
+ the server under 2 metrics called "cpu_{name}" and
6
+ "mem_{name}". Valid values for "process_path" are
7
+ contained in the pinger config file.'
8
+ option :process_path, restricted: true
9
+ option :name
10
+ metric!
11
+
12
+ def run
13
+ path = options.process_path.gsub("/", "\\\\\\/")
14
+ cpu, mem = `ps aux | awk '/#{path}/ {cpu += $3; mem += $4} END {print cpu, mem;}'`.split
15
+ if `uname -s`.strip == 'Darwin'
16
+ core_count = `sysctl -n hw.ncpu`
17
+ else
18
+ core_count = `cat /proc/cpuinfo | grep processor | awk '{count += 1} END {print count}'`
19
+ end
20
+
21
+ {
22
+ "cpu_#{options.name}" => cpu.to_f / core_count.to_i,
23
+ "mem_#{options.name}" => mem.to_f
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ module Perus::Pinger
2
+ class Screenshot < Command
3
+ description 'Takes a screenshot of the primary screen on the client and
4
+ uploads it. The screenshot is saved to "path" before being
5
+ uploaded. Valid values for "path" are contained in the
6
+ pinger config file.'
7
+ option :path, default: '/tmp/screenshot.jpg', restricted: true
8
+ option :resize, default: '20%'
9
+ metric!
10
+
11
+ def run
12
+ if `uname -s`.strip == 'Darwin'
13
+ if options.resize[-1] != '%'
14
+ raise 'Non percent resize option unsupported by OS X currently'
15
+ else
16
+ percent = options.resize.to_f / 100
17
+ end
18
+
19
+ `screencapture -m -t jpg -x #{options.path}`
20
+ width = `sips -g pixelWidth #{options.path}`.match(/pixelWidth: (\d+)/)[1]
21
+ height = `sips -g pixelHeight #{options.path}`.match(/pixelHeight: (\d+)/)[1]
22
+ `sips -z #{height.to_i * percent} #{width.to_i * percent} #{options.path}`
23
+ else
24
+ `export DISPLAY=:0; import -window root -resize #{options.resize} #{options.path}`
25
+ end
26
+
27
+ @screenshot_file = File.new(options.path)
28
+ {screenshot: @screenshot_file}
29
+ end
30
+
31
+ def cleanup
32
+ @screenshot_file.close unless @screenshot_file.closed?
33
+ File.delete(options.path)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module Perus::Pinger
2
+ class Temp < Command
3
+ description 'Measures the temperature of "device" on the client. By
4
+ default, this will be a CPU.'
5
+ option :device, default: 'Physical id 0'
6
+ metric!
7
+
8
+ def run
9
+ if `uname -s`.strip == 'Darwin'
10
+ degrees = `istats cpu temp`.split[2].match(/([0-9\.]+)/)[0]
11
+ else
12
+ degrees = `sensors | grep "#{options.device}:"`.match(/#{options.device}:\s+(\S+)/)[1]
13
+ end
14
+
15
+ {temp: degrees.to_f}
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Perus::Pinger
2
+ class Uptime < Command
3
+ description 'Runs the uptime command and returns the result'
4
+ metric!
5
+
6
+ def run
7
+ {uptime: `uptime`.strip}
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module Perus::Pinger
2
+ class Value < Command
3
+ description 'Searches the file specified by "path" with the regular
4
+ expression specified by "grep". Returns the resulting
5
+ string to the server under the metric called "name". Valid
6
+ values for "path" are contained in the pinger config file.'
7
+ option :path, restricted: true
8
+ option :grep
9
+ option :name
10
+ metric!
11
+
12
+ def run
13
+ line = `cat #{options.path} | grep #{options.grep}`
14
+ value = line.match(Regexp.compile(options.match))[1]
15
+ {options.name => value}
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,218 @@
1
+ require 'securerandom'
2
+ require 'rest-client'
3
+ require 'ostruct'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ DEFAULT_PINGER_OPTIONS = {
8
+ '__anonymous__' => {
9
+ 'system_id' => 1,
10
+ 'server' => 'http://127.0.0.1:3000/'
11
+ },
12
+
13
+ # restricted values
14
+ 'Value' => {
15
+ 'path' => []
16
+ },
17
+
18
+ 'Screenshot' => {
19
+ 'path' => []
20
+ },
21
+
22
+ 'Process' => {
23
+ 'process_path' => []
24
+ },
25
+
26
+ 'Upload' => {
27
+ 'path' => []
28
+ },
29
+
30
+ 'Replace' => {
31
+ 'path' => []
32
+ },
33
+
34
+ 'RemovePath' => {
35
+ 'path' => []
36
+ },
37
+
38
+ 'KillProcess' => {
39
+ 'process_name' => []
40
+ }
41
+ }
42
+
43
+ module Perus::Pinger
44
+ class Pinger
45
+ def self.options
46
+ @options ||= Perus::Options.new
47
+ end
48
+
49
+ def initialize(options_path = DEFAULT_PINGER_OPTIONS_PATH)
50
+ Pinger.options.load(options_path, DEFAULT_PINGER_OPTIONS)
51
+
52
+ # cache urls on initialisation since the urls depend on values known
53
+ # at startup and that won't change over the object lifetime
54
+ config_path = URI("/systems/#{Pinger.options.system_id}/config")
55
+ pinger_path = URI("/systems/#{Pinger.options.system_id}/ping")
56
+ server_uri = URI(Pinger.options.server)
57
+
58
+ @config_url = (server_uri + config_path).to_s
59
+ @pinger_url = (server_uri + pinger_path).to_s
60
+
61
+ @metrics = []
62
+ @metric_results = {}
63
+ @metric_errors = {}
64
+
65
+ @actions = []
66
+ @action_results = {}
67
+ @late_actions = []
68
+ end
69
+
70
+ def run
71
+ load_config
72
+ run_metrics
73
+ run_actions
74
+ send_response
75
+ cleanup
76
+ end
77
+
78
+ #----------------------
79
+ # configuration
80
+ #----------------------
81
+ def load_config
82
+ # load the system config by requesting it from the perus server
83
+ json = JSON.parse(RestClient.get(@config_url))
84
+ json['metrics'] ||= []
85
+ json['actions'] ||= []
86
+
87
+ # load metric and command modules based on the config
88
+ json['metrics'].each do |config|
89
+ begin
90
+ if ::Perus::Pinger.const_defined?(config['type'])
91
+ metric = ::Perus::Pinger.const_get(config['type'])
92
+ @metric_errors[metric.name] ||= []
93
+ @metrics << metric.new(config['options'])
94
+ else
95
+ @metric_errors[config['type']] = e.inspect
96
+ end
97
+ rescue => e
98
+ @metric_errors[metric.name] << e.inspect
99
+ end
100
+ end
101
+
102
+ json['actions'].each do |config|
103
+ begin
104
+ command = ::Perus::Pinger.const_get(config['type'])
105
+ @actions << command.new(config['options'], config['id'])
106
+ rescue => e
107
+ if config['id']
108
+ @action_results[config['id']] = e.inspect
109
+ else
110
+ puts 'Error - action does not have an associated id'
111
+ p config
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ #----------------------
118
+ # run
119
+ #----------------------
120
+ def run_metrics
121
+ @metrics.each do |metric|
122
+ begin
123
+ result = metric.run
124
+ @metric_results.merge!(result)
125
+ rescue => e
126
+ @metric_errors[metric.class.name] << e.inspect
127
+ end
128
+ end
129
+ end
130
+
131
+ def run_actions
132
+ @actions.each do |action|
133
+ begin
134
+ result = action.run
135
+
136
+ if result.instance_of?(Proc)
137
+ @late_actions << result
138
+ result = true
139
+ end
140
+
141
+ @action_results[action.id] = result
142
+
143
+ rescue => e
144
+ @action_results[action.id] = e.inspect
145
+ end
146
+ end
147
+ end
148
+
149
+ #----------------------
150
+ # response
151
+ #----------------------
152
+ def add_to_payload(payload, field, results)
153
+ results.each do |name, val|
154
+ next unless val.instance_of?(File)
155
+ uuid = SecureRandom.uuid
156
+ results[name] = {file: uuid}
157
+ payload[uuid] = val
158
+ end
159
+
160
+ payload[field] = JSON.dump(results)
161
+ end
162
+
163
+ def send_response
164
+ # prepare the response and replace file results with a reference
165
+ # to the uploaded file. files are sent as top level parameters in
166
+ # the payload, while metric and action results are sent as a json
167
+ # object with a reference to these files.
168
+ payload = {}
169
+ add_to_payload(payload, 'metrics', @metric_results)
170
+ add_to_payload(payload, 'actions', @action_results)
171
+
172
+ # metric_errors is created with a key for each metric type. most
173
+ # metrics should run without any errors, so remove these entries
174
+ # before adding errors to the payload.
175
+ @metric_errors.reject! {|metric, errors| errors.empty?}
176
+ add_to_payload(payload, 'metric_errors', @metric_errors)
177
+
178
+ begin
179
+ RestClient.post(@pinger_url, payload)
180
+ rescue => e
181
+ puts 'Ping failed with exception'
182
+ puts e.inspect
183
+ end
184
+ end
185
+
186
+ #----------------------
187
+ # cleanup
188
+ #----------------------
189
+ def cleanup
190
+ @metrics.each do |metric|
191
+ begin
192
+ metric.cleanup
193
+ rescue => e
194
+ puts 'Error running metric cleanup'
195
+ puts e.inspect
196
+ end
197
+ end
198
+
199
+ @actions.each do |action|
200
+ begin
201
+ action.cleanup
202
+ rescue => e
203
+ puts 'Error running action cleanup'
204
+ puts e.inspect
205
+ end
206
+ end
207
+
208
+ @late_actions.each do |code|
209
+ begin
210
+ code.call
211
+ rescue => e
212
+ puts 'Error running late action'
213
+ puts e.inspect
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,38 @@
1
+ module Perus
2
+ module Pinger
3
+ Dir.chdir(__dir__) do
4
+ # commands
5
+ require './pinger/command'
6
+ require './pinger/chrome_command'
7
+ require './pinger/commands/chrome_execute'
8
+ require './pinger/commands/chrome_navigate'
9
+ require './pinger/commands/chrome_reload'
10
+ require './pinger/commands/kill_process'
11
+ require './pinger/commands/remove_path'
12
+ require './pinger/commands/replace'
13
+ require './pinger/commands/restart'
14
+ require './pinger/commands/upload'
15
+ require './pinger/commands/upgrade'
16
+ require './pinger/commands/script'
17
+
18
+ # metrics
19
+ require './pinger/metrics/chrome'
20
+ require './pinger/metrics/cpu'
21
+ require './pinger/metrics/hd'
22
+ require './pinger/metrics/mem'
23
+ require './pinger/metrics/process'
24
+ require './pinger/metrics/screenshot'
25
+ require './pinger/metrics/temp'
26
+ require './pinger/metrics/value'
27
+ require './pinger/metrics/uptime'
28
+
29
+ # pinger
30
+ require './pinger/pinger'
31
+ end
32
+
33
+ # options file
34
+ DEFAULT_PINGER_OPTIONS_PATH = '/etc/perus-pinger'
35
+ end
36
+
37
+ Pinger::Pinger.new.run if __FILE__ == $0
38
+ end
@@ -0,0 +1,83 @@
1
+ module Perus::Server
2
+ module Admin
3
+ def admin(type, redirect_to_record = false)
4
+ plural = type.to_s.pluralize
5
+ singular = plural.singularize
6
+ title = singular.titleize
7
+ klass = title
8
+
9
+ class_eval "
10
+ use Sinatra.new {
11
+ helpers Helpers
12
+
13
+ before do
14
+ @plural = '#{plural}'
15
+ @title = '#{title}'
16
+ load_site_information
17
+ end
18
+
19
+ # list
20
+ get '/admin/#{plural}' do
21
+ @records = #{klass}.all
22
+ erb :'admin/index'
23
+ end
24
+
25
+ # new form
26
+ get '/admin/#{plural}/new' do
27
+ @record = #{klass}.new
28
+ @form = Form.new(@record)
29
+ erb :'admin/new'
30
+ end
31
+
32
+ # create
33
+ post '/admin/#{plural}' do
34
+ @record = #{klass}.new(params[:record])
35
+ if @record.valid?
36
+ begin
37
+ @record.save
38
+ if #{redirect_to_record}
39
+ redirect url_prefix + 'admin/#{plural}/' + @record.id.to_s
40
+ else
41
+ redirect url_prefix + 'admin/#{plural}'
42
+ end
43
+ rescue
44
+ end
45
+ end
46
+
47
+ @form = Form.new(@record)
48
+ erb :'admin/new'
49
+ end
50
+
51
+ # edit
52
+ get '/admin/#{plural}/:id' do
53
+ @record = #{klass}.with_pk!(params['id'])
54
+ @form = Form.new(@record)
55
+ erb :'admin/edit'
56
+ end
57
+
58
+ # update
59
+ put '/admin/#{plural}/:id' do
60
+ @record = #{klass}.with_pk!(params['id'])
61
+ if @record.valid?
62
+ begin
63
+ @record.update(params[:record])
64
+ redirect url_prefix + 'admin/#{plural}'
65
+ rescue
66
+ end
67
+ end
68
+
69
+ @form = Form.new(@record)
70
+ erb :'admin/edit'
71
+ end
72
+
73
+ # delete
74
+ delete '/admin/#{plural}/:id' do
75
+ @record = #{klass}.with_pk!(params['id'])
76
+ @record.destroy
77
+ redirect url_prefix + 'admin/#{plural}'
78
+ end
79
+ }
80
+ "
81
+ end
82
+ end
83
+ end