datadog-statsd-schema 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8ecccf4d0e55d1139596eca6ecfc41cf25a4bcd637aebdb4cb963150aa10ce7
4
- data.tar.gz: 002504ebc4073bb7b34a5d979cb49f89d86e8eff5356594046bd3e1e223c8ed6
3
+ metadata.gz: 252a3536a931724dfbebc6755ddecbb01206794037ccd7d0b3bd91c150b66f6d
4
+ data.tar.gz: 4a1eafd370b04d5087c72de5694fc01cd0689df6c8b7a280992bae822e3373cc
5
5
  SHA512:
6
- metadata.gz: b8f866b339297e39236ba1e546f50aff716f46e74d6856386a5de95ace0dfea5e62f060aec4e5e61320dd352a5d0bf971c38be4c7e9ee15ff17ea4b0083ad389
7
- data.tar.gz: ff341e1836eebc261125c6cbd42103e359286bcb1bed0ed47fc96df9c2727f8873f6cd31e6e3591ca991b4b977fb9bdae42d5ef228eeebda7847da9129635ea9
6
+ metadata.gz: 1ceac7e83b4e9678c4751981cc51c8d526e34b9f959971535157536938b6842757a35e7023c859d64df135b4623cf0668048f0a87f55b3a8cb21784eb39ebadb
7
+ data.tar.gz: 93046e96ae66782fb616d618f8a6c2c6e571eee6f779f1834ee434251a4f9680c47df8c3be75769a9550f0bf001aa699d00040c6923e0d6186299b10f197e024
data/.rubocop.yml CHANGED
@@ -25,6 +25,3 @@ Layout/LineLength:
25
25
 
26
26
  Style/Documentation:
27
27
  Enabled: false
28
-
29
- Metrics/MethodLength:
30
- Enabled: false
data/README.md CHANGED
@@ -141,63 +141,12 @@ end
141
141
  Analyze the schema to understand metric costs:
142
142
 
143
143
  ```bash
144
- dss analyze --file metrics_schema.rb
144
+ dss analyze --file metrics_schema.rb --color
145
145
  ```
146
146
 
147
147
  **Output:**
148
- ```
149
- ┌──────────────────────────────────────────────────────────────────────────────────────────────┐
150
- │ Detailed Metric Analysis: │
151
- └──────────────────────────────────────────────────────────────────────────────────────────────┘
152
-
153
- • gauge('web.memory_usage')
154
- Expanded names:
155
- • web.memory_usage.count
156
- • web.memory_usage.min
157
- • web.memory_usage.max
158
- • web.memory_usage.sum
159
- • web.memory_usage.avg
160
-
161
- Unique tags: 2
162
- Total tag values: 6
163
- Possible combinations: 45
164
-
165
- ──────────────────────────────────────────────────────────────────────────────────────────────
166
-
167
- • counter('web.requests.total')
168
-
169
- Unique tags: 3
170
- Total tag values: 9
171
- Possible combinations: 27
172
-
173
- ──────────────────────────────────────────────────────────────────────────────────────────────
174
-
175
- • distribution('web.requests.duration')
176
- Expanded names:
177
- • web.requests.duration.count
178
- • web.requests.duration.min
179
- • web.requests.duration.max
180
- • web.requests.duration.sum
181
- • web.requests.duration.avg
182
- • web.requests.duration.p50
183
- • web.requests.duration.p75
184
- • web.requests.duration.p90
185
- • web.requests.duration.p95
186
- • web.requests.duration.p99
187
-
188
- Unique tags: 3
189
- Total tag values: 9
190
- Possible combinations: 270
191
-
192
- ──────────────────────────────────────────────────────────────────────────────────────────────
193
- ┌──────────────────────────────────────────────────────────────────────────────────────────────┐
194
- │ Schema Analysis Results: │
195
- │ SUMMARY │
196
- └──────────────────────────────────────────────────────────────────────────────────────────────┘
197
-
198
- Total unique metrics: 16
199
- Total possible custom metric combinations: 342
200
- ```
148
+
149
+ ![output](./docs/img/dss-analyze.png)
201
150
 
202
151
  This analysis shows that your schema will generate **342 custom metrics** across **16 unique metric names**. Understanding this before deployment helps prevent unexpected Datadog billing surprises.
