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 +4 -4
- data/.rubocop.yml +0 -3
- data/README.md +173 -81
- data/Rakefile +5 -1
- data/docs/img/dss-analyze.png +0 -0
- data/examples/schema/example_marathon.rb +4 -0
- data/examples/schema/web_schema.rb +32 -0
- data/lib/datadog/statsd/schema/analyzer.rb +142 -62
- data/lib/datadog/statsd/schema/commands/analyze.rb +16 -5
- data/lib/datadog/statsd/schema/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 252a3536a931724dfbebc6755ddecbb01206794037ccd7d0b3bd91c150b66f6d
|
4
|
+
data.tar.gz: 4a1eafd370b04d5087c72de5694fc01cd0689df6c8b7a280992bae822e3373cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ceac7e83b4e9678c4751981cc51c8d526e34b9f959971535157536938b6842757a35e7023c859d64df135b4623cf0668048f0a87f55b3a8cb21784eb39ebadb
|
7
|
+
data.tar.gz: 93046e96ae66782fb616d618f8a6c2c6e571eee6f779f1834ee434251a4f9680c47df8c3be75769a9550f0bf001aa699d00040c6923e0d6186299b10f197e024
|
data/.rubocop.yml
CHANGED
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
|
+

|
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
|
-
###
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
328
|
+
emitter.increment('order_processing.success')
|
377
329
|
rescue => error
|
378
|
-
|
379
|
-
|
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
|
-
|
384
|
-
|
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
|
396
|
-
bundle exec
|
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
|
-
|
30
|
+
RuboCop::RakeTask.new(:rubocop)
|
31
|
+
|
32
|
+
# Define the default task to include both
|
33
|
+
task default: %i[spec rubocop]
|
Binary file
|
@@ -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(
|
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
|
-
|
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
|
82
|
-
metrics_analysis
|
83
|
-
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
|
-
#
|
331
|
-
#
|
332
|
-
#
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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
|
-
|
343
|
-
|
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
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
output
|
357
|
-
|
358
|
-
output.
|
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
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
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
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
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
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
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
|
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]).
|
47
|
+
color: options[:color]).tap do |analyzer|
|
48
|
+
puts analyzer.render
|
49
|
+
end.analyze
|
43
50
|
end
|
44
51
|
|
45
|
-
def warn(
|
46
|
-
self.class.stderr.puts
|
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
|
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.
|
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-
|
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
|