perus 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 599b70600ece749fcd4c4f45ac930f4b795cff09
4
- data.tar.gz: ace224657f580232d911a0965426af0696fb5225
3
+ metadata.gz: 820c3e7205d8bb9dcceb2fd9d52ceeb76db9e39d
4
+ data.tar.gz: 2ae3ddcda6b97e2c21c01d1fadef7dda294a7c62
5
5
  SHA512:
6
- metadata.gz: 22e9fb25b86654bdc57454173ba6b29cd444bd15c40785551a535feb54178ba394759e418ae39a4758e95e9c0d25b3dc2e5535d33e34996606c5c6ab2c2e6ef9
7
- data.tar.gz: 88c08f91f40ac189bfc48932f60bf4e6489ec8a7147da0ae00eee8ff10f7d36fe2afe38baef5a9595aff25430b482813f70302e20c2cd6b4620167742ac27ba5
6
+ metadata.gz: 968cf198e14eec35f27faf8b8f6e6a090524c1e6b5b9540197090449d570464dbcf2b113d4165fb3da4d895a37bd4439959b3a61a934c05dcbbcf44aa55ce961
7
+ data.tar.gz: 31080180df8b4553f3b6cf2cc5073b830fdea47a95874cd10a784fbd35214297c8683bf571f150e2304ecc426987e67945c74529233d87d6e67fb0c0df07d517
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ require 'perus'
3
+ require 'optparse'
4
+ require 'irb'
5
+
6
+ options_path = Perus::Server::DEFAULT_SERVER_OPTIONS_PATH
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: perus-console [options]"
10
+
11
+ opts.on('-c', '--config PATH', "Path to config file (default: #{Perus::Server::DEFAULT_SERVER_OPTIONS_PATH})") do |c|
12
+ options_path = c
13
+ end
14
+
15
+ opts.on('-h', '--help', 'Prints this help') do
16
+ puts opts
17
+ exit
18
+ end
19
+ end.parse!
20
+
21
+ include Perus::Server
22
+ Server.load_options(options_path)
23
+ DB.start
24
+ IRB.start
@@ -16,6 +16,7 @@ module Perus
16
16
  require './server/admin'
17
17
  require './server/app'
18
18
  require './server/db'
19
+ require './server/stats'
19
20
  require './server/server'
20
21
  end
21
22
  end
@@ -102,6 +102,11 @@ module Perus::Server
102
102
  redirect "#{url_prefix}admin/configs/#{params['config_id']}"
103
103
  end
104
104
 
105
+ get '/admin/stats' do
106
+ @stats = Stats.new
107
+ erb :stats
108
+ end
109
+
105
110
 
106
111
  #----------------------
107
112
  # API
@@ -142,30 +147,33 @@ module Perus::Server
142
147
 
143
148
  # receive data from a client
144
149
  post '/systems/:id/ping' do
145
- timestamp = Time.now.to_i
150
+ Server.ping_queue.post do
151
+ timestamp = Time.now.to_i
146
152
 
147
- # update the system with its last known ip and update time
148
- system = System.with_pk!(params['id'])
149
- system.last_updated = timestamp
153
+ # update the system with its last known ip and update time
154
+ system = System.with_pk!(params['id'])
155
+ system.last_updated = timestamp
150
156
 
151
- if request.ip == '127.0.0.1' && ENV['RACK_ENV'] == 'production'
152
- system.ip = request.env['HTTP_X_FORWARDED_FOR']
153
- else
154
- system.ip = request.ip
155
- end
157
+ if request.ip == '127.0.0.1' && ENV['RACK_ENV'] == 'production'
158
+ system.ip = request.env['HTTP_X_FORWARDED_FOR']
159
+ else
160
+ system.ip = request.ip
161
+ end
162
+
163
+ # errors is either nil or a hash of the format - module: [err, ...]
164
+ system.save_metric_errors(params, timestamp)
156
165
 
157
- # errors is either nil or a hash of the format - module: [err, ...]
158
- system.save_metric_errors(params, timestamp)
166
+ # add each new value, a later process cleans up old values
167
+ system.save_values(params, timestamp)
159
168
 
160
- # add each new value, a later process cleans up old values
161
- system.save_values(params, timestamp)
169
+ # save action return values and prevent them from running again
170
+ system.save_actions(params, timestamp)
162
171
 
163
- # save action return values and prevent them from running again
164
- system.save_actions(params, timestamp)
172
+ # ip, last updated, uploads and metrics are now updated. these are
173
+ # stored on the system.
174
+ system.save
175
+ end
165
176
 
166
- # ip, last updated, uploads and metrics are now updated. these are
167
- # stored on the system.
168
- system.save
169
177
  content_type :json
170
178
  {success: true}.to_json
171
179
  end
@@ -220,6 +228,19 @@ module Perus::Server
220
228
  redirect "#{url_prefix}groups/#{params['id']}/systems"
221
229
  end
222
230
 
231
+ # delete completed actions in a group
232
+ delete '/groups/:id/systems/actions' do
233
+ group = Group.with_pk!(params['id'])
234
+ group.systems.each do |system|
235
+ system.actions.each do |action|
236
+ next if action.timestamp.nil?
237
+ action.destroy
238
+ end
239
+ end
240
+
241
+ redirect "#{url_prefix}groups/#{params['id']}/systems"
242
+ end
243
+
223
244
  # create an action for all systems
224
245
  post '/systems/actions' do
225
246
  System.each do |system|
@@ -229,6 +250,16 @@ module Perus::Server
229
250
  redirect "#{url_prefix}systems"
230
251
  end
231
252
 
253
+ # delete all completed actions
254
+ delete '/systems/actions' do
255
+ Action.each do |action|
256
+ next if action.timestamp.nil?
257
+ action.destroy
258
+ end
259
+
260
+ redirect "#{url_prefix}systems"
261
+ end
262
+
232
263
  # delete an action. deletion also clears any uploaded files.
233
264
  delete '/systems/:system_id/actions/:id' do
234
265
  action = Action.with_pk!(params['id'])
@@ -4,6 +4,8 @@ require 'concurrent'
4
4
 
5
5
  module Perus::Server
6
6
  module DB
7
+ MAX_VACUUM_ATTEMPTS = 5
8
+
7
9
  def self.db
8
10
  @db
9
11
  end
@@ -42,7 +44,31 @@ module Perus::Server
42
44
  # restructures the db so system records in the values index should
43
45
  # be sequentially stored
44
46
  vacuum_task = Concurrent::TimerTask.new do
45
- @db.execute('vacuum')
47
+ attempts = 0
48
+ complete = false
49
+
50
+ while !complete && attempts < MAX_VACUUM_ATTEMPTS
51
+ begin
52
+ puts "Vacuuming, attempt #{attempts + 1}"
53
+ start = Time.now
54
+ @db.execute('vacuum')
55
+ Stats.vacuumed!(Time.now - start)
56
+ complete = true
57
+ puts "Vacuuming complete"
58
+
59
+ rescue
60
+ attempts += 1
61
+ if attempts < MAX_VACUUM_ATTEMPTS
62
+ puts "Vacuum failed, will reattempt after short sleep"
63
+ sleep(5)
64
+ end
65
+ end
66
+ end
67
+
68
+ if !complete
69
+ puts "Vacuum failed more than MAX_VACUUM_ATTEMPTS"
70
+ Stats.vacuumed!('failed')
71
+ end
46
72
  end
47
73
 
48
74
  # fire every 12 hours
@@ -54,7 +80,13 @@ module Perus::Server
54
80
  # are removed from a system, the accompanying metric record is also
55
81
  # removed.
56
82
  cleanup_task = Concurrent::TimerTask.new do
57
- Perus::Server::DB.cleanup
83
+ begin
84
+ start = Time.now
85
+ Perus::Server::DB.cleanup
86
+ Stats.cleaned!(Time.now - start)
87
+ rescue
88
+ Stats.cleaned!('failed')
89
+ end
58
90
  end
59
91
 
60
92
  # fire every hour
@@ -66,10 +98,16 @@ module Perus::Server
66
98
  # cached so lookups can be done against the db, rather than running
67
99
  # each alert for each system on a page load.
68
100
  cache_alerts_task = Concurrent::TimerTask.new do
69
- Perus::Server::Alert.cache_active_alerts
101
+ begin
102
+ start = Time.now
103
+ Perus::Server::Alert.cache_active_alerts
104
+ Stats.alerts_cached!(Time.now - start)
105
+ rescue
106
+ Stats.alerts_cached!('failed')
107
+ end
70
108
  end
71
109
 
72
- cache_alerts_task.execution_interval = Server.options.cache_alerts_mins * 60
110
+ cache_alerts_task.execution_interval = Server.options.cache_alerts_secs
73
111
  cache_alerts_task.execute