203
152
 
@@ -318,6 +267,9 @@ tag :user_tier, values: %w[free premium enterprise]
318
267
  tag :user_cohort, values: %w[new_user returning_user power_user]
319
268
  ```
320
269
 
270
+ > [!CAUTION]
271
+ > Be mindful of the number of tags and tag values your schema allows.
272
+
321
273
  ### Schema Validation
322
274
 
323
275
  Always validate your schema before deployment:
@@ -333,67 +285,207 @@ end
333
285
 
334
286
  ## Integration Examples
335
287
 
336
- ### Rails Integration
288
+ ### Sidekiq Job Monitoring
289
+
290
+ Imagine that we are building a Rails application, and we prefer to create our own tracking of the jobs performed, failed, succeeded, as well as their duration.
291
+
292
+ > [!TIP]
293
+ > A very similar approach would work for tracking eg. requests coming to the `ApplicationController` subclasses.
294
+
295
+ First, let's initialize the schema from a file (we'll dive into the schema a bit later):
337
296
 
338
297
  ```ruby
339
298
  # config/initializers/datadog_statsd.rb
340
- schema = Datadog::Statsd::Schema.load_file(Rails.root.join('config/metrics_schema.rb'))
299
+ SIDEKIQ_SCHEMA = Datadog::Statsd::Schema.load_file(Rails.root.join('config/metrics/sidekiq.rb'))
341
300
 
342
301
  Datadog::Statsd::Schema.configure do |config|
343
302
  config.statsd = Datadog::Statsd.new
344
- config.schema = schema
303
+ config.schema = SIDEKIQ_SCHEMA
345
304
  config.tags = {
346
305
  environment: Rails.env,
347
- service: 'my-rails-app'
306
+ service: 'my-rails-app',
307
+ version: ENV['DEPLOY_SHA']
348
308
  }
349
309
  end
350
-
351
- # app/controllers/application_controller.rb
352
- class ApplicationController < ActionController::Base
353
- before_action :setup_metrics
354
-
355
- private
356
-
357
- def setup_metrics
358
- @metrics = Datadog::Statsd::Emitter.new(
359
- validation_mode: Rails.env.production? ? :disabled : :warn
360
- )
361
- end
362
- end
363
310
  ```
364
311
 
365
- ### Background Job Monitoring
312
+ #### Adding Statsd Tracking to a Worker
313
+
314
+ In this example, a job monitors itself by submitting a relevant statsd metrics:
366
315
 
367
316
  ```ruby
368
317
  class OrderProcessingJob
318
+ QUEUE = 'orders'.freeze
319
+
320
+ include Sidekiq::Job
321
+ sidekiq_options queue: QUEUE
322
+
369
323
  def perform(order_id)
370
- metrics = Datadog::Statsd::Emitter.new
371
-
372
324
  start_time = Time.current
373
325
 
374
326
  begin
375
327
  process_order(order_id)
376
- metrics.increment('jobs.order_processing.success', tags: { queue: 'orders' })
328
+ emitter.increment('order_processing.success')
377
329
  rescue => error
378
- metrics.increment('jobs.order_processing.failure',
379
- tags: { queue: 'orders', error_type: error.class.name })
330
+ emitter.increment(
331
+ 'order_processing.failure',
332
+ tags: { error_type: error.class.name }
333
+ )
380
334
  raise
381
335
  ensure
382
336
  duration = Time.current - start_time
