google-cloud-debugger 0.26.1 → 0.27.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.
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "google/cloud/debugger/breakpoint/status_message"
17
+
16
18
  module Google
17
19
  module Cloud
18
20
  module Debugger
@@ -83,12 +85,21 @@ module Google
83
85
 
84
86
  ##
85
87
  # Max number of member variables to evaluate in compound variables
86
- MAX_MEMBERS = 10
88
+ MAX_MEMBERS = 1000
87
89
 
88
90
  ##
89
91
  # Max length on variable inspect results. Truncate extra and replace
90
92
  # with ellipsis.
91
- MAX_STRING_LENGTH = 260
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 = "Buffer full. Use an expression to see more data."
92
103
 
93
104
  ##
94
105
  # @private Name of the variable, if any.
@@ -111,6 +122,17 @@ module Google
111
122
  # @return [Array<Variable>]
112
123
  attr_accessor :members
113
124
 
125
+ ##
126
+ # @private Reference to a variable in the shared variable table. More
127
+ # than one variable can reference the same variable in the table. The
128
+ # var_table_index field is an index into variable_table in Breakpoint.
129
+ attr_accessor :var_table_index
130
+
131
+ ##
132
+ # @private The variable table this variable references to (if
133
+ # var_table_index is set).
134
+ attr_accessor :var_table
135
+
114
136
  ##
115
137
  # @private Status associated with the variable. This field will
116
138
  # usually stay unset. A status of a single variable only applies to
@@ -121,8 +143,12 @@ module Google
121
143
  # VARIABLE_NAME. Alternatively refers_to will be set to
122
144
  # VARIABLE_VALUE. In either case variable value and members will be
123
145
  # unset.
124
- # TODO: Implement variable status
125
- # attr_accessor :status
146
+ attr_accessor :status
147
+
148
+ ##
149
+ # @private The original Ruby object this Breakpoint::Variable is based
150
+ # upon.
151
+ attr_accessor :source_var
126
152
 
127
153
  ##
128
154
  # @private Create an empty Variable object.
@@ -132,22 +158,29 @@ module Google
132
158
 
133
159
  ##
134
160
  # Convert a Ruby variable into a
135
- # Google::Cloud::Debugger::Breakpoint::Variable object.
161
+ # Google::Cloud::Debugger::Breakpoint::Variable object. If
162
+ # a variable table is provided, it will store all the subsequently
163
+ # created compound variables into the variable table for sharing.
136
164
  #
137
165
  # @param [Any] source Source Ruby variable to convert from
138
166
  # @param [String] name Name of the varaible
139
167
  # @param [Integer] depth Number of levels to evaluate in compound
140
168
  # variables. Default to
141
169
  # {Google::Cloud::Debugger::Breakpoint::Variable::MAX_DEPTH}
170
+ # @param [Breakpoint::VariableTable] var_table A variable table
171
+ # to store shared compound variables. Optional.
172
+ # @param [Integer] limit Maximum number of bytes this conversion
173
+ # should take. This include nested compound member variables'
174
+ # conversions.
142
175
  #
143
- # @example
176
+ # @example Simple variable conversion
144
177
  # x = 3
145
178
  # var = Variable.from_rb_var x, name: "x"
146
179
  # var.name #=> "x"
147
180
  # var.value #=> "3"
148
181
  # var.type #=> "Integer"
149
182
  #
150
- # @example
183
+ # @example Hash conversion
151
184
  # hash = {a: 1, b: :two}
152
185
  # var = Variable.from_rb_var hash, name: "hash"
153
186
  # var.name #=> "hash"
@@ -159,8 +192,9 @@ module Google
159
192
  # var.members[1].value #=> "two"
160
193
  # var.members[1].type #=> "Symbol"
161
194
  #
162
- # @example
195
+ # @example Custom compound variable conversion
163
196
  # foo = Foo.new(a: 1, b: []) #=> #<Foo:0x0000 @a: 1, @b: []>
197
+ # var_table = VariableTable.new
164
198
  # var = Variable.from_rb_var foo, name: "foo"
165
199
  # var.name #=> "foo"
166
200
  # var.type #=> "Foo"
@@ -171,71 +205,186 @@ module Google
171
205
  # var.members[1].value #=> "[]"
172
206
  # var.members[1].type #=> "Array"
173
207
  #
