google-cloud-debugger 0.40.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +233 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/INSTRUMENTATION.md +115 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +266 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/ext/google/cloud/debugger/debugger_c/debugger.c +31 -0
  13. data/ext/google/cloud/debugger/debugger_c/debugger.h +26 -0
  14. data/ext/google/cloud/debugger/debugger_c/evaluator.c +115 -0
  15. data/ext/google/cloud/debugger/debugger_c/evaluator.h +25 -0
  16. data/ext/google/cloud/debugger/debugger_c/extconf.rb +22 -0
  17. data/ext/google/cloud/debugger/debugger_c/tracer.c +542 -0
  18. data/ext/google/cloud/debugger/debugger_c/tracer.h +25 -0
  19. data/lib/google-cloud-debugger.rb +181 -0
  20. data/lib/google/cloud/debugger.rb +259 -0
  21. data/lib/google/cloud/debugger/agent.rb +255 -0
  22. data/lib/google/cloud/debugger/backoff.rb +70 -0
  23. data/lib/google/cloud/debugger/breakpoint.rb +443 -0
  24. data/lib/google/cloud/debugger/breakpoint/evaluator.rb +1099 -0
  25. data/lib/google/cloud/debugger/breakpoint/source_location.rb +74 -0
  26. data/lib/google/cloud/debugger/breakpoint/stack_frame.rb +109 -0
  27. data/lib/google/cloud/debugger/breakpoint/status_message.rb +93 -0
  28. data/lib/google/cloud/debugger/breakpoint/validator.rb +92 -0
  29. data/lib/google/cloud/debugger/breakpoint/variable.rb +595 -0
  30. data/lib/google/cloud/debugger/breakpoint/variable_table.rb +96 -0
  31. data/lib/google/cloud/debugger/breakpoint_manager.rb +311 -0
  32. data/lib/google/cloud/debugger/credentials.rb +50 -0
  33. data/lib/google/cloud/debugger/debuggee.rb +222 -0
  34. data/lib/google/cloud/debugger/debuggee/app_uniquifier_generator.rb +76 -0
  35. data/lib/google/cloud/debugger/logpoint.rb +98 -0
  36. data/lib/google/cloud/debugger/middleware.rb +200 -0
  37. data/lib/google/cloud/debugger/project.rb +110 -0
  38. data/lib/google/cloud/debugger/rails.rb +174 -0
  39. data/lib/google/cloud/debugger/request_quota_manager.rb +95 -0
  40. data/lib/google/cloud/debugger/service.rb +88 -0
  41. data/lib/google/cloud/debugger/snappoint.rb +208 -0
  42. data/lib/google/cloud/debugger/tracer.rb +137 -0
  43. data/lib/google/cloud/debugger/transmitter.rb +199 -0
  44. data/lib/google/cloud/debugger/version.rb +22 -0
  45. metadata +353 -0
