pack_stats 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -20
- data/lib/pack_stats/private/datadog_reporter.rb +1 -0
- data/lib/pack_stats/private/metrics/dependencies.rb +47 -0
- data/lib/pack_stats/private/metrics/packages.rb +43 -56
- data/lib/pack_stats/private/metrics/packages_by_team.rb +35 -33
- data/lib/pack_stats/private/metrics/packwerk_checker_usage.rb +42 -23
- data/lib/pack_stats/private/metrics.rb +2 -2
- data/lib/pack_stats/private.rb +0 -45
- data/lib/pack_stats.rb +1 -21
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb012a138ad533b84c50a2349e006b2ad0fd4d1a6e4170c8883e17a709051377
|
4
|
+
data.tar.gz: fd679a7ea57cf9508e9573ea1746fc31b2ac23070eed7f2b9375a6efc7d991c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f224241cc9183501c6be5738a4ee61f53a863610ac883587dd7e43f0fa2c409fc2bd52a5ef97e1b840e8699346b79618397caecc937133a8f3f3d2108433e49c
|
7
|
+
data.tar.gz: 7ebcf204658cf0cfdf96f86de7fb8f4e302eca96d5ecac23f65a07702680e674b2ed7396b499ef50be75143dd3289f34b2d0e1b1e0a8b66c5f9f04f222c77fe5
|
data/README.md
CHANGED
@@ -46,42 +46,51 @@ PackStats.report_to_datadog!(
|
|
46
46
|
|
47
47
|
It's recommended to run this in CI on the main/development branch so each new commit has metrics emitted for it.
|
48
48
|
|
49
|
-
# Tracking Privacy and Dependency Violations Reliably
|
50
|
-
With [`packwerk`](https://github.com/Shopify/packwerk), privacy and dependency violations do not show up until a package has set `enforce_privacy` and `enforce_dependency` (respectively) to `true`. As such, when you're first starting off, you'll see no violations, and then periodic large increases as teams start using these protections. If you're interested in looking at privacy and dependency violations over time as if all packages were enforcing dependencies and privacy the whole time, we recommend setting these values to be true before running modularization statistics in your CI.
|
51
|
-
|
52
49
|
```ruby
|
53
50
|
require 'pack_stats'
|
54
51
|
|
55
|
-
|
52
|
+
def report(verbose:, max_enforcements: false)
|
53
|
+
ignored_paths = Pathname.glob('spec/fixtures/**/**')
|
54
|
+
source_code_pathnames = Pathname.glob('{app,components,lib,packs,spec}/**/**').select(&:file?) - ignored_paths
|
55
|
+
|
56
|
+
PackStats.report_to_datadog!(
|
57
|
+
datadog_client: Dogapi::Client.new(ENV.fetch('DATADOG_API_KEY')),
|
58
|
+
app_name: Rails.application.class.module_parent_name,
|
59
|
+
source_code_pathnames: source_code_pathnames,
|
60
|
+
verbose: verbose,
|
61
|
+
max_enforcements: max_enforcements
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
namespace(:pack_stats) do
|
56
66
|
desc(
|
57
|
-
'Publish
|
58
|
-
'Example: bin/rails "
|
67
|
+
'Publish pack_stats to datadog. ' \
|
68
|
+
'Example: bin/rails "pack_stats:upload"'
|
59
69
|
)
|
60
|
-
task(:
|
61
|
-
|
62
|
-
|
70
|
+
task(:upload, [:verbose] => :environment) do |_, args|
|
71
|
+
verbose = args[:verbose] == 'true' || false
|
72
|
+
|
73
|
+
# First send without any changes, tagging metrics with max_enforcements:false
|
74
|
+
report(verbose: verbose, max_enforcements: false)
|
63
75
|
|
64
|
-
#
|
65
|
-
# `enforce_dependencies` and `enforce_privacy`
|
76
|
+
# At Gusto, it's useful to be able to view the dashboard as if all enforce_x were set to true.
|
77
|
+
# To do this, we rewrite all `package.yml` files with `enforce_dependencies` and `enforce_privacy`
|
78
|
+
# set to true, then bin/packwerk update-todo.
|
66
79
|
old_packages = ParsePackwerk.all
|
67
80
|
old_packages.each do |package|
|
68
81
|
new_package = package.with(enforce_dependencies: true, enforce_privacy: true)
|
69
82
|
ParsePackwerk.write_package_yml!(new_package)
|
70
83
|
end
|
71
84
|
|
72
|
-
Packwerk::Cli.new.execute_command(['update-
|
85
|
+
Packwerk::Cli.new.execute_command(['update-todo'])
|
73
86
|
|
74
87
|
# Now we reset it back so that the protection values are the same as the native packwerk configuration
|
75
88
|
old_packages.each do |package|
|
76
89
|
ParsePackwerk.write_package_yml!(package)
|
77
90
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
app_name: Rails.application.class.module_parent_name,
|
82
|
-
source_code_pathnames: source_code_pathnames,
|
83
|
-
verbose: args[:verbose] == 'true' || false
|
84
|
-
)
|
91
|
+
|
92
|
+
# Then send after maxing out enforcements, tagging metrics with max_enforcements:true
|
93
|
+
report(verbose: verbose, max_enforcements: true)
|
85
94
|
end
|
86
95
|
end
|
87
96
|
```
|
@@ -98,7 +107,7 @@ Gusto has two dashboards that we've created to view these metrics. We've also ex
|
|
98
107
|
|
99
108
|
This helps answer questions like:
|
100
109
|
- How are we doing on reducing dependency and privacy violations in your monolith overall?
|
101
|
-
- How are we doing overall on adopting
|
110
|
+
- How are we doing overall on adopting packwerk?
|
102
111
|
|
103
112
|
[Dashboard JSON](docs/executive_summary.json)
|
104
113
|
|
@@ -7,6 +7,7 @@ require 'pack_stats/private/metrics/files'
|
|
7
7
|
require 'pack_stats/private/metrics/public_usage'
|
8
8
|
require 'pack_stats/private/metrics/packwerk_checker_usage'
|
9
9
|
require 'pack_stats/private/metrics/rubocop_usage'
|
10
|
+
require 'pack_stats/private/metrics/dependencies'
|
10
11
|
require 'pack_stats/private/metrics/packages'
|
11
12
|
require 'pack_stats/private/metrics/packages_by_team'
|
12
13
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module PackStats
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class Dependencies
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], app_name: String).returns(T::Array[GaugeMetric]) }
|
11
|
+
def self.get_metrics(prefix, packages, app_name)
|
12
|
+
all_metrics = T.let([], T::Array[GaugeMetric])
|
13
|
+
inbound_explicit_dependency_by_package = {}
|
14
|
+
packages.each do |package|
|
15
|
+
package.dependencies.each do |explicit_dependency|
|
16
|
+
inbound_explicit_dependency_by_package[explicit_dependency] ||= []
|
17
|
+
inbound_explicit_dependency_by_package[explicit_dependency] << package.name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
packages.each do |package| # rubocop:disable Style/CombinableLoops
|
22
|
+
package_tags = Metrics.tags_for_package(package, app_name)
|
23
|
+
|
24
|
+
#
|
25
|
+
# EXPLICIT DEPENDENCIES
|
26
|
+
#
|
27
|
+
package.dependencies.each do |explicit_dependency|
|
28
|
+
to_package = ParsePackwerk.find(explicit_dependency)
|
29
|
+
if to_package.nil?
|
30
|
+
raise StandardError, "Could not find matching package #{explicit_dependency}"
|
31
|
+
end
|
32
|
+
|
33
|
+
owner = Private.package_owner(to_package)
|
34
|
+
tags = package_tags + [Tag.for('other_package', Metrics.humanized_package_name(explicit_dependency))] + Metrics.tags_for_other_team(owner)
|
35
|
+
all_metrics << GaugeMetric.for('by_package.dependencies.by_other_package.count', 1, tags)
|
36
|
+
end
|
37
|
+
|
38
|
+
all_metrics << GaugeMetric.for('by_package.dependencies.count', package.dependencies.count, package_tags)
|
39
|
+
all_metrics << GaugeMetric.for('by_package.depended_on.count', inbound_explicit_dependency_by_package[package.name]&.count || 0, package_tags)
|
40
|
+
end
|
41
|
+
|
42
|
+
all_metrics
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -20,12 +20,13 @@ module PackStats
|
|
20
20
|
|
21
21
|
all_metrics << GaugeMetric.for('all_packages.count', packages.count, package_tags)
|
22
22
|
all_metrics << GaugeMetric.for('all_packages.dependencies.count', packages.sum { |package| package.dependencies.count }, package_tags)
|
23
|
-
all_metrics << GaugeMetric.for('all_packages.dependency_violations.count', packages.sum { |package| Metrics.file_count(package.violations.select(&:dependency?)) }, package_tags)
|
24
|
-
all_metrics << GaugeMetric.for('all_packages.privacy_violations.count', packages.sum { |package| Metrics.file_count(package.violations.select(&:privacy?)) }, package_tags)
|
25
|
-
all_metrics << GaugeMetric.for('all_packages.enforcing_dependencies.count', packages.count(&:enforces_dependencies?), package_tags)
|
26
|
-
all_metrics << GaugeMetric.for('all_packages.enforcing_privacy.count', packages.count(&:enforces_privacy?), package_tags)
|
27
23
|
|
28
|
-
|
24
|
+
PackwerkCheckerUsage::CHECKERS.each do |checker|
|
25
|
+
violation_count = packages.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) }
|
26
|
+
tags = package_tags + [checker.violation_type_tag]
|
27
|
+
all_metrics << GaugeMetric.for("all_packages.violations.count", violation_count, tags)
|
28
|
+
end
|
29
|
+
|
29
30
|
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('all_packages', packages, package_tags)
|
30
31
|
all_metrics << GaugeMetric.for('all_packages.has_readme.count', packages.count { |package| Metrics.has_readme?(package) }, package_tags)
|
31
32
|
|
@@ -37,66 +38,52 @@ module PackStats
|
|
37
38
|
|
38
39
|
packages.each do |package|
|
39
40
|
package_tags = Metrics.tags_for_package(package, app_name)
|
41
|
+
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_package', [package], package_tags)
|
40
42
|
|
41
|
-
#
|
42
|
-
# VIOLATIONS (implicit dependencies)
|
43
|
-
#
|
44
43
|
outbound_violations = package.violations
|
45
44
|
inbound_violations = inbound_violations_by_package[package.name] || []
|
46
|
-
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
47
|
-
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
48
|
-
|
49
|
-
all_metrics << GaugeMetric.for('by_package.dependency_violations.count', Metrics.file_count(all_dependency_violations), package_tags)
|
50
|
-
all_metrics << GaugeMetric.for('by_package.privacy_violations.count', Metrics.file_count(all_privacy_violations), package_tags)
|
51
|
-
|
52
|
-
all_metrics << GaugeMetric.for('by_package.outbound_dependency_violations.count', Metrics.file_count(outbound_violations.select(&:dependency?)), package_tags)
|
53
|
-
all_metrics << GaugeMetric.for('by_package.inbound_dependency_violations.count', Metrics.file_count(inbound_violations.select(&:dependency?)), package_tags)
|
54
45
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
46
|
+
PackwerkCheckerUsage::CHECKERS.each do |checker|
|
47
|
+
direction = checker.direction
|
48
|
+
|
49
|
+
case direction
|
50
|
+
when PackwerkCheckerUsage::Direction::Outbound
|
51
|
+
all_violations_of_type = outbound_violations.select { |v| v.type == checker.violation_type }
|
52
|
+
|
53
|
+
packages.each do |other_package|
|
54
|
+
violations = package.violations.select{|v| v.to_package_name == other_package.name && v.type == checker.violation_type }
|
55
|
+
|
56
|
+
tags = package_tags + [
|
57
|
+
Tag.for('other_package', Metrics.humanized_package_name(other_package.name)),
|
58
|
+
*Metrics.tags_for_other_team(Private.package_owner(other_package)),
|
59
|
+
checker.violation_type_tag
|
60
|
+
]
|
61
|
+
|
62
|
+
all_metrics << GaugeMetric.for("by_package.violations.by_other_package.count", Metrics.file_count(violations), tags)
|
63
|
+
end
|
64
|
+
when PackwerkCheckerUsage::Direction::Inbound
|
65
|
+
all_violations_of_type = inbound_violations.select { |v| v.type == checker.violation_type }
|
66
|
+
|
67
|
+
packages.each do |other_package|
|
68
|
+
violations = other_package.violations.select{|v| v.to_package_name == package.name && v.type == checker.violation_type }
|
69
|
+
tags = package_tags + [
|
70
|
+
Tag.for('other_package', Metrics.humanized_package_name(other_package.name)),
|
71
|
+
*Metrics.tags_for_other_team(Private.package_owner(other_package)),
|
72
|
+
checker.violation_type_tag
|
73
|
+
]
|
74
|
+
|
75
|
+
all_metrics << GaugeMetric.for("by_package.violations.by_other_package.count", Metrics.file_count(violations), tags)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
T.absurd(direction)
|
64
79
|
end
|
65
80
|
|
66
|
-
tags = package_tags + [
|
67
|
-
all_metrics << GaugeMetric.for(
|
68
|
-
all_metrics << GaugeMetric.for('by_package.outbound_privacy_violations.per_package.count', Metrics.file_count(violations.select(&:privacy?)), tags)
|
81
|
+
tags = package_tags + [checker.violation_type_tag]
|
82
|
+
all_metrics << GaugeMetric.for("by_package.violations.count", Metrics.file_count(all_violations_of_type), tags)
|
69
83
|
end
|
70
84
|
end
|
71
85
|
|
72
|
-
|
73
|
-
packages.each do |package|
|
74
|
-
package.dependencies.each do |explicit_dependency|
|
75
|
-
inbound_explicit_dependency_by_package[explicit_dependency] ||= []
|
76
|
-
inbound_explicit_dependency_by_package[explicit_dependency] << package.name
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
packages.each do |package| # rubocop:disable Style/CombinableLoops
|
81
|
-
package_tags = Metrics.tags_for_package(package, app_name)
|
82
|
-
|
83
|
-
#
|
84
|
-
# EXPLICIT DEPENDENCIES
|
85
|
-
#
|
86
|
-
package.dependencies.each do |explicit_dependency|
|
87
|
-
to_package = ParsePackwerk.find(explicit_dependency)
|
88
|
-
if to_package.nil?
|
89
|
-
raise StandardError, "Could not find matching package #{explicit_dependency}"
|
90
|
-
end
|
91
|
-
|
92
|
-
owner = Private.package_owner(to_package)
|
93
|
-
tags = package_tags + [Tag.for('to_package', Metrics.humanized_package_name(explicit_dependency))] + Metrics.tags_for_to_team(owner)
|
94
|
-
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.per_package.count', 1, tags)
|
95
|
-
end
|
96
|
-
|
97
|
-
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.count', package.dependencies.count, package_tags)
|
98
|
-
all_metrics << GaugeMetric.for('by_package.inbound_explicit_dependencies.count', inbound_explicit_dependency_by_package[package.name]&.count || 0, package_tags)
|
99
|
-
end
|
86
|
+
all_metrics += Metrics::Dependencies.get_metrics('by_package', packages, app_name)
|
100
87
|
|
101
88
|
all_metrics
|
102
89
|
end
|
@@ -18,47 +18,49 @@ module PackStats
|
|
18
18
|
app_level_tag = Tag.for('app', app_name)
|
19
19
|
|
20
20
|
|
21
|
-
all_packages.group_by { |package| Private.package_owner(package) }.each do |team_name,
|
21
|
+
all_packages.group_by { |package| Private.package_owner(package) }.each do |team_name, packages_for_team|
|
22
|
+
team_tags = Metrics.tags_for_team(team_name) + [app_level_tag]
|
23
|
+
all_metrics << GaugeMetric.for('by_team.all_packages.count', packages_for_team.count, team_tags)
|
24
|
+
all_metrics += Metrics::PackwerkCheckerUsage.get_checker_metrics('by_team', packages_for_team, team_tags)
|
25
|
+
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_team', packages_for_team, team_tags)
|
26
|
+
all_metrics << GaugeMetric.for('by_team.has_readme.count', packages_for_team.count { |package| Metrics.has_readme?(package) }, team_tags)
|
27
|
+
|
28
|
+
outbound_violations = packages_for_team.flat_map(&:violations)
|
22
29
|
# We look at `all_packages` because we care about ALL inbound violations across all teams
|
23
30
|
inbound_violations_by_package = all_packages.flat_map(&:violations).group_by(&:to_package_name)
|
31
|
+
# Here we only look at packages_for_team because we only care about inbound violations onto packages for this team
|
32
|
+
inbound_violations = packages_for_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# VIOLATIONS (implicit dependencies)
|
31
|
-
#
|
32
|
-
outbound_violations = packages_by_team.flat_map(&:violations)
|
33
|
-
# Here we only look at packages_by_team because we only care about inbound violations onto packages for this team
|
34
|
-
inbound_violations = packages_by_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
|
35
|
-
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
36
|
-
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
37
|
-
|
38
|
-
all_metrics << GaugeMetric.for('by_team.dependency_violations.count', Metrics.file_count(all_dependency_violations), team_tags)
|
39
|
-
all_metrics << GaugeMetric.for('by_team.privacy_violations.count', Metrics.file_count(all_privacy_violations), team_tags)
|
34
|
+
PackwerkCheckerUsage::CHECKERS.each do |checker|
|
35
|
+
direction = checker.direction
|
36
|
+
case direction
|
37
|
+
when PackwerkCheckerUsage::Direction::Outbound
|
38
|
+
all_violations_of_type = outbound_violations.select { |v| v.type == checker.violation_type }
|
40
39
|
|
41
|
-
|
42
|
-
|
40
|
+
violation_count = packages_for_team.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) }
|
41
|
+
tags = team_tags + [checker.violation_type_tag]
|
42
|
+
all_metrics << GaugeMetric.for("by_team.violations.count", violation_count, tags)
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
all_packages.group_by { |package| Private.package_owner(package) }.each do |other_team_name, other_teams_packages|
|
45
|
+
violations = outbound_violations.select{|v| other_teams_packages.map(&:name).include?(v.to_package_name) && v.type == checker.violation_type}
|
46
|
+
tags = team_tags + Metrics.tags_for_other_team(other_team_name) + [checker.violation_type_tag]
|
47
|
+
all_metrics << GaugeMetric.for("by_team.violations.by_other_team.count", Metrics.file_count(violations), tags)
|
48
|
+
end
|
49
|
+
when PackwerkCheckerUsage::Direction::Inbound
|
50
|
+
all_violations_of_type = inbound_violations.select { |v| v.type == checker.violation_type }
|
46
51
|
|
47
|
-
|
52
|
+
violation_count = packages_for_team.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) }
|
53
|
+
tags = team_tags + [checker.violation_type_tag]
|
54
|
+
all_metrics << GaugeMetric.for("by_team.violations.count", violation_count, tags)
|
48
55
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
all_packages.group_by { |package| Private.package_owner(package) }.each do |other_team_name, other_teams_packages|
|
57
|
+
violations = other_teams_packages.flat_map(&:violations).select{|v| packages_for_team.map(&:name).include?(v.to_package_name) && v.type == checker.violation_type}
|
58
|
+
tags = team_tags + Metrics.tags_for_other_team(other_team_name) + [checker.violation_type_tag]
|
59
|
+
all_metrics << GaugeMetric.for("by_team.violations.by_other_team.count", Metrics.file_count(violations), tags)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
T.absurd(direction)
|
53
63
|
end
|
54
|
-
|
55
|
-
Private.package_owner(to_package)
|
56
|
-
end
|
57
|
-
|
58
|
-
grouped_outbound_violations.each do |to_team_name, violations|
|
59
|
-
tags = team_tags + Metrics.tags_for_to_team(to_team_name)
|
60
|
-
all_metrics << GaugeMetric.for('by_team.outbound_dependency_violations.per_team.count', Metrics.file_count(violations.select(&:dependency?)), tags)
|
61
|
-
all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.per_team.count', Metrics.file_count(violations.select(&:privacy?)), tags)
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
@@ -8,37 +8,56 @@ module PackStats
|
|
8
8
|
module Metrics
|
9
9
|
class PackwerkCheckerUsage
|
10
10
|
extend T::Sig
|
11
|
-
|
11
|
+
|
12
|
+
# Some violations (e.g. dependency, visibility, architecture) matter for the referencing (outbound) package.
|
13
|
+
# Other violations (e.g. privacy) matter for the referenced (inbound) package.
|
14
|
+
class Direction < T::Enum
|
15
|
+
enums do
|
16
|
+
Inbound = new
|
17
|
+
Outbound = new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
12
21
|
# Later, we might find a way we can get this directly from `packwerk`
|
13
22
|
class PackwerkChecker < T::Struct
|
14
|
-
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
const :key, String
|
26
|
+
const :violation_type, String
|
27
|
+
const :direction, Direction
|
28
|
+
|
29
|
+
sig { returns(Tag) }
|
30
|
+
def violation_type_tag
|
31
|
+
Tag.new(
|
32
|
+
key: 'violation_type',
|
33
|
+
value: violation_type
|
34
|
+
)
|
35
|
+
end
|
15
36
|
end
|
16
37
|
|
38
|
+
CHECKERS = T.let([
|
39
|
+
PackwerkChecker.new(key: 'enforce_dependencies', violation_type: 'dependency', direction: Direction::Outbound),
|
40
|
+
PackwerkChecker.new(key: 'enforce_privacy', violation_type: 'privacy', direction: Direction::Inbound),
|
41
|
+
PackwerkChecker.new(key: 'enforce_architecture', violation_type: 'architecture', direction: Direction::Outbound),
|
42
|
+
PackwerkChecker.new(key: 'enforce_visibility', violation_type: 'visibility', direction: Direction::Outbound),
|
43
|
+
], T::Array[PackwerkChecker])
|
44
|
+
|
17
45
|
sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
18
46
|
def self.get_checker_metrics(prefix, packages, package_tags)
|
19
47
|
metrics = T.let([], T::Array[GaugeMetric])
|
20
48
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
when 'true'
|
34
|
-
checker_setting && checker_setting != 'strict'
|
35
|
-
when 'strict'
|
36
|
-
checker_setting == 'strict'
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
metric_name = "#{prefix}.packwerk_checkers.#{checker.setting}.#{enabled_mode}.count"
|
41
|
-
metrics << GaugeMetric.for(metric_name, count_of_packages, package_tags)
|
49
|
+
CHECKERS.each do |checker|
|
50
|
+
checker_values = packages.map do |package|
|
51
|
+
YAML.load_file(package.yml)[checker.key]
|
52
|
+
end
|
53
|
+
|
54
|
+
checker_values_tally = checker_values.map(&:to_s).tally
|
55
|
+
|
56
|
+
['false', 'true', 'strict'].each do |possible_value|
|
57
|
+
count = checker_values_tally.fetch(possible_value, 0)
|
58
|
+
metric_name = "#{prefix}.packwerk_checkers.#{possible_value}.count"
|
59
|
+
tags = package_tags + [checker.violation_type_tag]
|
60
|
+
metrics << GaugeMetric.for(metric_name, count, tags)
|
42
61
|
end
|
43
62
|
end
|
44
63
|
|
@@ -22,8 +22,8 @@ module PackStats
|
|
22
22
|
end
|
23
23
|
|
24
24
|
sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) }
|
25
|
-
def self.
|
26
|
-
[Tag.for('
|
25
|
+
def self.tags_for_other_team(team_name)
|
26
|
+
[Tag.for('other_team', team_name || Metrics::UNKNOWN_OWNER)]
|
27
27
|
end
|
28
28
|
|
29
29
|
sig { params(name: String).returns(String) }
|
data/lib/pack_stats/private.rb
CHANGED
@@ -4,51 +4,6 @@ module PackStats
|
|
4
4
|
module Private
|
5
5
|
extend T::Sig
|
6
6
|
|
7
|
-
METRIC_REPLACEMENTS = T.let({
|
8
|
-
# enforce_dependencies
|
9
|
-
'packwerk_checkers.enforce_dependencies.false' => 'prevent_this_package_from_violating_its_stated_dependencies.no',
|
10
|
-
'packwerk_checkers.enforce_dependencies.true' => 'prevent_this_package_from_violating_its_stated_dependencies.fail_the_build_if_new_instances_appear',
|
11
|
-
'packwerk_checkers.enforce_dependencies.strict' => 'prevent_this_package_from_violating_its_stated_dependencies.fail_the_build_on_any_instances',
|
12
|
-
# enforce_privacy
|
13
|
-
'packwerk_checkers.enforce_privacy.false' => 'prevent_other_packages_from_using_this_packages_internals.no',
|
14
|
-
'packwerk_checkers.enforce_privacy.true' => 'prevent_other_packages_from_using_this_packages_internals.fail_the_build_if_new_instances_appear',
|
15
|
-
'packwerk_checkers.enforce_privacy.strict' => 'prevent_other_packages_from_using_this_packages_internals.fail_the_build_on_any_instances',
|
16
|
-
# Packs/TypedPublicApis
|
17
|
-
'rubocops.packs_typedpublicapis.false' => 'prevent_this_package_from_exposing_an_untyped_api.no',
|
18
|
-
'rubocops.packs_typedpublicapis.true' => 'prevent_this_package_from_exposing_an_untyped_api.fail_the_build_if_new_instances_appear',
|
19
|
-
'rubocops.packs_typedpublicapis.strict' => 'prevent_this_package_from_exposing_an_untyped_api.fail_the_build_on_any_instances',
|
20
|
-
'rubocops.packs_typedpublicapis.exclusions' => 'prevent_this_package_from_exposing_an_untyped_api.rubocop_exclusions',
|
21
|
-
# Packs/RootNamespaceIsPackName
|
22
|
-
'rubocops.packs_rootnamespaceispackname.false' => 'prevent_this_package_from_creating_other_namespaces.no',
|
23
|
-
'rubocops.packs_rootnamespaceispackname.true' => 'prevent_this_package_from_creating_other_namespaces.fail_the_build_if_new_instances_appear',
|
24
|
-
'rubocops.packs_rootnamespaceispackname.strict' => 'prevent_this_package_from_creating_other_namespaces.fail_the_build_on_any_instances',
|
25
|
-
'rubocops.packs_rootnamespaceispackname.exclusions' => 'prevent_this_package_from_creating_other_namespaces.rubocop_exclusions',
|
26
|
-
# Packs/ClassMethodsAsPublicApis
|
27
|
-
'rubocops.packs_classmethodsaspublicapis.false' => 'prevent_this_package_from_exposing_instance_method_public_apis.no',
|
28
|
-
'rubocops.packs_classmethodsaspublicapis.true' => 'prevent_this_package_from_exposing_instance_method_public_apis.fail_the_build_if_new_instances_appear',
|
29
|
-
'rubocops.packs_classmethodsaspublicapis.strict' => 'prevent_this_package_from_exposing_instance_method_public_apis.fail_the_build_on_any_instances',
|
30
|
-
'rubocops.packs_classmethodsaspublicapis.exclusions' => 'prevent_this_package_from_exposing_instance_method_public_apis.rubocop_exclusions',
|
31
|
-
# Packs/DocumentedPublicApis
|
32
|
-
'rubocops.packs_documentedpublicapis.false' => 'prevent_this_package_from_exposing_undocumented_public_apis.no',
|
33
|
-
'rubocops.packs_documentedpublicapis.true' => 'prevent_this_package_from_exposing_undocumented_public_apis.fail_the_build_if_new_instances_appear',
|
34
|
-
'rubocops.packs_documentedpublicapis.strict' => 'prevent_this_package_from_exposing_undocumented_public_apis.fail_the_build_on_any_instances',
|
35
|
-
'rubocops.packs_documentedpublicapis.exclusions' => 'prevent_this_package_from_exposing_undocumented_public_apis.rubocop_exclusions',
|
36
|
-
}, T::Hash[String, String])
|
37
|
-
|
38
|
-
sig { params(metrics: T::Array[GaugeMetric]).returns(T::Array[GaugeMetric]) }
|
39
|
-
def self.convert_metrics_to_legacy(metrics)
|
40
|
-
metrics.map do |metric|
|
41
|
-
new_metric = metric
|
42
|
-
METRIC_REPLACEMENTS.each do |current_name, legacy_name|
|
43
|
-
new_metric = new_metric.with(
|
44
|
-
name: new_metric.name.gsub(current_name, legacy_name)
|
45
|
-
)
|
46
|
-
end
|
47
|
-
|
48
|
-
new_metric
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
7
|
sig { params(package: ParsePackwerk::Package).returns(T.nilable(String) )}
|
53
8
|
def self.package_owner(package)
|
54
9
|
pack = Packs.find(package.name)
|
data/lib/pack_stats.rb
CHANGED
@@ -39,8 +39,6 @@ module PackStats
|
|
39
39
|
# See note on get_metrics
|
40
40
|
packaged_source_code_locations: T.nilable(T::Array[Pathname]),
|
41
41
|
# See note on get_metrics
|
42
|
-
use_gusto_legacy_names: T::Boolean,
|
43
|
-
# See note on get_metrics
|
44
42
|
max_enforcements_tag_value: T::Boolean
|
45
43
|
).void
|
46
44
|
end
|
@@ -52,7 +50,6 @@ module PackStats
|
|
52
50
|
report_time: Time.now, # rubocop:disable Rails/TimeZone
|
53
51
|
verbose: false,
|
54
52
|
packaged_source_code_locations: [],
|
55
|
-
use_gusto_legacy_names: false,
|
56
53
|
max_enforcements_tag_value: false
|
57
54
|
)
|
58
55
|
|
@@ -60,7 +57,6 @@ module PackStats
|
|
60
57
|
source_code_pathnames: source_code_pathnames,
|
61
58
|
componentized_source_code_locations: componentized_source_code_locations,
|
62
59
|
app_name: app_name,
|
63
|
-
use_gusto_legacy_names: use_gusto_legacy_names,
|
64
60
|
max_enforcements_tag_value: max_enforcements_tag_value,
|
65
61
|
)
|
66
62
|
|
@@ -71,10 +67,6 @@ module PackStats
|
|
71
67
|
end
|
72
68
|
end
|
73
69
|
|
74
|
-
if use_gusto_legacy_names
|
75
|
-
all_metrics = Private.convert_metrics_to_legacy(all_metrics)
|
76
|
-
end
|
77
|
-
|
78
70
|
Private::DatadogReporter.report!(
|
79
71
|
datadog_client: datadog_client,
|
80
72
|
report_time: report_time,
|
@@ -89,11 +81,6 @@ module PackStats
|
|
89
81
|
app_name: String,
|
90
82
|
# This field is deprecated
|
91
83
|
packaged_source_code_locations: T.nilable(T::Array[Pathname]),
|
92
|
-
# It is not recommended to set this to true.
|
93
|
-
# Gusto uses this to preserve historical trends in Dashboards as the names of
|
94
|
-
# things changed, but new dashboards can use names that better match current tooling conventions.
|
95
|
-
# The behavior of setting this parameter to true might change without warning
|
96
|
-
use_gusto_legacy_names: T::Boolean,
|
97
84
|
# You can set this to `true` to tag all metrics with `max_enforcements:true`.
|
98
85
|
# This is useful if you want to submit two sets of metrics:
|
99
86
|
# Once with the violation counts as configured in the app
|
@@ -106,25 +93,18 @@ module PackStats
|
|
106
93
|
componentized_source_code_locations:,
|
107
94
|
app_name:,
|
108
95
|
packaged_source_code_locations: [],
|
109
|
-
use_gusto_legacy_names: false,
|
110
96
|
max_enforcements_tag_value: false
|
111
97
|
)
|
112
98
|
|
113
99
|
GaugeMetric.set_max_enforcements_tag(max_enforcements_tag_value)
|
114
100
|
|
115
|
-
|
101
|
+
Private::DatadogReporter.get_metrics(
|
116
102
|
source_code_files: source_code_files(
|
117
103
|
source_code_pathnames: source_code_pathnames,
|
118
104
|
componentized_source_code_locations: componentized_source_code_locations,
|
119
105
|
),
|
120
106
|
app_name: app_name
|
121
107
|
)
|
122
|
-
|
123
|
-
if use_gusto_legacy_names
|
124
|
-
all_metrics = Private.convert_metrics_to_legacy(all_metrics)
|
125
|
-
end
|
126
|
-
|
127
|
-
all_metrics
|
128
108
|
end
|
129
109
|
|
130
110
|
sig do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pack_stats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_teams
|
@@ -192,6 +192,7 @@ files:
|
|
192
192
|
- lib/pack_stats/private.rb
|
193
193
|
- lib/pack_stats/private/datadog_reporter.rb
|
194
194
|
- lib/pack_stats/private/metrics.rb
|
195
|
+
- lib/pack_stats/private/metrics/dependencies.rb
|
195
196
|
- lib/pack_stats/private/metrics/files.rb
|
196
197
|
- lib/pack_stats/private/metrics/packages.rb
|
197
198
|
- lib/pack_stats/private/metrics/packages_by_team.rb
|