Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
31443a0
Initial sketch at open metrics generation
jeremiahishere Jun 2, 2023
39538d8
Hacked together histograms
jeremiahishere Jun 6, 2023
b86b437
WIP...working on timestamps and figuring out how to pass them through…
Mycobee Jun 7, 2023
15583a3
Added gauge tests. They sort of work
jeremiahishere Jun 7, 2023
4586498
Fixed docstring escaping
jeremiahishere Jun 7, 2023
88be230
Added toy feature to store timestamp in label and counter tests to pr…
jeremiahishere Jun 7, 2023
802c4f8
More requirementsg
jeremiahishere Jun 7, 2023
4be5c1a
Rewrite some tests
jeremiahishere Jun 8, 2023
705d1c0
Exemplars working on counters
jeremiahishere Jun 8, 2023
d247afc
Added _created and _total support to counters
jeremiahishere Jun 9, 2023
52009d8
Gauge exemplars
jeremiahishere Jun 9, 2023
9194d41
Basic tests
jeremiahishere Jun 9, 2023
14d11aa
Setup summaries without exemplars
jeremiahishere Jun 13, 2023
2cb8006
Added exemplar support to summaries but fought a losing battle
jeremiahishere Jun 14, 2023
3c134e6
Fixing weird file load issues and require problems
jeremiahishere Jun 14, 2023
172ee63
Turn on open metrics
jeremiahishere Jun 15, 2023
31a388b
Enabled open metrics and found some more broken stuff but I need to g…
jeremiahishere Jun 15, 2023
452dff6
unfarting the code
jeremiahishere Jun 15, 2023
641d976
added an exemplar empty method
jeremiahishere Jun 15, 2023
5865d95
Remove potential issue with counters ending in _total and turn on ope…
jeremiahishere Jun 16, 2023
3121673
Fixed content type, fixed eof, removed some hard coded text/plain tests
jeremiahishere Jun 16, 2023
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
4 changes: 2 additions & 2 deletions lib/prometheus/client/counter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ def type
:counter
end

def increment(by: 1, labels: {})
def increment(by: 1, labels: {}, exemplar: nil)
raise ArgumentError, 'increment must be a non-negative number' if by < 0

label_set = label_set_for(labels)
@store.increment(labels: label_set, by: by)
@store.increment(labels: label_set, by: by, exemplar: exemplar)
end
end
end
Expand Down
8 changes: 4 additions & 4 deletions lib/prometheus/client/data_stores/direct_file_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ def synchronize
end
end

def set(labels:, val:)
def set(labels:, val:, exemplar: nil)
in_process_sync do
internal_store.write_value(store_key(labels), val.to_f)
end
end

def increment(labels:, by: 1)
def increment(labels:, by: 1, exemplar: nil)
if @values_aggregation_mode == DirectFileStore::MOST_RECENT
raise InvalidStoreSettingsError,
"The :most_recent aggregation does not support the use of increment"\
Expand All @@ -118,13 +118,13 @@ def increment(labels:, by: 1)
end
end

def get(labels:)
def get(labels:, with_exemplars: false)
in_process_sync do
internal_store.read_value(store_key(labels))
end
end

def all_values
def all_values(with_exemplars: false)
stores_data = Hash.new{ |hash, key| hash[key] = [] }

# There's no need to call `synchronize` here. We're opening a second handle to
Expand Down
36 changes: 27 additions & 9 deletions lib/prometheus/client/data_stores/single_threaded.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
require "prometheus/client/value_with_exemplars"
require "prometheus/client/exemplar_collection"
require "prometheus/client/exemplar"

module Prometheus
module Client
module DataStores
Expand Down Expand Up @@ -25,27 +29,41 @@ def validate_metric_settings(metric_settings:)

class MetricStore
def initialize
@internal_store = Hash.new { |hash, key| hash[key] = 0.0 }
@internal_store = Hash.new { |hash, key| hash[key] = Prometheus::Client::ValueWithExemplars.new }
end

def synchronize
yield
end

def set(labels:, val:)
@internal_store[labels] = val.to_f
def set(labels:, val:, exemplar: nil)
@internal_store[labels].set(value: val, exemplar: exemplar)
end

def increment(labels:, by: 1)
@internal_store[labels] += by
def increment(labels:, by: 1, exemplar: nil)
@internal_store[labels].increment(by: by, exemplar: exemplar)
end

