rest-ftp-daemon 0.247.1 → 0.250.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +6 -6
  3. data/README.md +1 -1
  4. data/config.ru +3 -0
  5. data/lib/rest-ftp-daemon.rb +6 -0
  6. data/lib/rest-ftp-daemon/api/config.rb +25 -0
  7. data/lib/rest-ftp-daemon/api/dashboard.rb +1 -12
  8. data/lib/rest-ftp-daemon/api/debug.rb +37 -0
  9. data/lib/rest-ftp-daemon/api/job_presenter.rb +0 -2
  10. data/lib/rest-ftp-daemon/api/jobs.rb +1 -26
  11. data/lib/rest-ftp-daemon/api/root.rb +32 -107
  12. data/lib/rest-ftp-daemon/api/status.rb +41 -0
  13. data/lib/rest-ftp-daemon/constants.rb +4 -1
  14. data/lib/rest-ftp-daemon/job.rb +10 -5
  15. data/lib/rest-ftp-daemon/job_queue.rb +81 -60
  16. data/lib/rest-ftp-daemon/notification.rb +9 -2
  17. data/lib/rest-ftp-daemon/stats.rb +42 -0
  18. data/lib/rest-ftp-daemon/views/dashboard.haml +3 -2
  19. data/lib/rest-ftp-daemon/views/dashboard_footer.haml +0 -1
  20. data/lib/rest-ftp-daemon/views/dashboard_header.haml +3 -3
  21. data/lib/rest-ftp-daemon/views/dashboard_jobs.haml +6 -6
  22. data/lib/rest-ftp-daemon/views/dashboard_rates.haml +36 -0
  23. data/lib/rest-ftp-daemon/views/dashboard_stats.haml +21 -0
  24. data/lib/rest-ftp-daemon/views/dashboard_table.haml +0 -8
  25. data/lib/rest-ftp-daemon/views/dashboard_tokens.haml +9 -7
  26. data/lib/rest-ftp-daemon/views/dashboard_workers.haml +27 -25
  27. data/lib/rest-ftp-daemon/worker_job.rb +1 -1
  28. data/rest-ftp-daemon.gemspec +1 -1
  29. data/spec/rest-ftp-daemon/features/{routes_spec.rb → debug_spec.rb} +3 -3
  30. data/spec/rest-ftp-daemon/features/status_spec.rb +2 -2
  31. metadata +11 -6
  32. data/lib/rest-ftp-daemon/views/dashboard_counters.haml +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 09ad0a98b4d033900852d99282cf2e8e259a25df
4
- data.tar.gz: 9e10478bbf7e263fcbbeeb1720d5d9ea54a9518a
3
+ metadata.gz: 56ef0db0f52451ad8dd48acea697310637a460a7
4
+ data.tar.gz: 8c1c45f0077e055a48c6d80bb1d9f60892019d2e
5
5
  SHA512:
6
- metadata.gz: 8282173e27456be64a164bc83d58f157dd6351b227121d4f4a53ae1e59bc36e875e52d9404d078a04e14697e1bc320eae23c28679e0706c969dd23f5d6b16b64
7
- data.tar.gz: cda917f1dfd9e26260aee4679f708399dc9088e7e36fb4c873dfc3ff1c7c9a742d78df5893b95fe2641fbfef6fdab4d9583a745b8c2eec8954707b222177c599
6
+ metadata.gz: d8120b207886ab8a2667b68761e539a23cc547d04880844d0898ac6560a2edeea313033dca26de45b6af51f0715a97a3e7ad82e626b3915f2255d530ab0dadee
7
+ data.tar.gz: 43e8dff779837f56751a1accb9ddf69fe122cc31af4ec97c83ce556028b27199164bb9bc9b7aebe708b1a108e060a3fbb8a8752aae33d21f90eb137ab33d0378
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rest-ftp-daemon (0.247.1)
4
+ rest-ftp-daemon (0.250.0)
5
5
  double-bag-ftps
6
6
  facter
7
7
  get_process_mem
@@ -13,7 +13,7 @@ PATH
13
13
  newrelic_rpm
14
14
  settingslogic
15
15
  sys-cpu
16
- thin (~> 1.6)
16
+ thin (~> 1.6.4)
17
17
 
18
18
  GEM
19
19
  remote: http://rubygems.org/
@@ -80,8 +80,8 @@ GEM
80
80
  ice_nine (0.11.2)
81
81
  json (1.8.3)
82
82
  method_source (0.8.2)
83
- minitest (5.8.4)
84
- multi_json (1.12.0)
83
+ minitest (5.9.0)
84
+ multi_json (1.12.1)
85
85
  multi_xml (0.5.5)
86
86
  mustermann19 (0.4.3)
87
87
  enumerable-lazy
@@ -120,7 +120,7 @@ GEM
120
120
  powerpack (~> 0.1)
121
121
  rainbow (>= 1.99.1, < 3.0)
122
122
  ruby-progressbar (~> 1.4)
123
- ruby-progressbar (1.8.0)
123
+ ruby-progressbar (1.8.1)
124
124
  settingslogic (2.0.9)
125
125
  slop (3.6.0)
126
126
  sys-cpu (0.7.2)
@@ -130,7 +130,7 @@ GEM
130
130
  eventmachine (~> 1.0, >= 1.0.4)
131
131
  rack (~> 1.0)
132
132
  thread_safe (0.3.5)
133
- tilt (2.0.2)
133
+ tilt (2.0.4)
134
134
  tzinfo (1.2.2)
135
135
  thread_safe (~> 0.1)
