Skip to content

Commit

Permalink
Allow stats to be tagged with max_enforcements tag (#31)
Browse files Browse the repository at this point in the history
* make tests pass

* write more failing tests

* fix tests

* Bump version

* add code ownership config to rspec

* tighten sig for Tag property
  • Loading branch information
Alex Evanczuk authored May 3, 2023
1 parent 1931335 commit 5509292
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 16 deletions.
16 changes: 8 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
pack_stats (0.0.4)
pack_stats (0.0.5)
code_ownership
code_teams
dogapi
Expand All @@ -13,24 +13,24 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.4.2)
activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
code_ownership (1.31.0)
code_ownership (1.32.17)
code_teams (~> 1.0)
packs
sorbet-runtime
code_teams (1.0.0)
code_teams (1.0.1)
sorbet-runtime
coderay (1.1.3)
concurrent-ruby (1.2.2)
diff-lcs (1.5.0)
dogapi (1.45.0)
multi_json
i18n (1.12.0)
i18n (1.13.0)
concurrent-ruby (~> 1.0)
json (2.6.3)
method_source (1.0.0)
Expand All @@ -40,7 +40,7 @@ GEM
packs (0.0.6)
sorbet-runtime
parallel (1.22.1)
parse_packwerk (0.18.0)
parse_packwerk (0.19.1)
sorbet-runtime
parser (3.1.1.0)
ast (~> 2.4.1)
Expand All @@ -54,7 +54,7 @@ GEM
parser (>= 2.6.4.0)
sorbet-runtime (>= 0.5.9204)
unparser
regexp_parser (2.7.0)
regexp_parser (2.8.0)
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
Expand All @@ -81,7 +81,7 @@ GEM
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
rubocop-packs (0.0.35)
rubocop-packs (0.0.38)
activesupport
packs
parse_packwerk
Expand Down
21 changes: 17 additions & 4 deletions lib/pack_stats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ module PackStats
# See note on get_metrics
packaged_source_code_locations: T.nilable(T::Array[Pathname]),
# See note on get_metrics
use_gusto_legacy_names: T::Boolean
use_gusto_legacy_names: T::Boolean,
# See note on get_metrics
max_enforcements_tag_value: T::Boolean
).void
end
def self.report_to_datadog!(
Expand All @@ -50,14 +52,16 @@ def self.report_to_datadog!(
report_time: Time.now, # rubocop:disable Rails/TimeZone
verbose: false,
packaged_source_code_locations: [],
use_gusto_legacy_names: false
use_gusto_legacy_names: false,
max_enforcements_tag_value: false
)

all_metrics = self.get_metrics(
source_code_pathnames: source_code_pathnames,
componentized_source_code_locations: componentized_source_code_locations,
app_name: app_name,
use_gusto_legacy_names: use_gusto_legacy_names,
max_enforcements_tag_value: max_enforcements_tag_value,
)

# This helps us debug what metrics are being sent
Expand Down Expand Up @@ -89,16 +93,25 @@ def self.report_to_datadog!(
# Gusto uses this to preserve historical trends in Dashboards as the names of
# things changed, but new dashboards can use names that better match current tooling conventions.
# The behavior of setting this parameter to true might change without warning
use_gusto_legacy_names: T::Boolean
use_gusto_legacy_names: T::Boolean,
# You can set this to `true` to tag all metrics with `max_enforcements:true`.
# This is useful if you want to submit two sets of metrics:
# Once with the violation counts as configured in the app
# Another time with the violation counts after turning on all enforcements and running `bin/packwerk update`.
max_enforcements_tag_value: T::Boolean
).returns(T::Array[GaugeMetric])
end
def self.get_metrics(
source_code_pathnames:,
componentized_source_code_locations:,
app_name:,
packaged_source_code_locations: [],
use_gusto_legacy_names: false
use_gusto_legacy_names: false,
max_enforcements_tag_value: false
)

GaugeMetric.set_max_enforcements_tag(max_enforcements_tag_value)

all_metrics = Private::DatadogReporter.get_metrics(
source_code_files: source_code_files(
source_code_pathnames: source_code_pathnames,
Expand Down
14 changes: 13 additions & 1 deletion lib/pack_stats/gauge_metric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ def self.for(metric_name, count, tags)
raise StandardError.new("Metrics names must not exceed 200 characters: #{name}") # rubocop:disable Style/RaiseArgs
end

all_tags = [*tags, max_enforcements_tag]
new(
name: name,
count: count,
tags: tags
tags: all_tags
)
end

Expand All @@ -35,5 +36,16 @@ def ==(other)
other.count == self.count &&
other.tags == self.tags
end

sig { params(tag_value: T::Boolean).void }
def self.set_max_enforcements_tag(tag_value)
@max_enforcements_tag = T.let(@max_enforcements_tag, T.nilable(Tag))
@max_enforcements_tag = Tag.new(key: 'max_enforcements', value: tag_value ? 'true' : 'false')
end

sig { returns(Tag) }
def self.max_enforcements_tag
@max_enforcements_tag || Tag.new(key: 'max_enforcements', value: 'false')
end
end
end
2 changes: 1 addition & 1 deletion pack_stats.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = 'pack_stats'
spec.version = '0.0.4'
spec.version = '0.0.5'
spec.authors = ['Gusto Engineers']
spec.email = ['dev@gusto.com']

Expand Down
3 changes: 2 additions & 1 deletion spec/pack_stats_legacy_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping
'modularization.some_metric',
[[report_time, 11]],
type: 'gauge',
tags: ['mykey:myvalue', 'myotherkey:myothervalue']
tags: ['mykey:myvalue', 'myotherkey:myothervalue', 'max_enforcements:false']
)
report_to_datadog
end
Expand All @@ -64,6 +64,7 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping
- packs/*
- packs/*/*
YML
write_file('config/code_ownership.yml', YAML.dump({}))
end

context 'in empty app' do
Expand Down
68 changes: 67 additions & 1 deletion spec/pack_stats_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping
RSpec.describe PackStats do
before do
ParsePackwerk.bust_cache!
write_file('config/code_ownership.yml', YAML.dump({}))
end

describe 'PackStats.report_to_datadog!' do
Expand Down Expand Up @@ -33,7 +34,7 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping
'modularization.some_metric',
[[report_time, 11]],
type: 'gauge',
tags: ['mykey:myvalue', 'myotherkey:myothervalue']
tags: ['mykey:myvalue', 'myotherkey:myothervalue', 'max_enforcements:false']
)
report_to_datadog
end
Expand Down Expand Up @@ -1073,6 +1074,71 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping
expect(metrics).to include_metric GaugeMetric.for('all_packages.rubocops.packs_rootnamespaceispackname.exclusions.count', 6, Tags.for(['app:MyApp']))
end
end

context 'when getting metrics after turning all protections to max' do
let(:subject) do
PackStats.get_metrics(
app_name: 'MyApp',
source_code_pathnames: Pathname.glob('**/**.rb'),
componentized_source_code_locations: [Pathname.new('components')],
max_enforcements_tag_value: true
)
end

include_context 'only one team'

before do
write_file('empty_file.rb')
write_file('packs/only_package/app/some_package_file.rb')
write_file('packs/only_package/package.yml', <<~CONTENTS)
enforce_dependencies: true
enforce_privacy: true
CONTENTS

write_file('packs/only_package/spec/some_package_file_spec.rb')
end

it 'emits the right metrics' do
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.component_files.by_team', count: 0, tags: Tags.for(['team:Some team', 'app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.packaged_files.by_team', count: 2, tags: Tags.for(['team:Some team', 'app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_files.by_team', count: 3, tags: Tags.for(['team:Some team', 'app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.component_files.totals', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.packaged_files.totals', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_files.totals', count: 3, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.dependencies.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.dependency_violations.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.privacy_violations.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.enforcing_dependencies.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.enforcing_privacy.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.with_violations.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.packwerk_checkers.enforce_dependencies.strict.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.packwerk_checkers.enforce_dependencies.true.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.packwerk_checkers.enforce_privacy.strict.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.packwerk_checkers.enforce_privacy.true.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.rubocops.packs_typedpublicapis.strict.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.rubocops.packs_typedpublicapis.true.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.rubocops.packs_rootnamespaceispackname.strict.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.package_based_file_ownership.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.using_public_directory.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.dependency_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.privacy_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.outbound_dependency_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.inbound_dependency_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.outbound_privacy_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.inbound_privacy_violations.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.outbound_explicit_dependencies.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.inbound_explicit_dependencies.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.using_public_directory.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.all_files.count', count: 2, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.all_packages.public_files.count', count: 0, tags: Tags.for(['app:MyApp', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_team.using_public_directory.count', count: 0, tags: Tags.for(['app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_team.all_files.count', count: 2, tags: Tags.for(['app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_team.public_files.count', count: 0, tags: Tags.for(['app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.using_public_directory.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.all_files.count', count: 2, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true']))
expect(metrics).to include_metric GaugeMetric.new(name: 'modularization.by_package.public_files.count', count: 0, tags: Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown', 'max_enforcements:true'])) end
end
end
end
end

0 comments on commit 5509292

Please sign in to comment.