Skip to content

Commit

Permalink
Added watermark_scale_factor tuning (juju#609)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
brettmilford and brettmilford authored Sep 23, 2021
1 parent b5725ac commit 26efcd0
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 0 deletions.
Empty file.
104 changes: 104 additions & 0 deletions charmhelpers/contrib/sysctl/watermark_scale_factor.py
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.
68 changes: 68 additions & 0 deletions tests/contrib/sysctl/test_watermark_scale_factor.py
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))

0 comments on commit 26efcd0

Please sign in to comment.