136
136
  unf (0.1.4)
data/README.md CHANGED
@@ -214,7 +214,7 @@ TODO for this document
214
214
  * Document /status
215
215
  * Document /routes
216
216
  * Document mkdir and overwrite options
217
- * Document counters
217
+ * Document stats
218
218
 
219
219
 
220
220
 
data/config.ru CHANGED
@@ -6,6 +6,9 @@ require "rest-ftp-daemon"
6
6
  # Create global queue
7
7
  $queue = RestFtpDaemon::JobQueue.new
8
8
 
9
+ # Create global stats
10
+ $stats = RestFtpDaemon::Stats.new
11
+
9
12
  # Initialize workers and conchita subsystem
10
13
  $pool = RestFtpDaemon::WorkerPool.new
11
14
 
@@ -10,6 +10,8 @@ require "net/http"
10
10
  require "thread"
11
11
  require "singleton"
12
12
  require "newrelic_rpm"
13
+ require "grape"
14
+ require "grape-entity"
13
15
 
14
16
  # Project's libs
15
17
  require_relative "rest-ftp-daemon/constants"
@@ -23,6 +25,7 @@ require_relative "rest-ftp-daemon/logger"
23
25
  require_relative "rest-ftp-daemon/paginate"
24
26
  require_relative "rest-ftp-daemon/uri"
25
27
  require_relative "rest-ftp-daemon/job_queue"
28
+ require_relative "rest-ftp-daemon/stats"
26
29
  require_relative "rest-ftp-daemon/worker"
27
30
  require_relative "rest-ftp-daemon/worker_conchita"
28
31
  require_relative "rest-ftp-daemon/worker_job"
@@ -38,6 +41,9 @@ require_relative "rest-ftp-daemon/remote_sftp"
38
41
  require_relative "rest-ftp-daemon/api/job_presenter"
39
42
  require_relative "rest-ftp-daemon/api/jobs"
40
43
  require_relative "rest-ftp-daemon/api/dashboard"
44
+ require_relative "rest-ftp-daemon/api/status"
45
+ require_relative "rest-ftp-daemon/api/config"
46
+ require_relative "rest-ftp-daemon/api/debug"
41
47
  require_relative "rest-ftp-daemon/api/root"
42
48
 
43
49
  # Haml monkey-patching
@@ -0,0 +1,25 @@
1
+ module RestFtpDaemon
2
+ module API
3
+ class Config < Grape::API
4
+
5
+ desc "Show daemon config"
6
+ get "/" do
7
+ status 200
8
+ return Helpers.get_censored_config
9
+ end
10
+
11
+ desc "Reload daemon config"
12
+ post "/reload" do
13
+ if Settings.at(:debug, :allow_reload)==true
14
+ Settings.reload!
15
+ status 200
16
+ return Helpers.get_censored_config
17
+ else
18
+ status 403
19
+ return "Config reload not permitted"
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -1,21 +1,16 @@
1
- require "grape"
2
1
  require "haml"
3
2
  require "sys/cpu"
3
+ require "get_process_mem"
4
4
  require "facter"
5
5
 
6
6
  module RestFtpDaemon
7
7
  module API
8
8
 
9
- # Offers an HTML dashboard through the Grape API (hum...)
10
9
  class Dashbaord < Grape::API
11
10
 
12
11
  ### HELPERS
13
12
 
14
13
  helpers do
15
- def logger
16
- Root.logger
17
- end
18
-
19
14
  def render name, values={}
20
15
  template = File.read("#{APP_LIBS}/views/#{name}.haml")
21
16
 
@@ -65,12 +60,6 @@ module RestFtpDaemon
65
60
  end
66
61
 
67
62
 
68
- ### Common request logging
69
- before do
70
- log_info "HTTP #{request.request_method} #{request.fullpath}", params
71
- end
72
-
73
-
74
63
  ### DASHBOARD
75
64
  desc "Show a global dashboard"
76
65
  get "/" do
@@ -0,0 +1,37 @@
1
+ module RestFtpDaemon
2
+ module API
3
+ class Debug < Grape::API
4
+
5
+ desc "Show app routes, params encodings"
6
+ get "/" do
7
+ # Encodings
8
+ encodings = {}
9
+ jobs = $queue.jobs
10
+
11
+ jobs.each do |job|
12
+ # here = out[job.id] = {}
13
+ me = encodings[job.id] = {}
14
+
15
+ me[:error] = job.error.encoding.to_s unless job.error.nil?
16
+ me[:status] = job.status.encoding.to_s unless job.status.nil?
17
+
18
+ Job::FIELDS.each do |name|
19
+ value = job.send(name)
20
+ me[name] = value.encoding.to_s if value.is_a? String
21
+ end
22
+
23
+ job.infos.each do |name, value|
24
+ me["infos_#{name}"] = value.encoding.to_s if value.is_a? String
25
+ end
26
+ end
27
+
28
+ # Build response
29
+ return {
30
+ routes: RestFtpDaemon::API::Root.routes,
31
+ encodings: encodings,
32
+ }
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,3 @@
1
- require "grape-entity"
2
-
3
1
  module RestFtpDaemon
4
2
  module API
5
3
  module Entities
@@ -1,26 +1,7 @@
1
- require "grape"
2
-
3
1
  module RestFtpDaemon
4
2
  module API
5
3
  class Jobs < Grape::API
6
4
 