def get(labels:)
@internal_store[labels]
def get(labels:, with_exemplars: false)
if with_exemplars
@internal_store[labels]
else
@internal_store[labels].value
end
end

def all_values
@internal_store.dup
def all_values(with_exemplars: false)
if with_exemplars
@internal_store.dup
else
# this mess is just for backwards compatibility
output = Hash.new { |hash, key| hash[key] = 0.0 }
@internal_store.keys.each do |k|
output[k] = @internal_store[k].value
end

output
end
end
end

Expand Down
39 changes: 30 additions & 9 deletions lib/prometheus/client/data_stores/synchronized.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# no idea why this is reuired but giving up for now
require 'prometheus/client/exemplar'
require 'prometheus/client/exemplar_collection'
require 'prometheus/client/value_with_exemplars'

module Prometheus
module Client
module DataStores
Expand All @@ -24,34 +29,50 @@ def validate_metric_settings(metric_settings:)

class MetricStore
def initialize
@internal_store = Hash.new { |hash, key| hash[key] = 0.0 }
@internal_store = Hash.new { |hash, key| hash[key] = Prometheus::Client::ValueWithExemplars.new }
@lock = Monitor.new
end

def synchronize
@lock.synchronize { yield }
end

def set(labels:, val:)
def set(labels:, val:, exemplar: nil)
synchronize do
@internal_store[labels] = val.to_f
@internal_store[labels].set(value: val, exemplar: exemplar)
end
end

def increment(labels:, by: 1)
def increment(labels:, by: 1, exemplar: nil)
synchronize do
@internal_store[labels] += by
@internal_store[labels].increment(by: by, exemplar: exemplar)
end
end

def get(labels:)
def get(labels:, with_exemplars: false)
synchronize do
@internal_store[labels]
if with_exemplars
@internal_store[labels]
else
@internal_store[labels].value
end
end
end

def all_values
synchronize { @internal_store.dup }
def all_values(with_exemplars: false)
synchronize do
if with_exemplars
@internal_store.dup
else
# this mess is just for backwards compatibility
output = Hash.new { |hash, key| hash[key] = 0.0 }
@internal_store.keys.each do |k|
output[k] = @internal_store[k].value
end

output
end
end
end
end

Expand Down
34 changes: 34 additions & 0 deletions lib/prometheus/client/exemplar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# encoding: UTF-8

module Prometheus
module Client

# Store a snapshot of metric data including an extra set of kv pairs for a specific moment in
# time and specific moment of code execution.
#
# Value is expected to be set after the exemplar is initialized. Exemplars without a value will
# not be exported.
class Exemplar
# The kv pairs that make up the unique information in the exemplar.
#
# We generally store trace ids here
attr_reader :labels

# The time the exemplar was recorded
attr_reader :timestamp

# The value of the metric at the time the exemplar was recorded
attr_writer :value
attr_reader :value

def initialize(labels: {}, timestamp: nil)
@labels = labels
@timestamp = timestamp || Time.now.to_i
end

def empty?
value.nil?
end
end
end
end
68 changes: 68 additions & 0 deletions lib/prometheus/client/exemplar_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# encoding: UTF-8

module Prometheus
module Client
class ExemplarCollection
include Enumerable

# store the exemplars attached to a metric + label set
def initialize
@collection = {}

# theoretical way to avoid causing a major memory leak
#
# This could store two minutes of requests with 2 requests per second with these settings.
# I am not clear on the situation where this wouldn't be enough but I am sure I will find it
@max_timestamps = 120
@max_exemplars = 240
@exemplar_count = 0
end

def add(exemplar)
cleanup_if_necessary
@collection[exemplar.timestamp] ||= []
@collection[exemplar.timestamp] << exemplar

exemplar
end

def most_recent
@collection[last_key]&.last
end

def last
most_recent
end

def each
@collection.keys.sort.each do |key|
@collection[key].each do |exemplar|
yield(exemplar)
end
end
end

def size
@exemplar_count
end

private

def cleanup_if_necessary
if @exemplar_count >= @max_exemplars || @collection.keys.size >= @max_timestamps
@exemplar_count -= @collection.delete(first_key)&.size
end

@exemplar_count += 1
end

def first_key
@collection.keys.sort&.first
end

def last_key
@collection.keys.sort&.last
end
end
end
end
Loading