scout_apm 1.0.0 → 1.1.0.pre1

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