spec/elastic_apm/metrics_spec.rb (154 lines of code) (raw):
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# frozen_string_literal: true
require 'spec_helper'
module ElasticAPM
RSpec.describe Metrics do
let(:config) { Config.new }
let(:callback) { ->(_) {} }
describe 'life cycle' do
subject { described_class.new(config, &callback) }
describe '#start' do
before { subject.start }
it { should be_running }
end
describe '#stop' do
it 'stops the collector' do
subject.start
subject.stop
expect(subject).to_not be_running
end
end
describe 'stop and start again' do
before do
subject.start
subject.stop
end
after { subject.stop }
it 'restarts collecting metrics' do
subject.start
expect(subject.instance_variable_get(:@timer_task)).to be_running
end
end
context 'when disabled' do
let(:config) { Config.new metrics_interval: '0s' }
it "doesn't start" do
subject.start
expect(subject).to_not be_running
subject.stop
expect(subject).to_not be_running
end
end
end
describe '.new' do
subject { described_class.new(config, &callback) }
it { should be_a Metrics::Registry }
end
describe '.collect' do
subject { described_class.new(config, &callback) }
after { subject.stop }
it 'samples all samplers' do
subject.define_sets
subject.sets.each_value do |sampler|
expect(sampler).to receive(:collect).at_least(:once)
end
subject.start
subject.collect
end
end
describe '.collect_and_send' do
context 'when samples' do
subject { described_class.new(config, &callback) }
let(:callback) { ->(_) {} }
before { subject.start }
after { subject.stop }
it 'calls callback' do
subject.collect_and_send # disable on unsupported jruby
next unless subject.sets.values.select { |s| s.metrics.any? }.any?
expect(callback).to receive(:call).with(Metricset).at_least(1)
subject.collect_and_send
end
end
context 'when no samples' do
it 'calls callback' do
callback = ->(_) {}
subject = described_class.new(config, &callback)
subject.define_sets
subject.sets.each_value do |sampler|
expect(sampler).to receive(:collect).at_least(:once) { nil }
end
subject.start
expect(callback).to_not receive(:call)
subject.collect_and_send
subject.stop
end
end
context 'when recording is false' do
subject { described_class.new(config, &callback) }
let(:callback) { ->(_) {} }
let(:config) { Config.new(recording: false) }
it 'does not collect metrics' do
expect(subject).to_not receive(:collect)
subject.collect_and_send
end
end
end
xcontext 'thread safety stress test', :mock_intake do
it 'handles multiple threads reporting and collecting at the same time' do
thread_count = 1_000
names = Array.new(5).map do
SecureRandom.hex(5)
end
with_agent(metrics_interval: '100ms') do
metrics = ElasticAPM.agent.metrics
Array.new(thread_count).map do
Thread.new do
metrics.get(:breakdown).counter('a').inc!
metrics.get(:breakdown).counter('b').inc!
metrics.get(:breakdown).counter('c').dec!
metrics.get(:transaction).counter(
:a_with_tags,
tags: { 'name': names.sample },
reset_on_collect: true
).inc!
metrics.get(:transaction).counter(
:b_with_tags,
tags: { 'name': names.sample },
reset_on_collect: true
).inc!
metrics.get(:transaction).counter(
:c_with_tags,
tags: { 'name': names.sample },
reset_on_collect: true
).inc!
sleep 0.15 # longer than metrics_interval
end
end.each(&:join)
end
samples =
@mock_intake.metricsets.each_with_object({}) do |set, result|
result.merge! set['samples']
end
expect(samples['a']['value']).to eq(thread_count)
expect(samples['b']['value']).to eq(thread_count)
expect(samples['c']['value']).to eq(0 - thread_count)
expect(samples['a_with_tags']['value']).to be > 0
expect(samples['b_with_tags']['value']).to be > 0
expect(samples['c_with_tags']['value']).to be > 0
end
end
describe '#handle_forking!' do
subject { described_class.new(config, &callback) }
before do
subject.handle_forking!
end
after { subject.stop }
it 'restarts the TimerTask' do
expect(subject.instance_variable_get(:@timer_task)).to be_running
end
context 'when not collecting metrics' do
let(:config) { Config.new(metrics_interval: 0) }
it 'does not create a TimerTask' do
expect(subject.instance_variable_get(:@timer_task)).to be nil
end
end
end
end
end