74
112
  end
75
113
 
@@ -292,25 +292,25 @@ main {
292
292
  width: 450px;
293
293
  }
294
294
 
295
- #meta dl {
295
+ dl {
296
296
  display: inline-block;
297
297
  vertical-align: top;
298
298
  width: 450px;
299
299
  }
300
300
 
301
- #meta dt, #meta dd {
301
+ dt, dd {
302
302
  display: inline-block;
303
303
  vertical-align: top;
304
304
  margin-bottom: 10px;
305
305
  font-size: 13px;
306
306
  }
307
307
 
308
- #meta dt {
308
+ dt {
309
309
  width: 100px;
310
310
  font-weight: 600;
311
311
  }
312
312
 
313
- #meta dd {
313
+ dd {
314
314
  width: 340px;
315
315
  line-height: 16px;
316
316
  }
@@ -507,3 +507,22 @@ article.graph {
507
507
  padding: 15px 20px;
508
508
  border-radius: 4px;
509
509
  }
510
+
511
+ #actions-menu {
512
+ }
513
+
514
+ #actions-menu p {
515
+ display: inline-block;
516
+ float: left;
517
+ }
518
+
519
+ #actions-menu form {
520
+ display: inline-block;
521
+ float: right;
522
+ margin-top: 30px;
523
+ }
524
+
525
+ h2.stats {
526
+ margin-bottom: 10px;
527
+ margin-top: 20px;
528
+ }
@@ -1,3 +1,4 @@
1
+ require 'concurrent'
1
2
  require 'thin'
2
3
 
3
4
  DEFAULT_SERVER_OPTIONS = {
@@ -10,7 +11,8 @@ DEFAULT_SERVER_OPTIONS = {
10
11
  'site_name' => 'Perus',
11
12
  'url_prefix' => '/',
12
13
  'keep_hours' => 24,
13
- 'cache_alerts_mins' => 1
14
+ 'cache_alerts_secs' => 20,
15
+ 'stats_path' => '/var/perus.stats'
14
16
  }
15
17
  }
16
18
 
@@ -19,6 +21,8 @@ module Perus::Server
19
21
  def initialize(options_path = DEFAULT_SERVER_OPTIONS_PATH, environment = 'development')
20
22
  self.class.load_options(options_path)
21
23
  ENV['RACK_ENV'] = environment
24
+
25
+ # initialise/migrate the db and start cleanup/caching timers
22
26
  DB.start
23
27
  DB.start_timers
24
28
  end
@@ -31,6 +35,19 @@ module Perus::Server
31
35
  )
32
36
  end
33
37
 
38
+ def self.ping_queue
39
+ # ping data is processed in a thread pool
40
+ @ping_queue ||= Concurrent::ThreadPoolExecutor.new(
41
+ min_threads: 2,
42
+ max_threads: 2,
43
+ max_queue: 0
44
+ )
45
+ end
46
+
47
+ def self.shutdown
48
+ @ping_queue.shutdown
49
+ end
50
+
34
51
  def self.options
35
52
  @options ||= Perus::Options.new
36
53
  end