383
- metrics.distribution('jobs.order_processing.duration', duration * 1000,
384
- tags: { queue: 'orders' })
337
+ emitter.distribution(
338
+ 'jobs.order_processing.duration',
339
+ duration * 1000
340
+ )
341
+ end
342
+ end
343
+
344
+ # Create an instance of an Emitter equipped with our metric
345
+ # prefix and the tags.
346
+ def emitter
347
+ @emitter ||= Datadog::Statsd::Emitter.new(
348
+ self,
349
+ metric: 'job',
350
+ tags: { queue: QUEUE }
351
+ )
352
+ end
353
+ end
354
+ ```
355
+
356
+ The above Emitter will generate the following metrics:
357
+
358
+ * `job.order_processing.success` (counter)
359
+ * `job.order_processing.failure` (counter)
360
+ * `job.order_processing.duration.count`
361
+ * `job.order_processing.duration.min`
362
+ * `job.order_processing.duration.max`
363
+ * `job.order_processing.duration.sum`
364
+ * `job.order_processing.duration.avg`
365
+
366
+
367
+ However, you can see that doing this in each job is not practical. Therefore the first question that should be on our mind is — how do we make it so that this behavior would automatically apply to any Job we create?
368
+
369
+ #### Tracking All Sidekiq Workers At Once
370
+
371
+ The qustion postulated above is — can we come up with a class design patter that allows us to write this code once and forget about it?
372
+
373
+ **Let's take Ruby's metaprogramming for a spin.**
374
+
375
+ One of the most flexible methods to add functionality to all jobs is to create a module that the job classes include *instead of* the implementation-specific `Sidekiq::Job`.
376
+
377
+ So let's create our own module, let's call it `BackgroundWorker`, that we'll include into our classes instead. Once created, we'd like for our job classes to look like this:
378
+
379
+ ```ruby
380
+ class OrderProcessingJob
381
+ include BackgroundWorker
382
+ sidekiq_options queue: 'orders'
383
+
384
+ def perform(order_id)
385
+ # perform the work for the given order ID
386
+ end
387
+ end
388
+ ```
389
+
390
+ So our module, when included, should:
391
+
392
+ * include `Sidekiq::Job` as well
393
+ * define the `emitter` method so that it's available to all Job instances
394
+ * wrap `perform` method in the exception handling block that emits corresponding metrics as in our example before.
395
+
396
+ The only tricky part here is the last one: wrapping `perform` method in some shared code. This used to require "monkey patching", but no more. These days Ruby gives us an all-powerful `prepend` method that does exactly what we need.
397
+
398
+ Final adjustment we'd like to make is the metric naming.
399
+
400
+ While the metrics such as:
401
+
402
+ * `job.order_processing.success` (counter)
403
+ * `job.order_processing.failure` (counter)
404
+
405
+ are easy to understand, the question begs: do we really need to insert the job's class name into the metric name? Or — is there a better way?
406
+
407
+ The truth is — there is! Why create 7 unique metrics **per job** when we can simply submit the same metrics for all jobs, tagged with our job's class name as an "emitter" source?
408
+
409
+ #### Module for including into Backround Worker
410
+
411
+ So, without furether ado, here we go:
412
+
413
+ ```ruby
414
+ module BackgroundWorker
415
+ class << self
416
+ def included(klass)
417
+ klass.include(Sidekiq::Job)
418
+ klass.prepend(InstanceMethods)
419
+ end
420
+
421
+ module InstanceMethods
422
+ def perform(...)
423
+ start_time = Time.current
424
+ tags = {}
425
+ error = nil
426
+
427
+ begin
428
+ super(...)
429
+ emitter.increment("success")
430
+ rescue => error
431
+ tags.merge!({ error_type: error.class.name } )
432
+ emitter.increment("failure", tags:)
433
+ raise
434
+ ensure
435
+ duration = Time.current - start_time
436
+ emitter.distribution(
437
+ "duration",
438
+ duration * 1000,
439
+ tags:
440
+ )
441
+ end
442
+ end
443
+
444
+ def emitter
445
+ @emitter ||= Datadog::Statsd::Emitter.new(
446
+ metric: 'sidekiq.job',
447
+ tags: {
448
+ queue: sidekiq_options[:queue],
449
+ job: self.class.name
450
+ }
451
+ )
452
+ end
385
453
  end
386
454
  end
387
455
  end
