-
Notifications
You must be signed in to change notification settings - Fork 1
/
tripleo_perf_profile.py
executable file
·224 lines (199 loc) · 9.11 KB
/
tripleo_perf_profile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/env python
# Filename: tripleo_perf_profile.py
# Description: Demo part of gerrit 423304
# Supported Langauge(s): Python 2.7.x
# Time-stamp: <2017-01-26 14:33:36 jfulton>
# -------------------------------------------------------
# Demo part of https://review.openstack.org/#/c/423304
#
# I. From perf_profiles.yaml get the profile selected by the user
# II. From Heat env files get the requested OSD count
# III. From Ironic get Hardware/CPU specs from tagged Ceph node
# IV. Derive THT parameters based on the collected information
# V. User may then `openstack overcloud deploy -e derived.yaml`
#
# Tested on stack@undercloud deployed by tripleo-quickstart
# -------------------------------------------------------
def ironic_download(match=""):
"""
Downloads the ironic introspection data from Swift.
Downloads all objects unless a match is specified.
If match is specified, then only the inspection data
of the ironic node that has a 'node' or 'profile'
containing match is downloaded.
Stores the ironic data in directory called 'hardware'
Returns the ironic_id that matched the match argument
Calls external shell script (quick hack for POC)
"""
use_example_hardware_dir = True
## Either import your own ironic introspection data
## Or just use the example hardware directory which
## came from introspecing 4 R730XDs + 3 M630s
if use_example_hardware_dir:
# this ironic_id is from a Ceph node in sample data
ironic_id = '1432cc77-ce47-4def-ade8-6be30542036e'
else:
from subprocess import check_output
ironic_id = check_output(['bash', 'ironic_download.sh', match])
return ironic_id
def get_files(directory, filter='(.*?)'):
"""
Returns list of files in $directory matching regex $filter
filter default is '(.*?)', which will match anything
"""
import re
filter_re = re.compile(filter)
files = []
from os import walk
for (dirpath, dirnames, filenames) in walk(directory):
for f in filenames:
if filter_re.search(f):
files.append(dirpath + '/' + f)
return files
def files_to_objects(files):
"""
Returns list of objects parsed from
a list of paths to yaml $files in CWD
"""
objs = []
import yaml
for f in files:
with open(f, 'r') as stream:
try:
objs.append(yaml.load(stream))
except yaml.YAMLError as exc:
print exc
return objs
def print_objects(objects):
"""Print list of objects indented and in color for debugging"""
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import Terminal256Formatter
from pprint import pformat
for obj in objects:
print highlight(pformat(obj), PythonLexer(), Terminal256Formatter())
print "----------------------"
def find(key, dictionary):
"""
Find all occurences of $key in nested dictionaries and lists
variation of https://gist.github.com/douglasmiranda/5127251
"""
if isinstance(dictionary, dict):
for k, v in dictionary.iteritems():
if k == key:
yield v
elif isinstance(v, dict):
for result in find(key, v):
yield result
elif isinstance(v, list):
for d in v:
for result in find(key, d):
yield result
def hci_nova_mem_cpu_calc(mem_mb, cores, osds, average_guest_size_mb,
average_guest_util_percent, profile_name):
"""
Returns a dictionary of derived Heat environment parameters to be
converted into yaml conforming to a Heat environment file.
This is an example of one useful set of derived parameters for HCI
but the real implementation of the spec should allow others to plugin
their own rules for deriving parameters based on deployment data.
The formulas below are based on lab tests of HCI and recommended
by Red Hat Performance Engineering. More details in:
https://access.redhat.com/articles/2861641
"""
hci_nova_limits = {} # dictonary to capture derived nova.conf params
msg = "# This Heat Environment file generated by tripleo_perf_profiles\n"
msg += "# The HCI profile limits Nova resources to avoid taking resources from Ceph\n"
msg += "# These derivations are based on load testing HCI to prevent contention\n"
# these vars should probably be moved to perf_profiles.yaml
MB_per_GB = 1024
GB_per_OSD = 3
GB_overhead_per_guest = 0.5 # based on measurement in test environment
cores_per_OSD = 1.0 # may be a little low in I/O intensive workloads
average_guest_util = average_guest_util_percent / 100.0
average_guest_size = average_guest_size_mb / float(MB_per_GB)
mem = mem_mb / float(MB_per_GB)
msg += "#\n# The profile requested was: '%s'\n" % profile_name
msg += "# - Average guest memory size in GB: %d \n" % average_guest_size
msg += "# - Average guest CPU utilization: %.0f%% \n" % average_guest_util_percent
msg += "#\n# Hardware and Deployment requested: \n"
msg += "# - Total host RAM in GB: %d \n" % mem
msg += "# - Total host cores: %d \n" % cores
msg += "# - Ceph OSDs per host: %d \n" % osds
# calculate operating parameters based on memory constraints only
left_over_mem = mem - (GB_per_OSD * osds)
number_of_guests = int(left_over_mem /
(average_guest_size + GB_overhead_per_guest))
nova_reserved_mem_MB = MB_per_GB * int(left_over_mem -
(number_of_guests * GB_overhead_per_guest))
nonceph_cores = cores - (cores_per_OSD * osds)
guest_vCPUs = nonceph_cores / float(average_guest_util)
cpu_allocation_ratio = guest_vCPUs / cores
msg += "#\n# Anticipated capacities: \n"
msg += "# - number of guests allowed based on memory = %d \n" % number_of_guests
msg += "# - number of guest vCPUs allowed = %d\n" % int(guest_vCPUs)
if nova_reserved_mem_MB < (mem * 0.5):
msg += "# WARNING: you do not have enough memory to run hyperconverged \n"
if cpu_allocation_ratio < 0.5:
msg += "# WARNING: you do not have enough CPU to run hyperconverged \n"
# TripleO refers reserved_host_memory_mb variable by Nova as reserved_host_memory
hci_nova_limits['nova::compute::reserved_host_memory'] = nova_reserved_mem_MB
hci_nova_limits['nova::cpu_allocation_ratio'] = cpu_allocation_ratio
return hci_nova_limits, msg
if __name__ == "__main__":
# I. From perf_profile get the following based on user input:
# Avg-guest-size-GB
# Avg-guest-CPU-util
import sys
try:
profile_name = sys.argv[1]
except:
print "Usage: " + sys.argv[0] + " profile_name"
exit(1)
profiles = files_to_objects(['perf_profiles.yaml'])
if profile_name not in profiles[0]['parameter_defaults'].keys():
print profile_name + " is not a valid profile (see perf_profiles.yaml)"
exit(1)
profile = profiles[0]['parameter_defaults'][profile_name]
# II. From Heat env files get:
# OSDs-per-server
envs = files_to_objects(get_files('custom-templates', '.yaml$'))
for env in envs:
osds = list(find('ceph::profile::params::osds', env))
if len(osds) > 0: # does the array contain a dict for this?
break
osd_count = len(osds[0]) # in that dict, how many entries are there?
# III. From Ironic get the following from the designated Ceph nodes:
# Total-host-RAM-GB
# Total-host-cores
# This assumes a command like the following was run:
# ironic node-update $id replace properties/capabilities='node:ceph-0,boot_option:local'
ironic_id = ironic_download('ceph') # get introspection data from host matching "ceph"
servers = files_to_objects(get_files('hardware', ironic_id))
for server in servers:
cpus = list(find('cpus', server))
memory = list(find('memory_mb', server))
# IV. Derive THT parameters based on collected deployment information
hci_nova_limits, comment = hci_nova_mem_cpu_calc(mem_mb=int(memory[0]), \
cores=int(cpus[0]), \
osds=int(osd_count), \
average_guest_size_mb=\
int(profile['workload::average_guest_memory_size_in_mb']),\
average_guest_util_percent=\
float(profile['workload::average_guest_CPU_utilization_percentage']),\
profile_name=profile_name\
)
# V. Export internal data structure to derived.yaml
# build up nested dictionary of configuration
extra_config = {}
extra_config['ExtraConfig'] = hci_nova_limits
parameter_defaults = {}
parameter_defaults['parameter_defaults'] = extra_config
# dict > json > yaml
import json, yaml
derived = yaml.dump(yaml.load(json.dumps(parameter_defaults)),
default_flow_style=False)
# write file
f = open('derived.yaml', 'w')
f.write(comment + "\n" + derived)
f.close()