rvc 1.5.0 → 1.6.0

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.
Files changed (54) hide show
  1. data/README.rdoc +1 -1
  2. data/Rakefile +2 -1
  3. data/VERSION +1 -1
  4. data/bin/rvc +53 -9
  5. data/lib/rvc/completion.rb +57 -19
  6. data/lib/rvc/extensions/ComputeResource.rb +2 -2
  7. data/lib/rvc/extensions/DVPortSetting.rb +108 -0
  8. data/lib/rvc/extensions/Datacenter.rb +19 -4
  9. data/lib/rvc/extensions/Datastore.rb +6 -1
  10. data/lib/rvc/extensions/DistributedVirtualPort.rb +146 -0
  11. data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +274 -10
  12. data/lib/rvc/extensions/DistributedVirtualSwitch.rb +124 -3
  13. data/lib/rvc/extensions/Folder.rb +9 -2
  14. data/lib/rvc/extensions/HostSystem.rb +60 -0
  15. data/lib/rvc/extensions/ManagedEntity.rb +19 -0
  16. data/lib/rvc/extensions/ParaVirtualSCSIController.rb +25 -0
  17. data/lib/rvc/extensions/PerfCounterInfo.rb +26 -0
  18. data/lib/rvc/extensions/PerformanceManager.rb +83 -0
  19. data/lib/rvc/extensions/ResourcePool.rb +21 -0
  20. data/lib/rvc/extensions/VirtualDevice.rb +59 -0
  21. data/lib/rvc/extensions/VirtualDisk.rb +25 -0
  22. data/lib/rvc/extensions/VirtualEthernetCard.rb +32 -0
  23. data/lib/rvc/extensions/VirtualMachine.rb +112 -1
  24. data/lib/rvc/field.rb +122 -0
  25. data/lib/rvc/filesystem_session.rb +20 -0
  26. data/lib/rvc/inventory.rb +35 -12
  27. data/lib/rvc/known_hosts.rb +20 -0
  28. data/lib/rvc/memory_session.rb +20 -0
  29. data/lib/rvc/modules.rb +67 -7
  30. data/lib/rvc/modules/alarm.rb +37 -0
  31. data/lib/rvc/modules/basic.rb +172 -41
  32. data/lib/rvc/modules/cluster.rb +18 -2
  33. data/lib/rvc/modules/core.rb +63 -0
  34. data/lib/rvc/modules/datastore.rb +158 -0
  35. data/lib/rvc/modules/device.rb +275 -0
  36. data/lib/rvc/modules/esxcli.rb +193 -0
  37. data/lib/rvc/modules/find.rb +125 -0
  38. data/lib/rvc/modules/issue.rb +33 -0
  39. data/lib/rvc/modules/perf.rb +284 -0
  40. data/lib/rvc/modules/permissions.rb +20 -0
  41. data/lib/rvc/modules/resource_pool.rb +69 -0
  42. data/lib/rvc/modules/role.rb +23 -3
  43. data/lib/rvc/modules/snapshot.rb +20 -0
  44. data/lib/rvc/modules/vds.rb +605 -0
  45. data/lib/rvc/modules/vim.rb +103 -26
  46. data/lib/rvc/modules/vm.rb +93 -220
  47. data/lib/rvc/modules/vnc.rb +50 -13
  48. data/lib/rvc/option_parser.rb +50 -2
  49. data/lib/rvc/readline-ffi.rb +2 -1
  50. data/lib/rvc/shell.rb +34 -33
  51. data/lib/rvc/util.rb +120 -2
  52. data/test/test_fs.rb +9 -5
  53. data/test/test_metric.rb +79 -0
  54. metadata +33 -3
@@ -0,0 +1,193 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ raw_opts :execute, "Execute an esxcli command"
22
+
23
+ EsxcliCache = TTLCache.new 60
24
+
25
+ def lookup_esxcli host, args
26
+ cur = EsxcliCache[host, :esxcli]
27
+ i = 0
28
+ while i < args.length
29
+ k = args[i]
30
+ if cur.namespaces.member? k
31
+ cur = cur.namespaces[k]
32
+ elsif cur.commands.member? k
33
+ cur = cur.commands[k]
34
+ break
35
+ else
36
+ err "nonexistent esxcli namespace or command #{k.inspect}"
37
+ end
38
+ i += 1
39
+ end
40
+ return cur
41
+ end
42
+
43
+ rvc_completor :execute do |line, args, word, argnum|
44
+ if argnum == 0
45
+ # HostSystem argument
46
+ RVC::Completion.fs_candidates word
47
+ else
48
+ # esxcli namespace/method/arguments
49
+ host = lookup_single! args[0], VIM::HostSystem
50
+ o = lookup_esxcli host, args[1...argnum]
51
+
52
+ case o
53
+ when VIM::EsxcliCommand
54
+ parser = o.option_parser
55
+ candidates = parser.specs.map { |k,v| "--#{v[:long]}" }.sort
56
+ when VIM::EsxcliNamespace
57
+ candidates = o.namespaces.keys + o.commands.keys
58
+ else
59
+ fail "unreachable"
60
+ end
61
+
62
+ candidates.grep(/^#{Regexp.escape word}/).
63
+ map { |x| [x, ' '] }
64
+ end
65
+ end
66
+
67
+ def execute *args
68
+ host_path = args.shift or err "host argument required"
69
+ host = lookup_single! host_path, VIM::HostSystem
70
+ o = lookup_esxcli host, args
71
+
72
+ case o
73
+ when VIM::EsxcliCommand
74
+ cmd = o
75
+ parser = cmd.option_parser
76
+ begin
77
+ opts = parser.parse args
78
+ rescue Trollop::CommandlineError
79
+ err "error: #{$!.message}"
80
+ rescue Trollop::HelpNeeded
81
+ parser.educate
82
+ return
83
+ end
84
+ begin
85
+ opts.reject! { |k,v| !opts.member? :"#{k}_given" }
86
+ result = cmd.call(opts)
87
+ rescue RbVmomi::Fault
88
+ puts "#{$!.message}"
89
+ puts "cause: #{$!.faultCause}" if $!.respond_to? :faultCause and $!.faultCause
90
+ $!.faultMessage.each { |x| puts x } if $!.respond_to? :faultMessage
91
+ $!.errMsg.each { |x| puts "error: #{x}" } if $!.respond_to? :errMsg
92
+ end
93
+ output_formatted cmd, result
94
+ when VIM::EsxcliNamespace
95
+ ns = o
96
+ unless ns.commands.empty?
97
+ puts "Available commands:"
98
+ ns.commands.each do |k,v|
99
+ puts "#{k}: #{v.cli_info.help}"
100
+ end
101
+ puts unless ns.namespaces.empty?
102
+ end
103
+ unless ns.namespaces.empty?
104
+ puts "Available namespaces:"
105
+ ns.namespaces.each do |k,v|
106
+ puts "#{k}: #{v.cli_info.help}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ rvc_alias :execute, :esxcli
113
+ rvc_alias :execute, :x
114
+
115
+ def output_formatted cmd, result
116
+ hints = Hash[cmd.cli_info.hints]
117
+ formatter = hints['formatter']
118
+ formatter = "none" if formatter == ""
119
+ sym = :"output_formatted_#{formatter}"
120
+ if respond_to? sym
121
+ send sym, result, cmd.cli_info, hints
122
+ else
123
+ puts "Unknown formatter #{formatter.inspect}"
124
+ pp result
125
+ end
126
+ end
127
+
128
+ def output_formatted_none result, info, hints
129
+ pp result if result != true
130
+ end
131
+
132
+ def output_formatted_simple result, info, hints
133
+ case result
134
+ when Array
135
+ result.each do |r|
136
+ output_formatted_simple r, info, hints
137
+ puts
138
+ end
139
+ when RbVmomi::BasicTypes::DataObject
140
+ prop_descs = result.class.ancestors.
141
+ take_while { |x| x != RbVmomi::BasicTypes::DataObject &&
142
+ x != VIM::DynamicData }.
143
+ map(&:props_desc).flatten(1)
144
+ prop_descs.each do |desc|
145
+ print "#{desc['name']}: "
146
+ pp result.send desc['name']
147
+ end
148
+ else
149
+ pp result
150
+ end
151
+ end
152
+
153
+ def table_key str
154
+ str.downcase.gsub(/[^\w\d_]/, '')
155
+ end
156
+
157
+ def output_formatted_table result, info, hints
158
+ if result.empty?
159
+ puts "Empty result"
160
+ return
161
+ end
162
+
163
+ columns =
164
+ if hints.member? 'table-columns'
165
+ hints['table-columns'].split ','
166
+ elsif k = hints.keys.find { |k| k =~ /^fields:/ }
167
+ hints[k].split ','
168
+ else []
169
+ end
170
+ ordering = columns.map { |x| table_key x }
171
+
172
+ units = Hash[hints.select { |k,v| k =~ /^units:/ }.map { |k,v| [table_key(k.match(/[^.]+$/).to_s), v] }]
173
+
174
+ table = Terminal::Table.new :headings => columns
175
+ result.each do |r|
176
+ row = []
177
+ r.class.full_props_desc.each do |desc|
178
+ name = desc['name']
179
+ key = table_key name
180
+ next unless idx = ordering.index(key)
181
+ val = r.send name
182
+ unit = units[key]
183
+ row[idx] =
184
+ case unit
185
+ when nil then val
186
+ when '%' then "#{val}#{unit}"
187
+ else "#{val} #{unit}"
188
+ end
189
+ end
190
+ table.add_row row
191
+ end
192
+ puts table
193
+ end
@@ -0,0 +1,125 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ opts :find do
22
+ summary "Find objects matching certain criteria"
23
+ arg :args, "Paths or +terms", :required => false, :multi => true
24
+ opt :mark, "Store results in an aggregate mark", :default => 'A'
25
+ opt :type, "Type of objects to return", :multi => true, :type => :string
26
+ end
27
+
28
+ rvc_alias :find
29
+
30
+ def find args, opts
31
+ args = args.group_by do |arg|
32
+ case arg
33
+ when /^\+/ then :term
34
+ else :root
35
+ end
36
+ end
37
+
38
+ results = find_items args[:term], args[:root], opts[:type]
39
+
40
+ CMD.mark.mark opts[:mark], results
41
+
42
+ i = 0
43
+ cwd = $shell.fs.cur.rvc_path_str
44
+ cwd_prefix = /^#{Regexp.escape cwd}\//
45
+ results.each do |r|
46
+ puts "#{i} #{r.rvc_path_str.gsub(cwd_prefix, '')}"
47
+ CMD.mark.mark i.to_s, [r]
48
+ i += 1
49
+ end
50
+ end
51
+
52
+ def find_items terms = nil, roots = nil, types = nil
53
+ roots ||= ['.']
54
+ terms ||= []
55
+
56
+ types.each { |t| terms << "+type=#{t}" }
57
+ roots = roots.map { |x| lookup x }.flatten(1)
58
+ terms = terms.map { |x| term x[1..-1] }
59
+
60
+ candidates = leaves roots, types
61
+ results = candidates.select { |r| terms.all? { |t| t[r] } }
62
+ end
63
+
64
+ def leaves roots, types = []
65
+ leaves = Set.new
66
+ new_nodes = roots
67
+ while not new_nodes.empty?
68
+ nodes = new_nodes
69
+ new_nodes = Set.new
70
+ nodes.each do |node|
71
+ if (node.class.traverse? or roots.member? node) and
72
+ (types & (node.field('type') || [])).empty?
73
+ node.children.each { |k,v| v.rvc_link(node, k); new_nodes << v }
74
+ else
75
+ leaves << node
76
+ end
77
+ end
78
+ end
79
+ leaves
80
+ end
81
+
82
+ def term x
83
+ case x
84
+ when /^([\w.]+)(!)?(>=|<=|=|>|<|~)/
85
+ lhs = $1
86
+ negate = $2 != nil
87
+ op = $3
88
+ rhs = $'
89
+ lambda do |o|
90
+ a = o.field(lhs)
91
+ a = [a].compact unless a.is_a? Enumerable
92
+ return negate if a.empty?
93
+ type = a.first.class
94
+ fail "all objects in field #{lhs.inspect} must have the same type" unless a.all? { |x| x.is_a? type }
95
+ b = coerce_str type, rhs
96
+ a.any? do |x|
97
+ case op
98
+ when '=' then x == b
99
+ when '>' then x > b
100
+ when '>=' then x >= b
101
+ when '<' then x < b
102
+ when '<=' then x <= b
103
+ when '~' then x =~ Regexp.new(b)
104
+ end
105
+ end ^ negate
106
+ end
107
+ when /^\w+$/
108
+ lambda { |o| o.field(x) }
109
+ else
110
+ err "failed to parse expression #{x.inspect}"
111
+ end
112
+ end
113
+
114
+ def coerce_str type, v
115
+ fail "expected String, got #{v.class}" unless v.is_a? String
116
+ if type <= Integer then v.to_i
117
+ elsif type == Float then v.to_f
118
+ elsif type == TrueClass or type == FalseClass then v == 'true'
119
+ elsif type == NilClass then v == 'nil' ? nil : !nil
120
+ elsif v == 'nil' then nil
121
+ elsif type == String then v
122
+ elsif type.respond_to? :parse then type.parse(v)
123
+ else fail "unexpected coercion type #{type}"
124
+ end
125
+ end
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ opts :show do
22
+ summary "Show issues on the given entities"
23
+ arg :entity, nil, :lookup => VIM::ManagedEntity, :multi => true
24
+ end
25
+
26
+ def show objs
27
+ issues = objs.map(&:configIssue).flatten.uniq
28
+ issues.each do |issue|
29
+ puts issue.fullFormattedMessage
30
+ end
31
+ end
32
+
33
+ rvc_alias :show, :issues
@@ -0,0 +1,284 @@
1
+ begin
2
+ require 'gnuplot'
3
+ RVC::HAVE_GNUPLOT = true
4
+ rescue LoadError
5
+ RVC::HAVE_GNUPLOT = false
6
+ end
7
+
8
+ TIMEFMT = '%Y-%m-%dT%H:%M:%SZ'
9
+
10
+ DISPLAY_TIMEFMT = {
11
+ :realtime => '%H:%M',
12
+ 1 => '%H:%M',
13
+ 2 => '%m/%d',
14
+ 3 => '%m/%d',
15
+ 4 => '%Y/%m/%d',
16
+ }
17
+
18
+ opts :plot do
19
+ summary "Plot a graph of the given performance counters"
20
+ arg :counter, "Counter name"
21
+ arg :obj, "", :lookup => VIM::ManagedEntity, :multi => true
22
+ opt :terminal, "Display plot on terminal", :default => ENV['DISPLAY'].nil?
23
+ opt :start, "Start time", :type => :date, :short => 's'
24
+ opt :end, "End time", :type => :date, :short => 'e'
25
+ end
26
+
27
+ def plot counter_name, objs, opts
28
+ err "The gnuplot gem is not installed" unless RVC::HAVE_GNUPLOT
29
+ vim = single_connection objs
30
+ pm = vim.serviceContent.perfManager
31
+ group_key, counter_key, rollup_type = counter_name.split('.', 3)
32
+
33
+ now = Time.now
34
+ opts[:end] ||= now
35
+ opts[:start] ||= opts[:end] - 1800
36
+
37
+ err "end time is in the future" unless opts[:end] <= Time.now
38
+ ago = now - opts[:start]
39
+
40
+ if ago < 3600
41
+ #puts "Using realtime interval, period = 20 seconds."
42
+ interval_id = 20
43
+ display_timefmt = DISPLAY_TIMEFMT[:realtime]
44
+ else
45
+ intervals = pm.historicalInterval
46
+ interval = intervals.find { |x| now - x.length < opts[:start] }
47
+ err "start time is too long ago" unless interval
48
+ #puts "Using historical interval #{interval.name.inspect}, period = #{interval.samplingPeriod} seconds."
49
+ interval_id = interval.samplingPeriod
50
+ display_timefmt = DISPLAY_TIMEFMT[interval.key]
51
+ end
52
+
53
+ all_counters = Hash[pm.perfCounter.map { |x| [x.key, x] }]
54
+
55
+ metrics = pm.QueryAvailablePerfMetric(
56
+ :entity => objs.first,
57
+ :interval => interval_id)
58
+
59
+ metric = metrics.find do |metric|
60
+ counter = all_counters[metric.counterId]
61
+ counter.groupInfo.key == group_key &&
62
+ counter.nameInfo.key == counter_key &&
63
+ counter.rollupType == rollup_type
64
+ end or err "no such metric"
65
+ counter = all_counters[metric.counterId]
66
+
67
+ specs = objs.map do |obj|
68
+ {
69
+ :entity => obj,
70
+ :metricId => [metric],
71
+ :intervalId => interval_id,
72
+ :startTime => opts[:start],
73
+ :endTime => opts[:end],
74
+ :format => 'csv',
75
+ }
76
+ end
77
+
78
+ with_gnuplot(true) do |gp|
79
+ plot = Gnuplot::Plot.new(gp) do |plot|
80
+ if objs.size == 1
81
+ plot.title "#{counter_name} on #{objs[0].name}"
82
+ else
83
+ plot.title counter_name
84
+ end
85
+
86
+ plot.ylabel counter.unitInfo.label
87
+ plot.xlabel "Time"
88
+ plot.terminal 'dumb' if opts[:terminal]
89
+
90
+ plot.set 'xdata', 'time'
91
+ plot.set 'format', "x '#{display_timefmt}'"
92
+ plot.set 'timefmt', TIMEFMT.inspect
93
+
94
+ if counter.unitInfo.key == 'percent'
95
+ plot.set 'yrange', '[0:100]'
96
+ end
97
+
98
+ plot.data = retrieve_datasets pm, counter, specs
99
+ end
100
+ gp.puts
101
+ end
102
+ end
103
+
104
+ def retrieve_datasets pm, counter, specs
105
+ results = pm.QueryPerf(:querySpec => specs)
106
+ datasets = results.map do |result|
107
+ times = result.sampleInfoCSV.split(',').select { |x| x['T'] }
108
+ data = result.value[0].value.split(',').map(&:to_i)
109
+
110
+ if counter.unitInfo.key == 'percent'
111
+ times.length.times do |i|
112
+ times[i] = data[i] = nil if data[i] < 0
113
+ end
114
+
115
+ times.compact!
116
+ data.compact!
117
+ data.map! { |x| x/100.0 }
118
+ end
119
+
120
+ Gnuplot::DataSet.new([times, data]) do |ds|
121
+ ds.notitle if specs.size == 1
122
+ ds.with = "lines"
123
+ ds.using = '1:2'
124
+ ds.title = result.entity.name
125
+ end
126
+ end
127
+ end
128
+
129
+ def with_gnuplot persist
130
+ if $rvc_gnuplot
131
+ yield $rvc_gnuplot
132
+ else
133
+ cmd = Gnuplot.gnuplot(persist) or err 'gnuplot not found'
134
+ $rvc_gnuplot = IO::popen(cmd, "w")
135
+ begin
136
+ yield $rvc_gnuplot
137
+ ensure
138
+ gp = $rvc_gnuplot
139
+ $rvc_gnuplot = nil
140
+ gp.close
141
+ end
142
+ end
143
+ end
144
+
145
+
146
+ opts :watch do
147
+ summary "Watch a graph of the given performance counters"
148
+ arg :counter, "Counter name"
149
+ arg :objs, "", :lookup => VIM::ManagedEntity, :multi => true
150
+ opt :interval, "Seconds between updates", :short => 'i', :default => 10
151
+ opt :terminal, "Display plot on terminal", :default => ENV['DISPLAY'].nil?
152
+ end
153
+
154
+ def watch counter_name, objs, opts
155
+ err "The gnuplot gem is not installed" unless RVC::HAVE_GNUPLOT
156
+ with_gnuplot false do |gp|
157
+ puts "Press Ctrl-C to stop."
158
+ while true
159
+ plot counter_name, objs, :terminal => opts[:terminal]
160
+ sleep opts[:interval]
161
+ if opts[:terminal]
162
+ $stdout.write "\e[25A"
163
+ $stdout.flush
164
+ end
165
+ end
166
+ end
167
+ rescue Interrupt
168
+ end
169
+
170
+
171
+ opts :counters do
172
+ summary "Display available perf counters"
173
+ arg :obj, nil, :lookup => VIM::ManagedEntity
174
+ end
175
+
176
+ def counters obj
177
+ pm = obj._connection.serviceContent.perfManager
178
+ interval = pm.provider_summary(obj).refreshRate
179
+ if interval == -1
180
+ # Object does not support real time stats
181
+ interval = nil
182
+ end
183
+
184
+ metrics = pm.QueryAvailablePerfMetric(
185
+ :entity => obj,
186
+ :intervalId => interval)
187
+ available_counters = metrics.map(&:counterId).uniq.
188
+ map { |id| pm.perfcounter_idhash[id] }
189
+
190
+ groups = available_counters.group_by { |counter| counter.groupInfo }
191
+ groups.sort_by { |group,counters| group.key }.each do |group,counters|
192
+ puts "#{group.label}:"
193
+ counters.sort_by(&:pretty_name).each do |counter|
194
+ puts " #{counter.pretty_name}: #{counter.nameInfo.label} (#{counter.unitInfo.label})"
195
+ end
196
+ end
197
+ end
198
+
199
+
200
+ opts :counter do
201
+ summary "Retrieve detailed information about a perf counter"
202
+ arg :metric, nil, :type => :string
203
+ arg :obj, nil, :lookup => VIM::ManagedEntity, :required => false
204
+ end
205
+
206
+ def counter counter_name, obj
207
+ vim = obj ? obj._connection : lookup_single('~@')
208
+ pm = vim.serviceContent.perfManager
209
+ counter = pm.perfcounter_hash[counter_name] or err "no such counter #{counter_name.inspect}"
210
+
211
+ intervals = pm.historicalInterval
212
+ active_intervals = lambda { |level| intervals.select { |x| x.level >= level } }
213
+ active_intervals_text = lambda do |level|
214
+ xs = active_intervals[level]
215
+ xs.empty? ? 'none' : xs.map(&:name).map(&:inspect) * ', '
216
+ end
217
+
218
+ puts "Label: #{counter.nameInfo.label}"
219
+ puts "Summary: #{counter.nameInfo.summary}"
220
+ puts "Unit label: #{counter.unitInfo.label}"
221
+ puts "Unit summary: #{counter.unitInfo.summary}"
222
+ puts "Rollup type: #{counter.rollupType}"
223
+ puts "Stats type: #{counter.statsType}"
224
+ puts "Level: #{counter.level}"
225
+ puts " Enabled in intervals: #{active_intervals_text[counter.level]}"
226
+ puts "Per-device level: #{counter.perDeviceLevel}"
227
+ puts " Enabled in intervals: #{active_intervals_text[counter.perDeviceLevel]}"
228
+
229
+ if obj
230
+ interval = pm.provider_summary(obj).refreshRate
231
+ if interval == -1
232
+ # Object does not support real time stats
233
+ interval = nil
234
+ end
235
+ puts "Real time interval: #{interval || 'N/A'}"
236
+ metrics = pm.QueryAvailablePerfMetric(:entity => obj, :intervalId => interval)
237
+ metrics.select! { |x| x.counterId == counter.key }
238
+ instances = metrics.map(&:instance).reject(&:empty?)
239
+ unless instances.empty?
240
+ puts "Instances:"
241
+ instances.map do |x|
242
+ puts " #{x}"
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ opts :stats do
249
+ summary "Retrieve performance stats for given object"
250
+ arg :metrics, nil, :type => :string
251
+ arg :obj, nil, :multi => true, :lookup => VIM::ManagedEntity
252
+ opt :samples, "Number of samples to retrieve", :type => :int
253
+ end
254
+
255
+ def stats metrics, objs, opts
256
+ metrics = metrics.split(",")
257
+ obj = objs.first
258
+ pm = obj._connection.serviceContent.perfManager
259
+ interval = pm.provider_summary(obj).refreshRate
260
+ start_time = nil
261
+ if interval == -1
262
+ # Object does not support real time stats
263
+ interval = 300
264
+ start_time = Time.now - 300 * 5
265
+ end
266
+ stat_opts = {
267
+ :interval => interval,
268
+ :startTime => start_time,
269
+ }
270
+ stat_opts[:max_samples] = opts[:samples] if opts[:samples]
271
+ res = pm.retrieve_stats objs, metrics, stat_opts
272
+
273
+ table = Terminal::Table.new
274
+ table.add_row ['Object', 'Metric', 'Values', 'Unit']
275
+ table.add_separator
276
+ objs.each do |obj|
277
+ metrics.each do |metric|
278
+ stat = res[obj][:metrics][metric]
279
+ metric_info = pm.perfcounter_hash[metric]
280
+ table.add_row([obj.name, metric, stat.join(','), metric_info.unitInfo.label])
281
+ end
282
+ end
283
+ puts table
284
+ end