@@ -0,0 +1,74 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # SourceLocation
22
+ #
23
+ # Additional information about the source code location that's
24
+ # associated with the breakpoint.
25
+ #
26
+ # See also {Breakpoint#location}.
27
+ #
28
+ class SourceLocation
29
+ ##
30
+ # Path to the source file within the source context of the target
31
+ # binary.
32
+ attr_accessor :path
33
+
34
+ ##
35
+ # Line inside the file. The first line in the file has the value 1.
36
+ attr_accessor :line
37
+
38
+ ##
39
+ # @private Create an empty SourceLocation object.
40
+ def initialize; end
41
+
42
+ ##
43
+ # @private New Google::Cloud::Debugger::Breakpoint::SourceLocation
44
+ # from a Google::Cloud::Debugger::V2::SourceLocation object.
45
+ def self.from_grpc grpc
46
+ return new if grpc.nil?
47
+ new.tap do |o|
48
+ o.path = grpc.path
49
+ o.line = grpc.line
50
+ end
51
+ end
52
+
53
+ ##
54
+ # @private Determines if the SourceLocation has any data.
55
+ def empty?
56
+ path.nil? &&
57
+ line.nil?
58
+ end
59
+
60
+ ##
61
+ # @private Exports the SourceLocation to a
62
+ # Google::Cloud::Debugger::V2::SourceLocation object.
63
+ def to_grpc
64
+ return nil if empty?
65
+ Google::Cloud::Debugger::V2::SourceLocation.new(
66
+ path: path.to_s,
67
+ line: line
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,109 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/debugger/breakpoint/source_location"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ class Breakpoint
22
+ ##
23
+ # # StackFrame
24
+ #
25
+ # Represents a stack frame context.
26
+ #
27
+ # See also {Breakpoint#stack_frames}.
28
+ #
29
+ class StackFrame
30
+ ##
31
+ # Demangled function name at the call site.
32
+ attr_accessor :function
33
+
34
+ ##
35
+ # Source location of the call site.
36
+ attr_accessor :location
37
+
38
+ ##
39
+ # Set of arguments passed to this function. Note that this might not
40
+ # be populated for all stack frames.
41
+ attr_accessor :arguments
42
+
43
+ ##
44
+ # Set of local variables at the stack frame location. Note that this
45
+ # might not be populated for all stack frames.
46
+ attr_accessor :locals
47
+
48
+ ##
49
+ # @private Create an empty StackFrame object.
50
+ def initialize
51
+ @location = SourceLocation.new
52
+ @arguments = []
53
+ @locals = []
54
+ end
55
+
56
+ ##
57
+ # @private New Google::Cloud::Debugger::Breakpoint::SourceLocation
58
+ # from a Google::Cloud::Debugger::V2::SourceLocation object.
59
+ def self.from_grpc grpc
60
+ new.tap do |o|
61
+ o.function = grpc.function
62
+ o.location = SourceLocation.from_grpc grpc.location
63
+ o.arguments = Variable.from_grpc_list grpc.arguments
64
+ o.locals = Variable.from_grpc_list grpc.locals
65
+ end
66
+ end
67
+
68
+ ##
69
+ # @private Determines if the StackFrame has any data.
70
+ def empty?
71
+ function.nil? &&
72
+ location.nil? &&
73
+ arguments.nil? &&
74
+ locals.nil?
75
+ end
76
+
77
+ ##
78
+ # @private Exports the StackFrame to a
79
+ # Google::Cloud::Debugger::V2::StackFrame object.
80
+ def to_grpc
81
+ return nil if empty?
82
+ Google::Cloud::Debugger::V2::StackFrame.new(
83
+ function: function.to_s,
84
+ location: location.to_grpc,
85
+ arguments: arguments_to_grpc,
86
+ locals: locals_to_grpc
87
+ )
88
+ end
89
+
90
+ private
91
+
92
+ ##
93
+ # @private Exports the StackFrame arguments to an array of
94
+ # Google::Cloud::Debugger::V2::Variable objects.
95
+ def arguments_to_grpc
96
+ arguments.nil? ? [] : arguments.map(&:to_grpc)
97
+ end
98
+
99
+ ##
100
+ # @private Exports the StackFrame locals to an array of
101
+ # Google::Cloud::Debugger::V2::Variable objects.
102
+ def locals_to_grpc
103
+ locals.nil? ? [] : locals.map(&:to_grpc)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,93 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # StatusMessage
22
+ #
23
+ # Represents a contextual status message. The message can indicate an
24
+ # error or informational status, and refer to specific parts of the
25
+ # containing object. For example, the Breakpoint.status field can
26
+ # indicate an error referring to the BREAKPOINT_SOURCE_LOCATION with
27
+ # the message Location not found.
28
+ class StatusMessage
29
+ ##
30
+ # Constants used as references to which the message applies.
31
+ UNSPECIFIED = :UNSPECIFIED
32
+ BREAKPOINT_SOURCE_LOCATION = :BREAKPOINT_SOURCE_LOCATION
33
+ BREAKPOINT_CONDITION = :BREAKPOINT_CONDITION
34
+ BREAKPOINT_EXPRESSION = :BREAKPOINT_EXPRESSION
35
+ BREAKPOINT_AGE = :BREAKPOINT_AGE
36
+ VARIABLE_NAME = :VARIABLE_NAME
37
+ VARIABLE_VALUE = :VARIABLE_VALUE
38
+
39
+ ##
40
+ # Distinguishes errors from informational messages.
41
+ attr_accessor :is_error
42
+
43
+ ##
44
+ # Reference to which the message applies.
45
+ attr_accessor :refers_to
46
+
47
+ ##
48
+ # Status message text.
49
+ attr_accessor :description
50
+
51
+ ##
52
+ # New Google::Cloud::Debugger::Breakpoint::StatusMessage
53
+ # from a Google::Cloud::Debugger::V2::StatusMessage object.
54
+ def self.from_grpc grpc
55
+ return nil if grpc.nil?
56
+ new.tap do |s|
57
+ s.is_error = grpc.is_error
58
+ s.refers_to = grpc.refers_to
59
+ s.description = grpc.description.format
60
+ end
61
+ end
62
+
63
+ ##
64
+ # @private Construct a new StatusMessage instance.
65
+ def initialize
66
+ @refers_to = UNSPECIFIED
67
+ end
68
+
69
+ ##
70
+ # @private Determines if the Variable has any data.
71
+ def empty?
72
+ is_error.nil? &&
73
+ refers_to.nil? &&
74
+ description.nil?
75
+ end
76
+
77
+ ##
78
+ # Exports the StatusMessage to a
79
+ # Google::Cloud::Debugger::V2::StatusMessage object.
80
+ def to_grpc
81
+ return nil if empty?
82
+ description_grpc = Google::Cloud::Debugger::V2::FormatMessage.new format: description.to_s
83
+
84
+ Google::Cloud::Debugger::V2::StatusMessage.new \
85
+ is_error: true,
86
+ refers_to: refers_to,
87
+ description: description_grpc
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,92 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Debugger
19
+ class Breakpoint
20
+ ##
21
+ # # Validator
22
+ #
23
+ # A collection of static methods to help validate a given breakpoint.
24
+ module Validator
25
+ FILE_NOT_FOUND_MSG = "File not found.".freeze
26
+ WRONG_FILE_TYPE_MSG = "File must be a `.rb` file.".freeze
27
+ INVALID_LINE_MSG = "Invalid line.".freeze
28
+
29
+ ##
30
+ # Validate a given breakpoint. Set breakpoint to error state if
31
+ # the breakpoint fails validation.
32
+ def self.validate breakpoint
33
+ error_msg = nil
34
+ if !verify_file_path breakpoint.full_path
35
+ error_msg = FILE_NOT_FOUND_MSG
36
+ elsif !verify_file_type breakpoint.full_path
37
+ error_msg = WRONG_FILE_TYPE_MSG
38
+ elsif !verify_line breakpoint.full_path, breakpoint.line
39
+ error_msg = INVALID_LINE_MSG
40
+ end
41
+
42
+ if error_msg
43
+ cause = Breakpoint::StatusMessage::BREAKPOINT_SOURCE_LOCATION
44
+ breakpoint.set_error_state error_msg, refers_to: cause
45
+ false
46
+ else
47
+ true
48
+ end
49
+ end
50
+
51
+ ##
52
+ # @private Verifies the given file path exists
53
+ def self.verify_file_path file_path
54
+ File.exist? file_path
55
+ rescue StandardError
56
+ false
57
+ end
58
+
59
+ ##
60
+ # @private Check the file from given file path is a Ruby file
61
+ def self.verify_file_type file_path
62
+ File.extname(file_path) == ".rb" ||
63
+ File.basename(file_path) == "config.ru"
64
+ rescue StandardError
65
+ false
66
+ end
67
+
68
+ ##
69
+ # @private Verifies the given line from a Ruby
70
+ def self.verify_line file_path, line
71
+ file = File.open file_path, "r"
72
+
73
+ # Skip through lines from beginning
74
+ file.gets while file.lineno < line - 1 && !file.eof?
75
+
76
+ line = file.gets
77
+
78
+ # Make sure we have a line (not eof)
79
+ return false unless line
80
+
81
+ blank_line = line =~ /^\s*$/
82
+ comment_line = line =~ /^\s*#.*$/
83
+
84
+ blank_line || comment_line ? false : true
85
+ rescue StandardError
86
+ false
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,595 @@
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/debugger/breakpoint/status_message"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Debugger
21
+ class Breakpoint
22
+ ##
23
+ # # Variable
24
+ #
25
+ # Represents a variable or an argument possibly of a compound object
26
+ # type. Note how the following variables are represented:
27
+ #
28
+ # A simple Variable:
29
+ # ```ruby
30
+ # x = 5
31
+ # # Captured variable:
32
+ # # { name: "x", value: "5", type: "Integer" }
33
+ # ```
34
+ #
35
+ # A Compound Variable:
36
+ # ```ruby
37
+ # class T
38
+ # attr_accessor :m1, :m2
39
+ # ...
40
+ # end
41
+ # v = T.new(1, "2")
42
+ # # Captured variable:
43
+ # # {
44
+ # # name: "v",
45
+ # # type: "T",
46
+ # # members: [
47
+ # # { name: "@m1", value: "1", type: "Integer" },
48
+ # # { name: "@m2", value: "2", type: "String" }
49
+ # # ]
50
+ # # }
51
+ # ```
52
+ #
53
+ # A Hash object:
54
+ # ```ruby
55
+ # hash = { a: 1, b: :two }
56
+ # # Captured variable:
57
+ # # {
58
+ # # name: "hash",
59
+ # # type: "Hash",
60
+ # # members: [
61
+ # # { name: "a", value: "1", type: "Integer" },
62
+ # # { name: "b", value: ":2", type: "Symbol" }
63
+ # # ]
64
+ # # }
65
+ # ```
66
+ #
67
+ # An Array object:
68
+ # ```ruby
69
+ # ary = [1, nil]
70
+ # # Captured variable:
71
+ # # {
72
+ # # name: "ary",
73
+ # # type: "Array",
74
+ # # members: [
75
+ # # { name: "[0]", value: "1", type: "Integer" },
76
+ # # { name: "[1]", value: "nil", type: "NilClass" }
77
+ # # ]
78
+ # # }
79
+ # ```
80
+ #
81
+ class Variable
82
+ ##
83
+ # Max depth to convert on compound variables
84
+ MAX_DEPTH = 3
85
+
86
+ ##
87
+ # Max number of member variables to evaluate in compound variables
88
+ MAX_MEMBERS = 1000
89
+
90
+ ##
91
+ # Max length on variable inspect results. Truncate extra and replace
92
+ # with ellipsis.
93
+ MAX_STRING_LENGTH = 500
94
+
95
+ ##
96
+ # @private Minimum amount of size limit needed for evaluation.
97
+ MIN_REQUIRED_SIZE = 100
98
+
99
+ ##
100
+ # @private Message to display on variables when snapshot buffer is
101
+ # full.
102
+ BUFFER_FULL_MSG =
103
+ "Buffer full. Use an expression to see more data.".freeze
104
+
105
+ ##
106
+ # @private Error message when variable can't be converted.
107
+ FAIL_CONVERSION_MSG = "Error: Unable to inspect value".freeze
108
+
109
+ ##
110
+ # @private Name of the variable, if any.
111
+ # @return [String]
112
+ attr_accessor :name
113
+
114
+ ##
115
+ # @private Simple value of the variable.
116
+ # @return [String]
117
+ attr_accessor :value
118
+
119
+ ##
120
+ # @private Variable type (e.g. MyClass). If the variable is split with
121
+ # var_table_index, type goes next to value.
122
+ # @return [String]
123
+ attr_accessor :type
124
+
125
+ ##
126
+ # @private Members contained or pointed to by the variable.
127
+ # @return [Array<Variable>]
128
+ attr_accessor :members
129
+
130
+ ##
131
+ # @private Reference to a variable in the shared variable table. More
132
+ # than one variable can reference the same variable in the table. The
133
+ # var_table_index field is an index into variable_table in Breakpoint.
134
+ attr_accessor :var_table_index
135
+
136
+ ##
137
+ # @private The variable table this variable references to (if
138
+ # var_table_index is set).
139
+ attr_accessor :var_table
140
+
141
+ ##
142
+ # @private Status associated with the variable. This field will
143
+ # usually stay unset. A status of a single variable only applies to
144
+ # that variable or expression. The rest of breakpoint data still
145
+ # remains valid. Variables might be reported in error state even when
146
+ # breakpoint is not in final state.
147
+ # The message may refer to variable name with refers_to set to
148
+ # VARIABLE_NAME. Alternatively refers_to will be set to
149
+ # VARIABLE_VALUE. In either case variable value and members will be
150
+ # unset.
151
+ attr_accessor :status
152
+
153
+ ##
154
+ # @private The original Ruby object this Breakpoint::Variable is based
155
+ # upon.
156
+ attr_accessor :source_var
157
+
158
+ ##
159
+ # @private Create an empty Variable object.
160
+ def initialize
161
+ @members = []
162
+ end
163
+
164
+ ##
165
+ # Convert a Ruby variable into a
166
+ # Google::Cloud::Debugger::Breakpoint::Variable object. If
167
+ # a variable table is provided, it will store all the subsequently
168
+ # created compound variables into the variable table for sharing.
169
+ #
170
+ # @param [Any] source Source Ruby variable to convert from
171
+ # @param [String] name Name of the varaible
172
+ # @param [Integer] depth Number of levels to evaluate in compound
173
+ # variables. Default to
174
+ # {Google::Cloud::Debugger::Breakpoint::Variable::MAX_DEPTH}
175
+ # @param [Breakpoint::VariableTable] var_table A variable table
176
+ # to store shared compound variables. Optional.
177
+ # @param [Integer] limit Maximum number of bytes this conversion
178
+ # should take. This include nested compound member variables'
179
+ # conversions.
180
+ #
181
+ # @example Simple variable conversion
182
+ # x = 3.0
183
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
184
+ # x, name: "x"
185
+ # var.name #=> "x"
186
+ # var.value #=> "3.0"
187
+ # var.type #=> "Float"
188
+ #
189
+ # @example Hash conversion
190
+ # hash = {a: 1.0, b: :two}
191
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
192
+ # hash, name: "hash"
193
+ # var.name #=> "hash"
194
+ # var.type #=> "Hash"
195
+ # var.members[0].name #=> "a"
196
+ # var.members[0].value #=> "1.0"
197
+ # var.members[0].type #=> "Float"
198
+ # var.members[1].name #=> "b"
199
+ # var.members[1].value #=> ":two"
200
+ # var.members[1].type #=> "Symbol"
201
+ #
202
+ # @example Custom compound variable conversion
203
+ # foo = Foo.new(a: 1.0, b: [])
204
+ # foo.inspect #=> "#<Foo:0xXXXXXX @a=1.0, @b=[]>"
205
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
206
+ # foo, name: "foo"
207
+ # var.name #=> "foo"
208
+ # var.type #=> "Foo"
209
+ # var.members[0].name #=> "@a"
210
+ # var.members[0].value #=> "1.0"
211
+ # var.members[0].type #=> "Float"
212
+ # var.members[1].name #=> "@b"
213
+ # var.members[1].value #=> "[]"
214
+ # var.members[1].type #=> "Array"
215
+ #
216
+ # @example Use variable table for shared compound variables
217
+ # hash = {a: 1.0}
218
+ # ary = [hash, hash]
219
+ # var_table = Google::Cloud::Debugger::Breakpoint::VariableTable.new
220
+ # var = Google::Cloud::Debugger::Breakpoint::Variable.from_rb_var \
221
+ # ary, name: "ary", var_table: var_table
222
+ # var.name #=> "ary"
223
+ # var.var_table_index #=> 0
224
+ # var_table[0].type #=> "Array"
225
+ # var_table[0].members[0].name #=> "[0]"
226
+ # var_table[0].members[0].var_table_index #=> 1
227
+ # var_table[0].members[1].name #=> "[1]"
228
+ # var_table[0].members[1].var_table_index #=> 1
229
+ # var_table[1].type #=> "Hash"
230
+ # var_table[1].members[0].name #=> "a"
231
+ # var_table[1].members[0].type #=> "Float"
232
+ # var_table[1].members[0].value #=> "1.0"
233
+ #
234
+ # @return [Google::Cloud::Debugger::Breakpoint::Variable] Converted
235
+ # variable.
236
+ #
237
+ def self.from_rb_var source, name: nil, depth: MAX_DEPTH,
238
+ var_table: nil, limit: nil
239
+ return source if source.is_a? Variable
240
+
241
+ if limit && limit < MIN_REQUIRED_SIZE
242
+ return buffer_full_variable var_table
243
+ end
244
+
245
+ # If source is a non-empty Array or Hash, or source has instance
246
+ # variables, evaluate source as a compound variable.
247
+ if compound_var?(source) && depth > 0
248
+ from_compound_var source, name: name, depth: depth,
249
+ var_table: var_table, limit: limit
250
+ else
251
+ from_primitive_var source, name: name, limit: limit
252
+ end
253
+ rescue StandardError
254
+ new.tap do |var|
255
+ var.name = name.to_s if name
256
+ var.set_error_state FAIL_CONVERSION_MSG
257
+ var.source_var = source
258
+ end
259
+ end
260
+
261
+ ##
262
+ # @private Helper method that converts primitive variables.
263
+ def self.from_primitive_var source, name: nil, limit: nil
264
+ new.tap do |var|
265
+ var.name = name.to_s if name
266
+ var.type = source.class.to_s
267
+ var.source_var = source
268
+ limit = deduct_limit limit,
269
+ var.name.to_s.bytesize + var.type.bytesize
270
+
271
+ var.value = truncate_value source.inspect, limit
272
+ end
273
+ end
274
+
275
+ ##
276
+ # @private Helper method that converts compound variables.
277
+ def self.from_compound_var source, name: nil, depth: MAX_DEPTH,
278
+ var_table: nil, limit: nil
279
+ return source if source.is_a? Variable
280
+
281
+ if limit && limit < MIN_REQUIRED_SIZE
282
+ return buffer_full_variable var_table
283
+ end
284
+
285
+ var = new
286
+ var.name = name.to_s if name
287
+ var.source_var = source
288
+ limit = deduct_limit limit, var.name.to_s.bytesize
289
+
290
+ if var_table
291
+ var.var_table = var_table
292
+ var.var_table_index =
293
+ var_table.rb_var_index(source) ||
294
+ add_shared_compound_var(source, depth, var_table, limit: limit)
295
+ else
296
+ var.type = source.class.to_s
297
+ limit = deduct_limit limit, var.type.bytesize
298
+ add_compound_members var, source, depth, limit: limit
299
+ end
300
+ var
301
+ end
302
+
303
+ ##
304
+ # @private Determine if a given Ruby variable is a compound variable.
305
+ def self.compound_var? source
306
+ ((source.is_a?(Hash) || source.is_a?(Array)) && !source.empty?) ||
307
+ !source.instance_variables.empty?
308
+ end
309
+
310
+ ##
311
+ # @private Add a shared compound variable to the breakpoint
312
+ # variable table.
313
+ def self.add_shared_compound_var source, depth, var_table, limit: nil
314
+ var = new
315
+ var.type = source.class.to_s
316
+ var.source_var = source
317
+ limit = deduct_limit limit, var.type.bytesize
318
+
319
+ table_index = var_table.size
320
+ var_table.add var
321
+
322
+ add_compound_members var, source, depth, var_table, limit: limit
323
+
324
+ table_index
325
+ end
326
+
327
+ ##
328
+ # @private Add member variables to a compound variable.
329
+ def self.add_compound_members var, source, depth, var_table = nil,
330
+ limit: nil
331
+ case source
332
+ when Hash
333
+ add_member_vars var, source, limit: limit do |(k, v), _, lmt|
334
+ from_rb_var v, name: k, depth: depth - 1, var_table: var_table,
335
+ limit: lmt
336
+ end
337
+ when Array
338
+ add_member_vars var, source, limit: limit do |el, i, lmt|
339
+ from_rb_var el, name: "[#{i}]", depth: depth - 1,
340
+ var_table: var_table, limit: lmt
341
+ end
342
+ else
343
+ members = source.instance_variables
344
+ add_member_vars var, members, limit: limit do |var_name, _, lmt|
345
+ instance_var = source.instance_variable_get var_name
346
+ from_rb_var instance_var, name: var_name, depth: depth - 1,
347
+ var_table: var_table, limit: lmt
348
+ end
349
+ end
350
+ end
351
+
352
+ ##
353
+ # @private Help interate through collection of member variables for
354
+ # compound variables.
355
+ def self.add_member_vars var, members, limit: nil
356
+ members.each_with_index do |member, i|
357
+ member_var = yield member, i, limit
358
+
359
+ limit = deduct_limit limit, member_var.total_size
360
+
361
+ buffer_full = (limit && limit < 0) ||
362
+ i >= MAX_MEMBERS ||
363
+ member_var.buffer_full_variable?
364
+
365
+ if buffer_full
366
+ var.members << Variable.new.tap do |last_var|
367
+ last_var.set_error_state \
368
+ "Only first #{i} items were captured. Use in " \
369
+ "an expression to see all items."
370
+ end
371
+ break
372
+ else
373
+ var.members << member_var
374
+ end
375
+ end
376
+ end
377
+
378
+ ##
379
+ # @private Create an empty variable that points to the shared
380
+ # "Buffer Full" variable in the given variable table (always index 0)
381
+ # if a variable table is passed in. Otherwise create an error variable
382
+ # with the buffer full message.
383
+ def self.buffer_full_variable var_table = nil, name: nil
384
+ new.tap do |var|
385
+ var.name = name if name
386
+
387
+ if var_table && var_table.first &&
388
+ var_table.first.buffer_full_variable?
389
+ var.var_table = var_table
390
+ var.var_table_index = 0
391
+ else
392
+ var.set_error_state BUFFER_FULL_MSG
393
+ end
394
+ end
395
+ end
396
+
397
+ ##
398
+ # @private Helper method to calculate bytesize limit deduction.
399
+ def self.deduct_limit limit, used
400
+ limit.nil? ? nil : limit - used
401
+ end
402
+
403
+ private_class_method :add_compound_members,
404
+ :add_shared_compound_var,
405
+ :add_member_vars,
406
+ :compound_var?,
407
+ :deduct_limit
408
+
409
+ ##
410
+ # @private New Google::Cloud::Debugger::Breakpoint::Variable
411
+ # from a Google::Cloud::Debugger::V2::Variable object.
412
+ def self.from_grpc grpc
413
+ return new if grpc.nil?
414
+ new.tap do |o|
415
+ o.name = grpc.name
416
+ o.value = grpc.value
417
+ o.type = grpc.type
418
+ o.members = from_grpc_list grpc.members
419
+ o.var_table_index = var_table_index_from_grpc grpc.var_table_index
420
+ o.status = Breakpoint::StatusMessage.from_grpc grpc.status
421
+ end
422
+ end
423
+
424
+ ##
425
+ # @private New array of Google::Cloud::Debugger::Breakpoint::Variable
426
+ # from an array of Google::Cloud::Debugger::V2::Variable
427
+ # objects.
428
+ def self.from_grpc_list grpc_list
429
+ return [] if grpc_list.nil?
430
+ grpc_list.map { |var_grpc| from_grpc var_grpc }
431
+ end
432
+
433
+ ##
434
+ # @private Extract var_table_index from the equivalent GRPC struct
435
+ def self.var_table_index_from_grpc grpc
436
+ grpc.nil? ? nil : grpc.value
437
+ end
438
+
439
+ ##
440
+ # @private Limit string to MAX_STRING_LENTH. Replace extra characters
441
+ # with ellipsis
442
+ def self.truncate_value str, limit = nil
443
+ limit ||= MAX_STRING_LENGTH
444
+ str.gsub(/(.{#{limit - 3}}).+/, '\1...')
445
+ end
446
+ private_class_method :add_compound_members, :truncate_value
447
+
448
+ ##
449
+ # @private Determines if the Variable has any data.
450
+ def empty?
451
+ name.nil? &&
452
+ value.nil? &&
453
+ type.nil? &&
454
+ members.nil? &&
455
+ var_table_index.nil? &&
456
+ status.nil?
457
+ end
458
+
459
+ ##
460
+ # Exports the Variable to a
461
+ # Google::Cloud::Debugger::V2::Variable object.
462
+ def to_grpc
463
+ return nil if empty?
464
+ Google::Cloud::Debugger::V2::Variable.new(
465
+ name: name.to_s,
466
+ value: value.to_s,
467
+ type: type.to_s,
468
+ var_table_index: var_table_index_to_grpc,
469
+ members: members_to_grpc || [],
470
+ status: status_to_grpc
471
+ )
472
+ end
473
+
474
+ ##
475
+ # Set this variable to an error state by setting the status field
476
+ def set_error_state message, refers_to: StatusMessage::VARIABLE_VALUE
477
+ @status = StatusMessage.new.tap do |s|
478
+ s.is_error = true
479
+ s.refers_to = refers_to
480
+ s.description = message
481
+ end
482
+ end
483
+
484
+ ##
485
+ # Calculate the total bytesize of all the attributes and that of the
486
+ # member variables, plus references into other variables in the
487
+ # variable table.
488
+ #
489
+ # @return [Integer] The total payload size of this variable in bytes.
490
+ def total_size
491
+ unless @total_size
492
+ vars = [self, *(unique_members || [])]
493
+
494
+ @total_size = vars.inject payload_size do |sum, var|
495
+ if var.var_table && var.var_table_index
496
+ sum + var.var_table[var.var_table_index].total_size
497
+ else
498
+ sum
499
+ end
500
+ end
501
+ end
502
+
503
+ @total_size
504
+ end
505
+
506
+ ##
507
+ # Calculate the bytesize of all the attributes and that of the
508
+ # member variables.
509
+ #
510
+ # @return [Integer] The total payload size of this variable in bytes.
511
+ def payload_size
512
+ unless @payload_size
513
+ @payload_size = name.to_s.bytesize +
514
+ type.to_s.bytesize +
515
+ value.to_s.bytesize
516
+
517
+ unless members.nil?
518
+ @payload_size = members.inject @payload_size do |sum, member|
519
+ sum + member.payload_size
520
+ end
521
+ end
522
+ end
523
+
524
+ @payload_size
525
+ end
526
+
527
+ ##
528
+ # @private Get a unique array of members that don't reference
529
+ # same object in variable table
530
+ def unique_members
531
+ seen_indices = {}
532
+
533
+ members.select do |member|
534
+ if seen_indices[member.var_table_index]
535
+ false
536
+ else
537
+ seen_indices[member.var_table_index] = true
538
+ true
539
+ end
540
+ end
541
+ end
542
+
543
+ ##
544
+ # @private Whether this variable is a reference variable into
545
+ # the shared variable table or not.
546
+ def reference_variable?
547
+ value.nil? && members.empty? && !var_table_index.nil?
548
+ end
549
+
550
+ ##
551
+ # @private Check if a given variable is a buffer full variable, or an
552
+ # reference variable to the shared buffer full variable
553
+ def buffer_full_variable?
554
+ if (status &&
555
+ status.description == BUFFER_FULL_MSG) ||
556
+ (var_table &&
557
+ reference_variable? &&
558
+ var_table_index.zero? &&
559
+ var_table[0] &&
560
+ var_table[0].status &&
561
+ var_table[0].status.description == BUFFER_FULL_MSG)
562
+ true
563
+ else
564
+ false
565
+ end
566
+ end
567
+
568
+ private
569
+
570
+ ##
571
+ # @private Exports the Variable status to grpc
572
+ def status_to_grpc
573
+ status.nil? ? nil : status.to_grpc
574
+ end
575
+
576
+ ##
577
+ # @private Exports the Variable var_table_index attribute to
578
+ # an Int32Value gRPC struct
579
+ def var_table_index_to_grpc
580
+ return unless var_table_index
581
+
582
+ Google::Protobuf::Int32Value.new value: var_table_index
583
+ end
584
+
585
+ ##
586
+ # @private Exports the Variable members to an array of
587
+ # Google::Cloud::Debugger::V2::Variable objects.
588
+ def members_to_grpc
589
+ members.nil? ? nil : members.map(&:to_grpc)
590
+ end
591
+ end
592
+ end
593
+ end
594
+ end
595
+ end