scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -1,31 +1,29 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class SlowRequestConverter < ConverterBase
4
- def initialize(*)
5
- @backtraces = [] # An Array of MetricMetas that have a backtrace
6
- super
7
-
8
- # After call to super, so @request is populated
9
- @points = if request.web?
10
- ScoutApm::Agent.instance.slow_request_policy.score(request)
11
- else
12
- -1
13
- end
4
+ ###################
5
+ # Converter API #
6
+ ###################
7
+ def record!
8
+ return nil unless request.web?
9
+ @points = ScoutApm::Agent.instance.slow_request_policy.score(request)
10
+
11
+ # Let the store know we're here, and if it wants our data, it will call
12
+ # back into #call
13
+ @store.track_slow_transaction!(self)
14
14
  end
15
15
 
16
- def name
17
- request.unique_name
18
- end
19
-
20
- def score
21
- @points
22
- end
16
+ #####################
17
+ # ScoreItemSet API #
18
+ #####################
19
+ def name; request.unique_name; end
20
+ def score; @points; end
23
21
 
24
22
  # Unconditionally attempts to convert this into a SlowTransaction object.
25
23
  # Can return nil if the request didn't have any scope_layer.
26
24
  def call
27
- scope = scope_layer
28
- return nil unless scope
25
+ return nil unless request.web?
26
+ return nil unless scope_layer
29
27
 
30
28
  ScoutApm::Agent.instance.slow_request_policy.stored!(request)
31
29
 
@@ -35,12 +33,13 @@ module ScoutApm
35
33
  uri = request.annotations[:uri] || ""
36
34
 
37
35
  timing_metrics, allocation_metrics = create_metrics
36
+
38
37
  unless ScoutApm::Instruments::Allocations::ENABLED
39
38
  allocation_metrics = {}
40
39
  end
41
40
 
42
41
  SlowTransaction.new(uri,
43
- scope.legacy_metric_name,
42
+ scope_layer.legacy_metric_name,
44
43
  root_layer.total_call_time,
45
44
  timing_metrics,
46
45
  allocation_metrics,
@@ -49,119 +48,36 @@ module ScoutApm
49
48
  [], # stackprof, now unused.
50
49
  mem_delta,
51
50
  root_layer.total_allocations,
52
- @points)
53
- end
54
-
55
- # Iterates over the TrackedRequest's MetricMetas that have backtraces and attaches each to correct MetricMeta in the Metric Hash.
56
- def attach_backtraces(metric_hash)
57
- @backtraces.each do |meta_with_backtrace|
58
- metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
59
- end
60
- metric_hash
51
+ @points,
52
+ limited?)
61
53
  end
62
54
 
63
55
  # Full metrics from this request. These get stored permanently in a SlowTransaction.
64
56
  # Some merging of metrics will happen here, so if a request calls the same
65
57
  # ActiveRecord or View repeatedly, it'll get merged.
66
- #
58
+ #
67
59
  # This returns a 2-element of Metric Hashes (the first element is timing metrics, the second element is allocation metrics)
68
60
  def create_metrics
61
+ # Create a new walker, and wire up the subscope stuff
62
+ walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
63
+ register_hooks(walker)
64
+
69
65
  metric_hash = Hash.new
70
66
  allocation_metric_hash = Hash.new
71
67
 
72
- # Keep a list of subscopes, but only ever use the front one. The rest
73
- # get pushed/popped in cases when we have many levels of subscopable
74
- # layers. This lets us push/pop without otherwise keeping track very closely.
75
- subscope_layers = []
76
-
77
- walker.before do |layer|
78
- if layer.subscopable?
79
- subscope_layers.push(layer)
80
- end
81
- end
82
-
83
- walker.after do |layer|
84
- if layer.subscopable?
85
- subscope_layers.pop
86
- end
68
+ walker.on do |layer|
69
+ next if skip_layer?(layer)
70
+ store_specific_metric(layer, metric_hash, allocation_metric_hash)
71
+ store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
87
72
  end
88
73
 