208
+ # @example Use variable table for shared compound variables
209
+ # hash = {a: 1}
210
+ # ary = [hash, hash]
211
+ # var_table = VariableTable.new
212
+ # var = Variable.from_rb_var ary, name: "ary", var_table: var_table
213
+ # var.name #=> "ary"
214
+ # var.var_table_index #=> 0
215
+ # var_table[0].type #=> "Array"
216
+ # var_table[0].members[0].name #=> "[0]"
217
+ # var_table[0].members[0].var_table_index #=> 1
218
+ # var_table[0].members[1].name #=> "[1]"
219
+ # var_table[0].members[1].var_table_index #=> 1
220
+ # var_table[1].type #=> "Hash"
221
+ # var_table[1].members[0].name #=> "a"
222
+ # var_table[1].members[0].type #=> "Integer"
223
+ # var_table[1].members[0].value #=> "1"
224
+ #
174
225
  # @return [Google::Cloud::Debugger::Breakpoint::Variable] Converted
175
226
  # variable.
176
227
  #
177
- def self.from_rb_var source, name: nil, depth: MAX_DEPTH
228
+ def self.from_rb_var source, name: nil, depth: MAX_DEPTH,
229
+ var_table: nil, limit: nil
178
230
  return source if source.is_a? Variable
179
231
 
232
+ if limit && limit < MIN_REQUIRED_SIZE
233
+ return buffer_full_variable var_table
234
+ end
235
+
180
236
  # If source is a non-empty Array or Hash, or source has instance
181
237
  # variables, evaluate source as a compound variable.
182
- if (((source.is_a?(Hash) || source.is_a?(Array)) &&
183
- !source.empty?) || !source.instance_variables.empty?) &&
184
- depth > 0
185
- from_compound_var source, name: name, depth: depth
238
+ if compound_var?(source) && depth > 0
239
+ from_compound_var source, name: name, depth: depth,
240
+ var_table: var_table, limit: limit
186
241
  else
187
- var = Variable.new
188
- var.name = name.to_s if name
189
- var.type = source.class.to_s
190
- var.value = truncate_value(source.inspect)
191
-
192
- var
242
+ new.tap do |var|
243
+ var.name = name.to_s if name
244
+ var.type = source.class.to_s
245
+ limit = deduct_limit limit,
246
+ var.name.to_s.bytesize + var.type.bytesize
247
+ var.value = truncate_value source.inspect, limit
248
+ var.source_var = source
249
+ end
193
250
  end
194
251
  end
195
252
 
196
253
  ##
197
254
  # @private Helper method that converts compound variables.
198
- def self.from_compound_var source, name: nil, depth: MAX_DEPTH
255
+ def self.from_compound_var source, name: nil, depth: MAX_DEPTH,
256
+ var_table: nil, limit: nil
199
257
  return source if source.is_a? Variable
200
- var = Variable.new
258
+
259
+ if limit && limit < MIN_REQUIRED_SIZE
260
+ return buffer_full_variable var_table
261
+ end
262
+
263
+ var = new
201
264
  var.name = name.to_s if name
265
+ var.source_var = source
266
+ limit = deduct_limit limit, var.name.to_s.bytesize
267
+
268
+ if var_table
269
+ var.var_table = var_table
270
+ var.var_table_index =
271
+ var_table.rb_var_index(source) ||
272
+ add_shared_compound_var(source, depth, var_table, limit: limit)
273
+ else
274
+ var.type = source.class.to_s
275
+ limit = deduct_limit limit, var.type.bytesize
276
+ add_compound_members var, source, depth, limit: limit
277
+ end
278
+ var
279
+ end
280
+
281
+ ##
282
+ # @private Determine if a given Ruby variable is a compound variable.
283
+ def self.compound_var? source
284
+ ((source.is_a?(Hash) || source.is_a?(Array)) && !source.empty?) ||
285
+ !source.instance_variables.empty?
286
+ end
287
+
288
+ ##
289
+ # @private Add a shared compound variable to the breakpoint
290
+ # variable table.
291
+ def self.add_shared_compound_var source, depth, var_table, limit: nil
292
+ var = new
202
293
  var.type = source.class.to_s
294
+ var.source_var = source
295
+ limit = deduct_limit limit, var.type.bytesize
296
+
297
+ table_index = var_table.size
298
+ var_table.add var
299
+
300
+ add_compound_members var, source, depth, var_table, limit: limit
203
301
 
302
+ table_index
303
+ end
304
+
305
+ ##
306
+ # @private Add member variables to a compound variable.
307
+ def self.add_compound_members var, source, depth, var_table = nil,
308
+ limit: nil
204
309
  case source
205
310
  when Hash
206
- add_compound_members var, source do |(k, v)|
207
- from_rb_var(v, name: k, depth: depth - 1)
311
+ add_member_vars var, source, limit: limit do |(k, v), _, lmt|
312
+ from_rb_var v, name: k, depth: depth - 1, var_table: var_table,
313
+ limit: lmt
208
314
  end
