dropsonde 0.0.2 → 0.0.6
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/README.md +20 -7
- data/bin/dropsonde +60 -17
- data/lib/dropsonde/cache.rb +42 -31
- data/lib/dropsonde/metrics/dependencies.rb +46 -25
- data/lib/dropsonde/metrics/environments.rb +53 -0
- data/lib/dropsonde/metrics/modules.rb +86 -53
- data/lib/dropsonde/metrics/platforms.rb +126 -0
- data/lib/dropsonde/metrics/puppetfiles.rb +44 -25
- data/lib/dropsonde/metrics.rb +116 -76
- data/lib/dropsonde/monkeypatches.rb +12 -11
- data/lib/dropsonde/version.rb +3 -1
- data/lib/dropsonde.rb +75 -24
- metadata +6 -4
@@ -1,3 +1,6 @@
|
|
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
|
@@ -5,10 +8,10 @@ class Dropsonde::Metrics::Modules
|
|
5
8
|
end
|
6
9
|
|
7
10
|
def self.description
|
8
|
-
<<~
|
11
|
+
<<~DESCRIPTION
|
9
12
|
This group of metrics exports name & version information about the public
|
10
13
|
modules installed in all environments, ignoring private modules.
|
11
|
-
|
14
|
+
DESCRIPTION
|
12
15
|
end
|
13
16
|
|
14
17
|
def self.schema
|
@@ -18,49 +21,49 @@ class Dropsonde::Metrics::Modules
|
|
18
21
|
{
|
19
22
|
"fields": [
|
20
23
|
{
|
21
|
-
"description":
|
22
|
-
"mode":
|
23
|
-
"name":
|
24
|
-
"type":
|
24
|
+
"description": 'The module name',
|
25
|
+
"mode": 'NULLABLE',
|
26
|
+
"name": 'name',
|
27
|
+
"type": 'STRING',
|
25
28
|
},
|
26
29
|
{
|
27
|
-
"description":
|
28
|
-
"mode":
|
29
|
-
"name":
|
30
|
-
"type":
|
30
|
+
"description": 'The module slug (author-name)',
|
31
|
+
"mode": 'NULLABLE',
|
32
|
+
"name": 'slug',
|
33
|
+
"type": 'STRING',
|
31
34
|
},
|
32
35
|
{
|
33
|
-
"description":
|
34
|
-
"mode":
|
35
|
-
"name":
|
36
|
-
"type":
|
37
|
-
}
|
36
|
+
"description": 'The module version',
|
37
|
+
"mode": 'NULLABLE',
|
38
|
+
"name": 'version',
|
39
|
+
"type": 'STRING',
|
40
|
+
},
|
38
41
|
],
|
39
|
-
"description":
|
40
|
-
"mode":
|
41
|
-
"name":
|
42
|
-
"type":
|
42
|
+
"description": 'List of modules in all environments.',
|
43
|
+
"mode": 'REPEATED',
|
44
|
+
"name": 'modules',
|
45
|
+
"type": 'RECORD',
|
43
46
|
},
|
44
47
|
{
|
45
48
|
"fields": [
|
46
49
|
{
|
47
|
-
"description":
|
48
|
-
"mode":
|
49
|
-
"name":
|
50
|
-
"type":
|
50
|
+
"description": 'The class name',
|
51
|
+
"mode": 'NULLABLE',
|
52
|
+
"name": 'name',
|
53
|
+
"type": 'STRING',
|
51
54
|
},
|
52
55
|
{
|
53
|
-
"description":
|
54
|
-
"mode":
|
55
|
-
"name":
|
56
|
-
"type":
|
57
|
-
}
|
56
|
+
"description": 'How many nodes it is declared on',
|
57
|
+
"mode": 'NULLABLE',
|
58
|
+
"name": 'count',
|
59
|
+
"type": 'INTEGER',
|
60
|
+
},
|
58
61
|
],
|
59
|
-
"description":
|
60
|
-
"mode":
|
61
|
-
"name":
|
62
|
-
"type":
|
63
|
-
}
|
62
|
+
"description": 'List of classes and counts in all environments.',
|
63
|
+
"mode": 'REPEATED',
|
64
|
+
"name": 'classes',
|
65
|
+
"type": 'RECORD',
|
66
|
+
},
|
64
67
|
]
|
65
68
|
end
|
66
69
|
|
@@ -68,43 +71,73 @@ class Dropsonde::Metrics::Modules
|
|
68
71
|
# run just before generating this metric
|
69
72
|
end
|
70
73
|
|
71
|
-
def self.run
|
74
|
+
def self.run(puppetdb_session = nil)
|
72
75
|
# 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|
|
76
|
+
environments = Puppet.lookup(:environments).list.map { |e| e.name }
|
77
|
+
modules = environments.map { |env|
|
78
|
+
Puppet.lookup(:environments).get(env).modules.map do |mod|
|
76
79
|
next unless mod.forge_module?
|
77
80
|
|
78
81
|
{
|
79
|
-
:
|
80
|
-
:
|
81
|
-
:
|
82
|
+
name: mod.name,
|
83
|
+
slug: mod.forge_slug,
|
84
|
+
version: mod.version,
|
82
85
|
}
|
83
86
|
end
|
84
|
-
|
87
|
+
}.flatten.compact.uniq
|
85
88
|
|
86
|
-
if
|
89
|
+
if puppetdb_session
|
87
90
|
# classes and how many nodes they're enforced on
|
88
|
-
results =
|
89
|
-
'resources[certname, type, title] { type = "Class" }'
|
90
|
-
).data
|
91
|
+
results = puppetdb_session.puppet_db.request('', 'resources[type, title] { type = "Class" }').data
|
91
92
|
|
92
|
-
# select only classes from public modules
|
93
|
-
|
94
|
-
|
93
|
+
# select only classes from public modules.
|
94
|
+
# Use uniq to reduce the iteration over very large datasets
|
95
|
+
classes = results.uniq.map { |klass|
|
96
|
+
title = klass['title']
|
97
|
+
modname = title.split('::').first.downcase
|
98
|
+
next unless modules.find { |mod| mod[:name] == modname }
|
95
99
|
|
96
100
|
{
|
97
|
-
:
|
98
|
-
:
|
101
|
+
name: title,
|
102
|
+
count: results.count { |row| row['title'] == title },
|
99
103
|
}
|
100
|
-
|
104
|
+
}.compact
|
101
105
|
else
|
102
106
|
classes = []
|
103
107
|
end
|
104
108
|
|
105
109
|
[
|
106
|
-
{ :
|
107
|
-
{ :
|
110
|
+
{ modules: modules },
|
111
|
+
{ classes: classes },
|
112
|
+
]
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.example
|
116
|
+
# this method is used to generate a table filled with randomized data to
|
117
|
+
# make it easier to write data aggregation queries without access to the
|
118
|
+
# actual private data that users have submitted.
|
119
|
+
|
120
|
+
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']
|
121
|
+
classes = ['', '::Config', '::Service', '::Server', '::Client', '::Packages']
|
122
|
+
dropsonde_cache = Dropsonde::Cache.new('foo', 7, true)
|
123
|
+
[
|
124
|
+
modules: dropsonde_cache.modules
|
125
|
+
.sample(rand(100))
|
126
|
+
.map do |item|
|
127
|
+
{
|
128
|
+
name: item.split('-').last,
|
129
|
+
slug: item,
|
130
|
+
version: versions.sample,
|
131
|
+
}
|
132
|
+
end,
|
133
|
+
classes: dropsonde_cache.modules
|
134
|
+
.sample(rand(500))
|
135
|
+
.map do |item|
|
136
|
+
{
|
137
|
+
name: item.split('-').last.capitalize + classes.sample,
|
138
|
+
count: rand(750),
|
139
|
+
}
|
140
|
+
end,
|
108
141
|
]
|
109
142
|
end
|
110
143
|
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# platforms plugin
|
4
|
+
class Dropsonde::Metrics::Platforms
|
5
|
+
def self.initialize_platforms
|
6
|
+
# require any libraries needed here -- no need to load puppet; it's already initialized
|
7
|
+
# All plugins are initialized before any metrics are generated.
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.description
|
11
|
+
<<~DESCRIPTION
|
12
|
+
This group of metrics generates usage patterns by platform.
|
13
|
+
Currently implemented is a list of classes, the platforms
|
14
|
+
they are declared on, and a count of each combination.
|
15
|
+
DESCRIPTION
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.schema
|
19
|
+
# return an array of hashes of a partial schema to be merged into the complete schema
|
20
|
+
# See https://cloud.google.com/bigquery/docs/schemas#specifying_a_json_schema_file
|
21
|
+
[
|
22
|
+
{
|
23
|
+
"fields": [
|
24
|
+
{
|
25
|
+
"description": 'The class name name',
|
26
|
+
"mode": 'NULLABLE',
|
27
|
+
"name": 'name',
|
28
|
+
"type": 'STRING',
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"description": 'The osfamily of the node the class is declared on',
|
32
|
+
"mode": 'NULLABLE',
|
33
|
+
"name": 'platform',
|
34
|
+
"type": 'STRING',
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"description": 'The number of time this combination is declared',
|
38
|
+
"mode": 'NULLABLE',
|
39
|
+
"name": 'count',
|
40
|
+
"type": 'INTEGER',
|
41
|
+
},
|
42
|
+
],
|
43
|
+
"description": "List of all classes in the infrastructure and platforms they're declared on.",
|
44
|
+
"mode": 'REPEATED',
|
45
|
+
"name": 'class_platforms',
|
46
|
+
"type": 'RECORD',
|
47
|
+
},
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.setup
|
52
|
+
# run just before generating this metric
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.run(puppetdb_session = nil)
|
56
|
+
# skip this metric if we don't have an active PuppetDB connection
|
57
|
+
return unless puppetdb_session
|
58
|
+
|
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
|
61
|
+
|
62
|
+
# All public Forge modules that are installed.
|
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
|
68
|
+
|
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
|
+
}
|
82
|
+
}.compact
|
83
|
+
|
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
|
89
|
+
|
90
|
+
[
|
91
|
+
class_platforms: data,
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.example
|
96
|
+
# this method is used to generate a table filled with randomized data to
|
97
|
+
# make it easier to write data aggregation queries without access to the
|
98
|
+
# actual private data that users have submitted.
|
99
|
+
|
100
|
+
platforms = %w[RedHat Debian Windows Suse FreeBSD Darwin Archlinux AIX]
|
101
|
+
classes = ['', '::Config', '::Service', '::Server', '::Client', '::Packages']
|
102
|
+
|
103
|
+
dropsonde_cache = Dropsonde::Cache.new('foo', 7, true)
|
104
|
+
data = dropsonde_cache.modules
|
105
|
+
.sample(rand(35))
|
106
|
+
.map { |item|
|
107
|
+
name = item.split('-').last.capitalize + classes.sample
|
108
|
+
|
109
|
+
rand(5).times.map do
|
110
|
+
{
|
111
|
+
name: name,
|
112
|
+
platform: platforms.sample,
|
113
|
+
count: rand(1000),
|
114
|
+
}
|
115
|
+
end
|
116
|
+
}.flatten
|
117
|
+
|
118
|
+
[
|
119
|
+
class_platforms: data.uniq,
|
120
|
+
]
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.cleanup
|
124
|
+
# run just after generating this metric
|
125
|
+
end
|
126
|
+
end
|
@@ -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,46 @@ 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 },
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.example
|
77
|
+
# this method is used to generate a table filled with randomized data to
|
78
|
+
# make it easier to write data aggregation queries without access to the
|
79
|
+
# actual private data that users have submitted.
|
80
|
+
[
|
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) },
|
88
|
+
].shuffle,
|
70
89
|
]
|
71
90
|
end
|
72
91
|
|