Skip to content

Commit

Permalink
Merge pull request #3734 from DataDog/anmarchenko/metrics_management
Browse files Browse the repository at this point in the history
[SDTEST-409] Telemetry metrics data model
  • Loading branch information
anmarchenko committed Jun 26, 2024
2 parents 154f72e + a7974f2 commit 7064144
Show file tree
Hide file tree
Showing 6 changed files with 610 additions and 0 deletions.
27 changes: 27 additions & 0 deletions lib/datadog/core/telemetry/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,33 @@ def type
'app-closing'
end
end

# Telemetry class for the 'generate-metrics' event
class GenerateMetrics < Base
def type
'generate-metrics'
end

def initialize(namespace, metric_series)
super()
@namespace = namespace
@metric_series = metric_series
end

def payload(_)
{
namespace: @namespace,
series: @metric_series.map(&:to_h)
}
end
end

# Telemetry class for the 'distributions' event
class Distributions < GenerateMetrics
def type
'distributions'
end
end
end
end
end
Expand Down
149 changes: 149 additions & 0 deletions lib/datadog/core/telemetry/metric.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# frozen_string_literal: true

module Datadog
module Core
module Telemetry
# Telemetry metrics data model (internal Datadog metrics for client libraries)
module Metric
def self.metric_id(type, name, tags = [])
"#{type}::#{name}::#{tags.join(',')}"
end

# Base class for all metric types
class Base
attr_reader :name, :tags, :values, :common, :interval

# @param name [String] metric name
# @param tags [Array<String>|Hash{String=>String}] metric tags as hash of array of "tag:val" strings
# @param common [Boolean] true if the metric is common for all languages, false for Ruby-specific metric
# @param interval [Integer] metrics aggregation interval in seconds
def initialize(name, tags: {}, common: true, interval: nil)
@name = name
@values = []
@tags = tags_to_array(tags)
@common = common
@interval = interval
end

def track(value); end

def type; end

def to_h
# @type var res: Hash[Symbol, untyped]
res = {
metric: name,
points: values,
type: type,
tags: tags,
common: common
}
res[:interval] = interval if interval
res
end

private

def tags_to_array(tags)
return tags if tags.is_a?(Array)

tags.map { |k, v| "#{k}:#{v}" }
end
end

# Count metric adds up all the submitted values in a time interval. This would be suitable for a
# metric tracking the number of website hits, for instance.
class Count < Base
TYPE = 'count'

def type
TYPE
end

def inc(value = 1)
track(value)
end

def dec(value = 1)
track(-value)
end

def track(value)
if values.empty?
values << [Time.now.to_i, value]
else
values[0][0] = Time.now.to_i
values[0][1] += value
end
end
end

# A gauge type takes the last value reported during the interval. This type would make sense for tracking RAM or
# CPU usage, where taking the last value provides a representative picture of the host’s behavior during the time
# interval.
class Gauge < Base
TYPE = 'gauge'

def type
TYPE
end

def track(value)
if values.empty?
values << [Time.now.to_i, value]
else
values[0][0] = Time.now.to_i
values[0][1] = value
end
end
end

# The rate type takes the count and divides it by the length of the time interval. This is useful if you’re
# interested in the number of hits per second.
class Rate < Base
TYPE = 'rate'

def initialize(name, tags: {}, common: true, interval: nil)
super

@value = 0.0
end

def type
TYPE
end

def track(value = 1.0)
@value += value

rate = interval ? @value / interval : 0.0
@values = [[Time.now.to_i, rate]]
end
end

# Distribution metric represents the global statistical distribution of a set of values.
class Distribution < Base
TYPE = 'distributions'

def type
TYPE
end

def track(value)
values << value
end

# distribution metric data does not have type field
def to_h
{
metric: name,
points: values,
tags: tags,
common: common
}
end
end
end
end
end
end
10 changes: 10 additions & 0 deletions sig/datadog/core/telemetry/event.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ module Datadog

