Class: Promiscuous::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/promiscuous/cli.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



4
5
6
# File 'lib/promiscuous/cli.rb', line 4

def options
  @options
end

Instance Method Details

#bootObject



212
213
214
215
216
217
218
# File 'lib/promiscuous/cli.rb', line 212

def boot
  self.options = parse_args(ARGV)
  daemonize if options[:daemonize]
  write_pid if options[:pid_file]
  load_app
  run
end

#daemonizeObject



220
221
222
# File 'lib/promiscuous/cli.rb', line 220

def daemonize
  Process.daemon(true)
end

#generate_mocksObject



113
114
115
116
# File 'lib/promiscuous/cli.rb', line 113

def generate_mocks
  f = options[:output] ? File.open(options[:output], 'w') : STDOUT
  f.write Promiscuous::Publisher::MockGenerator.generate
end

#load_appObject



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/promiscuous/cli.rb', line 197

def load_app
  if options[:require]
    begin
      require options[:require]
    rescue LoadError
      require "./#{options[:require]}"
    end
  else
    require 'rails'
    require 'promiscuous/railtie'
    require File.expand_path("./config/environment")
    ::Rails.application.eager_load!
  end
end

#parse_args(args) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/promiscuous/cli.rb', line 118

def parse_args(args)
  options = {}

  require 'optparse'
  parser = OptionParser.new do |opts|
    opts.banner = "Usage: promiscuous [options] action"

    opts.separator ""
    opts.separator "Actions:"
    opts.separator "    promiscuous publish \"Model1.where(:updated_at.gt => 1.day.ago)\" [Model2 Model3...]"
    opts.separator "    promiscuous publisher_recovery"
    opts.separator "    promiscuous subscribe"
    opts.separator "    promiscuous mocks"
    opts.separator "    promiscuous record logfile"
    opts.separator "    promiscuous replay logfile"
    opts.separator ""
    opts.separator "Options:"

    opts.on "-l", "--require FILE", "File to require to load your app. Don't worry about it with rails" do |file|
      options[:require] = file
    end

    opts.on "-p", "--prefetch [NUM]", "Number of messages to prefetch" do |prefetch|
      exit 1 if prefetch.to_i == 0
      Promiscuous::Config.prefetch = prefetch.to_i
    end

    opts.on "-o", "--output FILE", "Output file for mocks. Defaults to stdout" do |file|
      options[:output] = file
    end

    opts.on "-s", "--stat-interval [DURATION]", "Stats refresh rate (0 to disable)" do |duration|
      Promiscuous::Config.stats_interval = duration.to_f
    end

    opts.on "-t", "--threads [NUM]", "Number of subscriber worker threads to run. Defaults to 10." do |threads|
      Promiscuous::Config.subscriber_threads = threads.to_i
    end

    opts.on "-D", "--daemonize", "Daemonize process" do
      options[:daemonize] = true
    end

    opts.on "-P", "--pid-file [pid_file]", "Set a pid-file" do |pid_file|
      options[:pid_file] = pid_file
    end

    opts.on("-V", "--version", "Show version") do
      puts "Promiscuous #{Promiscuous::VERSION}"
      puts "License MIT"
      exit
    end
  end

  args = args.dup
  parser.parse!(args)

  options[:action] = args.shift.try(:to_sym)
  options[:criterias] = args
  options[:log_file] = args.first

  case options[:action]
  when :publish             then raise "Please specify one or more criterias" unless options[:criterias].present?
  when :subscribe           then raise "Why are you specifying a criteria?"   if     options[:criterias].present?
  when :record              then raise "Please specify a log file to record"  unless options[:log_file].present?
  when :replay              then raise "Please specify a log file to replay"  unless options[:log_file].present?
  when :publisher_recovery
  when :mocks
  else puts parser; exit 1
  end

  options
rescue SystemExit
  exit
rescue Exception => e
  puts e
  exit
end


242
243
244
245
# File 'lib/promiscuous/cli.rb', line 242

def print_status(msg)
  Promiscuous.info msg
  STDERR.puts msg
end

#publishObject



50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/promiscuous/cli.rb', line 50

