Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDTEST-409] Telemetry metrics data model #3734

Merged
merged 6 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading