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.
- data/README.rdoc +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/rvc +53 -9
- data/lib/rvc/completion.rb +57 -19
- data/lib/rvc/extensions/ComputeResource.rb +2 -2
- data/lib/rvc/extensions/DVPortSetting.rb +108 -0
- data/lib/rvc/extensions/Datacenter.rb +19 -4
- data/lib/rvc/extensions/Datastore.rb +6 -1
- data/lib/rvc/extensions/DistributedVirtualPort.rb +146 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +274 -10
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +124 -3
- data/lib/rvc/extensions/Folder.rb +9 -2
- data/lib/rvc/extensions/HostSystem.rb +60 -0
- data/lib/rvc/extensions/ManagedEntity.rb +19 -0
- data/lib/rvc/extensions/ParaVirtualSCSIController.rb +25 -0
- data/lib/rvc/extensions/PerfCounterInfo.rb +26 -0
- data/lib/rvc/extensions/PerformanceManager.rb +83 -0
- data/lib/rvc/extensions/ResourcePool.rb +21 -0
- data/lib/rvc/extensions/VirtualDevice.rb +59 -0
- data/lib/rvc/extensions/VirtualDisk.rb +25 -0
- data/lib/rvc/extensions/VirtualEthernetCard.rb +32 -0
- data/lib/rvc/extensions/VirtualMachine.rb +112 -1
- data/lib/rvc/field.rb +122 -0
- data/lib/rvc/filesystem_session.rb +20 -0
- data/lib/rvc/inventory.rb +35 -12
- data/lib/rvc/known_hosts.rb +20 -0
- data/lib/rvc/memory_session.rb +20 -0
- data/lib/rvc/modules.rb +67 -7
- data/lib/rvc/modules/alarm.rb +37 -0
- data/lib/rvc/modules/basic.rb +172 -41
- data/lib/rvc/modules/cluster.rb +18 -2
- data/lib/rvc/modules/core.rb +63 -0
- data/lib/rvc/modules/datastore.rb +158 -0
- data/lib/rvc/modules/device.rb +275 -0
- data/lib/rvc/modules/esxcli.rb +193 -0
- data/lib/rvc/modules/find.rb +125 -0
- data/lib/rvc/modules/issue.rb +33 -0
- data/lib/rvc/modules/perf.rb +284 -0
- data/lib/rvc/modules/permissions.rb +20 -0
- data/lib/rvc/modules/resource_pool.rb +69 -0
- data/lib/rvc/modules/role.rb +23 -3
- data/lib/rvc/modules/snapshot.rb +20 -0
- data/lib/rvc/modules/vds.rb +605 -0
- data/lib/rvc/modules/vim.rb +103 -26
- data/lib/rvc/modules/vm.rb +93 -220
- data/lib/rvc/modules/vnc.rb +50 -13
- data/lib/rvc/option_parser.rb +50 -2
- data/lib/rvc/readline-ffi.rb +2 -1
- data/lib/rvc/shell.rb +34 -33
- data/lib/rvc/util.rb +120 -2
- data/test/test_fs.rb +9 -5
- data/test/test_metric.rb +79 -0
- 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
|