@@ -0,0 +1,126 @@
1
+ require 'json'
2
+
3
+ module Perus::Server
4
+ class Stats
5
+ MAX_HISTORY = 10
6
+ attr_reader :data
7
+
8
+ # data format (json):
9
+ # {
10
+ # "vacuums": [
11
+ # ["ISO time", duration (int) || 'failed'],
12
+ # ...
13
+ # ]
14
+ # }
15
+
16
+ def initialize
17
+ if File.exists?(Server.options.stats_path)
18
+ data = IO.read(Server.options.stats_path)
19
+ unless data.empty?
20
+ @data = JSON.parse(data)
21
+ return
22
+ end
23
+ end
24
+
25
+ @data = {
26
+ 'vacuums' => [],
27
+ 'alerts_caches' => [],
28
+ 'cleans' => []
29
+ }
30
+ end
31
+
32
+ def save
33
+ IO.write(Server.options.stats_path, JSON.dump(@data))
34
+ end
35
+
36
+ # vacuuming
37
+ def last_vacuum
38
+ entry = @data['vacuums'].last
39
+ entry ? entry.first : nil
40
+ end
41
+
42
+ def vacuum_time
43
+ entry = @data['vacuums'].last
44
+ entry ? entry.last : nil
45
+ end
46
+
47
+ def average_vacuum_time
48
+ entries = @data['vacuums']
49
+ return nil if entries.empty?
50
+ ints = entries.map(&:last).select {|time| time.is_a?(Numeric)}
51
+ ints.reduce(:+).to_f / ints.length
52
+ end
53
+
54
+ def self.vacuumed!(time)
55
+ stats = Stats.new
56
+ list = stats.data['vacuums']
57
+ list << [Time.now.to_s, time]
58
+
59
+ if list.length > MAX_HISTORY
60
+ list.drop(list.length - MAX_HISTORY)
61
+ end
62
+
63
+ stats.save
64
+ end
65
+
66
+ # alerts caching
67
+ def last_alerts_cache
68
+ entry = @data['alerts_caches'].last
69
+ entry ? entry.first : nil
70
+ end
71
+
72
+ def alerts_cache_time
73
+ entry = @data['alerts_caches'].last
74
+ entry ? entry.last : nil
75
+ end
76
+
77
+ def average_alerts_cache_time
78
+ entries = @data['alerts_caches']
79
+ return nil if entries.empty?
80
+ ints = entries.map(&:last).select {|time| time.is_a?(Numeric)}
81
+ ints.reduce(:+).to_f / ints.length
82
+ end
83
+
84
+ def self.alerts_cached!(time)
85
+ stats = Stats.new
86
+ list = stats.data['alerts_caches']
87
+ list << [Time.now.to_s, time]
88
+
89
+ if list.length > MAX_HISTORY
90
+ list.drop(list.length - MAX_HISTORY)
91
+ end
92
+
93
+ stats.save
94
+ end
95
+
96
+ # cleaning
97
+ def last_clean
98
+ entry = @data['cleans'].last
99
+ entry ? entry.first : nil
100
+ end
101
+
102
+ def clean_time
103
+ entry = @data['cleans'].last
104
+ entry ? entry.last : nil
105
+ end
106
+
107
+ def average_clean_time
108
+ entries = @data['cleans']
109
+ return nil if entries.empty?
110
+ ints = entries.map(&:last).select {|time| time.is_a?(Numeric)}
111
+ ints.reduce(:+).to_f / ints.length
112
+ end
113
+
114
+ def self.cleaned!(time)
115
+ stats = Stats.new
116
+ list = stats.data['cleans']
117
+ list << [Time.now.to_s, time]
118
+
119
+ if list.length > MAX_HISTORY
120
+ list.drop(list.length - MAX_HISTORY)
121
+ end
122
+
123
+ stats.save
124
+ end
125
+ end
126
+ end
@@ -31,6 +31,7 @@
31
31
  <%= nav_item("#{url_prefix}admin/configs", 'Configs') %>
32
32
  <%= nav_item("#{url_prefix}admin/alerts", 'Alerts') %>
33
33
  <%= nav_item("#{url_prefix}admin/scripts", 'Scripts') %>
34
+ <%= nav_item("#{url_prefix}admin/stats", 'Stats') %>
34
35
  </ul>
35
36
  <% end %>
36
37
  </li>
@@ -0,0 +1,31 @@
1
+ <h1>Background Stats</h1>
2
+
3
+ <h2 class="stats">Vacuuming</h2>
4
+ <dl>
5
+ <dt>Last Vacuum</dt>
6
+ <dd><%= @stats.last_vacuum || 'nil' %></dd>
7
+ <dt>Vacuum Time</dt>
8
+ <dd><%= "%0.4f" % @stats.vacuum_time rescue 'nil' %></dd>
9
+ <dt>Average Vacuum Time</dt>
10
+ <dd><%= "%0.4f" % @stats.average_vacuum_time rescue 'nil' %></dd>
11
+ </dl>
12
+
13
+ <h2 class="stats">Alerts Caching</h2>
14
+ <dl>
15
+ <dt>Last Alerts Cache</dt>
16
+ <dd><%= @stats.last_alerts_cache || 'nil' %></dd>
17
+ <dt>Alerts Cache Time</dt>
18
+ <dd><%= "%0.4f" % @stats.alerts_cache_time rescue 'nil' %></dd>
19
+ <dt>Average Alerts Cache Time</dt>
20
+ <dd><%= "%0.4f" % @stats.average_alerts_cache_time rescue 'nil' %></dd>
21
+ </dl>
22
+
23
+ <h2 class="stats">Cleaning</h2>
24
+ <dl>
25
+ <dt>Last Clean</dt>
26
+ <dd><%= @stats.last_clean || 'nil' %></dd>
27
+ <dt>Clean Time</dt>
28
+ <dd><%= "%0.4f" % @stats.clean_time rescue 'nil' %></dd>
29
+ <dt>Average Clean Time</dt>
30
+ <dd><%= "%0.4f" % @stats.average_clean_time rescue 'nil' %></dd>
31
+ </dl>
@@ -17,22 +17,29 @@
17
17
  <% end %>
