aikido-zen 1.5.0-aarch64-linux → 1.6.0-aarch64-linux

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/benchmarks/ipc/benchmark.rb +149 -0
  3. data/benchmarks/rpc/benchmark.rb +57 -0
  4. data/docs/rails.md +22 -0
  5. data/lib/aikido/zen/actor.rb +10 -10
  6. data/lib/aikido/zen/agent.rb +61 -33
  7. data/lib/aikido/zen/api_cache.rb +61 -0
  8. data/lib/aikido/zen/api_stream.rb +7 -26
  9. data/lib/aikido/zen/attack.rb +14 -14
  10. data/lib/aikido/zen/attack_wave.rb +8 -8
  11. data/lib/aikido/zen/collector/event.rb +26 -26
  12. data/lib/aikido/zen/collector/routes.rb +4 -4
  13. data/lib/aikido/zen/collector/sink_stats.rb +10 -10
  14. data/lib/aikido/zen/collector/stats.rb +17 -17
  15. data/lib/aikido/zen/config.rb +49 -39
  16. data/lib/aikido/zen/context.rb +4 -2
  17. data/lib/aikido/zen/errors.rb +0 -8
  18. data/lib/aikido/zen/event.rb +12 -12
  19. data/lib/aikido/zen/ipc/ipc.rb +506 -0
  20. data/lib/aikido/zen/ipc/rpc.rb +349 -0
  21. data/lib/aikido/zen/ipc.rb +4 -0
  22. data/lib/aikido/zen/middleware/fork_detector.rb +8 -2
  23. data/lib/aikido/zen/middleware/rack_throttler.rb +2 -4
  24. data/lib/aikido/zen/outbound_connection.rb +3 -3
  25. data/lib/aikido/zen/payload.rb +3 -3
  26. data/lib/aikido/zen/rate_limiter/result.rb +20 -0
  27. data/lib/aikido/zen/request.rb +14 -9
  28. data/lib/aikido/zen/route.rb +3 -3
  29. data/lib/aikido/zen/sinks/action_controller.rb +2 -4
  30. data/lib/aikido/zen/system_info.rb +13 -13
  31. data/lib/aikido/zen/version.rb +1 -1
  32. data/lib/aikido/zen/worker_process/agent/client.rb +141 -0
  33. data/lib/aikido/zen/worker_process/agent/server.rb +97 -0
  34. data/lib/aikido/zen/worker_process/agent.rb +4 -0
  35. data/lib/aikido/zen/worker_process.rb +3 -0
  36. data/lib/aikido/zen.rb +55 -29
  37. metadata +12 -6
  38. data/lib/aikido/zen/detached_agent/agent.rb +0 -79
  39. data/lib/aikido/zen/detached_agent/front_object.rb +0 -41
  40. data/lib/aikido/zen/detached_agent/server.rb +0 -78
  41. data/lib/aikido/zen/detached_agent.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcc4f05fee5eb4c2b6bc4b75313c8d1a8948282b2e1ea3e4ef0e735055e55f42
4
- data.tar.gz: 692ace187aae4b18bce5d1aefaa10c7aeacbfd28016645792556ae0aafe2c8dc
3
+ metadata.gz: eba16aa05cb3f3506e487a3dfd7c03a1d4bbc186a397053aaf43562fad1ed4f4
4
+ data.tar.gz: b9815eab35c5f245eba4587c64bf4bfda422c1bcfea1d1d21eee747057723e41
5
5
  SHA512:
6
- metadata.gz: a32eea6aec3687a23e54c6cc3e1365b4e4bd9e2cdcf93d39acf46289ad6063ca0671061ed1aada13ab18267459af66d0dc5660f08745c0f290f705086cdb214e
7
- data.tar.gz: a145a0fcd6e137f4ca276219e7a235811da1bb3aed09ea7e0a123ab7e69ae09b5df657a0050de99e30165040906db9b948979a24e0a736a61e992caad33fed53
6
+ metadata.gz: 9c99f6276ce2d33f42d3b9ec7f5430bb647cd72cb146e583066d97d918cc0e59b21d82999dada088197627be1226579cbea675f7750c609346a2d7487f39b3e5
7
+ data.tar.gz: ad2e3595f4fee8181ba2083f05a2a15181cf62db472f47c112a3480dd6575846cf50d7e9c3ac420a5f3ddbd48bda9b98cff815de50a12a35e427ebbce7e68427
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "benchmark"
4
+ require "securerandom"
5
+ require "aikido-zen"
6
+
7
+ class BasicIO
8
+ def initialize(data, timeout: 5)
9
+ @data = data
10
+ @size = data.bytesize
11
+ @timeout = timeout
12
+ end
13
+
14
+ def read(socket)
15
+ buffer = String.new(encoding: Encoding::BINARY)
16
+
17
+ while buffer.bytesize < @size
18
+ case chunk = socket.read_nonblock(@size - buffer.bytesize, exception: false)
19
+ when :wait_readable
20
+ raise Errno::ETIMEDOUT, "read timed out" unless IO.select([socket], nil, nil, @timeout)
21
+ when nil
22
+ raise EOFError
23
+ else
24
+ buffer << chunk
25
+ end
26
+ end
27
+
28
+ buffer
29
+ end
30
+
31
+ def write(socket)
32
+ written = 0
33
+
34
+ while written < @data.bytesize
35
+ case n = socket.write_nonblock(@data.byteslice(written..), exception: false)
36
+ when :wait_writable
37
+ raise Errno::ETIMEDOUT, "write timed out" unless IO.select(nil, [socket], nil, @timeout)
38
+ else
39
+ written += n
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class TimedIO
46
+ include Aikido::Zen::IPC::TimedIO
47
+
48
+ def initialize(data, timeout: 5)
49
+ @data = data
50
+ @size = data.bytesize
51
+ @timeout = timeout
52
+ end
53
+
54
+ def read(socket)
55
+ read_with_deadline(socket, @size, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
56
+ end
57
+
58
+ def write(socket)
59
+ write_with_deadline(socket, @data, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
60
+ end
61
+ end
62
+
63
+ class FramedIO
64
+ include Aikido::Zen::IPC::FramedIO
65
+
66
+ def initialize(data, timeout: 5)
67
+ @data = data
68
+ @timeout = timeout
69
+ @buffer = String.new(encoding: Encoding::BINARY)
70
+ end
71
+
72
+ def read(socket)
73
+ read_coalesced_frame_with_timeout(socket, @buffer, nil, @timeout)
74
+ end
75
+
76
+ def write(socket)
77
+ write_coalesced_frame_with_timeout(socket, @data, nil, @timeout)
78
+ end
79
+ end
80
+
81
+ def run(duration, io)
82
+ secret = SecureRandom.bytes(32)
83
+
84
+ server = Aikido::Zen::IPC::Server.start(secret) do |socket|
85
+ loop do
86
+ io.read(socket)
87
+ end
88
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
89
+ # disconnected
90
+ rescue IOError
91
+ # client stopped
92
+ end
93
+
94
+ client = Aikido::Zen::IPC::Client.new(secret, server.host, server.port)
95
+
96
+ sent = 0
97
+
98
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
99
+
100
+ result = Benchmark.measure do
101
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
102
+ io.write(client.socket)
103
+
104
+ sent += 1
105
+ end
106
+ end
107
+
108
+ [sent, result.real]
109
+ ensure
110
+ client.close
111
+ server.stop
112
+ end
113
+
114
+ def report(label, sent, real, data_size, baseline: nil)
115
+ rate = sent / real
116
+ mib_per_sec = rate * data_size / (1024 * 1024)
117
+
118
+ puts label
119
+ puts "#{rate.round} messages/sec, #{mib_per_sec.round(2)} MiB/sec"
120
+
121
+ if baseline
122
+ overhead = 100 - (100.0 * rate / baseline)
123
+ puts "#{overhead.round(1)}% fewer messages/sec than baseline"
124
+ end
125
+ end
126
+
127
+ if __FILE__ == $0
128
+ duration = (ENV["DURATION"] || 5).to_i
129
+ data_size = (ENV["DATA_SIZE"] || 1024).to_i
130
+
131
+ data = SecureRandom.bytes(data_size)
132
+
133
+ basic_io = BasicIO.new(data)
134
+ basic_io_sent, basic_io_real = run(duration, basic_io)
135
+ report("baseline", basic_io_sent, basic_io_real, data_size)
136
+ basic_io_rate = basic_io_sent / basic_io_real
137
+
138
+ puts
139
+
140
+ timed_io = TimedIO.new(data)
141
+ timed_io_sent, timed_io_real = run(duration, timed_io)
142
+ report("TimedIO", timed_io_sent, timed_io_real, data_size, baseline: basic_io_rate)
143
+
144
+ puts
145
+
146
+ framed_io = FramedIO.new(data)
147
+ framed_io_sent, framed_io_real = run(duration, framed_io)
148
+ report("FramedIO (coalesced)", framed_io_sent, framed_io_real, data_size, baseline: basic_io_rate)
149
+ end
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "benchmark"
4
+ require "securerandom"
5
+ require "logger"
6
+ require "aikido-zen"
7
+
8
+ def run(duration, concurrency)
9
+ secret = SecureRandom.bytes(32)
10
+ logger = Logger.new(File::NULL)
11
+
12
+ server = Aikido::Zen::RPC::Server.start(secret, logger: logger) do |server|
13
+ server.handle("echo") { |respond, value| respond.call(value) }
14
+ end
15
+
16
+ client = Aikido::Zen::RPC::Client.start(secret, server.host, server.port, logger: logger)
17
+
18
+ calls = Concurrent::AtomicFixnum.new(0)
19
+
20
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
21
+
22
+ result = Benchmark.measure do
23
+ threads = concurrency.times.map do
24
+ Thread.new do
25
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
26
+ client.invoke("echo", 5.0, "hello")
27
+
28
+ calls.increment
29
+ end
30
+ end
31
+ end
32
+
33
+ threads.each(&:join)
34
+ end
35
+
36
+ [calls.value, result.real]
37
+ ensure
38
+ client.stop
39
+ server.stop
40
+ end
41
+
42
+ def report(label, total_calls, real)
43
+ rate = total_calls / real
44
+ latency_ms = real / total_calls * 1000
45
+
46
+ puts label
47
+ puts "#{rate.round} calls/sec, #{latency_ms.round(3)} ms/call"
48
+ end
49
+
50
+ if __FILE__ == $0
51
+ duration = (ENV["DURATION"] || 5).to_i
52
+ concurrency = (ENV["CONCURRENCY"] || 1).to_i
53
+
54
+ calls, real = run(duration, concurrency)
55
+
56
+ report("RPC echo (concurrency: #{concurrency})", calls, real)
57
+ end
data/docs/rails.md CHANGED
@@ -27,6 +27,28 @@ Bundler.require(*Rails.groups)
27
27
  That's it! Zen will start to run inside your app when it starts getting
28
28
  requests.
29
29
 
30
+ ## Running with multiple worker processes (Puma)
31
+
32
+ Zen can share rate limiting state, runtime settings, and runtime stats
33
+ across all of an app's worker processes. This only works if Rails is booted
34
+ once and then forked into workers, which on Puma requires
35
+ [`preload_app!`][puma-preload]:
36
+
37
+ ```ruby
38
+ # config/puma.rb
39
+ preload_app!
40
+ ```
41
+
42
+ Without `preload_app!`, each worker boots Rails independently, so Zen
43
+ tracks rate limits, settings, and stats separately per worker instead
44
+ of sharing them.
45
+
46
+ > [!NOTE]
47
+ > This only matters for multi-process setups; it doesn't apply to
48
+ > single-process or threaded-only deployments.
49
+
50
+ [puma-preload]: https://github.com/puma/puma#cluster-mode
51
+
30
52
  ## Rate limiting and user blocking
31
53
 
32
54
  If you want to add the rate limiting feature to your app, modify your code like this:
@@ -40,11 +40,11 @@ module Aikido::Zen
40
40
  class Actor
41
41
  def self.from_json(data)
42
42
  new(
43
- id: data[:id],
44
- name: data[:name],
45
- ip: data[:lastIpAddress],
46
- first_seen_at: Time.at(data[:firstSeenAt] / 1000),
47
- last_seen_at: Time.at(data[:lastSeenAt] / 1000)
43
+ id: data["id"],
44
+ name: data["name"],
45
+ ip: data["lastIpAddress"],
46
+ first_seen_at: Time.at(data["firstSeenAt"] / 1000),
47
+ last_seen_at: Time.at(data["lastSeenAt"] / 1000)
48
48
  )
49
49
  end
50
50
 
@@ -135,11 +135,11 @@ module Aikido::Zen
135
135
 
136
136
  def as_json
137
137
  {
138
- id: id,
139
- name: name,
140
- lastIpAddress: ip,
141
- firstSeenAt: first_seen_at.to_i * 1000,
142
- lastSeenAt: last_seen_at.to_i * 1000
138
+ "id" => id,
139
+ "name" => name,
140
+ "lastIpAddress" => ip,
141
+ "firstSeenAt" => first_seen_at.to_i * 1000,
142
+ "lastSeenAt" => last_seen_at.to_i * 1000
143
143
  }.compact
144
144
  end
145
145
  end
@@ -19,14 +19,12 @@ module Aikido::Zen
19
19
  def initialize(
20
20
  config: Aikido::Zen.config,
21
21
  collector: Aikido::Zen.collector,
22
- detached_agent: Aikido::Zen.detached_agent,
23
22
  worker: Aikido::Zen::Worker.new(config: config),
24
23
  api_client: Aikido::Zen::APIClient.new(config: config),
25
24
  api_stream: Aikido::Zen::APIStream.new(config: config)
26
25
  )
27
26
  @config = config
28
27
  @collector = collector
29
- @detached_agent = detached_agent
30
28
  @worker = worker
31
29
  @api_client = api_client
32
30
  @api_stream = api_stream
@@ -61,35 +59,37 @@ module Aikido::Zen
61
59
  return
62
60
  end
63
61
 
62
+ # Code coverage is disabled here because the block is only called when
63
+ # the process exits.
64
+ # :nocov:
64
65
  at_exit { stop! if started? }
66
+ # :nocov:
65
67
 
66
68
  report(Events::Started.new(time: @started_at)) do |response|
67
- if update_settings_from_runtime_config!(response)
69
+ if update_settings_from_runtime_config!(response, reason: "after start")
68
70
  updated_settings!
69
- @config.logger.info("Updated runtime settings")
71
+ # :nocov:
72
+ else
73
+ # empty
70
74
  end
75
+ # :nocov:
71
76
  rescue => err
72
77
  @config.logger.error(err.message)
73
78
  end
74
79
 
75
80
  begin
76
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
77
- @config.logger.info("Updated runtime firewall list")
81
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after start")
78
82
  rescue => err
79
83
  @config.logger.error(err.message)
80
84
  end
81
85
 
82
86
  if @config.realtime_settings_updates_enabled?
83
- if @api_stream.can_connect?
84
- @api_stream.handle("config-updated") { |event| settings_updated(event) }
85
- @api_stream.start!
86
-
87
- # Use the realtime setting updates endpoint when polling to check
88
- # whether settings should be fetched.
89
- @api_client.should_fetch_settings_endpoint = @config.realtime_settings_updates_endpoint
90
- else
91
- @config.logger.warn("Can't reach #{Aikido::Zen.config.realtime_settings_updates_endpoint}, make sure it's in your outbound firewall allowlist. Realtime config updates won't be available, switched to polling.")
87
+ @api_stream.handle("config-updated") do |event|
88
+ @config.logger.debug("Received server-sent event: config-updated")
89
+ settings_updated(event)
92
90
  end
91
+
92
+ @api_stream.start!
93
93
  end
94
94
 
95
95
  poll_for_setting_updates
@@ -177,13 +177,15 @@ module Aikido::Zen
177
177
 
178
178
  heartbeat = @collector.flush
179
179
  report(heartbeat) do |response|
180
- if update_settings_from_runtime_config!(response)
180
+ if update_settings_from_runtime_config!(response, reason: "after heartbeat")
181
181
  updated_settings!
182
- @config.logger.info("Updated runtime settings after heartbeat")
183
182
 
184
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
185
- @config.logger.info("Updated runtime firewall list after heartbeat")
183
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after heartbeat")
184
+ # :nocov:
185
+ else
186
+ # empty
186
187
  end
188
+ # :nocov:
187
189
  end
188
190
  end
189
191
 
@@ -197,13 +199,15 @@ module Aikido::Zen
197
199
  def poll_for_setting_updates
198
200
  @worker.every(@config.polling_interval) do
199
201
  if @api_client.should_fetch_settings?
200
- if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
202
+ if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after polling")
201
203
  updated_settings!
202
- @config.logger.info("Updated runtime settings after polling")
204
+ # :nocov:
205
+ else
206
+ # empty
203
207
  end
208
+ # :nocov:
204
209
 
205
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
206
- @config.logger.info("Updated runtime firewall list after polling")
210
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after polling")
207
211
  end
208
212
  end
209
213
  end
@@ -214,13 +218,15 @@ module Aikido::Zen
214
218
  updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
215
219
 
216
220
  if should_fetch_settings?(updated_at)
217
- if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
221
+ if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after server-sent event")
218
222
  updated_settings!
219
- @config.logger.info("Updated runtime settings after server-side event")
220
223
 
221
- update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
222
- @config.logger.info("Updated runtime firewall list after server-side event")
224
+ update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after server-sent event")
225
+ # :nocov:
226
+ else
227
+ # empty
223
228
  end
229
+ # :nocov:
224
230
  end
225
231
  end
226
232
 
@@ -239,22 +245,44 @@ module Aikido::Zen
239
245
  end
240
246
 
241
247
  # @param data [Hash]
242
- # @return [Boolean]
243
- def update_settings_from_runtime_config!(data)
248
+ # @param reason [String] context appended to the log message when logged
249
+ # @return [Boolean] whether the settings were updated; or nil if another
250
+ # thread is already applying an update.
251
+ def update_settings_from_runtime_config!(data, reason:)
252
+ # :nocov:
244
253
  return unless @runtime_config_update_mutex.try_lock
254
+ # :nocov:
245
255
  begin
246
- Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
256
+ return false unless Aikido::Zen.api_cache.update_runtime_config(data)
257
+
258
+ if Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
259
+ @config.logger.info("Updated runtime settings #{reason}")
260
+ true
261
+ else
262
+ false
263
+ end
247
264
  ensure
248
265
  @runtime_config_update_mutex.unlock
249
266
  end
250
267
  end
251
268
 
252
269
  # @param data [Hash]
253
- # @return [Boolean]
254
- def update_settings_from_runtime_firewall_lists!(data)
270
+ # @param reason [String] context appended to the log message when logged
271
+ # @return [Boolean] whether the settings were updated; or nil if another
272
+ # thread is already applying an update.
273
+ def update_settings_from_runtime_firewall_lists!(data, reason:)
274
+ # :nocov:
255
275
  return unless @runtime_firewall_lists_update_mutex.try_lock
276
+ # :nocov:
256
277
  begin
257
- Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
278
+ return false unless Aikido::Zen.api_cache.update_runtime_firewall_lists(data)
279
+
280
+ if Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
281
+ @config.logger.info("Updated runtime firewall list #{reason}")
282
+ true
283
+ else
284
+ false
285
+ end
258
286
  ensure
259
287
  @runtime_firewall_lists_update_mutex.unlock
260
288
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ class APICache
5
+ attr_reader :runtime_config
6
+ attr_reader :runtime_firewall_lists
7
+ attr_reader :runtime_config_generation
8
+ attr_reader :runtime_firewall_lists_generation
9
+
10
+ def initialize
11
+ @runtime_config_generation = 0
12
+ @runtime_firewall_lists_generation = 0
13
+ end
14
+
15
+ def runtime_config=(value)
16
+ update_runtime_config(value)
17
+ end
18
+
19
+ def runtime_firewall_lists=(value)
20
+ update_runtime_firewall_lists(value)
21
+ end
22
+
23
+ # @param value [Hash, nil]
24
+ # @return [Boolean] whether the value actually changed
25
+ def update_runtime_config(value)
26
+ return false if value == @runtime_config
27
+
28
+ @runtime_config_generation += 1
29
+ @runtime_config = value
30
+
31
+ true
32
+ end
33
+
34
+ # @param value [Hash, nil]
35
+ # @return [Boolean] whether the value actually changed
36
+ def update_runtime_firewall_lists(value)
37
+ return false if value == @runtime_firewall_lists
38
+
39
+ @runtime_firewall_lists_generation += 1
40
+ @runtime_firewall_lists = value
41
+
42
+ true
43
+ end
44
+
45
+ # @param known_generation [Integer, nil]
46
+ # @return [Array(Object, Integer), nil] the current value and generation,
47
+ # or nil if the known generation is already current.
48
+ def config_if_changed(known_generation)
49
+ return nil if known_generation == runtime_config_generation
50
+ [runtime_config, runtime_config_generation]
51
+ end
52
+
53
+ # @param known_generation [Integer, nil]
54
+ # @return [Array(Object, Integer), nil] the current value and generation,
55
+ # or nil if the known generation is already current.
56
+ def firewall_lists_if_changed(known_generation)
57
+ return nil if known_generation == runtime_firewall_lists_generation
58
+ [runtime_firewall_lists, runtime_firewall_lists_generation]
59
+ end
60
+ end
61
+ end
@@ -27,7 +27,7 @@ module Aikido::Zen
27
27
  @thread = nil
28
28
  @http = nil
29
29
 
30
- endpoint = @config.realtime_settings_updates_endpoint
30
+ endpoint = @config.realtime_endpoint
31
31
 
32
32
  @host = endpoint.host
33
33
  @port = endpoint.port
@@ -37,31 +37,6 @@ module Aikido::Zen
37
37
  @handlers = Concurrent::Array.new
38
38
  end
39
39
 
40
- # @return [Boolean] whether we could connect to the realtime endpoint
41
- def can_connect?
42
- http = Net::HTTP.new(@host, @port)
43
- http.use_ssl = @use_ssl
44
- http.open_timeout = 5
45
- http.write_timeout = 5
46
- http.read_timeout = 5
47
- http.max_retries = 0
48
-
49
- request = Net::HTTP::Get.new("/config")
50
- request["Authorization"] = @token
51
-
52
- begin
53
- http.request(request)
54
-
55
- return true
56
- rescue Timeout::Error, SocketError, IOError, SystemCallError, OpenSSL::OpenSSLError => err
57
- @config.logger.debug("Error probing realtime endpoint: #{err.class}: #{err.message}")
58
- rescue => err
59
- @config.logger.error("Error probing realtime endpoint: #{err.class}: #{err.message}")
60
- end
61
-
62
- false
63
- end
64
-
65
40
  def running?
66
41
  @running.true?
67
42
  end
@@ -141,12 +116,18 @@ module Aikido::Zen
141
116
  http.open_timeout = @open_timeout
142
117
  http.write_timeout = @write_timeout
143
118
  http.read_timeout = @read_timeout
119
+ # Working around Net::HTTP cleverness; retries GET requests once by default.
144
120
  http.max_retries = 0
145
121
 
146
122
  request = Net::HTTP::Get.new("/api/runtime/stream")
147
123
  request["Authorization"] = @token
148
124
  request["Accept"] = "text/event-stream"
149
125
  request["Cache-Control"] = "no-cache"
126
+ request["X-Agent-Platform"] = "ruby"
127
+ request["X-Agent-Version"] = Aikido::Zen::VERSION
128
+ # Working around Net::HTTP cleverness; auto-negotiates gzip and buffers
129
+ # decompressed output internally.
130
+ request["Accept-Encoding"] = "identity"
150
131
 
151
132
  @config.logger.debug("API stream connecting")
152
133
  http.start
@@ -44,11 +44,11 @@ module Aikido::Zen
44
44
 
45
45
  def as_json
46
46
  {
47
- kind: kind,
48
- blocked: blocked?,
49
- metadata: metadata,
50
- operation: @operation,
51
- stack: @stack
47
+ "kind" => kind,
48
+ "blocked" => blocked?,
49
+ "metadata" => metadata,
50
+ "operation" => @operation,
51
+ "stack" => @stack
52
52
  }.compact.merge(input.as_json)
53
53
  end
54
54
 
@@ -70,7 +70,7 @@ module Aikido::Zen
70
70
 
71
71
  def metadata
72
72
  {
73
- filename: filepath
73
+ "filename" => filepath
74
74
  }
75
75
  end
76
76
 
@@ -107,7 +107,7 @@ module Aikido::Zen
107
107
 
108
108
  def metadata
109
109
  {
110
- command: @command
110
+ "command" => @command
111
111
  }
112
112
  end
113
113
 
@@ -139,9 +139,9 @@ module Aikido::Zen
139
139
 
140
140
  def metadata
141
141
  {
142
- sql: @query,
143
- dialect: @dialect.name,
144
- failedToTokenize: @failed_to_tokenize || nil
142
+ "sql" => @query,
143
+ "dialect" => @dialect.name,
144
+ "failedToTokenize" => @failed_to_tokenize || nil
145
145
  }.compact
146
146
  end
147
147
 
@@ -174,8 +174,8 @@ module Aikido::Zen
174
174
 
175
175
  def metadata
176
176
  {
177
- hostname: @request.uri.hostname,
178
- port: @request.uri.port.to_s
177
+ "hostname" => @request.uri.hostname,
178
+ "port" => @request.uri.port.to_s
179
179
  }
180
180
  end
181
181
  end
@@ -212,8 +212,8 @@ module Aikido::Zen
212
212
 
213
213
  def metadata
214
214
  {
215
- hostname: @hostname,
216
- privateIP: @address
215
+ "hostname" => @hostname,
216
+ "privateIP" => @address
217
217
  }
218
218
  end
219
219
  end
@@ -69,9 +69,9 @@ module Aikido::Zen
69
69
 
70
70
  def as_json
71
71
  {
72
- ipAddress: @ip_address,
73
- userAgent: @user_agent,
74
- source: @source
72
+ "ipAddress" => @ip_address,
73
+ "userAgent" => @user_agent,
74
+ "source" => @source
75
75
  }.compact
76
76
  end
77
77
 
@@ -101,10 +101,10 @@ module Aikido::Zen
101
101
 
102
102
  def as_json
103
103
  {
104
- metadata: {
105
- samples: @samples.as_json.to_json # The API only accepts string values in metadata
104
+ "metadata" => {
105
+ "samples" => @samples.as_json.to_json # The API only accepts string values in metadata
106
106
  },
107
- user: @user.as_json
107
+ "user" => @user.as_json
108
108
  }.compact
109
109
  end
110
110
 
@@ -130,8 +130,8 @@ module Aikido::Zen
130
130
 
131
131
  def as_json
132
132
  {
133
- method: @verb.as_json,
134
- url: @path.as_json
133
+ "method" => @verb.as_json,
134
+ "url" => @path.as_json
135
135
  }.compact
136
136
  end
137
137