209
315
  when Array
210
- add_compound_members var, source do |el, i|
211
- from_rb_var(el, name: "[#{i}]", depth: depth - 1)
316
+ add_member_vars var, source, limit: limit do |el, i, lmt|
317
+ from_rb_var el, name: "[#{i}]", depth: depth - 1,
318
+ var_table: var_table, limit: lmt
212
319
  end
213
320
  else
214
- add_compound_members var, source.instance_variables do |var_name|
321
+ members = source.instance_variables
322
+ add_member_vars var, members, limit: limit do |var_name, _, lmt|
215
323
  instance_var = source.instance_variable_get var_name
216
- from_rb_var(instance_var, name: var_name, depth: depth - 1)
324
+ from_rb_var instance_var, name: var_name, depth: depth - 1,
325
+ var_table: var_table, limit: lmt
217
326
  end
218
327
  end
219
- var
220
328
  end
221
329
 
222
330
  ##
223
331
  # @private Help interate through collection of member variables for
224
332
  # compound variables.
225
- def self.add_compound_members var, members
226
- members.each_with_index do |el, i|
227
- if i < MAX_MEMBERS
228
- var.members << yield(el, i)
229
- else
333
+ def self.add_member_vars var, members, limit: nil
334
+ members.each_with_index do |member, i|
335
+ member_var = yield(member, i, limit)
336
+
337
+ limit = deduct_limit limit, member_var.total_size
338
+
339
+ buffer_full = (limit && limit < 0) ||
340
+ i >= MAX_MEMBERS ||
341
+ member_var.buffer_full_variable?
342
+
343
+ if buffer_full
230
344
  var.members << Variable.new.tap do |last_var|
231
- last_var.value =
232
- "(Only first #{MAX_MEMBERS} items were captured)"
345
+ last_var.set_error_state \
346
+ "Only first #{i} items were captured. Use in " \
347
+ "an expression to see all items.",
348
+ refers_to: StatusMessage::VARIABLE_VALUE
233
349
  end
234
350
  break
351
+ else
352
+ var.members << member_var
353
+ end
354
+ end
355
+ end
356
+
357
+ ##
358
+ # @private Create an empty variable that points to the shared
359
+ # "Buffer Full" variable in the given variable table (always index 0)
360
+ # if a variable table is passed in. Otherwise create an error variable
361
+ # with the buffer full message.
362
+ def self.buffer_full_variable var_table = nil, name: nil
363
+ new.tap do |var|
364
+ var.name = name if name
365
+
366
+ if var_table && var_table.first &&
367
+ var_table.first.buffer_full_variable?
368
+ var.var_table = var_table
369
+ var.var_table_index = 0
370
+ else
371
+ var.set_error_state BUFFER_FULL_MSG,
372
+ refers_to: StatusMessage::VARIABLE_VALUE
235
373
  end
236
374
  end
237
375
  end
238
376
 
377
+ ##
378
+ # @private Helper method to calculate bytesize limit deduction.
379
+ def self.deduct_limit limit, used
380
+ limit.nil? ? nil : limit - used
381
+ end
382
+
383
+ private_class_method :add_compound_members,
384
+ :add_shared_compound_var,
385
+ :add_member_vars, :compound_var?,
386
+ :deduct_limit
387
+
239
388
  ##
240
389
  # @private New Google::Cloud::Debugger::Breakpoint::Variable
241
390
  # from a Google::Devtools::Clouddebugger::V2::Variable object.
@@ -246,6 +395,8 @@ module Google
246
395
  o.value = grpc.value
247
396
  o.type = grpc.type
248
397
  o.members = from_grpc_list grpc.members
398
+ o.var_table_index = var_table_index_from_grpc grpc.var_table_index
399
+ o.status = Breakpoint::StatusMessage.from_grpc grpc.status
249
400
  end
250
401
  end
251
402
 
@@ -258,11 +409,18 @@ module Google
258
409
  grpc_list.map { |var_grpc| from_grpc var_grpc }
259
410
  end
260
411
 
412
+ ##
413
+ # @private Extract var_table_index from the equivalent GRPC struct
414
+ def self.var_table_index_from_grpc grpc
415
+ grpc.nil? ? nil : grpc.value
416
+ end
417
+
261
418
  ##
262
419
  # @private Limit string to MAX_STRING_LENTH. Replace extra characters
263
420
  # with ellipsis
