-
Notifications
You must be signed in to change notification settings - Fork 377
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module Datadog | ||
module AppSec | ||
module Metrics | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |