forked from juju/charm-helpers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added watermark_scale_factor tuning (juju#609)
* Added watermark_scale_factor tuning Adds a method for determining an appropriate vm.watermark_scale_factor value for baremetal systems. Implements: spec memory-fragmentation-tuning * WIP: Added watermark_scale_factor tuning - improving tests - implemented suggested changes * Changes based on feedback * Working tests * pep8 fixes * py2.7 fixes * py2.7 fixes * watermark as const * Reuse get_total_ram from core.host Removed get_memtotal implementation and tests * const WMARK_MAX Co-authored-by: Brett Milford <brettmilford@gmail.com>
- Loading branch information
1 parent
b5725ac
commit 26efcd0
Showing
4 changed files
with
172 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#!/usr/bin/env python | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright 2014-2015 Canonical Limited. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from charmhelpers.core.hookenv import ( | ||
log, | ||
DEBUG, | ||
ERROR, | ||
) | ||
|
||
import re | ||
from charmhelpers.core.host import get_total_ram | ||
|
||
WMARK_MAX = 1000 | ||
WMARK_DEFAULT = 10 | ||
MEMTOTAL_MIN_BYTES = 17179803648 # 16G | ||
MAX_PAGES = 2500000000 | ||
|
||
|
||
def calculate_watermark_scale_factor(): | ||
"""Calculates optimal vm.watermark_scale_factor value | ||
:returns: watermark_scale_factor | ||
:rtype: int | ||
""" | ||
|
||
memtotal = get_total_ram() | ||
normal_managed_pages = get_normal_managed_pages() | ||
|
||
try: | ||
wmark = min([watermark_scale_factor(memtotal, managed_pages) | ||
for managed_pages in normal_managed_pages]) | ||
except ValueError as e: | ||
log("Failed to calculate watermark_scale_factor from normal managed pages: {}".format(normal_managed_pages), ERROR) | ||
raise e | ||
|
||
log("vm.watermark_scale_factor: {}".format(wmark), DEBUG) | ||
return wmark | ||
|
||
|
||
def get_normal_managed_pages(): | ||
"""Parse /proc/zoneinfo for managed pages of the | ||
normal zone on each node | ||
:returns: normal_managed_pages | ||
:rtype: [int] | ||
""" | ||
try: | ||
normal_managed_pages = [] | ||
with open('/proc/zoneinfo', 'r') as f: | ||
in_zone_normal = False | ||
# regex to search for strings that look like "Node 0, zone Normal" and last string to group 1 | ||
normal_zone_matcher = re.compile(r"^Node\s\d+,\s+zone\s+(\S+)$") | ||
# regex to match to a number at the end of the line. | ||
managed_matcher = re.compile(r"\s+managed\s+(\d+)$") | ||
for line in f.readlines(): | ||
match = normal_zone_matcher.search(line) | ||
if match: | ||
in_zone_normal = match.group(1) == 'Normal' | ||
if in_zone_normal: | ||
# match the number at the end of " managed 3840" into group 1. | ||
managed_match = managed_matcher.search(line) | ||
if managed_match: | ||
normal_managed_pages.append(int(managed_match.group(1))) | ||
in_zone_normal = False | ||
|
||
except OSError as e: | ||
log("Failed to read /proc/zoneinfo in calculating watermark_scale_factor: {}".format(e), ERROR) | ||
raise e | ||
|
||
return normal_managed_pages | ||
|
||
|
||
def watermark_scale_factor(memtotal, managed_pages): | ||
"""Calculate a value for vm.watermark_scale_factor | ||
:param memtotal: Total system memory in KB | ||
:type memtotal: int | ||
:param managed_pages: Number of managed pages | ||
:type managed_pages: int | ||
:returns: normal_managed_pages | ||
:rtype: int | ||
""" | ||
if memtotal <= MEMTOTAL_MIN_BYTES: | ||
return WMARK_DEFAULT | ||
else: | ||
WMARK = int(MAX_PAGES / managed_pages) | ||
if WMARK > WMARK_MAX: | ||
return WMARK_MAX | ||
else: | ||
return WMARK |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#!/usr/bin/env python | ||
|
||
from charmhelpers.contrib.sysctl.watermark_scale_factor import ( | ||
watermark_scale_factor, | ||
calculate_watermark_scale_factor, | ||
get_normal_managed_pages, | ||
) | ||
|
||
from mock import patch | ||
import unittest | ||
|
||
from tests.helpers import patch_open | ||
|
||
TO_PATCH = [ | ||
"log", | ||
"ERROR", | ||
"DEBUG" | ||
] | ||
|
||
PROC_ZONEINFO = """ | ||
Node 0, zone Normal | ||
pages free 1253032 | ||
min 16588 | ||
low 40833 | ||
high 65078 | ||
spanned 24674304 | ||
present 24674304 | ||
managed 24247810 | ||
protection: (0, 0, 0, 0, 0) | ||
""" | ||
|
||
|
||
class TestWatermarkScaleFactor(unittest.TestCase): | ||
|
||
def setUp(self): | ||
for m in TO_PATCH: | ||
setattr(self, m, self._patch(m)) | ||
|
||
def _patch(self, method): | ||
_m = patch('charmhelpers.contrib.sysctl.watermark_scale_factor.' + method) | ||
mock = _m.start() | ||
self.addCleanup(_m.stop) | ||
return mock | ||
|
||
@patch('charmhelpers.contrib.sysctl.watermark_scale_factor.get_normal_managed_pages') | ||
@patch('charmhelpers.core.host.get_total_ram') | ||
def test_calculate_watermark_scale_factor(self, get_total_ram, get_normal_managed_pages): | ||
get_total_ram.return_value = 101254156288 | ||
get_normal_managed_pages.return_value = [24247810] | ||
wmark = calculate_watermark_scale_factor() | ||
self.assertTrue(wmark >= 10, "ret {}".format(wmark)) | ||
self.assertTrue(wmark <= 1000, "ret {}".format(wmark)) | ||
|
||
def test_get_normal_managed_pages(self): | ||
with patch_open() as (mock_open, mock_file): | ||
mock_file.readlines.return_value = PROC_ZONEINFO.splitlines() | ||
self.assertEqual(get_normal_managed_pages(), [24247810]) | ||
mock_open.assert_called_with('/proc/zoneinfo', 'r') | ||
|
||
def test_watermark_scale_factor(self): | ||
mem_totals = [17179803648, 34359607296, 549753716736] | ||
managed_pages = [4194288, 24247815, 8388576, 134217216] | ||
arglists = [[mem, managed] for mem in mem_totals for managed in managed_pages] | ||
|
||
for arglist in arglists: | ||
wmark = watermark_scale_factor(*arglist) | ||
self.assertTrue(wmark >= 10, "assert failed for args: {}, ret {}".format(arglist, wmark)) | ||
self.assertTrue(wmark <= 1000, "assert failed for args: {}, ret {}".format(arglist, wmark)) |