rookout 0.1.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +12 -0
  3. data/bin/rookout +30 -0
  4. data/lib/rookout.rb +18 -0
  5. data/lib/rookout/augs/actions/action.rb +11 -0
  6. data/lib/rookout/augs/actions/action_run_processor.rb +29 -0
  7. data/lib/rookout/augs/aug.rb +121 -0
  8. data/lib/rookout/augs/aug_factory.rb +69 -0
  9. data/lib/rookout/augs/aug_rate_limiter.rb +96 -0
  10. data/lib/rookout/augs/augs_manager.rb +77 -0
  11. data/lib/rookout/augs/conditions/condition.rb +15 -0
  12. data/lib/rookout/augs/locations/location.rb +11 -0
  13. data/lib/rookout/augs/locations/location_file_line.rb +26 -0
  14. data/lib/rookout/com_ws/agent_com_ws.rb +221 -0
  15. data/lib/rookout/com_ws/backoff.rb +35 -0
  16. data/lib/rookout/com_ws/command_handler.rb +22 -0
  17. data/lib/rookout/com_ws/git.rb +53 -0
  18. data/lib/rookout/com_ws/information.rb +85 -0
  19. data/lib/rookout/com_ws/output.rb +135 -0
  20. data/lib/rookout/com_ws/token_bucket.rb +36 -0
  21. data/lib/rookout/config.rb +55 -0
  22. data/lib/rookout/exceptions.rb +140 -0
  23. data/lib/rookout/interface.rb +140 -0
  24. data/lib/rookout/logger.rb +158 -0
  25. data/lib/rookout/processor/namespace_serializer.rb +26 -0
  26. data/lib/rookout/processor/namespaces/container_namespace.rb +45 -0
  27. data/lib/rookout/processor/namespaces/frame_namespace.rb +65 -0
  28. data/lib/rookout/processor/namespaces/namespace.rb +28 -0
  29. data/lib/rookout/processor/namespaces/noop_namespace.rb +32 -0
  30. data/lib/rookout/processor/namespaces/ruby_object_namespace.rb +97 -0
  31. data/lib/rookout/processor/namespaces/ruby_object_serializer.rb +208 -0
  32. data/lib/rookout/processor/namespaces/ruby_utils_namespace.rb +65 -0
  33. data/lib/rookout/processor/namespaces/stack_namespace.rb +30 -0
  34. data/lib/rookout/processor/namespaces/traceback_namespace.rb +40 -0
  35. data/lib/rookout/processor/operations/operation.rb +11 -0
  36. data/lib/rookout/processor/operations/set_operation.rb +48 -0
  37. data/lib/rookout/processor/paths/arithmetic_path.rb +84 -0
  38. data/lib/rookout/processor/paths/canopy/actions.rb +184 -0
  39. data/lib/rookout/processor/paths/canopy/consts.rb +82 -0
  40. data/lib/rookout/processor/paths/canopy/maps.rb +2837 -0
  41. data/lib/rookout/processor/paths/canopy/markers.rb +186 -0
  42. data/lib/rookout/processor/paths/path.rb +15 -0
  43. data/lib/rookout/processor/processor.rb +34 -0
  44. data/lib/rookout/processor/processor_factory.rb +23 -0
  45. data/lib/rookout/processor/rook_error.rb +45 -0
  46. data/lib/rookout/protobuf/.gitignore +0 -0
  47. data/lib/rookout/protobuf/agent_info_pb.rb +68 -0
  48. data/lib/rookout/protobuf/controller_info_pb.rb +29 -0
  49. data/lib/rookout/protobuf/envelope_pb.rb +21 -0
  50. data/lib/rookout/protobuf/messages_pb.rb +189 -0
  51. data/lib/rookout/protobuf/variant_pb.rb +139 -0
  52. data/lib/rookout/rookout_singleton.rb +73 -0
  53. data/lib/rookout/services/position.rb +163 -0
  54. data/lib/rookout/services/tracer.rb +83 -0
  55. data/lib/rookout/trigger_services.rb +34 -0
  56. data/lib/rookout/user_warnings.rb +25 -0
  57. data/lib/rookout/version.rb +4 -0
  58. metadata +269 -0
