Class: Tasker::Telemetry::MetricTypes::Histogram
- Inherits:
-
Object
- Object
- Tasker::Telemetry::MetricTypes::Histogram
- Defined in:
- lib/tasker/telemetry/metric_types.rb
Overview
Histogram represents a metric that samples observations and counts them in buckets
Histograms are thread-safe and provide statistical analysis of observed values. They track count, sum, and bucket distributions for duration and size metrics.
Constant Summary collapse
- DEFAULT_BUCKETS =
Default bucket boundaries for duration metrics (in seconds)
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0].freeze
Instance Attribute Summary collapse
-
#bucket_boundaries ⇒ Array<Numeric>
readonly
The histogram bucket boundaries.
-
#created_at ⇒ Time
readonly
When this metric was first created.
-
#labels ⇒ Hash
readonly
The metric labels for dimensional data.
-
#name ⇒ String
readonly
The metric name.
Instance Method Summary collapse
-
#average ⇒ Float
Calculate the average of observed values.
-
#buckets ⇒ Hash
Get the current bucket counts.
-
#count ⇒ Integer
Get the total number of observations.
-
#description ⇒ String
Get a description of this metric for debugging.
-
#initialize(name, labels: {}, buckets: DEFAULT_BUCKETS) ⇒ Histogram
constructor
Initialize a new histogram metric.
-
#observe(value) ⇒ Numeric
Observe a value and update histogram buckets.
-
#reset! ⇒ void
Reset the histogram (primarily for testing).
-
#sum ⇒ Numeric
Get the sum of all observed values.
-
#to_h ⇒ Hash
Get a hash representation of this metric.
Constructor Details
#initialize(name, labels: {}, buckets: DEFAULT_BUCKETS) ⇒ Histogram
Initialize a new histogram metric
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/tasker/telemetry/metric_types.rb', line 287 def initialize(name, labels: {}, buckets: DEFAULT_BUCKETS) raise ArgumentError, 'Metric name cannot be nil or empty' if name.nil? || name.strip.empty? raise ArgumentError, 'Buckets must be an array' unless buckets.is_a?(Array) raise ArgumentError, 'Buckets cannot be empty' if buckets.empty? @name = name.to_s.freeze @labels = labels.freeze @bucket_boundaries = buckets.sort.freeze @created_at = Time.current.freeze # Thread-safe counters for each bucket + infinity bucket @bucket_counts = (@bucket_boundaries + [Float::INFINITY]).map do |_| Concurrent::AtomicFixnum.new(0) end @count = Concurrent::AtomicFixnum.new(0) @sum = Concurrent::AtomicReference.new(0.0) end |
Instance Attribute Details
#bucket_boundaries ⇒ Array<Numeric> (readonly)
Returns The histogram bucket boundaries.
273 274 275 |
# File 'lib/tasker/telemetry/metric_types.rb', line 273 def bucket_boundaries @bucket_boundaries end |
#created_at ⇒ Time (readonly)
Returns When this metric was first created.
276 277 278 |
# File 'lib/tasker/telemetry/metric_types.rb', line 276 def created_at @created_at end |
#labels ⇒ Hash (readonly)
Returns The metric labels for dimensional data.
270 271 272 |
# File 'lib/tasker/telemetry/metric_types.rb', line 270 def labels @labels end |
#name ⇒ String (readonly)
Returns The metric name.
267 268 269 |
# File 'lib/tasker/telemetry/metric_types.rb', line 267 def name @name end |
Instance Method Details
#average ⇒ Float
Calculate the average of observed values
358 359 360 361 362 363 |
# File 'lib/tasker/telemetry/metric_types.rb', line 358 def average current_count = count return 0.0 if current_count.zero? sum.to_f / current_count end |
#buckets ⇒ Hash
Get the current bucket counts
346 347 348 349 350 351 352 353 |
# File 'lib/tasker/telemetry/metric_types.rb', line 346 def buckets result = {} @bucket_boundaries.each_with_index do |boundary, index| result[boundary] = @bucket_counts[index].value end result[Float::INFINITY] = @bucket_counts.last.value result end |
#count ⇒ Integer
Get the total number of observations
332 333 334 |
# File 'lib/tasker/telemetry/metric_types.rb', line 332 def count @count.value end |
#description ⇒ String
Get a description of this metric for debugging
393 394 395 396 |
# File 'lib/tasker/telemetry/metric_types.rb', line 393 def description label_str = labels.empty? ? '' : format_labels_for_description(labels) "#{name}#{label_str} = #{count} observations, avg: #{average.round(3)} (histogram)" end |
#observe(value) ⇒ Numeric
Observe a value and update histogram buckets
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/tasker/telemetry/metric_types.rb', line 311 def observe(value) raise ArgumentError, "Observed value must be numeric, got: #{value.class}" unless value.is_a?(Numeric) # Update count and sum atomically @count.increment @sum.update { |current| current + value } # Increment all buckets where value <= boundary (cumulative histogram) @bucket_boundaries.each_with_index do |boundary, index| @bucket_counts[index].increment if value <= boundary end # Always increment the infinity bucket (total count) @bucket_counts.last.increment value end |
#reset! ⇒ void
This method returns an undefined value.
Reset the histogram (primarily for testing)
384 385 386 387 388 |
# File 'lib/tasker/telemetry/metric_types.rb', line 384 def reset! @count.value = 0 @sum.set(0.0) @bucket_counts.each { |bucket| bucket.value = 0 } end |
#sum ⇒ Numeric
Get the sum of all observed values
339 340 341 |
# File 'lib/tasker/telemetry/metric_types.rb', line 339 def sum @sum.get end |
#to_h ⇒ Hash
Get a hash representation of this metric
368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/tasker/telemetry/metric_types.rb', line 368 def to_h { name: name, labels: labels, buckets: buckets, count: count, sum: sum, average: average, type: :histogram, created_at: created_at } end |