Skip to content

Commit

Permalink
Add AppSec::Metrics::Collector
Browse files Browse the repository at this point in the history
  • Loading branch information
Strech committed Jan 16, 2025
1 parent 4bdcc0f commit 8d5d286
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 0 deletions.
11 changes: 11 additions & 0 deletions lib/datadog/appsec/metrics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Datadog
module AppSec
# This namespace contains classes related to metrics collection and exportation.
module Metrics
end
end
end

require_relative 'metrics/collector'
38 changes: 38 additions & 0 deletions lib/datadog/appsec/metrics/collector.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Datadog
module AppSec
module Metrics
# A class responsible for collecting WAF and RASP call metrics.
class Collector
WAFMetrics = Struct.new(:timeouts, :duration_ns, :duration_ext_ns, keyword_init: true)
RASPMetrics = Struct.new(:rule_evals, :timeouts, :duration_ns, :duration_ext_ns, keyword_init: true)

attr_reader :waf, :rasp

def initialize
@mutex = Mutex.new
@waf = WAFMetrics.new(timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
@rasp = RASPMetrics.new(rule_evals: 0, timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
end

def record_waf(result)
@mutex.synchronize do
@waf.timeouts += 1 if result.timeout?
@waf.duration_ns += result.duration_ns
@waf.duration_ext_ns += result.duration_ext_ns
end
end

def record_rasp(result)
@mutex.synchronize do
@rasp.rule_evals += 1
@rasp.timeouts = 1 if result.timeout?
@rasp.duration_ns += result.duration_ns
@rasp.duration_ext_ns += result.duration_ext_ns
end
end
end
end
end
end
6 changes: 6 additions & 0 deletions sig/datadog/appsec/metrics.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Datadog
module AppSec
module Metrics
end
end
end
46 changes: 46 additions & 0 deletions sig/datadog/appsec/metrics/collector.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module Datadog
module AppSec
module Metrics
# A class responsible for collecting WAF and RASP call metrics.
class Collector
class WAFMetrics < ::Struct[untyped]
attr_accessor timeouts: ::Integer

attr_accessor duration_ns: ::Integer

attr_accessor duration_ext_ns: ::Integer

def self.new: (timeouts: ::Integer, duration_ns: ::Integer, duration_ext_ns: ::Integer) -> void
end

class RASPMetrics < ::Struct[untyped]
attr_accessor rule_evals: ::Integer

attr_accessor timeouts: ::Integer

attr_accessor duration_ns: ::Integer

attr_accessor duration_ext_ns: ::Integer

def self.new: (rule_evals: ::Integer, timeouts: ::Integer, duration_ns: ::Integer, duration_ext_ns: ::Integer) -> void
end

@mutex: Mutex

@waf: WAFMetrics

@rasp: RASPMetrics

attr_reader waf: WAFMetrics

attr_reader rasp: RASPMetrics

def initialize: () -> void

def record_waf: (SecurityEngine::result result) -> void

def record_rasp: (SecurityEngine::result result) -> void
end
end
end
end
170 changes: 170 additions & 0 deletions spec/datadog/appsec/metrics/collector_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# frozen_string_literal: true

require 'datadog/appsec/spec_helper'
require 'datadog/appsec/metrics/collector'

RSpec.describe Datadog::AppSec::Metrics::Collector do
subject(:metrics) { described_class.new }

describe '#record_waf' do
context 'when no results were recorded' do
it 'contains all metrics in initial state' do
expect(metrics.waf.timeouts).to eq(0)
expect(metrics.waf.duration_ns).to eq(0)
expect(metrics.waf.duration_ext_ns).to eq(0)
end
end

context 'when a single result was recorded' do
before { metrics.record_waf(result) }

let(:result) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 100, duration_ext_ns: 200
)
end

it 'contains metrics of a single result' do
expect(metrics.waf.timeouts).to eq(0)
expect(metrics.waf.duration_ns).to eq(100)
expect(metrics.waf.duration_ext_ns).to eq(200)
end
end

context 'when multiple results were recorded' do
before do
metrics.record_waf(result_1)
metrics.record_waf(result_2)
end

let(:result_1) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 100, duration_ext_ns: 200
)
end

let(:result_2) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 1000, duration_ext_ns: 1200
)
end

it 'contains cumulative metrics of both results' do
expect(metrics.waf.timeouts).to eq(0)
expect(metrics.waf.duration_ns).to eq(1100)
expect(metrics.waf.duration_ext_ns).to eq(1400)
end
end

context 'when multiple recorded results contain timeout' do
before do
metrics.record_waf(result_1)
metrics.record_waf(result_2)
metrics.record_waf(result_3)
end

let(:result_1) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 100, duration_ext_ns: 500
)
end

let(:result_2) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 400, duration_ext_ns: 1200
)
end

let(:result_3) { Datadog::AppSec::SecurityEngine::Result::Error.new(duration_ext_ns: 300) }

it 'accumulates timeouts in addition to other metics' do
expect(metrics.waf.timeouts).to eq(2)
expect(metrics.waf.duration_ns).to eq(500)
expect(metrics.waf.duration_ext_ns).to eq(2000)
end
end
end

describe '#record_rasp' do
context 'when no results were recorded' do
it 'contains all metrics in initial state' do
expect(metrics.rasp.timeouts).to eq(0)
expect(metrics.rasp.rule_evals).to eq(0)
expect(metrics.rasp.duration_ns).to eq(0)
expect(metrics.rasp.duration_ext_ns).to eq(0)
end
end

context 'when a single result was recorded' do
before { metrics.record_rasp(result) }

let(:result) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 100, duration_ext_ns: 200
)
end

it 'contains metrics of a single result' do
expect(metrics.rasp.timeouts).to eq(0)
expect(metrics.rasp.rule_evals).to eq(1)
expect(metrics.rasp.duration_ns).to eq(100)
expect(metrics.rasp.duration_ext_ns).to eq(200)
end
end

context 'when multiple calls were made' do
before do
metrics.record_rasp(result_1)
metrics.record_rasp(result_2)
end

let(:result_1) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 100, duration_ext_ns: 200
)
end

let(:result_2) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: false, duration_ns: 1000, duration_ext_ns: 1200
)
end

it 'contains cumulative metrics of both results' do
expect(metrics.rasp.timeouts).to eq(0)
expect(metrics.rasp.rule_evals).to eq(2)
expect(metrics.rasp.duration_ns).to eq(1100)
expect(metrics.rasp.duration_ext_ns).to eq(1400)
end
end

context 'when multiple calls contain timeout' do
before do
metrics.record_rasp(result_1)
metrics.record_rasp(result_2)
metrics.record_rasp(result_3)
end

let(:result_1) do
Datadog::AppSec::SecurityEngine::Result::Ok.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 100, duration_ext_ns: 500
)
end

let(:result_2) do
Datadog::AppSec::SecurityEngine::Result::Match.new(
events: [], actions: {}, derivatives: {}, timeout: true, duration_ns: 400, duration_ext_ns: 1200
)
end

let(:result_3) { Datadog::AppSec::SecurityEngine::Result::Error.new(duration_ext_ns: 300) }

it 'accumulates timeouts in addition to other metics' do
expect(metrics.rasp.timeouts).to eq(1)
expect(metrics.rasp.rule_evals).to eq(3)
expect(metrics.rasp.duration_ns).to eq(500)
expect(metrics.rasp.duration_ext_ns).to eq(2000)
end
end
end
end

0 comments on commit 8d5d286

Please sign in to comment.