7
- ### HELPERS
8
-
9
- helpers do
10
- def logger
11
- Root.logger
12
- end
13
- end
14
-
15
-
16
- ### Common request logging
17
- before do
18
- log_info "HTTP #{request.request_method} #{request.fullpath}", params
19
- end
20
-
21
-
22
- ### READ ONE JOB
23
-
24
5
  desc "Read job with ID"
25
6
  params do
26
7
  requires :id, type: String, desc: "ID of the Job to read"
@@ -47,9 +28,6 @@ module RestFtpDaemon
47
28
  end
48
29
  end
49
30
 
50
-
51
- ### READ ALL JOBS
52
-
53
31
  desc "List all Jobs"
54
32
  get "/" do
55
33
  begin
@@ -67,9 +45,6 @@ module RestFtpDaemon
67
45
  end
68
46
  end
69
47
 
70
-
71
- ### CREATE A JOB
72
-
73
48
  desc "Create a new job"
74
49
  params do
75
50
  requires :source, type: String, desc: "Source file pattern"
@@ -102,7 +77,7 @@ module RestFtpDaemon
102
77
  $queue.push job
103
78
 
104
79
  # Increment a counter
105
- $queue.counter_inc :jobs_received
80
+ $stats.increment :jobs, :received
106
81
 
107
82
  rescue JSON::ParserError => exception
108
83
  log_error "JSON::ParserError: #{exception.message}"
@@ -1,23 +1,50 @@
1
- require "grape"
2
- require "get_process_mem"
3
-
4
1
  module RestFtpDaemon
5
2
  module API
6
3
  class Root < Grape::API
7
4
 
5
+ ### LOGGING & HELPERS
6
+
7
+ helpers do
8
+ def logger
9
+ Root.logger
10
+ end
11
+
12
+ def log_request
13
+ if env.nil?
14
+ puts "HTTP_ENV_IS_NIL: #{env.inspect}"
15
+ return
16
+ end
17
+
18
+ request_method = env['REQUEST_METHOD']
19
+ request_path = env['REQUEST_PATH']
20
+ request_uri = env['REQUEST_URI']
21
+ log_info "HTTP #{request_method} #{request_uri}", params
22
+ end
23
+ end
24
+
25
+ before do
26
+ log_request
27
+ end
28
+
29
+
8
30
  ### CLASS CONFIG
9
31
 
10
32
  helpers RestFtpDaemon::LoggerHelper
11
33
  logger RestFtpDaemon::LoggerPool.instance.get :api
12
-
13
34
  do_not_route_head!
14
35
  do_not_route_options!
15
-
36
+ # version 'v1'
16
37
  format :json
17
38
  content_type :json, 'application/json; charset=utf-8'
18
39
 
40
+
41
+ ### MOUNTPOINTS
42
+
43
+ mount RestFtpDaemon::API::Status => MOUNT_STATUS
19
44
  mount RestFtpDaemon::API::Jobs => MOUNT_JOBS
20
45
  mount RestFtpDaemon::API::Dashbaord => MOUNT_BOARD
46
+ mount RestFtpDaemon::API::Config => MOUNT_CONFIG
47
+ mount RestFtpDaemon::API::Debug => MOUNT_DEBUG
21
48
 
22
49
 
23
50
  ### INITIALIZATION
@@ -32,114 +59,12 @@ module RestFtpDaemon
32
59
  end
33
60
 
34
61
 
35
- ### HELPERS
36
-
37
- helpers do
38
- def logger
39
- Root.logger
40
- end
41
- end
42
-
43
-
44
- ### Common request logging
45
-
46
- before do
47
- log_info "HTTP #{request.request_method} #{request.fullpath}", params
48
- end
49
-
50
-
51
62
  ### ROOT URL ACCESS
52
63
 
53
64
  get "/" do
54
65
  redirect Helpers.dashboard_filter_url()
55
66
  end
56
67
 
57
-
58
- ### SHOW ROUTES
59
-
60
- desc "Show application routes"
61
- get "/routes" do
62
- status 200
63
- return RestFtpDaemon::API::Root.routes
64
- end
65
-
66
-
67
- ### SHOW STATUS
68
-
69
- desc "Show daemon status"
70
- get "/status" do
71
- mem = GetProcessMem.new
72
- status 200
73
- return {
74
- hostname: `hostname`.to_s.chomp,
75
- version: APP_VER,
76
- started: APP_STARTED,
77
- uptime: (Time.now - APP_STARTED).round(1),
78
- counters: $queue.counters,
79
- memory_bytes: mem.bytes.to_i,
80
- memory_mb: mem.mb.round(0),
81
- status: $queue.counts_by_status,
82
- workers: $pool.worker_variables,
83
- jobs_count: $queue.jobs_count,
84
- jobs_queued: $queue.queued_ids,
85
- }
86
- end
87
-
88
-
89
- ### SHOW CONFIG
90
-
91
- desc "Show daemon config"
92
- get "/config" do
93
- status 200
94
- return Helpers.get_censored_config
95
- end
96
-
97
-
98
- desc "List all Jobs params encodings"
99
- get "/encodings" do
100
- # Get jobs to display
101
- encodings = {}
102
- jobs = $queue.jobs
103
-
104
- jobs.each do |job|
105
- # here = out[job.id] = {}
106
- me = encodings[job.id] = {}
107
-
108
- me[:error] = job.error.encoding.to_s unless job.error.nil?
109
- me[:status] = job.status.encoding.to_s unless job.status.nil?
110
-
111
- Job::FIELDS.each do |name|
112
- value = job.send(name)
113
- me[name] = value.encoding.to_s if value.is_a? String
114
- end
115
-
116
- job.infos.each do |name, value|
117
- me["infos_#{name}"] = value.encoding.to_s if value.is_a? String
118
- end
119
-
120
- # # Computed fields
121
- # expose :age
122
- # expose :exectime
123
- end
124
-
125
- encodings
126
- end
127
-
128
-
129
- ### RELOAD CONFIG
130
-
131
- desc "Reload daemon config"
132
- post "/config/reload" do
133
- if Settings.at(:debug, :allow_reload)==true
134
- Settings.reload!
135
- status 200
136
- return Helpers.get_censored_config
137
- else
138
- status 403
139
- return "Config reload not permitted"
140
- end
141
- end
142
-
143
68
  end
