dropsonde 0.0.5 → 0.0.8
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/CHANGELOG.md +72 -0
- data/README.md +42 -13
- data/bin/dropsonde +13 -7
- data/lib/dropsonde/cache.rb +42 -32
- data/lib/dropsonde/metrics/dependencies.rb +36 -32
- data/lib/dropsonde/metrics/environments.rb +13 -10
- data/lib/dropsonde/metrics/modules.rb +150 -65
- data/lib/dropsonde/metrics/platforms.rb +63 -61
- data/lib/dropsonde/metrics/puppetfiles.rb +35 -32
- data/lib/dropsonde/metrics.rb +80 -77
- data/lib/dropsonde/monkeypatches.rb +12 -11
- data/lib/dropsonde/version.rb +3 -1
- data/lib/dropsonde.rb +64 -26
- metadata +5 -4
@@ -1,14 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# modules plugin
|
1
4
|
class Dropsonde::Metrics::Modules
|
2
5
|
def self.initialize_modules
|
3
6
|
# require any libraries needed here -- no need to load puppet; it's already initialized
|
4
7
|
# All plugins are initialized before any metrics are generated.
|
8
|
+
require 'puppet/info_service'
|
9
|
+
require 'puppet/info_service/class_information_service'
|
5
10
|
end
|
6
11
|
|
7
12
|
def self.description
|
8
|
-
<<~
|
13
|
+
<<~DESCRIPTION
|
9
14
|
This group of metrics exports name & version information about the public
|
10
15
|
modules installed in all environments, ignoring private modules.
|
11
|
-
|
16
|
+
DESCRIPTION
|
12
17
|
end
|
13
18
|
|
14
19
|
def self.schema
|
@@ -18,49 +23,89 @@ class Dropsonde::Metrics::Modules
|
|
18
23
|
{
|
19
24
|
"fields": [
|
20
25
|
{
|
21
|
-
"description":
|
22
|
-
"mode":
|
23
|
-
"name":
|
24
|
-
"type":
|
26
|
+
"description": 'The module name',
|
27
|
+
"mode": 'NULLABLE',
|
28
|
+
"name": 'name',
|
29
|
+
"type": 'STRING',
|
25
30
|
},
|
26
31
|
{
|
27
|
-
"description":
|
28
|
-
"mode":
|
29
|
-
"name":
|
30
|
-
"type":
|
32
|
+
"description": 'The module slug (author-name)',
|
33
|
+
"mode": 'NULLABLE',
|
34
|
+
"name": 'slug',
|
35
|
+
"type": 'STRING',
|
31
36
|
},
|
32
37
|
{
|
33
|
-
"description":
|
34
|
-
"mode":
|
35
|
-
"name":
|
36
|
-
"type":
|
37
|
-
}
|
38
|
+
"description": 'The module version',
|
39
|
+
"mode": 'NULLABLE',
|
40
|
+
"name": 'version',
|
41
|
+
"type": 'STRING',
|
42
|
+
},
|
38
43
|
],
|
39
|
-
"description":
|
40
|
-
"mode":
|
41
|
-
"name":
|
42
|
-
"type":
|
44
|
+
"description": 'List of modules in all environments.',
|
45
|
+
"mode": 'REPEATED',
|
46
|
+
"name": 'modules',
|
47
|
+
"type": 'RECORD',
|
43
48
|
},
|
44
49
|
{
|
45
50
|
"fields": [
|
46
51
|
{
|
47
|
-
"description":
|
48
|
-
"mode":
|
49
|
-
"name":
|
50
|
-
"type":
|
52
|
+
"description": 'The class name',
|
53
|
+
"mode": 'NULLABLE',
|
54
|
+
"name": 'name',
|
55
|
+
"type": 'STRING',
|
51
56
|
},
|
52
57
|
{
|
53
|
-
"description":
|
54
|
-
"mode":
|
55
|
-
"name":
|
56
|
-
"type":
|
57
|
-
}
|
58
|
+
"description": 'How many nodes it is declared on',
|
59
|
+
"mode": 'NULLABLE',
|
60
|
+
"name": 'count',
|
61
|
+
"type": 'INTEGER',
|
62
|
+
},
|
58
63
|
],
|
59
|
-
"description":
|
60
|
-
"mode":
|
61
|
-
"name":
|
62
|
-
"type":
|
63
|
-
}
|
64
|
+
"description": 'List of classes and counts in all environments.',
|
65
|
+
"mode": 'REPEATED',
|
66
|
+
"name": 'classes',
|
67
|
+
"type": 'RECORD',
|
68
|
+
},
|
69
|
+
{
|
70
|
+
"fields": [
|
71
|
+
{
|
72
|
+
"description": 'The module name',
|
73
|
+
"mode": 'NULLABLE',
|
74
|
+
"name": 'name',
|
75
|
+
"type": 'STRING',
|
76
|
+
},
|
77
|
+
{
|
78
|
+
"description": 'The module slug (author-name)',
|
79
|
+
"mode": 'NULLABLE',
|
80
|
+
"name": 'slug',
|
81
|
+
"type": 'STRING',
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"description": 'The module version',
|
85
|
+
"mode": 'NULLABLE',
|
86
|
+
"name": 'version',
|
87
|
+
"type": 'STRING',
|
88
|
+
},
|
89
|
+
],
|
90
|
+
"description": 'List of modules whose classes are not declared in any environments.',
|
91
|
+
"mode": 'REPEATED',
|
92
|
+
"name": 'unused_modules',
|
93
|
+
"type": 'RECORD',
|
94
|
+
},
|
95
|
+
{
|
96
|
+
"fields": [
|
97
|
+
{
|
98
|
+
"description": 'The class name',
|
99
|
+
"mode": 'NULLABLE',
|
100
|
+
"name": 'name',
|
101
|
+
"type": 'STRING',
|
102
|
+
},
|
103
|
+
],
|
104
|
+
"description": 'List of unused classes in all environments.',
|
105
|
+
"mode": 'REPEATED',
|
106
|
+
"name": 'unused_classes',
|
107
|
+
"type": 'RECORD',
|
108
|
+
},
|
64
109
|
]
|
65
110
|
end
|
66
111
|
|
@@ -68,46 +113,76 @@ class Dropsonde::Metrics::Modules
|
|
68
113
|
# run just before generating this metric
|
69
114
|
end
|
70
115
|
|
71
|
-
def self.run
|
116
|
+
def self.run(puppetdb_session = nil)
|
72
117
|
# return an array of hashes representing the data to be merged into the combined checkin
|
73
|
-
environments = Puppet.lookup(:environments).list.map{|e|e.name}
|
74
|
-
modules = environments.map
|
75
|
-
Puppet.lookup(:environments).get(env).modules.map do|mod|
|
118
|
+
environments = Puppet.lookup(:environments).list.map { |e| e.name }
|
119
|
+
modules = environments.map { |env|
|
120
|
+
Puppet.lookup(:environments).get(env).modules.map do |mod|
|
76
121
|
next unless mod.forge_module?
|
77
122
|
|
78
123
|
{
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
124
|
+
name: mod.name,
|
125
|
+
slug: mod.forge_slug,
|
126
|
+
version: mod.version,
|
82
127
|
}
|
83
128
|
end
|
84
|
-
|
129
|
+
}.flatten.compact.uniq
|
85
130
|
|
86
|
-
if
|
131
|
+
if puppetdb_session
|
87
132
|
# classes and how many nodes they're enforced on
|
88
|
-
results =
|
89
|
-
'resources[type, title] { type = "Class" }'
|
90
|
-
).data
|
133
|
+
results = puppetdb_session.puppet_db.request('', 'resources[type, title] { type = "Class" }').data
|
91
134
|
|
92
135
|
# select only classes from public modules.
|
93
136
|
# Use uniq to reduce the iteration over very large datasets
|
94
|
-
classes = results.uniq.map
|
137
|
+
classes = results.uniq.map { |klass|
|
95
138
|
title = klass['title']
|
96
139
|
modname = title.split('::').first.downcase
|
97
|
-
next unless modules.find {|mod| mod[:name] == modname }
|
140
|
+
next unless modules.find { |mod| mod[:name] == modname }
|
98
141
|
|
99
142
|
{
|
100
|
-
:
|
101
|
-
:
|
143
|
+
name: title,
|
144
|
+
count: results.count { |row| row['title'] == title },
|
102
145
|
}
|
103
|
-
|
146
|
+
}.compact
|
147
|
+
|
148
|
+
# now lets get a list of all classes so we can identify which are unused
|
149
|
+
infoservice = Puppet::InfoService::ClassInformationService.new
|
150
|
+
env_hash = {}
|
151
|
+
environments.each do |env|
|
152
|
+
manifests = Puppet.lookup(:environments).get(env).modules.reduce([]) do |acc, mod|
|
153
|
+
next acc unless mod.forge_module?
|
154
|
+
|
155
|
+
acc.concat mod.all_manifests
|
156
|
+
end
|
157
|
+
env_hash[env] = manifests
|
158
|
+
end
|
159
|
+
|
160
|
+
klasses_per_env = infoservice.classes_per_environment(env_hash)
|
161
|
+
|
162
|
+
installed_classes = klasses_per_env.reduce([]) do |klasses, (_key, env)|
|
163
|
+
names = env.reduce([]) do |acc, (_file, contents)|
|
164
|
+
acc.concat(contents[:classes].map { |c| c[:name] })
|
165
|
+
end
|
166
|
+
|
167
|
+
klasses.concat names
|
168
|
+
end
|
169
|
+
|
170
|
+
unused_modules = installed_classes.map { |c| c.split('::').first }.sort.uniq
|
171
|
+
classes.each { |c| unused_modules.delete(c[:name].split('::').first.downcase) }
|
172
|
+
|
173
|
+
unused_classes = installed_classes.dup
|
174
|
+
classes.each { |c| unused_classes.delete(c[:name].downcase) }
|
104
175
|
else
|
105
176
|
classes = []
|
177
|
+
unused_modules = []
|
178
|
+
unused_classes = []
|
106
179
|
end
|
107
180
|
|
108
181
|
[
|
109
|
-
{ :
|
110
|
-
{ :
|
182
|
+
{ modules: modules },
|
183
|
+
{ classes: classes },
|
184
|
+
{ unused_modules: unused_modules },
|
185
|
+
{ unused_classes: unused_classes },
|
111
186
|
]
|
112
187
|
end
|
113
188
|
|
@@ -119,19 +194,29 @@ class Dropsonde::Metrics::Modules
|
|
119
194
|
versions = ['1.3.2', '0.0.1', '0.1.2', '1.0.0', '3.0.2', '7.10', '6.1.0', '2.1.0', '1.4.0']
|
120
195
|
classes = ['', '::Config', '::Service', '::Server', '::Client', '::Packages']
|
121
196
|
[
|
122
|
-
:
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
197
|
+
modules: Dropsonde::Cache.modules
|
198
|
+
.sample(rand(100))
|
199
|
+
.map do |item|
|
200
|
+
{
|
201
|
+
name: item.split('-').last,
|
202
|
+
slug: item,
|
203
|
+
version: versions.sample,
|
204
|
+
}
|
205
|
+
end,
|
206
|
+
classes: Dropsonde::Cache.modules
|
207
|
+
.sample(rand(500))
|
208
|
+
.map do |item|
|
209
|
+
{
|
210
|
+
name: item.split('-').last.capitalize + classes.sample,
|
211
|
+
count: rand(750),
|
212
|
+
}
|
213
|
+
end,
|
214
|
+
unused_modules: Dropsonde::Cache.modules
|
215
|
+
.sample(rand(500))
|
216
|
+
.map { |item| item.split('-').last },
|
217
|
+
unused_classes: Dropsonde::Cache.modules
|
218
|
+
.sample(rand(500))
|
219
|
+
.map { |item| item.split('-').last.capitalize + classes.sample },
|
135
220
|
]
|
136
221
|
end
|
137
222
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# platforms plugin
|
1
4
|
class Dropsonde::Metrics::Platforms
|
2
5
|
def self.initialize_platforms
|
3
6
|
# require any libraries needed here -- no need to load puppet; it's already initialized
|
@@ -5,11 +8,11 @@ class Dropsonde::Metrics::Platforms
|
|
5
8
|
end
|
6
9
|
|
7
10
|
def self.description
|
8
|
-
<<~
|
11
|
+
<<~DESCRIPTION
|
9
12
|
This group of metrics generates usage patterns by platform.
|
10
13
|
Currently implemented is a list of classes, the platforms
|
11
14
|
they are declared on, and a count of each combination.
|
12
|
-
|
15
|
+
DESCRIPTION
|
13
16
|
end
|
14
17
|
|
15
18
|
def self.schema
|
@@ -19,29 +22,29 @@ class Dropsonde::Metrics::Platforms
|
|
19
22
|
{
|
20
23
|
"fields": [
|
21
24
|
{
|
22
|
-
"description":
|
23
|
-
"mode":
|
24
|
-
"name":
|
25
|
-
"type":
|
25
|
+
"description": 'The class name name',
|
26
|
+
"mode": 'NULLABLE',
|
27
|
+
"name": 'name',
|
28
|
+
"type": 'STRING',
|
26
29
|
},
|
27
30
|
{
|
28
|
-
"description":
|
29
|
-
"mode":
|
30
|
-
"name":
|
31
|
-
"type":
|
31
|
+
"description": 'The osfamily of the node the class is declared on',
|
32
|
+
"mode": 'NULLABLE',
|
33
|
+
"name": 'platform',
|
34
|
+
"type": 'STRING',
|
32
35
|
},
|
33
36
|
{
|
34
|
-
"description":
|
35
|
-
"mode":
|
36
|
-
"name":
|
37
|
-
"type":
|
37
|
+
"description": 'The number of time this combination is declared',
|
38
|
+
"mode": 'NULLABLE',
|
39
|
+
"name": 'count',
|
40
|
+
"type": 'INTEGER',
|
38
41
|
},
|
39
42
|
],
|
40
43
|
"description": "List of all classes in the infrastructure and platforms they're declared on.",
|
41
|
-
"mode":
|
42
|
-
"name":
|
43
|
-
"type":
|
44
|
-
}
|
44
|
+
"mode": 'REPEATED',
|
45
|
+
"name": 'class_platforms',
|
46
|
+
"type": 'RECORD',
|
47
|
+
},
|
45
48
|
]
|
46
49
|
end
|
47
50
|
|
@@ -49,44 +52,43 @@ class Dropsonde::Metrics::Platforms
|
|
49
52
|
# run just before generating this metric
|
50
53
|
end
|
51
54
|
|
52
|
-
def self.run
|
55
|
+
def self.run(puppetdb_session = nil)
|
53
56
|
# skip this metric if we don't have an active PuppetDB connection
|
54
|
-
return unless
|
57
|
+
return unless puppetdb_session
|
55
58
|
|
56
|
-
classes =
|
57
|
-
facts =
|
59
|
+
classes = puppetdb_session.puppet_db.request('', 'resources[certname, title] { type = "Class" }').data
|
60
|
+
facts = puppetdb_session.puppet_db.request('', 'facts[certname, value] { name = "osfamily" }').data
|
58
61
|
|
59
62
|
# All public Forge modules that are installed.
|
60
|
-
modules = Puppet.lookup(:environments).list.map {|env|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
}}.flatten.uniq
|
66
|
-
|
67
|
-
data = classes.map {|item|
|
68
|
-
# filter out any that don't come from public Forge modules
|
69
|
-
mod = item['title'].split('::').first.downcase
|
70
|
-
next unless modules.include? mod
|
71
|
-
|
72
|
-
item['platform'] = facts.find {|fact|
|
73
|
-
fact['certname'] == item['certname']
|
74
|
-
}['value']
|
63
|
+
modules = Puppet.lookup(:environments).list.map { |env|
|
64
|
+
env.modules.select { |mod| mod.forge_module? }.map do |fmod|
|
65
|
+
fmod.name
|
66
|
+
end
|
67
|
+
}.flatten.uniq
|
75
68
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
data = classes.map { |item|
|
70
|
+
# filter out any that don't come from public Forge modules
|
71
|
+
mod = item['title'].split('::').first.downcase
|
72
|
+
next unless modules.include? mod
|
73
|
+
|
74
|
+
item['platform'] = facts.find { |fact|
|
75
|
+
fact['certname'] == item['certname']
|
76
|
+
}['value']
|
77
|
+
|
78
|
+
{
|
79
|
+
name: item['title'],
|
80
|
+
platform: item['platform'],
|
81
|
+
}
|
80
82
|
}.compact
|
81
83
|
|
82
|
-
data.each
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
84
|
+
data.each do |item|
|
85
|
+
item[:count] = data.select { |i|
|
86
|
+
i[:name] == item[:name] and i[:platform] == item[:platform]
|
87
|
+
}.count
|
88
|
+
end
|
87
89
|
|
88
90
|
[
|
89
|
-
:
|
91
|
+
class_platforms: data,
|
90
92
|
]
|
91
93
|
end
|
92
94
|
|
@@ -95,25 +97,25 @@ class Dropsonde::Metrics::Platforms
|
|
95
97
|
# make it easier to write data aggregation queries without access to the
|
96
98
|
# actual private data that users have submitted.
|
97
99
|
|
98
|
-
platforms = [
|
100
|
+
platforms = %w[RedHat Debian Windows Suse FreeBSD Darwin Archlinux AIX]
|
99
101
|
classes = ['', '::Config', '::Service', '::Server', '::Client', '::Packages']
|
100
102
|
|
101
103
|
data = Dropsonde::Cache.modules
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
104
|
+
.sample(rand(35))
|
105
|
+
.map { |item|
|
106
|
+
name = item.split('-').last.capitalize + classes.sample
|
107
|
+
|
108
|
+
rand(5).times.map do
|
109
|
+
{
|
110
|
+
name: name,
|
111
|
+
platform: platforms.sample,
|
112
|
+
count: rand(1000),
|
113
|
+
}
|
114
|
+
end
|
115
|
+
}.flatten
|
114
116
|
|
115
117
|
[
|
116
|
-
:
|
118
|
+
class_platforms: data.uniq,
|
117
119
|
]
|
118
120
|
end
|
119
121
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# puppetfiles plugin
|
1
4
|
class Dropsonde::Metrics::Puppetfiles
|
2
5
|
def self.initialize_puppetfiles
|
3
6
|
# require any libraries needed here -- no need to load puppet; it's already initialized
|
@@ -6,10 +9,10 @@ class Dropsonde::Metrics::Puppetfiles
|
|
6
9
|
end
|
7
10
|
|
8
11
|
def self.description
|
9
|
-
<<~
|
12
|
+
<<~DESCRIPTION
|
10
13
|
This generates interesting stats about Puppetfiles used in your environments,
|
11
14
|
including whether your Puppetfiles have Ruby code in them.
|
12
|
-
|
15
|
+
DESCRIPTION
|
13
16
|
end
|
14
17
|
|
15
18
|
def self.schema
|
@@ -19,23 +22,23 @@ class Dropsonde::Metrics::Puppetfiles
|
|
19
22
|
{
|
20
23
|
"fields": [
|
21
24
|
{
|
22
|
-
"description":
|
23
|
-
"mode":
|
24
|
-
"name":
|
25
|
-
"type":
|
25
|
+
"description": 'The method name',
|
26
|
+
"mode": 'NULLABLE',
|
27
|
+
"name": 'name',
|
28
|
+
"type": 'STRING',
|
26
29
|
},
|
27
30
|
{
|
28
|
-
"description":
|
29
|
-
"mode":
|
30
|
-
"name":
|
31
|
-
"type":
|
32
|
-
}
|
31
|
+
"description": 'How many times is it used',
|
32
|
+
"mode": 'NULLABLE',
|
33
|
+
"name": 'count',
|
34
|
+
"type": 'INTEGER',
|
35
|
+
},
|
33
36
|
],
|
34
|
-
"description":
|
35
|
-
"mode":
|
36
|
-
"name":
|
37
|
-
"type":
|
38
|
-
}
|
37
|
+
"description": 'Ruby methods used in Puppetfiles.',
|
38
|
+
"mode": 'REPEATED',
|
39
|
+
"name": 'puppetfile_ruby_methods',
|
40
|
+
"type": 'RECORD',
|
41
|
+
},
|
39
42
|
]
|
40
43
|
end
|
41
44
|
|
@@ -43,30 +46,30 @@ class Dropsonde::Metrics::Puppetfiles
|
|
43
46
|
# run just before generating this metric
|
44
47
|
end
|
45
48
|
|
46
|
-
def self.run
|
47
|
-
methods = Dir.entries(Puppet.settings[:environmentpath]).map
|
49
|
+
def self.run(_puppetdb_session = nil)
|
50
|
+
methods = Dir.entries(Puppet.settings[:environmentpath]).map { |entry|
|
48
51
|
puppetfile = File.join(Puppet.settings[:environmentpath], entry, 'Puppetfile')
|
49
52
|
|
50
53
|
next if entry.start_with? '.'
|
51
54
|
next unless File.file? puppetfile
|
52
55
|
|
53
56
|
tokens = Ripper.sexp(File.read(puppetfile)).flatten
|
54
|
-
indices = tokens.map.with_index {|a, i| a == :command ? i : nil}.compact
|
57
|
+
indices = tokens.map.with_index { |a, i| (a == :command) ? i : nil }.compact
|
55
58
|
|
56
|
-
indices.map {|i| tokens[i+2] }
|
57
|
-
|
59
|
+
indices.map { |i| tokens[i + 2] }
|
60
|
+
}.flatten.compact
|
58
61
|
|
59
|
-
methods.reject! {|name| [
|
62
|
+
methods.reject! { |name| %w[mod forge moduledir].include? name }
|
60
63
|
|
61
64
|
methods = methods.uniq.map do |name|
|
62
65
|
{
|
63
|
-
:
|
64
|
-
:
|
66
|
+
name: name,
|
67
|
+
count: methods.count(name),
|
65
68
|
}
|
66
69
|
end
|
67
70
|
|
68
71
|
[
|
69
|
-
{ :
|
72
|
+
{ puppetfile_ruby_methods: methods },
|
70
73
|
]
|
71
74
|
end
|
72
75
|
|
@@ -75,13 +78,13 @@ class Dropsonde::Metrics::Puppetfiles
|
|
75
78
|
# make it easier to write data aggregation queries without access to the
|
76
79
|
# actual private data that users have submitted.
|
77
80
|
[
|
78
|
-
:
|
79
|
-
{:
|
80
|
-
{:
|
81
|
-
{:
|
82
|
-
{:
|
83
|
-
{:
|
84
|
-
{:
|
81
|
+
puppetfile_ruby_methods: [
|
82
|
+
{ name: 'require', count: rand(200) },
|
83
|
+
{ name: 'each', count: rand(200) },
|
84
|
+
{ name: 'puts', count: rand(200) },
|
85
|
+
{ name: 'select', count: rand(200) },
|
86
|
+
{ name: 'reject', count: rand(200) },
|
87
|
+
{ name: 'read', count: rand(200) },
|
85
88
|
].shuffle,
|
86
89
|
]
|
87
90
|
end
|