scout_apm 1.0.0 → 1.1.0.pre1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80cec2f6800c7bd268f6d18d7eb56570dd755ccc
4
- data.tar.gz: 102b7a884624afa4488fabbdcdafad92bdb390c8
3
+ metadata.gz: 6241efa65950ed1d898ba3d688c0d0e442ed4871
4
+ data.tar.gz: 166976b4a15c5e4481bb171abba5dcf2f155e70f
5
5
  SHA512:
6
- metadata.gz: ef2d0df4bf7cc94e5f24122599f5cccb6bbfba95c12dcf7d6681c03469901b7c7f0a0b21cd3031103b2ed729a247421dc55b729308e2ac87db3bbb3e7c553319
7
- data.tar.gz: e3f6369339505b4f0472fbbb676a4502cf0b4269e6e372bf55726d5357947ea8665ee98f5a68c924221942be80bcb1a5415e95189054b725c21054a377780bfa
6
+ metadata.gz: d0f7893d4df03ff5216bac6334ffa04e7e0bfa0e50e9eb14be6cb8a3d8e58b40afe140ed0691b3e324b311349119917f686f152e885dfbebbe20376c0d66c571
7
+ data.tar.gz: 4f6c3c86c84349989dfb406711a35791bd7d9f550b9139d67432fd7e37f512a9d879cfd7f08bbca6f8c30d9eb730fe8c469e7b7790138d87f02d0a25ce697993
data/lib/scout_apm.rb CHANGED
@@ -74,6 +74,7 @@ require 'scout_apm/layaway'
74
74
  require 'scout_apm/layaway_file'
75
75
  require 'scout_apm/reporter'
76
76
  require 'scout_apm/background_worker'
77
+ require 'scout_apm/bucket_name_splitter'
77
78
  require 'scout_apm/metric_meta'
78
79
  require 'scout_apm/metric_stats'
79
80
  require 'scout_apm/stack_item'
@@ -83,8 +84,10 @@ require 'scout_apm/context'
83
84
  require 'scout_apm/stackprof_tree_collapser'
84
85
  require 'scout_apm/slow_transaction'
85
86
  require 'scout_apm/capacity'
87
+ require 'scout_apm/attribute_arranger'
86
88
 
87
89
  require 'scout_apm/serializers/payload_serializer'
90
+ require 'scout_apm/serializers/payload_serializer_to_json'
88
91
  require 'scout_apm/serializers/directive_serializer'
89
92
  require 'scout_apm/serializers/app_server_load_serializer'
90
93
  require 'scout_apm/serializers/deploy_serializer'
@@ -49,7 +49,13 @@ module ScoutApm
49
49
 
50
50
  logger.debug "Total payload [#{payload.size/1024} KB] for #{total_request_count} requests and Slow Transactions [#{slow_transactions_kb} KB] for #{slow_transactions.size} transactions of durations: #{slow_transactions.map(&:total_call_time).join(',')}."
51
51
 
52
- response = reporter.report(payload)
52
+ if ScoutApm::Agent.instance.config.value("report_format") == 'json'
53
+ headers = {'Content-Type' => 'application/json'}
54
+ else
55
+ headers = {}
56
+ end
57
+
58
+ response = reporter.report(payload, headers)
53
59
 
54
60
  if response and response.is_a?(Net::HTTPSuccess)
55
61
  directives = ScoutApm::Serializers::DirectiveSerializer.deserialize(response.body)