144
69
  end
145
70
  end
@@ -0,0 +1,41 @@
1
+ require "get_process_mem"
2
+
3
+ module RestFtpDaemon
4
+ module API
5
+ class Status < Grape::API
6
+
7
+ desc "Show daemon status"
8
+ get "/" do
9
+ mem = GetProcessMem.new
10
+ status 200
11
+ return {
12
+ hostname: `hostname`.to_s.chomp,
13
+ version: APP_VER,
14
+
15
+ started: APP_STARTED,
16
+ uptime: (Time.now - APP_STARTED).round(1),
17
+
18
+ memory_bytes: mem.bytes.to_i,
19
+ memory_mb: mem.mb.round(0),
20
+
21
+ stats: $stats.stats,
22
+
23
+ jobs_count: $queue.jobs_count,
24
+ status: $queue.jobs_count_by_status,
25
+
26
+ # jobs_finished: $queue.jobs_finished,
27
+ #rate_by_pool1: $queue.rate_by_pool,
28
+ rate_by_pool: $queue.rate_by(:pool),
29
+ rate_by_targethost: $queue.rate_by(:targethost),
30
+ jobs_by_status: $queue.jobs_by_status,
31
+
32
+ #rates_per_host: $queue.rates_per_host,
33
+
34
+ workers: $pool.worker_variables,
35
+ #kpis: $queue.queued_ids,
36
+ }
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -1,7 +1,7 @@
1
1
  # Terrific constants
2
2
  APP_NAME = "rest-ftp-daemon"
3
3
  APP_NICK = "rftpd"
4
- APP_VER = "0.247.1"
4
+ APP_VER = "0.250.0"
5
5
 
6
6
  # Provide default config file information
7
7
  APP_LIB = File.expand_path(File.dirname(__FILE__))
@@ -92,6 +92,9 @@ BIND_PORT_LOCALHOST = "127.0.0.1"
92
92
  ENV_PRODUCTION = "production"
93
93
  MOUNT_JOBS = "/jobs"
94
94
  MOUNT_BOARD = "/board"
95
+ MOUNT_STATUS = "/status"
96
+ MOUNT_DEBUG = "/debug"
97
+ MOUNT_CONFIG = "/config"
95
98
 
96
99
 
97
100
  # Notifications
@@ -246,6 +246,10 @@ module RestFtpDaemon
246
246
  (Time.now - @queued_at).round(2)
247
247
  end
248
248
 
249
+ def targethost
250
+ get(:target_host)
251
+ end
252
+
249
253
  def json_target
250
254
  utf8 @target_method
251
255
  end
@@ -314,6 +318,7 @@ module RestFtpDaemon
314
318
  raise RestFtpDaemon::JobMissingAttribute unless @target
315
319
  target_uri = expand_url @target
316
320
  set_info :target_uri, target_uri.to_s
321
+ set_info :target_host, target_uri.host
317
322
  @target_path = Path.new target_uri.path, true
318
323
 
319
324
  #puts "@target_path: #{@target_path.inspect}"
@@ -485,9 +490,9 @@ module RestFtpDaemon
485
490
  set_status JOB_STATUS_DISCONNECTING
486
491
  @finished_at = Time.now
487
492
 
488
- # Update counters
489
- $queue.counter_inc :jobs_finished
490
- $queue.counter_add :transferred, @transfer_total
493
+ # Update stats
494
+ $stats.increment :jobs, :finished
495
+ $stats.add :global, :transferred, @transfer_total
491
496
  end
492
497
 
493
498
  def remote_push source, target
@@ -652,8 +657,8 @@ module RestFtpDaemon
652
657
  end
653
658
 
654
659
  # Increment counter for this error
655
- $queue.counter_inc "err_#{error}"
656
- $queue.counter_inc :jobs_failed
660
+ $stats.increment :errors, error
661
+ $stats.increment :jobs, :failed
657
662
 
658
663
  # Prepare notification if signal given
659
664
  return unless event
@@ -35,10 +35,6 @@ module RestFtpDaemon
35
35
  @last_id = 0
36
36
  @prefix = Helpers.identifier JOB_IDENT_LEN
37
37
  log_info "JobQueue initialized (prefix: #{@prefix})"
38
-
39
- # Mutex for counters
40
- @counters = {}
41
- @mutex_counters = Mutex.new
42
38
  end
43
39
 
44
40
  def generate_id
@@ -48,63 +44,58 @@ module RestFtpDaemon
48
44
  prefixed_id @last_id
49
45
  end
50
46
 
51
- def counter_add name, value
52
- @mutex_counters.synchronize do
53
- @counters[name] ||= 0
54
- @counters[name] += value
55
- end
47
+ def jobs_queued
48
+ @queues
49
+ #@queues.map { |status, jobs| jobs.size }
56
50
  end
57
51
 