89
- walker.walk do |layer|
90
- # Sometimes we start capturing a layer without knowing if we really
91
- # want to make an entry for it. See ActiveRecord instrumentation for
92
- # an example. We start capturing before we know if a query is cached
93
- # or not, and want to skip any cached queries.
94
- if layer.annotations[:ignorable]
95
- next
96
- end
97
-
98
- meta_options = if subscope_layers.first && layer != subscope_layers.first # Don't scope under ourself.
99
- subscope_name = subscope_layers.first.legacy_metric_name
100
- {:scope => subscope_name}
101
- elsif layer == scope_layer # We don't scope the controller under itself
102
- {}
103
- else
104
- {:scope => scope_layer.legacy_metric_name}
105
- end
106
-
107
- # Specific Metric
108
- meta_options.merge!(:desc => layer.desc.to_s) if layer.desc
109
- meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
110
- meta.extra.merge!(layer.annotations)
111
- if layer.backtrace
112
- bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
113
- if bt.any? # we could walk thru the call stack and not find in-app code
114
- meta.backtrace = bt
115
- # Why not just call meta.backtrace and call it done? The walker
116
- # could access a later later that generates the same MetricMeta
117
- # but doesn't have a backtrace. This could be lost in the
118
- # metric_hash if it is replaced by the new key.
119
- @backtraces << meta
120
- else
121
- ScoutApm::Agent.instance.logger.debug { "Unable to capture an app-specific backtrace for #{meta.inspect}\n#{layer.backtrace}" }
122
- end
123
- end
124
- metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
125
- allocation_metric_hash[meta] ||= MetricStats.new( meta_options.has_key?(:scope) )
126
- # timing
127
- stat = metric_hash[meta]
128
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
129
- stat.add_traces(layer.traces.as_json)
130
-
131
- # Debug logging for scoutprof traces
132
- if ScoutApm::Agent.instance.config.value('profile')
133
- if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
134
- ScoutApm::Agent.instance.logger.debug do
135
- traces_inspect = layer.traces.inspect
136
- "****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
137
- end
138
- end
139
- else
140
- if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
141
- ScoutApm::Agent.instance.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
142
- end
143
- end
144
-
145
- # allocations
146
- stat = allocation_metric_hash[meta]
147
- stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
148
-
149
- # Merged Metric (no specifics, just sum up by type)
150
- meta = MetricMeta.new("#{layer.type}/all")
151
- metric_hash[meta] ||= MetricStats.new(false)
152
- allocation_metric_hash[meta] ||= MetricStats.new(false)
153
- # timing
154
- stat = metric_hash[meta]
155
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
156
- # allocations
157
- stat = allocation_metric_hash[meta]
158
- stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
159
- end
74
+ # And now run through the walk we just defined
75
+ walker.walk
160
76
 
161
77
  metric_hash = attach_backtraces(metric_hash)
162
78
  allocation_metric_hash = attach_backtraces(allocation_metric_hash)
163
79
 
164
- [metric_hash,allocation_metric_hash]
80
+ [metric_hash, allocation_metric_hash]
165
81
  end
166
82
  end
167
83
  end
@@ -0,0 +1,126 @@
1
+ module ScoutApm
2
+ # A LimitedLayer is a lossy-compression approach to fall back on once we max out
3
+ # the number of detailed layer objects we store. See LayerChildrenSet for the
4
+ # logic on when that change over happens
5
+ #
6
+ # QUESTION: What do we do if we attempt to merge an item that has children?
7
+ class LimitedLayer
8
+ attr_reader :type
9
+
10
+ def initialize(type)
11
+ @type = type
12
+
13
+ @total_call_time = 0
14
+ @total_exclusive_time = 0
15
+ @total_allocations = 0
16
+ @total_exclusive_allocations = 0
17
+ @total_layers = 0
18
+ end
19
+
20
+ def absorb(layer)
21
+ @total_layers += 1
22
+
23
+ @total_call_time += layer.total_call_time
24
+ @total_exclusive_time += layer.total_exclusive_time
25
+
26
+ @total_allocations += layer.total_allocations
27
+ @total_exclusive_allocations += layer.total_exclusive_allocations
28
+ end
29
+
30
+ def total_call_time
31
+ @total_call_time
32
+ end
33
+
34
+ def total_exclusive_time
35
+ @total_exclusive_time
36
+ end
37
+
38
+ def total_allocations
39
+ @total_allocations
40
+ end
41
+
42
+ def total_exclusive_allocations
43
+ @total_exclusive_allocations
44
+ end
45
+
46
+ def count
47
+ @total_layers
48
+ end
49
+
50
+ # This is the old style name. This function is used for now, but should be
51
+ # removed, and the new type & name split should be enforced through the
52
+ # app.
53
+ def legacy_metric_name
54
+ "#{type}/Limited"
55
+ end
56
+
57
+ def children
58
+ Set.new
59
+ end
60
+
61
+ def annotations
62
+ nil
63
+ end
64
+
65
+ def to_s
66
+ "<LimitedLayer type=#{type} count=#{count}>"
67
+ end
68
+
69
+ def limited?
70
+ true
71
+ end
72
+
73
+ ######################################################
74
+ # Stub out some methods with static default values #
75
+ ######################################################
76
+ def subscopable?
77
+ false
78
+ end
79
+
80
+ def desc
81
+ nil
82
+ end
83
+
84
+ def backtrace
85
+ nil
86
+ end
87
+
88
+
89
+ #######################################################################
90
+ # Many methods don't make any sense on a limited layer. Raise errors #
91
+ # aggressively for now to detect mistaken calls #
92
+ #######################################################################
93
+
94
+ def add_child
95
+ raise "Should never call add_child on a limited_layer"
96
+ end
97
+
98
+ def record_stop_time!(*)
99
+ raise "Should never call record_stop_time! on a limited_layer"
100
+ end
101
+
102
+ def record_allocations!
103
+ raise "Should never call record_allocations! on a limited_layer"
104
+ end
105
+
106
+ def desc=(*)
107
+ raise "Should never call desc on a limited_layer"
108
+ end
109
+
110
+ def annotate_layer(*)
111
+ raise "Should never call annotate_layer on a limited_layer"
112
+ end
113
+
114
+ def subscopable!
115
+ raise "Should never call subscopable! on a limited_layer"
116
+ end
117
+
118
+ def capture_backtrace!
119
+ raise "Should never call capture_backtrace on a limited_layer"
120
+ end
121
+
122
+ def caller_array
123
+ raise "Should never call caller_array on a limited_layer"
124
+ end
125
+ end
126
+ end
@@ -33,11 +33,6 @@ class MetricMeta
33
33
  !!(metric_name =~ /\A(Controller|Job)\//)
34
34
  end
35
35
 
36
- # To avoid conflicts with different JSON libaries
37
- def to_json(*a)
38
- %Q[{"metric_id":#{metric_id || 'null'},"metric_name":#{metric_name.to_json},"scope":#{scope.to_json || 'null'}}]
39
- end
40
-
41
36
  def ==(o)
42
37
  self.eql?(o)
43
38
  end
@@ -1,6 +1,8 @@
1
1
  module ScoutApm
2
2
  class MetricSet
3
- # We can't aggregate CPU, Memory, Capacity, or Controller, so pass through these metrics directly
3
+ # We can't aggregate a handful of things like samplers (CPU, Memory), or
4
+ # Controller, and Percentiles so pass through these metrics directly
5
+ #
4
6
  # TODO: Figure out a way to not have this duplicate what's in Samplers, and also on server's ingest
5
7
  PASSTHROUGH_METRICS = ["CPU", "Memory", "Instance", "Controller", "SlowTransaction", "Percentile", "Job"]
6
8
 
@@ -49,5 +51,11 @@ module ScoutApm
49
51
  @combine_in_progress = false
50
52
  self
51
53
  end
54
+
55
+
56
+ def eql?(other)
57
+ metrics == other.metrics
58
+ end
59
+ alias :== :eql?
52
60
  end
53
61
  end
@@ -9,7 +9,6 @@ class MetricStats
9
9
  attr_accessor :sum_of_squares
10
10
  attr_accessor :queue
11
11
  attr_accessor :latency
12
- attr_accessor :traces
13
12
 
14
13
  def initialize(scoped = false)
15
14
  @scoped = scoped
@@ -19,7 +18,6 @@ class MetricStats
19
18
  self.min_call_time = 0.0
20
19
  self.max_call_time = 0.0
21
20
  self.sum_of_squares = 0.0
22
- self.traces = []
23
21
  end
24
22
 
25
23
  # Note, that you must include exclusive_time if you wish to set
@@ -41,10 +39,6 @@ class MetricStats
41
39
  self
42
40
  end
43
41
 
44
- def add_traces(traces)
45
- self.traces += Array(traces)
46
- end
47
-
48
42
  # combines data from another MetricStats object
49
43
  def combine!(other)
50
44
  self.call_count += other.call_count
@@ -53,12 +47,17 @@ class MetricStats
53
47
  self.min_call_time = other.min_call_time if self.min_call_time.zero? or other.min_call_time < self.min_call_time
54
48
  self.max_call_time = other.max_call_time if other.max_call_time > self.max_call_time
55
49
  self.sum_of_squares += other.sum_of_squares
56
- self.traces = Array(self.traces) + Array(other.traces)
57
50
  self
58
51
  end
59
52
 
60
53
  def as_json
61
- json_attributes = [:call_count, :total_call_time, :total_exclusive_time, :min_call_time, :max_call_time, :traces]
54
+ json_attributes = [
55
+ :call_count,
56
+ :max_call_time,
57
+ :min_call_time,
58
+ :total_call_time,
59
+ :total_exclusive_time,
60
+ ]
62
61
  ScoutApm::AttributeArranger.call(self, json_attributes)
63
62
  end
64
63
  end
@@ -0,0 +1,26 @@
1
+ module ScoutApm
2
+ module Rack
3
+ def self.install!
4
+ ScoutApm::Agent.instance.start(:skip_app_server_check => true)
5
+ ScoutApm::Agent.instance.start_background_worker
6
+ end
7
+
8
+ def self.transaction(endpoint_name, env)
9
+ req = ScoutApm::RequestManager.lookup
10
+ req.annotate_request(:uri => env["REQUEST_PATH"]) rescue nil
11
+ req.context.add_user(:ip => env["REMOTE_ADDR"]) rescue nil
12
+
13
+ req.web!
14
+ req.start_layer(ScoutApm::Layer.new('Controller', endpoint_name))
15
+
16
+ begin
17
+ yield
18
+ rescue
19
+ req.error!
20
+ raise
21
+ ensure
22
+ req.stop_layer
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module ScoutApm
2
+ module Remote
3
+ class Message
4
+ attr_reader :type
5
+ attr_reader :command
6
+ attr_reader :args
7
+
8
+ def initialize(type, command, *args)
9
+ @type = type
10
+ @command = command
11
+ @args = args
12
+ end
13
+
14
+ def self.decode(msg)
15
+ Marshal.load(msg)
16
+ end
17
+
18
+ def encode
19
+ Marshal.dump(self)
20
+ end
21
+ end
22
+ end
23
+ end