18
18
  <p id="no-systems" <% unless @systems.empty? %>style="display: none"<% end %>>No Systems</p>
19
19
 
20
- <p id="add-command">
21
- Add action to <%= @title %>:
22
- <select id="command-select">
23
- <option></option>
24
- <optgroup label="Commands">
25
- <% command_actions.each do |command| %>
26
- <option value="<%= command.name %>"><%= command.human_name %></option>
27
- <% end %>
28
- </optgroup>
29
- <optgroup label="Scripts">
30
- <% @scripts.each do |script| %>
31
- <option value="<%= script.code_name %>"><%= script.name %></option>
32
- <% end %>
33
- </optgroup>
34
- </select>
35
- </p>
20
+
21
+ <menu id="actions-menu">
22
+ <p id="add-command">
23
+ Add Action to <%= @title %>:
24
+ <select id="command-select">
25
+ <option></option>
26
+ <optgroup label="Commands">
27
+ <% command_actions.each do |command| %>
28
+ <option value="<%= command.name %>"><%= command.human_name %></option>
29
+ <% end %>
30
+ </optgroup>
31
+ <optgroup label="Scripts">
32
+ <% @scripts.each do |script| %>
33
+ <option value="<%= script.code_name %>"><%= script.name %></option>
34
+ <% end %>
35
+ </optgroup>
36
+ </select>
37
+ </p>
38
+ <form id="delete-completed" method="POST" action="<%= url_prefix %><%= @action_url %>">
39
+ <input type="hidden" name="_method" value="DELETE">
40
+ <input type="submit" value="Delete Completed Actions">
41
+ </form>
42
+ </menu>
36
43
 
37
44
  <!-- new commands -->
38
45
  <% command_actions.each do |command| %>
@@ -48,6 +55,11 @@
48
55
  <% end %>
49
56
 
50
57
  <script>
58
+ $('#delete-completed').submit(function(event) {
59
+ if (!confirm('Are you sure you want to delete all completed actions? Their results will no longer be available.'))
60
+ event.preventDefault();
61
+ });
62
+
51
63
  $('#command-select').change(function() {
52
64
  $('form.command').hide();
53
65
  $('form[data-command="' + $(this).val() + '"]').show();
@@ -1,3 +1,3 @@
1
1
  module Perus
2
- VERSION = "0.1.11"
2
+ VERSION = "0.1.12"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.11
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Cannings
@@ -196,6 +196,7 @@ description:
196
196
  email:
197
197
  - me@willcannings.com
198
198
  executables:
199
+ - perus-console
199
200
  - perus-pinger
200
201
  - perus-server
201
202
  extensions: []
@@ -210,6 +211,7 @@ files:
210
211
  - Rakefile
211
212
  - bin/console
212
213
  - bin/setup
214
+ - exe/perus-console
213
215
  - exe/perus-pinger
214
216
  - exe/perus-server
215
217
  - lib/perus.rb
@@ -304,6 +306,7 @@ files:
304
306
  - lib/perus/server/public/js/dygraph-combined.js
305
307
  - lib/perus/server/public/js/jquery.js
306
308
  - lib/perus/server/server.rb
309
+ - lib/perus/server/stats.rb
307
310
  - lib/perus/server/views/admin/edit.erb
308
311
  - lib/perus/server/views/admin/index.erb
309
312
  - lib/perus/server/views/admin/new.erb
@@ -317,6 +320,7 @@ files:
317
320
  - lib/perus/server/views/layout.erb
318
321
  - lib/perus/server/views/scripts/edit.erb
319
322
  - lib/perus/server/views/scripts/form.erb
323
+ - lib/perus/server/views/stats.erb
320
324
  - lib/perus/server/views/system.erb
321
325
  - lib/perus/server/views/systems.erb
322
326
  - lib/perus/server/views/systems/form.erb
@@ -347,3 +351,4 @@ signing_key:
347
351
  specification_version: 4
348
352
  summary: Simple system overview server
349
353
  test_files: []
354
+ has_rdoc: