perus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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