264
- def self.truncate_value str
265
- str.gsub(/(.{#{MAX_STRING_LENGTH - 3}}).+/, '\1...')
421
+ def self.truncate_value str, limit = nil
422
+ limit ||= MAX_STRING_LENGTH
423
+ str.gsub(/(.{#{limit - 3}}).+/, '\1...')
266
424
  end
267
425
  private_class_method :add_compound_members, :truncate_value
268
426
 
@@ -272,12 +430,13 @@ module Google
272
430
  name.nil? &&
273
431
  value.nil? &&
274
432
  type.nil? &&
275
- members.nil?
276
- # TODO: Add status when implementing variable status
433
+ members.nil? &&
434
+ var_table_index.nil? &&
435
+ status.nil?
277
436
  end
278
437
 
279
438
  ##
280
- # @private Exports the Variable to a
439
+ # Exports the Variable to a
281
440
  # Google::Devtools::Clouddebugger::V2::Variable object.
282
441
  def to_grpc
283
442
  return nil if empty?
@@ -285,12 +444,125 @@ module Google
285
444
  name: name.to_s,
286
445
  value: value.to_s,
287
446
  type: type.to_s,
288
- members: members_to_grpc || []
447
+ var_table_index: var_table_index_to_grpc,
448
+ members: members_to_grpc || [],
449
+ status: status_to_grpc
289
450
  )
290
451
  end
291
452
 
453
+ ##
454
+ # Set this variable to an error state by setting the status field
455
+ def set_error_state message, refers_to: StatusMessage::UNSPECIFIED
456
+ @status = StatusMessage.new.tap do |s|
457
+ s.is_error = true
458
+ s.refers_to = refers_to
459
+ s.description = message
460
+ end
461
+ end
462
+
463
+ ##
464
+ # Calculate the total bytesize of all the attributes and that of the
465
+ # member variables, plus references into other variables in the
466
+ # variable table.
467
+ #
468
+ # @return [Integer] The total payload size of this variable in bytes.
469
+ def total_size
470
+ unless @total_size
471
+ vars = [self, *(unique_members || [])]
472
+
473
+ @total_size = vars.inject(payload_size) do |sum, var|
474
+ if var.var_table && var.var_table_index
475
+ sum + var.var_table[var.var_table_index].total_size
476
+ else
477
+ sum
478
+ end
479
+ end
480
+ end
481
+
482
+ @total_size
483
+ end
484
+
485
+ ##
486
+ # Calculate the bytesize of all the attributes and that of the
487
+ # member variables.
488
+ #
489
+ # @return [Integer] The total payload size of this variable in bytes.
490
+ def payload_size
491
+ unless @payload_size
492
+ @payload_size = name.to_s.bytesize +
493
+ type.to_s.bytesize +
494
+ value.to_s.bytesize
495
+
496
+ unless members.nil?
497
+ @payload_size = members.inject(@payload_size) do |sum, member|
498
+ sum + member.payload_size
499
+ end
500
+ end
501
+ end
502
+
503
+ @payload_size
504
+ end
505
+
506
+ ##
507
+ # @private Get a unique array of members that don't reference
508
+ # same object in variable table
509
+ def unique_members
510
+ seen_indices = {}
511
+
512
+ members.select do |member|
513
+ if seen_indices[member.var_table_index]
514
+ false
515
+ else
516
+ seen_indices[member.var_table_index] = true
517
+ true
518
+ end
519
+ end
520
+ end
521
+
522
+ ##
523
+ # @private Whether this variable is a reference variable into
524
+ # the shared variable table or not.
525
+ def reference_variable?
526
+ value.nil? && members.empty? && !var_table_index.nil?
527
+ end
528
+
529
+ ##
530
+ # @private Check if a given variable is a buffer full variable, or an
531
+ # reference variable to the shared buffer full variable
532
+ def buffer_full_variable?
533
+ if (status &&
534
+ status.description == BUFFER_FULL_MSG) ||
535
+ (var_table &&
536
+ reference_variable? &&
537
+ var_table_index.zero? &&
538
+ var_table[0] &&
539
+ var_table[0].status &&
540
+ var_table[0].status.description == BUFFER_FULL_MSG)
541
+ true
542
+ else
543
+ false
544
+ end
545
+ end
546
+
292
547
  private
293
548
 
549
+ ##
550
+ # @private Exports the Variable status to grpc
551
+ def status_to_grpc
552
+ status.nil? ? nil : status.to_grpc
553
+ end
554
+
555
+ ##
556
+ # @private Exports the Variable var_table_index attribute to
557
+ # an Int32Value gRPC struct
558
+ def var_table_index_to_grpc
559
+ if var_table_index
560
+ Google::Protobuf::Int32Value.new value: var_table_index
561
+ else
562
+ nil
563
+ end
564
+ end
565
+
294
566
  ##
295
567
  # @private Exports the Variable members to an array of
296
568
  # Google::Devtools::Clouddebugger::V2::Variable objects.