class AppClosing < Base
end

class GenerateMetrics < Base
@namespace: String
@metric_series: Enumerable[Datadog::Core::Telemetry::Metric::Base]

def initialize: (String namespace, Enumerable[Datadog::Core::Telemetry::Metric::Base] metric_series) -> void
end

class Distributions < GenerateMetrics
end
end
end
end
Expand Down
102 changes: 102 additions & 0 deletions sig/datadog/core/telemetry/metric.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module Datadog
module Core
module Telemetry
module Metric
type metric_type = "count" | "gauge" | "rate" | "distributions" | nil

type input_value = Integer | Float

type metric_value = Array[input_value]
type distribution_value = input_value

type tags_input = ::Hash[String, String] | Array[String]

def self.metric_id: (metric_type type, String name, ?Array[String] tags) -> ::String

class Base
@name: String

@values: Array[untyped]

@tags: Array[String]

@common: bool

@interval: Integer?

attr_reader name: String

attr_reader tags: Array[String]

attr_reader values: Array[untyped]

attr_reader common: bool

attr_reader interval: Integer?

def initialize: (String name, ?tags: tags_input, ?common: bool, ?interval: Integer?) -> void

def track: (Numeric value) -> void

def type: () -> metric_type

def to_h: () -> Hash[Symbol, untyped]

private

def tags_to_array: (tags_input tags) -> Array[String]
end

class Count < Base
TYPE: "count"

@values: Array[metric_value]
attr_reader values: Array[metric_value]

def type: () -> "count"

def inc: (?::Integer value) -> void

def dec: (?::Integer value) -> void

def track: (Integer value) -> void
end

class Gauge < Base
TYPE: "gauge"

def type: () -> "gauge"

def track: (input_value value) -> void
end

class Rate < Base
@value: Float

@values: Array[metric_value]
attr_reader values: Array[metric_value]

TYPE: "rate"

def initialize: (String name, ?tags: tags_input, ?common: bool, ?interval: Integer?) -> void

def type: () -> "rate"

def track: (?::Float value) -> void
end

class Distribution < Base
TYPE: "distributions"

@values: Array[distribution_value]
attr_reader values: Array[distribution_value]

def type: () -> "distributions"

def track: (input_value value) -> void
def to_h: () -> { metric: String, points: Array[distribution_value], tags: Array[String], common: bool }
end
end
end
end
end
45 changes: 45 additions & 0 deletions spec/datadog/core/telemetry/event_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'spec_helper'

require 'datadog/core/telemetry/event'
require 'datadog/core/telemetry/metric'

RSpec.describe Datadog::Core::Telemetry::Event do
let(:id) { double('seq_id') }
Expand Down Expand Up @@ -207,4 +208,48 @@ def contain_configuration(*array)
is_expected.to eq({})
end
end

context 'GenerateMetrics' do
let(:event) { described_class::GenerateMetrics.new(namespace, metrics) }

let(:namespace) { 'general' }
let(:metric_name) { 'request_count' }
let(:metric) do
Datadog::Core::Telemetry::Metric::Count.new(metric_name, tags: { status: '200' })
end
let(:metrics) { [metric] }

let(:expected_metric_series) { [metric.to_h] }

it do
is_expected.to eq(
{
namespace: namespace,
series: expected_metric_series
}
)
end
end

context 'Distributions' do
let(:event) { described_class::Distributions.new(namespace, metrics) }

let(:namespace) { 'general' }
let(:metric_name) { 'request_duration' }
let(:metric) do
Datadog::Core::Telemetry::Metric::Distribution.new(metric_name, tags: { status: '200' })
end
let(:metrics) { [metric] }

let(:expected_metric_series) { [metric.to_h] }

it do
is_expected.to eq(
{
namespace: namespace,
series: expected_metric_series
}
)
end
end
end
Loading

0 comments on commit 7064144

Please sign in to comment.