Class: NewRelic::Agent::RequestSampler

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin, Coerce
Defined in:
lib/new_relic/agent/request_sampler.rb

Constant Summary collapse

DEFAULT_SAMPLE_RATE_MS =

The amount of time between samples, in milliseconds

50
MIN_SAMPLE_RATE_MS =

The minimum amount of time between samples, in milliseconds

25
DEFAULT_REPORT_FREQUENCY =

The number of seconds between harvests :TODO: Get this from the agent instead?

60
MAX_FAILED_REPORT_RETENTION =

Regardless of whether #throttle is successfully called, we will store at most this many harvest-cycles worth of samples total, to avoid unbounded memory growth when there’s a low-level failure talking to the collector.

10
CONFIG_NAMESPACE =

The namespace and keys of config values

'request_sampler'
SAMPLE_RATE_KEY =
"#{CONFIG_NAMESPACE}.sample_rate_ms".to_sym
ENABLED_KEY =
"#{CONFIG_NAMESPACE}.enabled".to_sym
SAMPLE_TYPE =

The type field of the sample

'Transaction'
TYPE_KEY =

Strings for static keys of the sample structure

'type'
TIMESTAMP_KEY =
'timestamp'
NAME_KEY =
'name'
DURATION_KEY =
'duration'

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Coerce

#float, #int, #log_failure, #string

Constructor Details

#initialize(event_listener) ⇒ RequestSampler

Create a new RequestSampler that will keep samples added to it every sample_rate_ms milliseconds.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/new_relic/agent/request_sampler.rb', line 47

def initialize( event_listener )
  super()

  @enabled               = false
  @sample_rate_ms        = DEFAULT_SAMPLE_RATE_MS
  @normal_sample_rate_ms = @sample_rate_ms
  @last_sample_taken     = nil
  @samples               = []
  @notified_max_samples  = false

  @sample_count          = 0
  @request_count         = 0
  @sample_count_total    = 0
  @request_count_total   = 0

  event_listener.subscribe( :transaction_finished, &method(:on_transaction_finished) )
  self.register_config_callbacks
end

Instance Attribute Details

#last_sample_takenObject (readonly)

The Time when the last sample was kept



80
81
82
# File 'lib/new_relic/agent/request_sampler.rb', line 80

def last_sample_taken
  @last_sample_taken
end

#normal_sample_rate_msObject (readonly)

The sample rate, in milliseconds between samples, that the sampler uses under normal circumstances



73
74
75
# File 'lib/new_relic/agent/request_sampler.rb', line 73

def normal_sample_rate_ms
  @normal_sample_rate_ms
end

#sample_rate_msObject (readonly)

The current sample rate, which may be different from the #normal_sample_rate_ms if the sampler is throttled.



77
78
79
# File 'lib/new_relic/agent/request_sampler.rb', line 77

def sample_rate_ms
  @sample_rate_ms
end

Instance Method Details

#<<(sample) ⇒ Object

Add a datapoint to the sampler if a sample is due. The sample should be of the form:

{
  'name' => '<transaction/metric name>',
  'duration' => <duration in seconds as a Float>,
}

This method is synchronized.



173
174
175
176
177
178
179
180
# File 'lib/new_relic/agent/request_sampler.rb', line 173

def <<( sample )
  self.synchronize do
    @request_count += 1
    self.add_sample( sample ) if should_sample?
  end

  return self
end

#on_transaction_finished(metric, duration, options = {}) ⇒ Object

Event handler for the :transaction_finished event.



149
150
151
152
153
154
155
# File 'lib/new_relic/agent/request_sampler.rb', line 149

def on_transaction_finished( metric, duration, options={} )
  return unless @enabled
  self << {
    NAME_KEY     => string(metric),
    DURATION_KEY => float(duration)
  }.merge(options)
end

#record_sampling_rate(request_count, sample_count) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/new_relic/agent/request_sampler.rb', line 109

def record_sampling_rate(request_count, sample_count)
  @request_count_total += request_count
  @sample_count_total  += sample_count

  NewRelic::Agent.logger.debug("Sampled #{sample_count} / #{request_count} (%.1f %%) requests this cycle" % (sample_count.to_f / request_count * 100.0))
  NewRelic::Agent.logger.debug("Sampled #{@sample_count_total} / #{@request_count_total} (%.1f %%) requests since startup" % (@sample_count_total.to_f / @request_count_total * 100.0))

  engine = NewRelic::Agent.instance.stats_engine
  engine.record_supportability_metric_count("RequestSampler/requests", request_count)
  engine.record_supportability_metric_count("RequestSampler/samples", sample_count)
end

#register_config_callbacksObject

:group: Event handlers



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/new_relic/agent/request_sampler.rb', line 125

def register_config_callbacks
  NewRelic::Agent.config.register_callback(SAMPLE_RATE_KEY) do |rate_ms|
    NewRelic::Agent.logger.debug "RequestSampler sample rate to %dms" % [ rate_ms ]

    if rate_ms < MIN_SAMPLE_RATE_MS
      NewRelic::Agent.logger.warn "  limiting RequestSampler frequency to %dms (was %dms)" %
        [ MIN_SAMPLE_RATE_MS, rate_ms ]
      rate_ms = MIN_SAMPLE_RATE_MS
    end

    @normal_sample_rate_ms = rate_ms
    @max_samples = calculate_max_samples
    NewRelic::Agent.logger.debug "RequestSampler max_samples set to #{@max_samples}"
    self.reset
  end

  NewRelic::Agent.config.register_callback(ENABLED_KEY) do |enabled|
    NewRelic::Agent.logger.info "%sabling the Request Sampler." % [ enabled ? 'En' : 'Dis' ]
    @enabled = enabled
  end
end

#resetObject

Clear any existing samples and reset the last sample time. (Synchronized)



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/new_relic/agent/request_sampler.rb', line 90

def reset
  NewRelic::Agent.logger.debug "Resetting RequestSampler"

  request_count = nil
  sample_count = nil

  self.synchronize do
    sample_count = @samples.size
    request_count = @request_count
    @request_count = 0
    @samples.clear
    @sample_rate_ms = @normal_sample_rate_ms
    @last_sample_taken = Time.now
    @notified_max_samples = false
  end

  record_sampling_rate(request_count, sample_count) if @enabled
end

#samplesObject

Fetch a copy of the sampler’s gathered samples. (Synchronized)



84
85
86
# File 'lib/new_relic/agent/request_sampler.rb', line 84

def samples
  return self.synchronize { @samples.dup }
end

#throttle(resolution = nil) ⇒ Object

Downsample the gathered data and reduce the sampling rate to conserve memory. The amount the sampler is throttled is proportional to resolution, which defaults to the number of normal report periods which have elapsed. E.g., if three sessions with the agent have failed, the sampler downsamples its data to include one out of even three samples, and only samples a third of the time it normally would.

This method is synchronized.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/new_relic/agent/request_sampler.rb', line 190

def throttle( resolution=nil )

  # Only throttle if the sampler was running
  self.synchronize do
    if @last_sample_taken && !@samples.empty?
      resolution ||= (Time.now - @last_sample_taken) / DEFAULT_REPORT_FREQUENCY
      @sample_rate_ms = @normal_sample_rate_ms * resolution
      self.downsample_data( resolution )
    end
  end

  if resolution
    NewRelic::Agent.logger.debug "  resolution is now: %d -> 1 sample every %dms" %
      [ resolution, @sample_rate_ms ]
  end
end