58
- def counter_inc name
59
- counter_add name, 1
60
- end
52
+ # Statistics on average rates
53
+ def rate_by method_name
54
+ # Init
55
+ rates = {}
56
+ return unless Job.new(0, {}).respond_to? method_name
61
57
 
62
- def counter_get name
63
- @mutex_counters.synchronize do
64
- @counters[name]
58
+ # Group jobs by method_name
59
+ jobs_grouped = @jobs.group_by do |job|
60
+ job.send(method_name)
65
61
  end
66
- end
67
62
 
68
- def counters
69
- @mutex_counters.synchronize do
70
- @counters
63
+ # Inside each group, sum up rates for interesting statuses
64
+ jobs_grouped.map do |group, jobs|
65
+ # Store their sum
66
+ rates[group] = rates_by_status (jobs)
71
67
  end
72
- end
73
68
 
74
- def jobs_queued
75
- @queues
69
+ # Return the rate
70
+ rates
76
71
  end
77
72
 
78
- def jobs_with_status status
79
- # No status filter: return all execept queued
80
- if status.empty?
81
- @jobs.reject { |job| job.status == JOB_STATUS_QUEUED }
82
-
83
- # Status filtering: only those jobs
84
- else
85
- @jobs.select { |job| job.status.to_s == status.to_s }
86
-
87
- end
73
+ # Queue infos
74
+ def jobs_count
75
+ @jobs.length
88
76
  end
89
77
 
90
- def counts_by_status
78
+ def jobs_by_status
91
79
  statuses = {}
92
80
  @jobs.group_by { |job| job.status }.map { |status, jobs| statuses[status] = jobs.size }
93
81
  statuses
94
82
  end
83
+ alias jobs_count_by_status jobs_by_status
95
84
 
96
- def jobs_count
97
- @jobs.length
85
+ def jobs_ids
86
+ @jobs.collect(&:id)
98
87
  end
99
88
 
100
- def queued_ids
101
- @queues.collect{|pool, jobs| jobs.collect(&:id)}
89
+ def empty?
90
+ @queue.empty?
102
91
  end
103
92
 
104
- def jobs_ids
105
- @jobs.collect(&:id)
93
+ def num_waiting
94
+ @waiting.size
106
95
  end
107
96
 
97
+
98
+ # Queue access
108
99
  def find_by_id id, prefixed = false
109
100
  # Build a prefixed id if expected
110
101
  id = prefixed_id(id) if prefixed
@@ -115,7 +106,6 @@ module RestFtpDaemon
115
106
  @jobs.find { |item| item.id == id }
116
107
  end
117
108
 
118
-
119
109
  def push job
120
110
  # Check that item responds to "priorty" method
121
111
  raise "JobQueue.push: job should respond to priority method" unless job.respond_to? :priority
@@ -174,42 +164,52 @@ module RestFtpDaemon
174
164
  alias shift pop
175
165
  alias deq pop
176
166
 
177
- def empty?
178
- @queue.empty?
179
- end
180
-
181
167
  def clear
182
168
  @queue.clear
183
169
  end
184
170
 
185
- def num_waiting
186
- @waiting.size
171
+ # Jobs acess and searching
172
+ def jobs_with_status status
173
+ # No status filter: return all execept queued
174
+ if status.empty?
175
+ @jobs.reject { |job| job.status == JOB_STATUS_QUEUED }
176
+
177
+ # Status filtering: only those jobs
178
+ else
179
+ @jobs.select { |job| job.status.to_s == status.to_s }
180
+
181
+ end
187
182
  end
188
183
 
184
+ # Jobs cleanup
189
185
  def expire status, maxage, verbose = false
190
186
  # FIXME: clean both @jobs and @queue
191
187
  # Init
192
188
  return if status.nil? || maxage <= 0
193
189
 
194
- # Compute oldest possible birthday
195
- before = Time.now - maxage.to_i
196
-
197
- # Verbose output ?
198
- log_info "JobQueue.expire \t[#{status}] \tbefore \t[#{before}]" if verbose
190
+ # Compute oldest limit
191
+ time_limit = Time.now - maxage.to_i
192
+ log_info "JobQueue.expire limit [#{time_limit}] status [#{status}]" if verbose
199
193
 
200
194
  @mutex.synchronize do
201
195
  # Delete jobs from the queue when they match status and age limits
202
196
  @jobs.delete_if do |job|
197
+ # log_info "testing job [#{job.id}] updated_at [#{job.updated_at}]"
203
198
 
204
- # Skip if wrong status, updated_at invalid, or too young
205
- next unless job.status == status.to_sym
199
+ # Skip if wrong status, updated_at invalid, or updated since time_limit
200
+ next unless job.status == status
206
201
  next if job.updated_at.nil?
207
- next if job.updated_at > before
202
+ next if job.updated_at >= time_limit
208
203
 
209
204
  # Ok, we have to clean it up ..
210
- log_info "expire [#{status}] [#{maxage}] > [#{job.id}] [#{job.updated_at}]"
211
- log_info "#{LOG_INDENT}unqueued" if @queue.delete(job)
205
+ log_info "expire [#{status}]: job [#{job.id}] updated_at [#{job.updated_at}]"
212
206
 
207
+ # From any queues, remove it
208
+ @queues.each do |pool, jobs|
209
+ log_info "#{LOG_INDENT}unqueued from [#{pool}]" if jobs.delete(job)
210
+ end
211
+
212
+ # Remember we have to delete the original job !
213
213
  true
214
214
  end
215
215
  end