def publish
  options[:criterias].map { |criteria| eval(criteria) }.each do |criteria|
    break if @stop
    title = criteria.name
    title = "#{title}#{' ' * [0, 20 - title.size].max}"
    bar = ProgressBar.create(:format => '%t |%b>%i| %c/%C %e', :title => title, :total => criteria.count)
    criteria.each do |doc|
      break if @stop
      doc.promiscuous.sync
      bar.increment
    end
  end
end

#publisher_recoveryObject



106
107
108
109
110
111
# File 'lib/promiscuous/cli.rb', line 106

def publisher_recovery
  @worker = Promiscuous::Publisher::Worker.new
  @worker.start
  print_status "Waiting for messages to recover..."
  sleep 0.2 until !@worker
end

#recordObject



64
65
66
67
68
69
# File 'lib/promiscuous/cli.rb', line 64

def record
  @worker = Promiscuous::Subscriber::Worker::Recorder.new(options[:log_file])
  @worker.start
  print_status "Recording..."
  sleep 0.2 until !@worker
end

#replayObject



82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/promiscuous/cli.rb', line 82

def replay
  require 'json'
  @num_msg = 0
  File.open(options[:log_file], 'r').each do |line|
    break if @stop
    case line
    when /^\[promiscuous\] \[receive\] ({.*})$/ then replay_payload($1)
    when /^\[promiscuous\] \[publish\] .* -> ({.*})$/ then replay_payload($1)
    when /^({.*})$/ then replay_payload($1)
    end
  end

  print_status "Replayed #{@num_msg} messages"
end

#replay_payload(payload) ⇒ Object



71
72
73
74
75
76
77
78
79
80
# File 'lib/promiscuous/cli.rb', line 71

def replay_payload(payload)
  endpoint = MultiJson.load(payload)['__amqp__']
  if endpoint
    # TODO confirm
    Promiscuous::AMQP.publish(:key => endpoint, :payload => payload)
    @num_msg += 1
  else
    puts "[warn] missing destination in #{payload}"
  end
end

#runObject



230
231
232
233
234
235
236
237
238
239
240
# File 'lib/promiscuous/cli.rb', line 230

def run
  trap_signals
  case options[:action]
  when :publish   then publish
  when :subscribe then subscribe
  when :record    then record
  when :replay    then replay
  when :mocks     then generate_mocks
  when :publisher_recovery  then publisher_recovery
  end
end

#subscribeObject



97
98
99
100
101
102
103
104
# File 'lib/promiscuous/cli.rb', line 97

def subscribe
  @worker = Promiscuous::Subscriber::Worker.new
  @worker.start
  Promiscuous::Config.subscriber_threads.tap do |threads|
    print_status "Replicating [#{threads} thread#{'s' if threads > 1}]..."
  end
  sleep 0.2 until !@worker
end

#trap_debug_signalsObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/promiscuous/cli.rb', line 6

def trap_debug_signals
  Signal.trap 'SIGUSR2' do
    # Using a thread because we cannot acquire mutexes in a trap context in
    # ruby 2.0
    Thread.new do
      Thread.list.each do |thread|
        next if Thread.current == thread

        print_status  '----[ Threads ]----' + '-' * (100-19)
        if thread.backtrace
          print_status "Thread #{thread} #{thread['label']}"
          print_status thread.backtrace.join("\n")
        else
          print_status "Thread #{thread} #{thread['label']} -- no backtrace"
        end
      end
    end
  end
end

#trap_exit_signalsObject



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/promiscuous/cli.rb', line 26

def trap_exit_signals
  %w(SIGTERM SIGINT).each do |signal|
    Signal.trap(signal) do
      # Using a thread because we cannot acquire mutexes in a trap context in
      # ruby 2.0
      Thread.new do
        print_status "Exiting..."
        if @stop
          @worker.try(:show_stop_status)
        else
          @stop = true
          @worker.try(:stop)
          @worker = nil
        end
      end.join
    end
  end
end

#trap_signalsObject



45
46
47
48
# File 'lib/promiscuous/cli.rb', line 45

def trap_signals
  trap_debug_signals
  trap_exit_signals
end

#write_pidObject



224
225
226
227
228
# File 'lib/promiscuous/cli.rb', line 224

def write_pid
  File.open(options[:pid_file], 'w') do |f|
    f.puts Process.pid
  end
end