@@ -0,0 +1,17 @@
1
+ module ScoutApm
2
+ module AttributeArranger
3
+ # pass in an array of symbols to return as hash keys
4
+ # if the symbol doesn't match the name of the method, pass an array: [:key, :method_name]
5
+ def self.call(subject, attributes_list)
6
+ attributes_list.inject({}) do |attribute_hash, attribute|
7
+ case attribute
8
+ when Array
9
+ attribute_hash[attribute[0]] = subject.send(attribute[1])
10
+ when Symbol
11
+ attribute_hash[attribute] = subject.send(attribute)
12
+ end
13
+ attribute_hash
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ module ScoutApm
2
+ module BucketNameSplitter
3
+ def bucket
4
+ split_metric_name(metric_name).first
5
+ end
6
+
7
+ def name
8
+ split_metric_name(metric_name).last
9
+ end
10
+
11
+ def key
12
+ {:bucket => bucket, :name => name}
13
+ end
14
+
15
+ private
16
+ def split_metric_name(name)
17
+ name.to_s.split(/\//, 2)
18
+ end
19
+
20
+ def scope_hash
21
+ if scope
22
+ scope_bucket, scope_name = split_metric_name(scope)
23
+ {:bucket => scope_bucket, :name => scope_name}
24
+ end
25
+ end
26
+ end
27
+ end
@@ -81,7 +81,7 @@ module ScoutApm
81
81
  value = key_value.values.last
82
82
  if !valid_type?([String, Symbol, Numeric, Time, Date, TrueClass, FalseClass],value)
83
83
  ScoutApm::Agent.instance.logger.warn "The value for [#{key_value.keys.first}] is not a valid type [#{value.class}]."
84
- false
84
+ false
85
85
  else
86
86
  true
87
87
  end
@@ -1,5 +1,7 @@
1
1
  # Contains the meta information associated with a metric. Used to lookup Metrics in to Store's metric_hash.
2
2
  class ScoutApm::MetricMeta
3
+ include ScoutApm::BucketNameSplitter
4
+
3
5
  def initialize(metric_name, options = {})
4
6
  @metric_name = metric_name
5
7
  @metric_id = nil
@@ -31,4 +33,10 @@ class ScoutApm::MetricMeta
31
33
  def eql?(o)
32
34
  self.class == o.class && metric_name.downcase.eql?(o.metric_name.downcase) && scope == o.scope && client_id == o.client_id && desc == o.desc
33
35
  end
36
+
37
+ def as_json
38
+ json_attributes = [:bucket, :name, :desc, :extra, [:scope, :scope_hash]]
39
+ # query, stack_trace
40
+ ScoutApm::AttributeArranger.call(self, json_attributes)
41
+ end
34
42
  end # class MetricMeta
@@ -46,4 +46,10 @@ class ScoutApm::MetricStats
46
46
  def to_json(*a)
47
47
  %Q[{"total_exclusive_time":#{total_exclusive_time*1000},"min_call_time":#{min_call_time*1000},"call_count":#{call_count},"sum_of_squares":#{sum_of_squares*1000},"total_call_time":#{total_call_time*1000},"max_call_time":#{max_call_time*1000}}]
48
48
  end
49
+
50
+ def as_json
51
+ json_attributes = [:call_count, :total_call_time, :total_exclusive_time, :min_call_time, :max_call_time]
52
+ # uri, context
53
+ ScoutApm::AttributeArranger.call(self, json_attributes)
54
+ end
49
55
  end # class MetricStats
@@ -3,7 +3,11 @@ module ScoutApm
3
3
  module Serializers
4
4
  class PayloadSerializer
5
5
  def self.serialize(metadata, metrics, slow_transactions)
6
- Marshal.dump(:metadata => metadata, :metrics => metrics, :slow_transactions => slow_transactions)
6
+ if ScoutApm::Agent.instance.config.value("report_format") == 'json'
7
+ ScoutApm::Serializers::PayloadSerializerToJson.serialize(metadata, metrics, slow_transactions)
8
+ else
9
+ Marshal.dump(:metadata => metadata, :metrics => metrics, :slow_transactions => slow_transactions)
10
+ end
7
11
  end
8
12
 
9
13
  def self.deserialize(data)
@@ -0,0 +1,67 @@
1
+ module ScoutApm
2
+ module Serializers
3
+ module PayloadSerializerToJson
4
+ class << self
5
+ def serialize(metadata, metrics, slow_transactions)
6
+ rearranged_metrics = rearrange_the_metrics(metrics)
7
+ rearranged_slow_transactions = rearrange_the_slow_transactions(slow_transactions)
8
+ jsonify_hash({:metadata => metadata, :metrics => rearranged_metrics, :slow_transactions => rearranged_slow_transactions})
9
+ end
10
+
11
+ def rearrange_the_metrics(metrics)
12
+ metrics.to_a.map do |meta, stats|
13
+ stats.as_json.merge(key: meta.as_json)
14
+ end
15
+ end
16
+
17
+ def rearrange_the_slow_transactions(slow_transactions)
18
+ slow_transactions.to_a.map do |slow_t|
19
+ slow_t.as_json.merge(metrics: rearrange_the_metrics(slow_t.metrics))
20
+ end
21
+ end
22
+
23
+ def jsonify_hash(hash)
24
+ str_parts = []
25
+ hash.each do |key, value|
26
+ formatted_key = format_by_type(key)
27
+ formatted_value = format_by_type(value)
28
+ str_parts << "#{formatted_key}:#{formatted_value}"
29
+ end
30
+ "{#{str_parts.join(",")}}"
31
+ end
32
+
33
+ def escape(string)
34
+ string = string.to_s
35
+ escapers = {
36
+ '\b' => '\\b',
37
+ '\t' => '\\t',
38
+ '\n' => '\\n',
39
+ '\f' => '\\f',
40
+ '\r' => '\\r',
41
+ '"' => '\\"',
42
+ '\\' => '\\\\'
43
+ }
44
+ # TODO escape control chars
45
+ escapers.each {|bad, good| string = string.gsub(bad, good)}
46
+ string
47
+ end
48
+
49
+ def format_by_type(formatee)
50
+ case formatee
51
+ when Hash
52
+ jsonify_hash(formatee)
53
+ when Array
54
+ all_the_elements = formatee.map {|value_guy| format_by_type(value_guy)}
55
+ "[#{all_the_elements.join(",")}]"
56
+ when Numeric
57
+ formatee
58
+ when nil
59
+ "null"
60
+ else # strings and everything
61
+ %Q["#{escape(formatee)}"]
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,7 @@
1
1
  module ScoutApm
2
2
  class SlowTransaction
3
+ include ScoutApm::BucketNameSplitter
4
+
3
5
  BACKTRACE_THRESHOLD = 0.5 # the minimum threshold to record the backtrace for a metric.
4
6
  BACKTRACE_LIMIT = 5 # Max length of callers to display
5
7
  MAX_SIZE = 100 # Limits the size of the metric hash to prevent a metric explosion.
@@ -45,5 +47,14 @@ module ScoutApm
45
47
  @metrics = nil
46
48
  self
47
49
  end
50
+
51
+ def as_json
52
+ json_attributes = [:key, :time, :total_call_time, :uri, [:context, :context_hash], :prof]
53
+ ScoutApm::AttributeArranger.call(self, json_attributes)
54
+ end
55
+
56
+ def context_hash
57
+ context.to_hash
58
+ end
48
59
  end
49
60
  end
@@ -150,7 +150,7 @@ module ScoutApm
150
150
  parent_stat.total_call_time,
151
151
  transaction_hash.dup,
152
152
  ScoutApm::Context.current,
153
- Thread::current[:scout_apm_trace_time],
153
+ Time.now,
154
154
  Thread::current[:scout_apm_prof])
155
155
  @slow_transactions.push(slow_transaction)
156
156
  ScoutApm::Agent.instance.logger.debug "Slow transaction sample added. [URI: #{uri}] [Context: #{ScoutApm::Context.current.to_hash}] Array Size: #{@slow_transactions.size}"
@@ -22,7 +22,6 @@ module ScoutApm
22
22
  # TODO - wrap a lot of this into a Trace class, store that as a Thread var.
23
23
  ScoutApm::Agent.instance.store.reset_transaction!
24
24
  ScoutApm::Context.current.add_user(:ip => options[:ip]) if options[:ip]
25
- Thread::current[:scout_apm_trace_time] = Time.now.utc
26
25
  ScoutApm::Agent.instance.capacity.start_transaction!
27
26
  e = nil
28
27
  instrument(metric_name, options) do
@@ -34,7 +33,6 @@ module ScoutApm
34
33
  end
35
34
  Thread::current[:scout_apm_scope_name] = nil
36
35
  end
37
- Thread::current[:scout_apm_trace_time] = nil
38
36
  ScoutApm::Agent.instance.capacity.finish_transaction!
39
37
  # The context is cleared after instrumentation (rather than before) as tracing controller-actions doesn't occur until the controller-action is called.
40
38
  # It does not trace before filters, which is a likely spot to add context. This means that any context applied during before_filters would be cleared.
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0.pre1"
3
3
  end
4
4
 
@@ -0,0 +1,212 @@
1
+ require 'test_helper'
2
+ require 'scout_apm/attribute_arranger'
3
+ require 'scout_apm/bucket_name_splitter'
4
+ require 'scout_apm/serializers/payload_serializer'
5
+ require 'scout_apm/serializers/payload_serializer_to_json'
6
+ require 'scout_apm/slow_transaction'
7
+ require 'scout_apm/metric_meta'
8
+ require 'scout_apm/metric_stats'
9
+ require 'scout_apm/stackprof_tree_collapser'
10
+ require 'scout_apm/utils/fake_stack_prof'
11
+ require 'scout_apm/context'
12
+ require 'ostruct'
13
+ require 'json' # to deserialize what has been manually serialized by the production code
14
+
15
+ # stub the report_format value
16
+ class ScoutApm::Agent
17
+ module Config
18
+ def self.value(key)
19
+ 'json'
20
+ end
21
+ end
22
+
23
+ def self.instance
24
+ OpenStruct.new(:config => Config)
25
+ end
26
+ end
27
+
28
+ class PayloadSerializerTest < Minitest::Test
29
+
30
+ def test_serializes_metadata_as_json
31
+ metadata = {
32
+ :app_root => "/srv/app/rootz",
33
+ :unique_id => "unique_idz",
34
+ :agent_version => 123
35
+ }
36
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, {}, {})
37
+
38
+ # symbol keys turn to strings
39
+ formatted_metadata = {
40
+ "app_root" => "/srv/app/rootz",
41
+ "unique_id" => "unique_idz",
42
+ "agent_version" => 123
43
+ }
44
+ assert_equal formatted_metadata, JSON.parse(payload)["metadata"]
45
+ end
46
+
47
+ def test_serializes_metrics_as_json
48
+ metrics = {
49
+ ScoutApm::MetricMeta.new('ActiveRecord/all').tap { |meta|
50
+ meta.desc = "SELECT * from users where filter=?"
51
+ meta.extra = {user: 'cooluser'}
52
+ meta.metric_id = nil
53
+ meta.scope = "Controller/apps/checkin"
54
+ } => ScoutApm::MetricStats.new.tap { |stats|
55
+ stats.call_count = 16
56
+ stats.max_call_time = 0.005338062
57
+ stats.min_call_time = 0.000613518
58
+ stats.sum_of_squares = 9.8040860751126e-05
59
+ stats.total_call_time = 0.033245704
60
+ stats.total_exclusive_time = 0.033245704
61
+ },
62
+ ScoutApm::MetricMeta.new("Controller/apps/checkin").tap { |meta|
63
+ meta.desc = nil
64
+ meta.extra = {}
65
+ meta.metric_id = nil
66
+ meta.scope = nil
67
+ } => ScoutApm::MetricStats.new.tap { |stats|
68
+ stats.call_count = 2
69
+ stats.max_call_time = 0.078521419
70
+ stats.min_call_time = 0.034881757
71
+ stats.sum_of_squares = 0.007382350213180609
72
+ stats.total_call_time = 0.113403176
73
+ stats.total_exclusive_time = 0.07813208899999999
74
+ }
75
+ }
76
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize({}, metrics, {})
77
+ formatted_metrics = [
78
+ {
79
+ "key" => {
80
+ "bucket" => "ActiveRecord",
81
+ "name" => "all",
82
+ "desc" => "SELECT * from users where filter=?",
83
+ "extra" => {
84
+ "user" => "cooluser",
85
+ },
86
+ "scope" => {
87
+ "bucket" => "Controller",
88
+ "name" => "apps/checkin",
89
+ },
90
+ },
91
+ "call_count" => 16,
92
+ "max_call_time" => 0.005338062,
93
+ "min_call_time" => 0.000613518,
94
+ "total_call_time" => 0.033245704,
95
+ "total_exclusive_time" => 0.033245704,
96
+ },
97
+ {
98
+ "key" => {
99
+ "bucket" => "Controller",
100
+ "name" => "apps/checkin",
101
+ "desc" => nil,
102
+ "extra" => {},
103
+ "scope" => nil,
104
+ },
105
+ "call_count" => 2,
106
+ "max_call_time" => 0.078521419,
107
+ "min_call_time" => 0.034881757,
108
+ "total_call_time" => 0.113403176,
109
+ "total_exclusive_time" => 0.07813208899999999,
110
+ }
111
+ ]
112
+ assert_equal formatted_metrics, JSON.parse(payload)["metrics"]
113
+ end
114
+
115
+ def test_serializes_slow_transactions_as_json
116
+ slow_transaction_metrics = {
117
+ ScoutApm::MetricMeta.new('ActiveRecord/all').tap { |meta|
118
+ meta.desc = "SELECT * from users where filter=?"
119
+ meta.extra = {user: 'cooluser'}
120
+ meta.metric_id = nil
121
+ meta.scope = "Controller/apps/checkin"
122
+ } => ScoutApm::MetricStats.new.tap { |stats|
123
+ stats.call_count = 16
124
+ stats.max_call_time = 0.005338062
125
+ stats.min_call_time = 0.000613518
126
+ stats.sum_of_squares = 9.8040860751126e-05
127
+ stats.total_call_time = 0.033245704
128
+ stats.total_exclusive_time = 0.033245704
129
+ },
130
+ ScoutApm::MetricMeta.new("Controller/apps/checkin").tap { |meta|
131
+ meta.desc = nil
132
+ meta.extra = {}
133
+ meta.metric_id = nil
134
+ meta.scope = nil
135
+ } => ScoutApm::MetricStats.new.tap { |stats|
136
+ stats.call_count = 2
137
+ stats.max_call_time = 0.078521419
138
+ stats.min_call_time = 0.034881757
139
+ stats.sum_of_squares = 0.007382350213180609
140
+ stats.total_call_time = 0.113403176
141
+ stats.total_exclusive_time = 0.07813208899999999
142
+ }
143
+ }
144
+ context = ScoutApm::Context.new
145
+ context.add({"this" => "that"})
146
+ context.add_user({"hello" => "goodbye"})
147
+ slow_t = ScoutApm::SlowTransaction.new("http://example.com/blabla", "Buckethead/something/else", 1.23, slow_transaction_metrics, context, Time.at(1448198788), StackProf.new)
148
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize({}, {}, [slow_t])
149
+ formatted_slow_transactions = [
150
+ {
151
+ "key" => {
152
+ "bucket" => "Buckethead",
153
+ "name" => "something/else"
154
+ },
155
+ "time" => "2015-11-22 06:26:28 -0700",
156
+ "total_call_time" => 1.23,
157
+ "uri" => "http://example.com/blabla",
158
+ "context" => {"this"=>"that", "user"=>{"hello"=>"goodbye"}},
159
+ "prof" => [],
160
+ "metrics" => [
161
+ {
162
+ "key" => {
163
+ "bucket" => "ActiveRecord",
164
+ "name" => "all",
165
+ "desc" => "SELECT * from users where filter=?",
166
+ "extra" => {
167
+ "user" => "cooluser",
168
+ },
169
+ "scope" => {
170
+ "bucket" => "Controller",
171
+ "name" => "apps/checkin",
172
+ },
173
+ },
174
+ "call_count" => 16,
175
+ "max_call_time" => 0.005338062,
176
+ "min_call_time" => 0.000613518,
177
+ "total_call_time" => 0.033245704,
178
+ "total_exclusive_time" => 0.033245704,
179
+ },
180
+ {
181
+ "key" => {
182
+ "bucket" => "Controller",
183
+ "name" => "apps/checkin",
184
+ "desc" => nil,
185
+ "extra" => {},
186
+ "scope" => nil,
187
+ },
188
+ "call_count" => 2,
189
+ "max_call_time" => 0.078521419,
190
+ "min_call_time" => 0.034881757,
191
+ "total_call_time" => 0.113403176,
192
+ "total_exclusive_time" => 0.07813208899999999,
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ assert_equal formatted_slow_transactions, JSON.parse(payload)["slow_transactions"]
198
+ end
199
+
200
+ def test_escapes_json_quotes
201
+ metadata = {
202
+ :quotie => "here are some \"quotes\"",
203
+ }
204
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, {}, {})
205
+
206
+ # symbol keys turn to strings
207
+ formatted_metadata = {
208
+ "quotie" => "here are some \"quotes\""
209
+ }
210
+ assert_equal formatted_metadata, JSON.parse(payload)["metadata"]
211
+ end
212
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-18 00:00:00.000000000 Z
12
+ date: 2015-12-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -71,7 +71,9 @@ files:
71
71
  - lib/scout_apm/agent/logging.rb
72
72
  - lib/scout_apm/agent/reporting.rb
73
73
  - lib/scout_apm/app_server_load.rb
74
+ - lib/scout_apm/attribute_arranger.rb
74
75
  - lib/scout_apm/background_worker.rb
76
+ - lib/scout_apm/bucket_name_splitter.rb
75
77
  - lib/scout_apm/capacity.rb
76
78
  - lib/scout_apm/config.rb
77
79
  - lib/scout_apm/context.rb
@@ -106,6 +108,7 @@ files:
106
108
  - lib/scout_apm/serializers/deploy_serializer.rb
107
109
  - lib/scout_apm/serializers/directive_serializer.rb
108
110
  - lib/scout_apm/serializers/payload_serializer.rb
111
+ - lib/scout_apm/serializers/payload_serializer_to_json.rb
109
112
  - lib/scout_apm/server_integrations/null.rb
110
113
  - lib/scout_apm/server_integrations/passenger.rb
111
114
  - lib/scout_apm/server_integrations/puma.rb
@@ -133,6 +136,7 @@ files:
133
136
  - test/unit/config_test.rb
134
137
  - test/unit/environment_test.rb
135
138
  - test/unit/instruments/active_record_instruments_test.rb
139
+ - test/unit/serializers/payload_serializer_test.rb
136
140
  - test/unit/sql_sanitizer_test.rb
137
141
  homepage: https://github.com/scoutapp/scout_apm_ruby
138
142
  licenses: []
@@ -149,9 +153,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
153
  version: '0'
150
154
  required_rubygems_version: !ruby/object:Gem::Requirement
151
155
  requirements:
152
- - - ">="
156
+ - - ">"
153
157
  - !ruby/object:Gem::Version
154
- version: '0'
158
+ version: 1.3.1
155
159
  requirements: []
156
160
  rubyforge_project: scout_apm
157
161
  rubygems_version: 2.2.2
@@ -164,4 +168,5 @@ test_files:
164
168
  - test/unit/config_test.rb
165
169
  - test/unit/environment_test.rb
166
170
  - test/unit/instruments/active_record_instruments_test.rb
171
+ - test/unit/serializers/payload_serializer_test.rb
167
172
  - test/unit/sql_sanitizer_test.rb