@@ -222,11 +222,32 @@ module RestFtpDaemon
222
222
  "#{@prefix}.#{id}"
223
223
  end
224
224
 
225
+ def rates_by_status jobs
226
+ rates = {}
227
+
228
+ # Sub-group by status
229
+ jobs.group_by(&:status).each do |status, jobset|
230
+
231
+ # Extract bitrate values
232
+ bitrates = jobset.collect do |job|
233
+ job.get(:transfer_bitrate)
234
+ end
235
+
236
+ # Store their sum
237
+ rates[status] = bitrates.reject(&:nil?).sum
238
+ # rates["#{status}_ids"] = jobset.collect(&:id).join(', ')
239
+ end
240
+
241
+ # Return rates
242
+ rates
243
+ end
244
+
225
245
  if Settings.newrelic_enabled?
226
- add_transaction_tracer :push, category: :task
227
- add_transaction_tracer :pop, category: :task
228
- add_transaction_tracer :expire, category: :task
229
- add_transaction_tracer :counts_by_status, category: :task
246
+ add_transaction_tracer :push, category: :task
247
+ add_transaction_tracer :pop, category: :task
248
+ add_transaction_tracer :expire, category: :task
249
+ add_transaction_tracer :rate_by, category: :task
250
+ add_transaction_tracer :jobs_count_by_status, category: :task
230
251
  end
231
252
 
232
253
  end
@@ -75,8 +75,9 @@ module RestFtpDaemon
75
75
  # Post notification, handle server response / multi-lines
76
76
  log_info "sending #{data}"
77
77
  response = http.post uri.path, data, headers
78
- response_lines = response.body.lines
79
78
 
79
+ # Log reponse body
80
+ response_lines = response.body.lines
80
81
  if response_lines.size > 1
81
82
  human_size = Helpers.format_bytes(response.body.bytesize, "B")
82
83
  log_info "received [#{response.code}] #{human_size} (#{response_lines.size} lines)", response_lines
@@ -85,8 +86,14 @@ module RestFtpDaemon
85
86
  end
86
87
 
87
88
  # Handle exceptions
89
+ rescue Net::OpenTimeout => ex
90
+ log_error "Net::OpenTimeout: #{ex.inspect}"
91
+
92
+ rescue SocketError => ex
93
+ log_error "SocketError: #{ex.inspect}"
94
+
88
95
  rescue StandardError => ex
89
- log_error "NOTIFICATION EXCEPTION: #{ex.inspect}"
96
+ log_error "UNHANDLED EXCEPTION: #{ex.inspect}"
90
97
  end
91
98
 
92
99
  def log_context
@@ -0,0 +1,42 @@
1
+ module RestFtpDaemon
2
+
3
+ # Queue that stores all the Jobs waiting to be processed or fully processed
4
+ class Stats
5
+ attr_reader :stats
6
+
7
+ if Settings.newrelic_enabled?
8
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
9
+ end
10
+
11
+ def initialize
12
+ @stats = {}
13
+ @mutex_stats = Mutex.new
14
+ end
15
+
16
+ def set group, name, value
17
+ @mutex_stats.synchronize do
18
+ @stats[group] ||= {}
19
+ @stats[group][name] = value
20
+ end
21
+ end
22
+
23
+ def get group, name
24
+ @mutex_stats.synchronize do
25
+ @stats[group][name] if @stats[group]
26
+ end
27
+ end
28
+
29
+ def add group, name, value
30
+ @mutex_stats.synchronize do
31
+ @stats[group] ||= {}
32
+ @stats[group][name] ||= 0
33
+ @stats[group][name] += value
34
+ end
35
+ end
36
+
37
+ def increment group, name
38
+ add group, name, 1
39
+ end
40
+
41
+ end
42
+ end
@@ -29,9 +29,10 @@
29
29
 
30
30
  #box-workers.col-md-3
31
31
  = render :dashboard_workers
32
+ = render :dashboard_stats
32
33
 
33
- #box-counters.col-md-3
34
- = render :dashboard_counters
34
+ #box-rates.col-md-3
35
+ = render :dashboard_rates
35
36
 
36
37
 
37
38
  .footer
@@ -1,5 +1,4 @@
1
1
  -# coding: utf-8
2
- - trans = $queue.counter_get :transferred
3
2
  - info_procs = (Facter.value :processorcount).to_i
4
3
 
5
4
 
@@ -1,9 +1,9 @@
1
1
  -# coding: utf-8
2
- - trans = $queue.counter_get :transferred
3
2
  - info_procs = (Facter.value :processorcount).to_i
4
3
  - info_load = Sys::CPU.load_avg.first.to_f
5
4
  - info_norm = info_procs.zero? ? "N/A" : (100 * info_load / info_procs).round(1)
6
- - info_processed = $queue.counter_get(:jobs_processed)
5
+ - info_trans = $stats.get :global, :transferred
6
+ - info_processed = $stats.get(:global, :processed)
7
7
  - mem = GetProcessMem.new
8
8
 
9
9
 
@@ -35,6 +35,6 @@
35
35
 
36
36
  .btn-group.btn-group-sm
37
37
  .btn.btn-default.btn-success Transferred
38
- .btn.btn-default= Helpers.format_bytes(trans, "B", 1)
38
+ .btn.btn-default= Helpers.format_bytes(info_trans, "B", 1)
39
39
 
40
40
 
@@ -1,5 +1,5 @@
1
1
  -# coding: utf-8
2
- - counts_by_status = $queue.counts_by_status
2
+ - jobs_count_by_status = $queue.jobs_count_by_status
3
3
  - counts_all = $queue.jobs_count
