rest-ftp-daemon 0.247.1 → 0.250.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 (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
-