@@ -0,0 +1,15 @@
1
+ module Rookout
2
+ module Augs
3
+ module Conditions
4
+ class Condition
5
+ def initialize condition
6
+ @path = ArithmeticPath.new condition
7
+ end
8
+
9
+ def evaluate namespace
10
+ @path.read_from(namespace).object == true
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Rookout
2
+ module Augs
3
+ module Locations
4
+ class Location
5
+ def add_aug _trigger_services, _aug
6
+ raise NotImplementedError
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Rookout
2
+ module Augs
3
+ module Locations
4
+ require_relative "location"
5
+
6
+ class LocationFileLine < Location
7
+ NAME = "file_line".freeze
8
+
9
+ def initialize arguments, _processor_factory
10
+ @filename = arguments["filename"]
11
+ @lineno = arguments["lineno"]
12
+
13
+ @file_hash = arguments["sha256"]
14
+ @line_crc = arguments["line_crc32_2"]
15
+ @line_unique = arguments["line_unique"] != 0
16
+ end
17
+
18
+ attr_reader :filename, :lineno, :file_hash, :line_crc, :line_unique
19
+
20
+ def add_aug trigger_services, aug
21
+ trigger_services.get_service("position").add_aug self, aug
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,221 @@
1
+ module Rookout
2
+ module ComWs
3
+ require "securerandom"
4
+ require "kontena-websocket-client"
5
+ require "event_emitter"
6
+ require "google/protobuf/well_known_types"
7
+ require "concurrent"
8
+
9
+ require_relative "../config"
10
+ require_relative "../logger"
11
+ require_relative "../user_warnings"
12
+ require_relative "../exceptions"
13
+
14
+ require_relative "../processor/rook_error"
15
+
16
+ require_relative "../protobuf/messages_pb"
17
+ require_relative "../protobuf/envelope_pb"
18
+
19
+ require_relative "backoff"
20
+ require_relative "information"
21
+
22
+ class AgentComWs
23
+ include EventEmitter
24
+
25
+ def initialize output, agent_host, agent_port, proxy, token, labels
26
+ agent_host_with_protocl = agent_host.include?("://") ? agent_host : "ws://#{agent_host}"
27
+ @uri = "#{agent_host_with_protocl}:#{agent_port}/v1"
28
+ if proxy.nil? || proxy.empty?
29
+ @proxy = nil
30
+ else
31
+ @proxy = proxy.include?("://") ? proxy : "http://#{proxy}"
32
+ end
33
+
34
+ @token = token
35
+ @token_valid = false
36
+
37
+ @output = output
38
+ @info = Information.new labels
39
+ reset_id
40
+
41
+ @thread = nil
42
+ @pending_messages = Queue.new
43
+
44
+ @running = false
45
+ @ready_event = Concurrent::Event.new
46
+ once("Com::Rookout::InitialAugsCommand") { @ready_event.set }
47
+ end
48
+
49
+ def add message
50
+ buffer = wrap_in_envelope message
51
+ if buffer.length > Config.agent_com_max_message_limit
52
+ exc = Exceptions::RookMessageSizeExceeded.new buffer.length, Coonfig.agent_com_max_message_limit
53
+ warning = RookError.new exc, message
54
+ UserWarnings.notify_warning warning
55
+
56
+ Logger.instance.warn "Dropping message, size was #{buffer.length} which is over the message size limit"
57
+ return
58
+ end
59
+
60
+ @pending_messages.push buffer if @pending_messages.length < Config.agent_com_max_queued_messages
61
+ end
62
+
63
+ def connect
64
+ @running = true
65
+
66
+ @thread = Thread.new { connection_thread }
67
+ @thread.name = "rookout-connection-thread"
68
+ end
69
+
70
+ def wait_for_ready
71
+ @ready_event.wait Config.agent_com_timeout
72
+ raise @connection_error if @connection_error
73
+ end
74
+
75
+ def flush_all_messages
76
+ flush = FlushMessage
77
+ @pending_messages.push flush
78
+ flush.event.wait Config.agent_com_flush_timeout
79
+ end
80
+
81
+ private
82
+
83
+ def connection_thread
84
+ backoff = Backoff.new
85
+
86
+ while @running
87
+ begin
88
+ backoff.before_connection_attempt
89
+ Kontena::Websocket::Client.connect(@uri, **create_options) do |client|
90
+ register client
91
+
92
+ Logger.instance.debug "WebSocket connected successfully"
93
+ @token_valid = true
94
+ backoff.after_connect
95
+
96
+ connection_pump client
97
+ end
98
+ rescue Exception => e
99
+ if !@token_valid && e.is_a?(Kontena::Websocket::ProtocolError) && e.message.include?("403")
100
+ @connection_error = Exceptions::RookInvalidToken.new @token
101
+ @ready_event.set
102
+ end
103
+
104
+ Logger.instance.info "Connection failed; reason = #{e.message}"
105
+ end
106
+
107
+ backoff.after_disconnect
108
+ Logger.instance.debug "Reconnecting"
109
+ end
110
+ end
111
+
112
+ def create_options
113
+ headers = {
114
+ "User-Agent" => "RookoutAgent/#{Config.rookout_version}+#{Config.rookout_commit}"
115
+ }
116
+
117
+ headers["X-Rookout-Token"] = @token unless @token.nil?
118
+
119
+ # NOTE: WE DONT HAVE PROXY SUPPORT (YET) - THIS WILL PROBABLY REQUIRE FORKING KONTENA
120
+ { headers: headers,
121
+ connect_timeout: Config.agent_com_timeout,
122
+ open_timeout: Config.agent_com_timeout,
123
+ ping_interval: Config.agent_com_ping_interval,
124
+ ping_timeout: Config.agent_com_ping_timeout }
125
+ end
126
+
127
+ def register client
128
+ Logger.instance.info "Registering agent with id #{@agent_id}"
129
+
130
+ msg = Com::Rookout::NewAgentMessage.new agent_info: @info.pack
131
+ client.send wrap_in_envelope(msg)
132
+ end
133
+
134
+ ACCEPTED_MESSAGE_TYPES = [
135
+ Com::Rookout::InitialAugsCommand,
136
+ Com::Rookout::AddAugCommand,
137
+ Com::Rookout::ClearAugsCommand,
138
+ Com::Rookout::PingMessage,
139
+ Com::Rookout::RemoveAugCommand
140
+ ].freeze
141
+
142
+ def connection_pump client
143
+ on_outgoing_exit = proc { client.disconnect }
144
+ send_thread = Thread.new { outgoing client, on_outgoing_exit }
145
+ send_thread.name = "rookout-outgoing-thread"
146
+
147
+ client.read do |raw_message|
148
+ envelope = Com::Rookout::Envelope.decode raw_message.pack("c*")
149
+
150
+ ACCEPTED_MESSAGE_TYPES.each do |klass|
151
+ next unless envelope.msg.is klass
152
+ emit klass.name, envelope.msg.unpack(klass)
153
+ end
154
+ end
155
+
156
+ Logger.instance.debug "Incoming loop - socket disconnected"
157
+
158
+ @pending_messages.push ExitMessage.new(send_thread)
159
+ send_thread.join
160
+ end
161
+
162
+ def outgoing client, on_exit
163
+ loop do
164
+ begin
165
+ msg = @pending_messages.pop true
166
+ rescue ThreadError
167
+ sleep 0.25
168
+ next
169
+ end
170
+
171
+ if msg.is_a? ExitMessage
172
+ break if msg.thread == Thread.current
173
+ elsif msg.is_a? FlushMessage
174
+ msg.event.set
175
+ else
176
+ begin
177
+ client.send msg
178
+ rescue RuntimeError
179
+ @queue << msg
180
+ break
181
+ end
182
+ end
183
+ end
184
+ rescue Exception => e
185
+ Logger.instance.exception "Outgoing thread failed", e
186
+ ensure
187
+ on_exit.call
188
+ end
189
+
190
+ def wrap_in_envelope message
191
+ any_message = Google::Protobuf::Any.pack message
192
+ timestamp = Google::Protobuf::Timestamp.new
193
+ timestamp.from_time Time.new
194
+ envelope = Com::Rookout::Envelope.new msg: any_message, timestamp: timestamp
195
+ envelope.to_proto.bytes
196
+ end
197
+
198
+ def reset_id
199
+ @agent_id = SecureRandom.uuid
200
+ @output.agent_id = @agent_id
201
+ @info.agent_id = @agent_id
202
+ end
203
+
204
+ class ExitMessage
205
+ def initialize thread
206
+ @thread = thread
207
+ end
208
+
209
+ attr_reader :thread
210
+ end
211
+
212
+ class FlushMessage
213
+ def initialize
214
+ @event = Concurrent::Event.new
215
+ end
216
+
217
+ attr_reader :event
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,35 @@
1
+ module Rookout
2
+ module ComWs
3
+ require_relative "../config"
4
+
5
+ class Backoff
6
+ def initialize
7
+ @connected = false
8
+ @last_successful_connection = Time.mktime 1970
9
+ reset_backoff
10
+ end
11
+
12
+ def before_connection_attempt
13
+ return unless Time.new > @last_successful_connection + Config.backoff_reset_time
14
+ reset_backoff
15
+ end
16
+
17
+ def after_disconnect
18
+ @connected = false
19
+ sleep @next_backoff
20
+ @next_backoff = [@next_backoff * 2, Config.backoff_max_time].min
21
+ end
22
+
23
+ def after_connect
24
+ @connected = true
25
+ @last_successful_connection = Time.now
26
+ end
27
+
28
+ private
29
+
30
+ def reset_backoff
31
+ @next_backoff = Config.backoff_factor
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ module Rookout
2
+ module ComWs
3
+ require "json"
4
+
5
+ class CommandHandler
6
+ def initialize agent_com, augs_manager
7
+ agent_com.on "Com::Rookout::InitialAugsCommand" do |initial_augs|
8
+ augs = initial_augs.augs.map { |aug_json| JSON.parse aug_json }
9
+ augs_manager.initialize_augs augs
10
+ end
11
+
12
+ agent_com.on "Com::Rookout::AddAugCommand" do |command|
13
+ augs_manager.add_aug JSON.parse(command.aug_json)
14
+ end
15
+
16
+ agent_com.on "Com::Rookout::RemoveAugCommand" do |command|
17
+ augs_manager.remove_aug command.aug_id
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ module Rookout
2
+ module ComWs
3
+ module Git
4
+ GIT_FOLDER = ".git".freeze
5
+ GIT_HEAD = "HEAD".freeze
6
+ GIT_CONFIG = "config".freeze
7
+
8
+ REMOTE_ORIGINS = /\[remote "origin"\]\s*url\s*=\s*(\S*)/.freeze
9
+
10
+ def find_root path
11
+ # Return if found
12
+ return path if git? path
13
+
14
+ # Get next, fail if there's no next
15
+ next_path = File.dirname path
16
+ return nil if next_path == path
17
+
18
+ # Recursive call
19
+ find_root next_path
20
+ end
21
+
22
+ def revision path
23
+ follow_sym_links File.join(path, GIT_FOLDER), GIT_HEAD
24
+ end
25
+
26
+ def remote_origin path
27
+ git_config_path = File.join path, GIT_FOLDER, GIT_CONFIG
28
+ git_config_contents = File.read git_config_path
29
+ match = git_config_contents.match REMOTE_ORIGINS
30
+ return "" if match.captures.empty?
31
+
32
+ match.captures[0]
33
+ end
34
+
35
+ def git? path
36
+ potential_git_folder = File.join path, GIT_FOLDER
37
+ File.directory? potential_git_folder
38
+ end
39
+
40
+ def follow_sym_links root, link
41
+ link_path = File.join root, link
42
+ link_contents = File.read link_path
43
+
44
+ if link_contents.start_with? "ref:"
45
+ next_link = (link_contents.split " ")[1].strip
46
+ follow_sym_links root, next_link
47
+ else
48
+ link_contents.strip
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ module Rookout
2
+ module ComWs
3
+ require "socket"
4
+ require "English"
5
+
6
+ require_relative "../config"
7
+
8
+ require_relative "../protobuf/agent_info_pb"
9
+
10
+ class Information
11
+ require_relative "git"
12
+ include Git
13
+
14
+ def initialize labels
15
+ @agent_id = nil
16
+ @labels = labels.clone
17
+ @labels["rookout_debug"] = "on"
18
+
19
+ @ip_addr = local_ip
20
+
21
+ @scm_info = create_scm_information
22
+ end
23
+
24
+ def pack
25
+ version_info = Com::Rookout::VersionInformation.new version: Config.rookout_version,
26
+ commit: Config.rookout_commit
27
+
28
+ network_info = Com::Rookout::NetworkInformation.new ip_addr: @ip_addr
29
+
30
+ system_info = Com::Rookout::SystemInformation.new hostname: Socket.gethostname,
31
+ os: RUBY_PLATFORM,
32
+ os_version: "",
33
+ distro: "",
34
+ arch: ""
35
+
36
+ platform_info = Com::Rookout::PlatformInformation.new platform: "ruby",
37
+ version: RUBY_VERSION,
38
+ variant: RUBY_ENGINE
39
+
40
+ Com::Rookout::AgentInformation.new agent_id: @agent_id,
41
+ version: version_info,
42
+ network: network_info,
43
+ system: system_info,
44
+ platform: platform_info,
45
+ executable: $PROGRAM_NAME,
46
+ command_arguments: ARGV,
47
+ process_id: Process.pid,
48
+ labels: @labels,
49
+ scm: @scm_info
50
+ end
51
+
52
+ attr_accessor :agent_id
53
+
54
+ private
55
+
56
+ # rubocop:disable Style/ParallelAssignment
57
+ def local_ip
58
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
59
+ UDPSocket.open do |s|
60
+ s.connect "10.255.255.255", 1
61
+ s.addr.last
62
+ end
63
+ ensure
64
+ Socket.do_not_reverse_lookup = orig
65
+ end
66
+ # rubocop:enable Style/ParallelAssignment
67
+
68
+ def create_scm_information
69
+ user_git_origin = Config.user_git_origin || ENV["ROOKOUT_ORIGIN"]
70
+ user_git_commit = Config.user_git_commit || ENV["ROOKOUT_COMMIT"]
71
+
72
+ if user_git_origin.nil? && user_git_commit.nil?
73
+ search_path = File.dirname File.absolute_path($PROGRAM_NAME)
74
+ git_root = find_root search_path
75
+ if git_root
76
+ user_git_origin = remote_origin git_root
77
+ user_git_commit = revision git_root
78
+ end
79
+ end
80
+
81
+ Com::Rookout::SCMInformation.new origin: user_git_origin, commit: user_git_commit
82
+ end
83
+ end
84
+ end
85
+ end