4
4
  - jobs = @paginate.subset
5
5
 
@@ -9,7 +9,7 @@
9
9
  ALL (#{counts_all})
10
10
 
11
11
  .btn-group.btn-group-xs.filters
12
- - counts_by_status.each do |status, count|
12
+ - jobs_count_by_status.each do |status, count|
13
13
  - klass = (status.to_s == @filter) ? "btn-info" : ""
14
14
  %a.btn.btn-default{href: Helpers.dashboard_filter_url(status), class: klass}
15
15
  #{status} (#{count})
@@ -50,10 +50,10 @@
50
50
  %tbody.jobs
51
51
  = render :dashboard_table, {jobs: jobs}
52
52
 
53
- %thead
54
- %tr
55
- %td{colspan: 14}
56
- %br
53
+ %thead
54
+ %tr
55
+ %td{colspan: 14}
56
+ %br
57
57
 
58
58
  - unless jobs.empty?
59
59
  %tbody.jobs
@@ -0,0 +1,36 @@
1
+ -# coding: utf-8
2
+
3
+ - groups = {pool: "pool", targethost: "target host"}
4
+
5
+
6
+ %h2 Transfer rates
7
+
8
+ %table.table.table-striped.table-hover.table-condensed
9
+
10
+ - groups.each do |group_by, group_title|
11
+ - rates_by_status = $queue.rate_by(group_by)
12
+
13
+ %thead
14
+ %tr
15
+ %th= group_title
16
+ %th.text-right=JOB_STATUS_FINISHED
17
+ %th.text-right=JOB_STATUS_UPLOADING
18
+
19
+ %tbody
20
+ - rates_by_status.each do |group, rates|
21
+ - next if group.nil?
22
+
23
+ %tr.info
24
+ %td= group
25
+ %td.text-right
26
+ = Helpers.format_bytes(rates[JOB_STATUS_FINISHED], "bps")
27
+ %td.text-right
28
+ = Helpers.format_bytes(rates[JOB_STATUS_UPLOADING], "bps")
29
+ -# %td= rates.inspect
30
+
31
+ %thead
32
+ %tr
33
+ %td{colspan: 3}
34
+ %br
35
+
36
+ -# JOB_STATUS_UPLOADING
@@ -0,0 +1,21 @@
1
+ -# coding: utf-8
2
+ %h2 Stats
3
+
4
+ %table.table.table-striped.table-hover.table-condensed
5
+
6
+ %thead
7
+ %tr
8
+ %th group
9
+ %th name
10
+ %th.text-right value
11
+
12
+ %tbody
13
+
14
+ - $stats.stats.each do |group, values|
15
+
16
+ - values.each do |name, value|
17
+ %tr
18
+ %td= group
19
+ %td= name
20
+ %td.text-right= value
21
+
@@ -77,14 +77,6 @@
77
77
  .label.label-default.flag.worker-label= job.priority
78
78
 
79
79
  %td
80
-
81
80
  .label.flag.worker-label{class: Helpers.job_runs_style(runs)}= runs
82
81
 
83
- -#%td
84
- - unless job.priority.nil?
85
- .label.label-default.flag.worker-label= job.priority
86
-
87
- - unless job.wid.nil?
88
- .label.label-warning.flag.worker-label= job.wid
89
-
90
82
 
@@ -3,11 +3,13 @@
3
3
 
4
4
  %table.table.table-striped.table-hover.table-condensed
5
5
 
6
- %tr
7
- %th token
8
- %th value
9
-
10
- - tokens.each do |token, value|
6
+ %thead
11
7
  %tr
12
- %td= Helpers.tokenize token
13
- %td= Helpers.hide_credentials_from_url value
8
+ %th token
9
+ %th value
10
+
11
+ %tbody
12
+ - tokens.each do |token, value|
13
+ %tr
14
+ %td= Helpers.tokenize token
15
+ %td= Helpers.hide_credentials_from_url value
@@ -3,33 +3,35 @@
3
3
 
4
4
  %table.table.table-striped.table-hover.table-condensed
5
5
 
6
- %tr
7
- %th ID
8
- %th pool
9
- %th status
10
- %th job
11
- %th.text-right seen
6
+ %thead
7
+ %tr
8
+ %th pool
9
+ %th worker
10
+ %th status
11
+ %th job
12
+ %th.text-right seen
12
13
 
13
- - @worker_variables.each do |vars|
14
- - wid = vars[:wid]
15
- - status = vars[:status]
16
- - alive = $pool.worker_alive? wid
17
- - trclass = WORKER_STYLES[status]
14
+ %tbody
15
+ - @worker_variables.each do |vars|
16
+ - wid = vars[:wid]
17
+ - status = vars[:status]
18
+ - alive = $pool.worker_alive? wid
19
+ - trclass = WORKER_STYLES[status]
18
20
 
19
- - unless alive
20
- - trclass = "danger"
21
- - status = "DEAD"
21
+ - unless alive
22
+ - trclass = "danger"
23
+ - status = "DEAD"
22
24
 
23
25
 
24
- %tr{class: trclass.to_s}
25
- %td= wid
26
- %td= vars[:pool]
27
- %td= status
28
- %td= vars[:jid]
29
- %td.text-right
26
+ %tr{class: trclass.to_s}
27
+ %td= vars[:pool]
28
+ %td= wid
29
+ %td= status
30
+ %td= vars[:jid]
31
+ %td.text-right
30
32
 
31
- - if vars[:updated_at].is_a? Time
32
- - no_news_for = (Time.now - vars[:updated_at]).round(0)
33
- = Helpers.formatted_duration no_news_for
34
- - else
35
- = "?"
33
+ - if vars[:updated_at].is_a? Time
34
+ - no_news_for = (Time.now - vars[:updated_at]).round(0)
35
+ = Helpers.formatted_duration no_news_for
36
+ - else
37
+ = "?"
@@ -86,7 +86,7 @@ module RestFtpDaemon
86
86
  worker_status WORKER_STATUS_FINISHED, job
87
87
 
88
88
  # Increment total processed jobs count
89
- $queue.counter_inc :jobs_processed
89
+ $stats.increment :global, :processed
90
90
 
91
91
  rescue RestFtpDaemon::JobTimeout => ex
92
92
  log_error "JOB TIMED OUT", ex.backtrace
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rubocop", "~> 0.32.0"
32
32
  spec.add_development_dependency "pry"
33
33
 
34
- spec.add_runtime_dependency "thin", "~> 1.6"
34
+ spec.add_runtime_dependency "thin", "~> 1.6.4"
35
35
  spec.add_runtime_dependency "grape"
36
36
  spec.add_runtime_dependency "grape-entity"
37
37
  spec.add_runtime_dependency "settingslogic"
@@ -1,10 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
- describe "Routes", feature: true do
3
+ describe "Debug", feature: true do
4
4
 
5
- let!(:response) { get "/routes" }
5
+ let!(:response) { get MOUNT_DEBUG }
6
6
 
7
- describe "GET /routes" do
7
+ describe "GET #{MOUNT_DEBUG}" do
8
8
 
9
9
  it "responds successfully" do
10
10
  expect(response.status).to eq 200
@@ -2,9 +2,9 @@ require "spec_helper"
2
2
 
3
3
  describe "Status", feature: true do
4
4
 
5
- let!(:response) { get "/status" }
5
+ let!(:response) { get MOUNT_STATUS }
6
6
 
7
- describe "GET /status" do
7
+ describe "GET #{MOUNT_DEBUG}" do
8
8
 
9
9
  it "responds successfully" do
10
10
  expect(response.status).to eq 200
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-ftp-daemon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.247.1
4
+ version: 0.250.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno MEDICI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-09 00:00:00.000000000 Z
11
+ date: 2016-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '1.6'
103
+ version: 1.6.4
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '1.6'
110
+ version: 1.6.4
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: grape
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -282,10 +282,13 @@ files:
282
282
  - bin/rest-ftp-daemon
283
283
  - config.ru
284
284
  - lib/rest-ftp-daemon.rb
285
+ - lib/rest-ftp-daemon/api/config.rb
285
286
  - lib/rest-ftp-daemon/api/dashboard.rb
287
+ - lib/rest-ftp-daemon/api/debug.rb
286
288
  - lib/rest-ftp-daemon/api/job_presenter.rb
287
289
  - lib/rest-ftp-daemon/api/jobs.rb
288
290
  - lib/rest-ftp-daemon/api/root.rb
291
+ - lib/rest-ftp-daemon/api/status.rb
289
292
  - lib/rest-ftp-daemon/array.rb
290
293
  - lib/rest-ftp-daemon/constants.rb
291
294
  - lib/rest-ftp-daemon/exceptions.rb
@@ -305,12 +308,14 @@ files:
305
308
  - lib/rest-ftp-daemon/settings.rb
306
309
  - lib/rest-ftp-daemon/static/css/bootstrap.css
307
310
  - lib/rest-ftp-daemon/static/css/main.css
311
+ - lib/rest-ftp-daemon/stats.rb
308
312
  - lib/rest-ftp-daemon/uri.rb
309
313
  - lib/rest-ftp-daemon/views/dashboard.haml
310
- - lib/rest-ftp-daemon/views/dashboard_counters.haml
311
314
  - lib/rest-ftp-daemon/views/dashboard_footer.haml
312
315
  - lib/rest-ftp-daemon/views/dashboard_header.haml
313
316
  - lib/rest-ftp-daemon/views/dashboard_jobs.haml
317
+ - lib/rest-ftp-daemon/views/dashboard_rates.haml
318
+ - lib/rest-ftp-daemon/views/dashboard_stats.haml
314
319
  - lib/rest-ftp-daemon/views/dashboard_table.haml
315
320
  - lib/rest-ftp-daemon/views/dashboard_tokens.haml
316
321
  - lib/rest-ftp-daemon/views/dashboard_workers.haml
@@ -321,8 +326,8 @@ files:
321
326
  - rest-ftp-daemon.gemspec
322
327
  - rest-ftp-daemon.yml.sample
323
328
  - spec/rest-ftp-daemon/features/dashboard_spec.rb
329
+ - spec/rest-ftp-daemon/features/debug_spec.rb
324
330
  - spec/rest-ftp-daemon/features/jobs_spec.rb
325
- - spec/rest-ftp-daemon/features/routes_spec.rb
326
331
  - spec/rest-ftp-daemon/features/status_spec.rb
327
332
  - spec/spec_helper.rb
328
333
  - spec/support/config.yml
@@ -1,15 +0,0 @@
1
- -# coding: utf-8
2
- %h2 Counters
3
-
4
- %table.table.table-striped.table-hover.table-condensed
5
-
6
- %tr
7
- %th name
8
- %th.text-right value
9
-
10
- - $queue.counters.each do |name, value|
11
-
12
- %tr
13
- %td= name
14
- %td.text-right= value
15
-