rookout 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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