388
456
  ```
389
457
 
458
+ > [!TIP]
459
+ > In a nutshell, we created a reusable module that, upon being included into any Job class, provides reliable tracking of job successes and failures, as well as the duration. The duration can be graphed for all successful jobs by ensuring the tag `error_type` does not exist.
460
+
461
+ So, the above strategy will generate the following metrics **FOR ALL** jobs:
462
+
463
+ The above Emitter will generate the following metrics:
464
+
465
+ * `sidekiq.job.success` (counter)
466
+ * `sidekiq.job.failure` (counter)
467
+ * `sidekiq.job.duration.count`
468
+ * `sidekiq.job.duration.min`
469
+ * `sidekiq.job.duration.max`
470
+ * `sidekiq.job.duration.sum`
471
+ * `sidekiq.job.duration.avg`
472
+
473
+ that will be tagged with the following tags:
474
+
475
+ * `queue: ... `
476
+ * `job: { 'OrderProcessingJob' | ... }`
477
+ * `environment: { "production" | "staging" | "development" }`
478
+ * `service: 'my-rails-app'`
479
+ * `version: { "git-sha" }`
480
+
390
481
  ## Development
391
482
 
392
483
  After checking out the repo, run:
393
484
 
394
485
  ```bash
395
- bin/setup # Install dependencies
396
- bundle exec rake spec # Run tests
486
+ bin/setup # Install dependencies
487
+ bundle exec rspec # Run Specs
488
+ bundle exec rubocop # Run Rubocop
397
489
  ```
398
490
 
399
491
  To install this gem onto your local machine:
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
5
6
  require "timeout"
6
7
 
7
8
  def shell(*args)
@@ -26,4 +27,7 @@ task build: :permissions
26
27
 
27
28
  RSpec::Core::RakeTask.new(:spec)
28
29
 
29
- task default: :spec
30
+ RuboCop::RakeTask.new(:rubocop)
31
+
32
+ # Define the default task to include both
33
+ task default: %i[spec rubocop]
Binary file
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vim: ft=ruby
4
+
1
5
  namespace "marathon" do
2
6
  tags do
3
7
  tag :course, values: %w[sf-marathon new-york austin]
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # vim: ft=ruby
4
+
5
+ namespace :web do
6
+ tags do
7
+ tag :environment, values: %w[production staging development]
8
+ tag :service, values: %w[api web worker]
9
+ tag :region, values: %w[us-east-1 us-west-2 eu-west-1]
10
+ end
11
+
12
+ namespace :requests do
13
+ metrics do
14
+ counter :total do
15
+ description "Total HTTP requests"
16
+ tags required: %i[environment service], allowed: %i[region]
17
+ end
18
+
19
+ distribution :duration do
20
+ description "Request processing time in milliseconds"
21
+ inherit_tags "web.requests.total"
22
+ end
23
+ end
24
+ end
25
+
26
+ metrics do
27
+ gauge :memory_usage do
28
+ description "Memory usage in bytes"
29
+ tags required: %i[environment], allowed: %i[service]
30
+ end
31
+ end
32
+ end
@@ -2,6 +2,10 @@
2
2
 
3
3
  require "stringio"
4
4
  require "colored2"
5
+ require "forwardable"
6
+ require "json"
7
+ require "yaml"
8
+
5
9
  module Datadog
6
10
  class Statsd
7
11
  module Schema
@@ -49,41 +53,66 @@ module Datadog
49
53
  histogram: %w[count min max sum avg]
50
54
  }.freeze
51
55
 
52
- attr_reader :schemas, :stdout, :stderr, :color
56
+ attr_reader :schemas, :stdout, :stderr, :color, :format, :analysis_result
57
+
58
+ SUPPORTED_FORMATS = %i[text json yaml].freeze
53
59
 
54
60
  # Initialize analyzer with schema(s)
55
61
  # @param schemas [Datadog::Statsd::Schema::Namespace, Array<Datadog::Statsd::Schema::Namespace>]
56
62
  # Single schema or array of schemas to analyze
57
- def initialize(schemas, stdout: $stdout, stderr: $stderr, color: true)
63
+ def initialize(
64
+ schemas,
65
+ stdout: $stdout,
66
+ stderr: $stderr,
67
+ color: true,
68
+ format: SUPPORTED_FORMATS.first
69
+ )
58
70
  @schemas = Array(schemas)
59
71
  @stdout = stdout
60
72
  @stderr = stderr
61
73
  @color = color
74
+ @format = format.to_sym
75
+
76
+ raise ArgumentError, "Unsupported format: #{format}. Supported formats are: #{SUPPORTED_FORMATS.join(", ")}" unless SUPPORTED_FORMATS.include?(format)
77
+
62
78
  if color
63
79
  Colored2.enable!
64
80
  else
65
81
  Colored2.disable!
66
82
  end
83
+
84
+ @analysis_result = analyze
67
85
  end
68
86
 
69
87
  # Perform comprehensive analysis of the schemas
70
88
  # @return [AnalysisResult] Complete analysis results
71
89
  def analyze
72
90
  all_metrics = collect_all_metrics
73
- metrics_analysis = analyze_metrics(all_metrics)
74
-
75
- total_unique_metrics = metrics_analysis.sum { |analysis| analysis.expanded_names.size }
76
- total_possible_custom_metrics = metrics_analysis.sum(&:total_combinations)
91
+ metrics_analysis = analyze_metrics(all_metrics).map(&:to_h)
77
92
 
78
- stdout.puts format_analysis_output(metrics_analysis, total_unique_metrics, total_possible_custom_metrics)
93
+ total_unique_metrics = metrics_analysis.sum { |analysis| analysis[:expanded_names].size }
94
+ total_possible_custom_metrics = metrics_analysis.sum { |e| e[:total_combinations] }
79
95
 
80
96
  AnalysisResult.new(
81
- total_unique_metrics: total_unique_metrics,
82
- metrics_analysis: metrics_analysis,
83
- total_possible_custom_metrics: total_possible_custom_metrics
97
+ total_unique_metrics:,
98
+ metrics_analysis:,
99
+ total_possible_custom_metrics:
84
100
  )
85
101
  end
86
102
 
103
+ def render
104
+ case format
105
+ when :text
106
+ TextFormatter.new(stdout:, stderr:, color:, analysis_result:).render
107
+ when :json
108
+ JSONFormatter.new(stdout:, stderr:, color:, analysis_result:).render
109
+ when :yaml
110
+ YAMLFormatter.new(stdout:, stderr:, color:, analysis_result:).render
111
+ else
112
+ raise ArgumentError, "Unsupported format: #{format}. Supported formats are: #{SUPPORTED_FORMATS.join(", ")}"
113
+ end
114
+ end
115
+
87
116
  private
88
117
 
89
118
  # Collect all metrics from all schemas with their context
@@ -327,69 +356,120 @@ module Datadog
327
356
  end
328
357
  end
329
358
 
330
- # Format the analysis output for display
331
- # @param metrics_analysis [Array<MetricAnalysis>] All metric analyses
332
- # @param total_unique_metrics [Integer] Total unique metrics
333
- # @param total_possible_custom_metrics [Integer] Total possible combinations
334
- # @return [String] Formatted output
335
- def format_analysis_output(
336
- metrics_analysis,
337
- total_unique_metrics,
338
- total_possible_custom_metrics
339
- )
340
- output = StringIO.new
359
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
360
+ # Formatter classes
361
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
362
+ class BaseFormatter
363
+ attr_reader :stdout, :stderr, :color,
364
+ :analysis_result,
365
+ :total_unique_metrics,
366
+ :metrics_analysis,
367
+ :total_possible_custom_metrics
368
+
369
+ def initialize(stdout:, stderr:, color:, analysis_result:)
370
+ @stdout = stdout
371
+ @stderr = stderr
372
+ @color = color
373
+ @analysis_result = analysis_result.to_h.transform_values { |v| v.is_a?(Data) ? v.to_h : v }
374
+ @total_unique_metrics = @analysis_result[:total_unique_metrics]
375
+ @metrics_analysis = @analysis_result[:metrics_analysis]
376
+ @total_possible_custom_metrics = @analysis_result[:total_possible_custom_metrics]
377
+ end
341
378
 
342
- format_metric_analysis_header(output)
343
- metrics_analysis.each do |analysis|
344
- output.puts
345
- format_metric_analysis(output, analysis)
346
- line(output, placement: :flat)
379
+ def render
380
+ raise NotImplementedError, "Subclasses must implement this method"
347
381
  end
348
- summary(output, total_unique_metrics, total_possible_custom_metrics)
349
- output.string
350
382
  end
351
383
 
352
- def line(output, placement: :top)
353
- if placement == :top
354
- output.puts "┌──────────────────────────────────────────────────────────────────────────────────────────────┐".white.on.blue
355
- elsif placement == :bottom
356
- output.puts "└──────────────────────────────────────────────────────────────────────────────────────────────┘".white.on.blue
357
- elsif placement == :middle
358
- output.puts "├──────────────────────────────────────────────────────────────────────────────────────────────┤".white.on.blue
359
- elsif placement == :flat
360
- output.puts " ──────────────────────────────────────────────────────────────────────────────────────────────".white.bold
384
+ class TextFormatter < BaseFormatter
385
+ attr_reader :output
386
+
387
+ def render
388
+ @output = StringIO.new
389
+ format_analysis_output
390
+ @output.string
361
391
  end
362
- end
363
392
 
364
- def summary(output, total_unique_metrics, total_possible_custom_metrics)
365
- line(output)
366
- output.puts "│ Schema Analysis Results: │".yellow.bold.on.blue
367
- output.puts "│ SUMMARY │".white.on.blue
368
- line(output, placement: :bottom)
369
- output.puts
370
- output.puts " Total unique metrics: #{("%3d" % total_unique_metrics).bold.green}"
371
- output.puts "Total possible custom metric combinations: #{("%3d" % total_possible_custom_metrics).bold.green}"
372
- output.puts
393
+ private
394
+
395
+ # Format the analysis output for display
396
+ def format_analysis_output
397
+ format_metric_analysis_header(output)
398
+
399
+ analysis_result[:metrics_analysis].each do |analysis|
400
+ output.puts
401
+ format_metric_analysis(output, analysis)
402
+ line(output, placement: :flat)
403
+ end
404
+
405
+ summary(
406
+ output,
407
+ analysis_result[:total_unique_metrics],
408
+ analysis_result[:total_possible_custom_metrics]
409
+ )
410
+ output.string
411
+ end
412
+
413
+ def line(output, placement: :top)
414
+ if placement == :top
415
+ output.puts "┌──────────────────────────────────────────────────────────────────────────────────────────────┐".white.on.blue
416
+ elsif placement == :bottom
417
+ output.puts "└──────────────────────────────────────────────────────────────────────────────────────────────┘".white.on.blue
418
+ elsif placement == :middle
419
+ output.puts "├──────────────────────────────────────────────────────────────────────────────────────────────┤".white.on.blue
420
+ elsif placement == :flat
421
+ output.puts " ──────────────────────────────────────────────────────────────────────────────────────────────".white.bold
422
+ end
423
+ end
424
+
425
+ def summary(output, total_unique_metrics, total_possible_custom_metrics)
426
+ line(output)
427
+ output.puts "│ Schema Analysis Results: │".yellow.bold.on.blue
428
+ output.puts "│ SUMMARY │".white.on.blue
429
+ line(output, placement: :bottom)
430
+ output.puts
431
+ output.puts " Total unique metrics: #{("%3d" % total_unique_metrics).bold.green}"
432
+ output.puts "Total possible custom metric combinations: #{("%3d" % total_possible_custom_metrics).bold.green}"
433
+ output.puts
434
+ end
435
+
436
+ def format_metric_analysis_header(output)
437
+ line(output)
438
+ output.puts "│ Detailed Metric Analysis: │".white.on.blue
439
+ line(output, placement: :bottom)
440
+ end
441
+
442
+ def format_metric_analysis(output, analysis)
443
+ output.puts " • #{analysis[:metric_type].to_s.cyan}('#{analysis[:metric_name].yellow.bold}')"
444
+ if analysis[:expanded_names].size > 1
445
+ output.puts " Expanded names:"
446
+ output.print " • ".yellow
447
+ output.puts analysis[:expanded_names].join("\n • ").yellow
448
+ end
449
+ output.puts
450
+ output.puts " Unique tags: #{("%3d" % analysis[:unique_tags]).bold.green}"
451
+ output.puts " Total tag values: #{("%3d" % analysis[:unique_tag_values]).bold.green}"
452
+ output.puts " Possible combinations: #{("%3d" % analysis[:total_combinations]).bold.green}"
453
+ output.puts
454
+ end
373
455
  end
374
456
 
375
- def format_metric_analysis_header(output)
376
- line(output)
377
- output.puts "│ Detailed Metric Analysis: │".white.on.blue
378
- line(output, placement: :bottom)
457
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
458
+ # JSON Formatter classes
459
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
460
+ class JSONFormatter < BaseFormatter
461
+ def render
462
+ JSON.pretty_generate(analysis_result.to_h)
463
+ end
379
464
  end
380
465
 
381
- def format_metric_analysis(output, analysis)
382
- output.puts " • #{analysis.metric_type.to_s.cyan}('#{analysis.metric_name.yellow.bold}')"
383
- if analysis.expanded_names.size > 1
384
- output.puts " Expanded names:"
385
- output.print " • ".yellow
386
- output.puts analysis.expanded_names.join("\n • ").yellow
466
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
467
+ # YAML Formatter classes
468
+ # ——————————————————————————————————————————————————————————————————————————————————————————————————————————————-
469
+ class YAMLFormatter < BaseFormatter
470
+ def render
471
+ YAML.dump(analysis_result.to_h)
387
472
  end
388
- output.puts
389
- output.puts " Unique tags: #{("%3d" % analysis.unique_tags).bold.green}"
390
- output.puts " Total tag values: #{("%3d" % analysis.unique_tag_values).bold.green}"
391
- output.puts " Possible combinations: #{("%3d" % analysis.total_combinations).bold.green}"
392
- output.puts
393
473
  end
394
474
  end
395
475
  end
@@ -18,7 +18,8 @@ module Datadog
18
18
  desc "Analyze a schema file for metrics and validation"
19
19
 
20
20
  option :file, aliases: %w[-f], type: :string, required: true, desc: "Path to the schema file to analyze"
21
- option :color, aliases: %w[-c], type: :boolean, required: false, desc: "Enable/Disable color output"
21
+ option :color, aliases: %w[-c], type: :boolean, required: false, desc: "Enable/Disable color output", default: true
22
+ option :format, aliases: %w[-o], type: :string, required: false, desc: "Output format, supports: json, yaml, text", default: :text
22
23
 
23
24
  # @description Analyze a schema file for metrics and validation
24
25
  # @param options [Hash] The options for the command
@@ -34,16 +35,26 @@ module Datadog
34
35
  exit 1
35
36
  end
36
37
 
37
- warn "Analyzing schema file: #{file}, color: #{options[:color]}"
38
+ warn "Analyzing Schema File:"
39
+ warn " • file #{file.green}"
40
+ warn " • color: #{(options[:color] ? "enabled" : "disabled").yellow}"
41
+ warn " • formar #{options[:format].to_s.red}"
38
42
  @schema = ::Datadog::Statsd::Schema.load_file(file)
39
43
  ::Datadog::Statsd::Schema::Analyzer.new([@schema],
44
+ format: (options[:format] || :text).to_sym,
40
45
  stdout: self.class.stdout,
41
46
  stderr: self.class.stderr,
42
- color: options[:color]).analyze
47
+ color: options[:color]).tap do |analyzer|
48
+ puts analyzer.render
49
+ end.analyze
43
50
  end
44
51
 
45
- def warn(message)
46
- self.class.stderr.puts message
52
+ def warn(...)
53
+ self.class.stderr.puts(...)
54
+ end
55
+
56
+ def puts(...)
57
+ self.class.stdout.puts(...)
47
58
  end
48
59
  end
49
60
  end
@@ -8,7 +8,7 @@ module Datadog
8
8
  module Schema
9
9
  # Current version of the datadog-statsd-schema gem
10
10
  # @return [String] The semantic version string
11
- VERSION = "0.2.0"
11
+ VERSION = "0.2.1"
12
12
  end
13
13
  end
14
14
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog-statsd-schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-07 00:00:00.000000000 Z
10
+ date: 2025-06-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -171,8 +171,10 @@ files:
171
171
  - LICENSE.txt
172
172
  - README.md
173
173
  - Rakefile
174
+ - docs/img/dss-analyze.png
174
175
  - examples/README.md
175
176
  - examples/schema/example_marathon.rb
177
+ - examples/schema/web_schema.rb
176
178
  - examples/schema_emitter.png
177
179
  - examples/schema_emitter.rb
178
180
  - examples/shared.rb