sqreen 1.19.1-java → 1.21.0.beta3-java

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 (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +34 -0
  3. data/lib/sqreen/actions/block_user.rb +1 -1
  4. data/lib/sqreen/actions/redirect_ip.rb +1 -1
  5. data/lib/sqreen/actions/redirect_user.rb +1 -1
  6. data/lib/sqreen/agent_message.rb +20 -0
  7. data/lib/sqreen/aggregated_metric.rb +25 -0
  8. data/lib/sqreen/attack_detected.html +1 -2
  9. data/lib/sqreen/ca.crt +24 -0
  10. data/lib/sqreen/condition_evaluator.rb +9 -2
  11. data/lib/sqreen/conditionable.rb +24 -6
  12. data/lib/sqreen/configuration.rb +11 -5
  13. data/lib/sqreen/deferred_logger.rb +50 -14
  14. data/lib/sqreen/deliveries/batch.rb +12 -2
  15. data/lib/sqreen/deliveries/simple.rb +4 -0
  16. data/lib/sqreen/deprecation.rb +38 -0
  17. data/lib/sqreen/ecosystem.rb +96 -0
  18. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  19. data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
  20. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  21. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  22. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  23. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  24. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  25. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  26. data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
  27. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  28. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  29. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  30. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  31. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  32. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  33. data/lib/sqreen/ecosystem/module_registry.rb +44 -0
  34. data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
  35. data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
  36. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  37. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  38. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  39. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  40. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  41. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  42. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  43. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  44. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  45. data/lib/sqreen/ecosystem_integration.rb +87 -0
  46. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
  47. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
  48. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  49. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  50. data/lib/sqreen/endpoint_testing.rb +184 -0
  51. data/lib/sqreen/event.rb +7 -5
  52. data/lib/sqreen/events/attack.rb +23 -18
  53. data/lib/sqreen/events/remote_exception.rb +0 -22
  54. data/lib/sqreen/events/request_record.rb +15 -71
  55. data/lib/sqreen/frameworks/generic.rb +24 -1
  56. data/lib/sqreen/frameworks/rails.rb +0 -7
  57. data/lib/sqreen/frameworks/request_recorder.rb +15 -2
  58. data/lib/sqreen/graft/call.rb +106 -19
  59. data/lib/sqreen/graft/callback.rb +1 -1
  60. data/lib/sqreen/graft/hook.rb +212 -100
  61. data/lib/sqreen/graft/hook_point.rb +18 -11
  62. data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
  63. data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
  64. data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
  65. data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
  66. data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
  67. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +57 -0
  68. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  69. data/lib/sqreen/legacy/old_event_submission_strategy.rb +228 -0
  70. data/lib/sqreen/legacy/waf_redactions.rb +49 -0
  71. data/lib/sqreen/log.rb +3 -2
  72. data/lib/sqreen/log/loggable.rb +2 -1
  73. data/lib/sqreen/logger.rb +24 -0
  74. data/lib/sqreen/metrics.rb +1 -0
  75. data/lib/sqreen/metrics/base.rb +3 -0
  76. data/lib/sqreen/metrics/req_detailed.rb +41 -0
  77. data/lib/sqreen/metrics_store.rb +33 -12
  78. data/lib/sqreen/null_logger.rb +22 -0
  79. data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
  80. data/lib/sqreen/remote_command.rb +4 -0
  81. data/lib/sqreen/rules.rb +12 -6
  82. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  83. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  84. data/lib/sqreen/rules/not_found_cb.rb +2 -0
  85. data/lib/sqreen/rules/rule_cb.rb +6 -2
  86. data/lib/sqreen/rules/waf_cb.rb +16 -13
  87. data/lib/sqreen/runner.rb +138 -16
  88. data/lib/sqreen/sensitive_data_redactor.rb +19 -31
  89. data/lib/sqreen/session.rb +53 -43
  90. data/lib/sqreen/signals/conversions.rb +288 -0
  91. data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
  92. data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
  93. data/lib/sqreen/version.rb +1 -1
  94. data/lib/sqreen/weave/budget.rb +35 -0
  95. data/lib/sqreen/weave/legacy/instrumentation.rb +277 -135
  96. data/lib/sqreen/worker.rb +6 -2
  97. metadata +86 -10
  98. data/lib/sqreen/backport.rb +0 -9
  99. data/lib/sqreen/backport/clock_gettime.rb +0 -74
  100. data/lib/sqreen/backport/original_name.rb +0 -88
  101. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -0,0 +1,228 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/aggregated_metric'
7
+ require 'sqreen/log/loggable'
8
+ require 'sqreen/legacy/waf_redactions'
9
+ require 'sqreen/kit/string_sanitizer'
10
+
11
+ module Sqreen
12
+ module Legacy
13
+ # see also Sqreen::Signals::SignalsSubmissionStrategy
14
+ # usage in Sqreen:Session
15
+ class OldEventSubmissionStrategy
16
+ include Sqreen::Log::Loggable
17
+
18
+ RETRY_MANY = 301
19
+
20
+ def initialize(post_proc)
21
+ @post_proc = post_proc
22
+ end
23
+
24
+ def post_metrics(metrics)
25
+ return if metrics.nil? || metrics.empty?
26
+ payload = { metrics: metrics.map { |m| EventToHash.convert_agg_metric(m) } }
27
+ post('metrics', payload, {}, RETRY_MANY)
28
+ end
29
+
30
+ # @param attack [Sqreen::Attack]
31
+ def post_attack(attack)
32
+ post('attack', EventToHash.convert_attack(attack), {}, RETRY_MANY)
33
+ end
34
+
35
+ # @param [Sqreen::RequestRecord] request_record
36
+ def post_request_record(request_record)
37
+ rr_hash = EventToHash.convert_request_record(request_record)
38
+ post('request_record', rr_hash, {}, RETRY_MANY)
39
+ end
40
+
41
+ # Post an exception to Sqreen for analysis
42
+ # @param exception [RemoteException] Exception and context to be sent over
43
+ def post_sqreen_exception(exception)
44
+ data = EventToHash.convert_exception(exception)
45
+ post('sqreen_exception', data, {}, 5)
46
+ rescue StandardError => e
47
+ logger.warn(format('Could not post exception (network down? %s) %s',
48
+ e.inspect,
49
+ exception.inspect))
50
+ nil
51
+ end
52
+
53
+ def post_batch(events)
54
+ batch = events.map do |event|
55
+ h = case event
56
+ when AggregatedMetric
57
+ logger.warn "Aggregated metric event in non-signal mode. Signals disabled at runtime?"
58
+ next
59
+ when Sqreen::Kit::Signals::Signal
60
+ logger.warn "Signal event in non-signal mode"
61
+ next
62
+ when Sqreen::Kit::Signals::Trace
63
+ logger.warn "Trace event in non-signal mode"
64
+ next
65
+ when Attack # in practice only found inside req rec
66
+ EventToHash.convert_attack event
67
+ when RemoteException
68
+ EventToHash.convert_exception event
69
+ when RequestRecord
70
+ EventToHash.convert_request_record event
71
+ else
72
+ logger.warn "Unexpected event type: #{event}"
73
+ next
74
+ end
75
+ h['event_type'] = event_kind(event)
76
+ h
77
+ end
78
+ Sqreen.log.debug do
79
+ tally = Hash[events.group_by(&:class).map { |k, v| [k, v.count] }]
80
+ "Doing batch with the following tally of event types: #{tally}"
81
+ end
82
+ post('batch', { batch: batch.compact }, {}, RETRY_MANY)
83
+ end
84
+
85
+ private
86
+
87
+ # see +Sqreen::Session.post+
88
+ def post(*args)
89
+ @post_proc[*args]
90
+ end
91
+
92
+ def event_kind(event)
93
+ case event
94
+ when Sqreen::RemoteException then 'sqreen_exception'
95
+ when Sqreen::Attack then 'attack'
96
+ when Sqreen::RequestRecord then 'request_record'
97
+ end
98
+ end
99
+ end
100
+
101
+ module EventToHash
102
+ class << self
103
+ # @param attack [Sqreen::Attack]
104
+ def convert_attack(attack)
105
+ payload = attack.payload
106
+ res = {}
107
+ rule_p = payload['rule']
108
+ request_p = payload['request']
109
+ res[:rule_name] = rule_p['name'] if rule_p && rule_p['name']
110
+ res[:rulespack_id] = rule_p['rulespack_id'] if rule_p && rule_p['rulespack_id']
111
+ res[:test] = rule_p['test'] if rule_p && rule_p['test']
112
+ res[:infos] = payload['infos'] if payload['infos']
113
+ res[:time] = attack.time
114
+ res[:client_ip] = request_p[:addr] if request_p && request_p[:addr]
115
+ res[:request] = request_p if request_p
116
+ res[:params] = payload['params'] if payload['params']
117
+ res[:context] = payload['context'] if payload['context']
118
+ res[:headers] = payload['headers'] if payload['headers']
119
+ res
120
+ end
121
+
122
+ # @param [Sqreen::RequestRecord] rr
123
+ def convert_request_record(rr)
124
+ res = { :version => '20171208' }
125
+ payload = rr.payload
126
+
127
+ if payload[:observed]
128
+ res[:observed] = payload[:observed].dup
129
+ rulespack = nil
130
+ if rr.observed[:attacks]
131
+ res[:observed][:attacks] = rr.observed[:attacks].map do |att|
132
+ natt = att.dup
133
+ [:attack_type, :block].each { |k| natt.delete(k) } # signals stuff
134
+ rulespack = natt.delete(:rulespack_id) || rulespack
135
+ natt
136
+ end
137
+ end
138
+ if rr.observed[:sqreen_exceptions]
139
+ res[:observed][:sqreen_exceptions] = rr.observed[:sqreen_exceptions].map do |exc|
140
+ nex = exc.dup
141
+ excp = nex.delete(:exception)
142
+ if excp
143
+ nex[:message] = excp.message
144
+ nex[:klass] = excp.class.name
145
+ end
146
+ rulespack = nex.delete(:rulespack_id) || rulespack
147
+ nex
148
+ end
149
+ end
150
+ res[:rulespack_id] = rulespack unless rulespack.nil?
151
+ if rr.observed[:observations]
152
+ res[:observed][:observations] = rr.observed[:observations].map do |cat, key, value, time|
153
+ { :category => cat, :key => key, :value => value, :time => time }
154
+ end
155
+ end
156
+ if rr.observed[:sdk] # rubocop:disable Style/IfUnlessModifier
157
+ res[:observed][:sdk] = rr.processed_sdk_calls
158
+ end
159
+ end
160
+ res[:local] = payload['local'] if payload['local']
161
+ if payload['request']
162
+ res[:request] = payload['request'].dup
163
+ res[:client_ip] = res[:request].delete(:client_ip) if res[:request][:client_ip]
164
+ else
165
+ res[:request] = {}
166
+ end
167
+ if payload['response']
168
+ res[:response] = payload['response'].dup
169
+ else
170
+ res[:response] = {}
171
+ end
172
+
173
+ res[:request][:parameters] = payload['params'] if payload['params']
174
+ res[:request][:headers] = payload['headers'] if payload['headers']
175
+
176
+ res = Sqreen::Kit::StringSanitizer.sanitize(res)
177
+
178
+ if rr.redactor
179
+ res[:request], redacted = rr.redactor.redact(res[:request])
180
+ redacted = redacted.uniq
181
+ if redacted.any? && res[:observed] && res[:observed][:attacks]
182
+ res[:observed][:attacks] = WafRedactions.redact_attacks!(res[:observed][:attacks], redacted)
183
+ end
184
+ if redacted.any? && res[:observed] && res[:observed][:sqreen_exceptions]
185
+ res[:observed][:sqreen_exceptions] = WafRedactions.redact_exceptions!(res[:observed][:sqreen_exceptions], redacted)
186
+ end
187
+ end
188
+
189
+ res
190
+ end
191
+
192
+ # @param exception_evt [Sqreen::RemoteException]
193
+ def convert_exception(exception_evt)
194
+ payload = exception_evt.payload
195
+ exception = payload['exception']
196
+ ev = {
197
+ :klass => exception.class.name,
198
+ :message => exception.message,
199
+ :params => payload['request_params'],
200
+ :time => payload['time'],
201
+ :infos => {
202
+ :client_ip => payload['client_ip'],
203
+ },
204
+ :request => payload['request_infos'],
205
+ :headers => payload['headers'],
206
+ :rule_name => payload['rule_name'],
207
+ :rulespack_id => payload['rulespack_id'],
208
+ }
209
+
210
+ ev[:infos].merge!(payload['infos']) if payload['infos']
211
+ return ev unless exception.backtrace
212
+ ev[:context] = { :backtrace => exception.backtrace.map(&:to_s) }
213
+ ev
214
+ end
215
+
216
+ # @param [Sqreen::AggregatedMetric] agg_metric
217
+ def convert_agg_metric(agg_metric)
218
+ {
219
+ name: agg_metric.name,
220
+ observation: agg_metric.data,
221
+ start: agg_metric.start,
222
+ finish: agg_metric.finish,
223
+ }
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,49 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ module Sqreen
7
+ module Legacy
8
+ module WafRedactions
9
+ class << self
10
+ def redact_attacks!(attacks, values)
11
+ return attacks if values.empty?
12
+
13
+ values = values.map { |v| v.downcase if v.is_a?(String) }
14
+
15
+ attacks.each do |e|
16
+ next(e) unless e[:infos]
17
+ next(e) unless e[:infos][:waf_data]
18
+
19
+ parsed = JSON.parse(e[:infos][:waf_data])
20
+ redacted = parsed.each do |w|
21
+ next unless (filters = w['filter'])
22
+
23
+ filters.each do |f|
24
+ next unless (v = f['resolved_value'])
25
+ next unless values.include?(v.downcase)
26
+
27
+ f['match_status'] = SensitiveDataRedactor::MASK
28
+ f['resolved_value'] = SensitiveDataRedactor::MASK
29
+ end
30
+ end
31
+ e[:infos][:waf_data] = JSON.dump(redacted)
32
+ end
33
+ end
34
+
35
+ # see https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000022-waf-data-sanitization.md#changes-to-the-agents
36
+ def redact_exceptions!(exceptions, values)
37
+ return exceptions if values.empty?
38
+
39
+ exceptions.each do |e|
40
+ next(e) unless e[:infos]
41
+ next(e) unless e[:infos][:waf]
42
+
43
+ e[:infos][:waf].delete(:args)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -14,16 +14,17 @@ require 'sqreen/deferred_logger'
14
14
 
15
15
  module Sqreen
16
16
  def self.log_init
17
+ deferred_logger = @logger
17
18
  @logger = Sqreen::Logger.new(
18
19
  Sqreen.config_get(:log_level).to_s.upcase,
19
20
  Sqreen.config_get(:log_location)
20
21
  )
21
- Sqreen::DeferredLogger.instance.flush_to(@logger.instance_eval { @logger })
22
+ deferred_logger.flush_to(@logger.instance_eval { @logger })
22
23
  rescue => e
23
24
  warn "Sqreen logger exception: #{e}"
24
25
  end
25
26
 
26
27
  def self::log
27
- @logger || Sqreen::DeferredLogger.instance
28
+ @logger ||= Sqreen::DeferredLogger.new
28
29
  end
29
30
  end
@@ -4,6 +4,7 @@
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
6
  require 'logger'
7
+ require 'sqreen/log'
7
8
 
8
9
  module Sqreen; end
9
10
  module Sqreen::Log; end
@@ -23,6 +24,6 @@ module Sqreen::Log::Loggable
23
24
  end
24
25
 
25
26
  def logger
26
- @logger || self.class.logger
27
+ @logger || singleton_class.logger
27
28
  end
28
29
  end
@@ -28,6 +28,26 @@ module Sqreen
28
28
  create_error_logger
29
29
  end
30
30
 
31
+ def debug?
32
+ @logger.debug?
33
+ end
34
+
35
+ def info?
36
+ @logger.info?
37
+ end
38
+
39
+ def warn?
40
+ @logger.warn?
41
+ end
42
+
43
+ def error?
44
+ @logger.error?
45
+ end
46
+
47
+ def fatal?
48
+ @logger.fatal?
49
+ end
50
+
31
51
  def debug(msg = nil, &block)
32
52
  @logger.debug(msg, &block)
33
53
  end
@@ -45,6 +65,10 @@ module Sqreen
45
65
  @logger.error(msg, &block)
46
66
  end
47
67
 
68
+ def unknown(msg = nil, &block)
69
+ @logger.unknown(msg, &block)
70
+ end
71
+
48
72
  def add(severity, msg = nil, &block)
49
73
  send(SEVERITY_TO_METHOD[severity], msg, &block)
50
74
  end
@@ -7,3 +7,4 @@ require 'sqreen/metrics/collect'
7
7
  require 'sqreen/metrics/average'
8
8
  require 'sqreen/metrics/sum'
9
9
  require 'sqreen/metrics/binning'
10
+ require 'sqreen/metrics/req_detailed'
@@ -12,6 +12,9 @@ module Sqreen
12
12
  FINISH_KEY = 'finish'.freeze
13
13
  # Base interface for a metric
14
14
  class Base
15
+ attr_accessor :name, :period # for signals serialization
16
+ attr_accessor :rule # optional
17
+
15
18
  def initialize(_opts={})
16
19
  @sample = nil
17
20
  end
@@ -0,0 +1,41 @@
1
+ require 'base64'
2
+ require 'sqreen/mono_time'
3
+ require 'sqreen/metrics/base'
4
+ begin
5
+ require 'sq_detailed_metrics'
6
+ rescue LoadError => _e # rubocop:disable Lint/HandleExceptions
7
+ end
8
+
9
+ module Sqreen
10
+ module Metric
11
+ class ReqDetailed < Base
12
+ attr_reader :num_requests
13
+
14
+ def initialize(opts = {})
15
+ raise 'SqDetailedMetrics unavailable' unless defined?(SqDetailedMetrics)
16
+ super(opts)
17
+ @coll = SqDetailedMetrics::RequestCollection.new
18
+ @start_time = Sqreen.time
19
+ @num_requests = 0
20
+ end
21
+
22
+ # @param [SqDetailedMetrics::Request] value
23
+ def update(_key, value)
24
+ @coll << value
25
+ @num_requests += 1
26
+ end
27
+
28
+ def next_sample(time)
29
+ data = @coll.serialize
30
+ @num_requests = 0
31
+ return nil unless data
32
+
33
+ {
34
+ OBSERVATION_KEY => { 'v1' => Base64.strict_encode64(data) },
35
+ START_KEY => @start_time,
36
+ FINISH_KEY => (@start_time = time),
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,6 +3,7 @@
3
3
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
+ require 'sqreen/aggregated_metric'
6
7
  require 'sqreen/metrics'
7
8
  require 'sqreen/mono_time'
8
9
  require 'sqreen/metrics_store/unknown_metric'
@@ -26,12 +27,16 @@ module Sqreen
26
27
  def initialize
27
28
  @store = []
28
29
  @metrics = {} # name => (metric, period, start)
30
+ @mutex = Mutex.new
29
31
  end
30
32
 
31
33
  # Definition contains a name,period and aggregate at least
32
34
  # @param definition [Hash] a metric definition
35
+ # @param rule [RuleCB] the rule associated with this metric, if any
33
36
  # @param mklass [Object] Override metric object (used in testing)
34
- def create_metric(definition, mklass = nil)
37
+ def create_metric(definition, rule = nil, mklass = nil)
38
+ @mutex.lock
39
+
35
40
  name = definition[NAME_KEY]
36
41
  kind = definition[KIND_KEY]
37
42
  klass = valid_metric(kind, name)
@@ -43,30 +48,41 @@ module Sqreen
43
48
  definition[PERIOD_KEY],
44
49
  nil # Start
45
50
  ]
51
+ metric.name = name
52
+ metric.rule = rule
53
+ metric.period = definition[PERIOD_KEY]
46
54
  metric
55
+ ensure
56
+ @mutex.unlock
47
57
  end
48
58
 
49
59
  def metric?(name)
50
60
  @metrics.key?(name)
51
61
  end
52
62
 
53
- # @params at [Time] when is the store emptied
63
+ # @param at [Time] when is the store emptied
54
64
  def update(name, at, key, value)
65
+ @mutex.lock
55
66
  metric, period, start = @metrics[name]
56
67
  raise UnregisteredMetric, "Unknown metric #{name}" unless metric
57
68
  next_sample(name, at) if start.nil? || (start + period) < at
58
69
  metric.update(key, value)
70
+ ensure
71
+ @mutex.unlock
59
72
  end
60
73
 
61
74
  # Drains every metrics and returns the store content
62
- # @params at [Time] when is the store emptied
75
+ # @param at [Time] when is the store emptied
63
76
  def publish(flush = true, at = Sqreen.time)
77
+ @mutex.lock
64
78
  @metrics.each do |name, (_, period, start)|
65
79
  next_sample(name, at) if flush || !start.nil? && (start + period) < at
66
80
  end
67
81
  out = @store
68
82
  @store = []
69
83
  out
84
+ ensure
85
+ @mutex.unlock
70
86
  end
71
87
 
72
88
  protected
@@ -75,15 +91,20 @@ module Sqreen
75
91
  metric = @metrics[name][0]
76
92
  r = metric.next_sample(at)
77
93
  @metrics[name][2] = at # new start
78
- if r
79
- r[NAME_KEY] = name
80
- obs = r[Metric::OBSERVATION_KEY]
81
- start_of_mono = Time.now.utc - Sqreen.time
82
- r[Metric::START_KEY] = start_of_mono + r[Metric::START_KEY]
83
- r[Metric::FINISH_KEY] = start_of_mono + r[Metric::FINISH_KEY]
84
- @store << r if obs && (!obs.respond_to?(:empty?) || !obs.empty?)
85
- end
86
- r
94
+ return unless r
95
+
96
+ r[NAME_KEY] = name
97
+ obs = r[Metric::OBSERVATION_KEY]
98
+ return unless obs && (!obs.respond_to?(:empty?) || !obs.empty?)
99
+ start_of_mono = Time.now.utc - Sqreen.time
100
+
101
+ agg = AggregatedMetric.new
102
+ agg.metric = metric
103
+ agg.rule = agg.metric.rule
104
+ agg.start = start_of_mono + r[Metric::START_KEY]
105
+ agg.finish = start_of_mono + r[Metric::FINISH_KEY]
106
+ agg.data = obs
107
+ @store << agg
87
108
  end
88
109
 
89
110
  def valid_metric(kind, name)