From a61786d4e1e030cd6f8b7d8c4f326d92143cb6a3 Mon Sep 17 00:00:00 2001 From: Kevin Carter Date: Sun, 19 Mar 2017 09:03:06 -0500 Subject: [PATCH] added rackspace maas format (#28) Signed-off-by: Kevin Carter --- monitorstack/cli.py | 5 +- monitorstack/common/formatters.py | 83 ++++++++++++++++++++-- tests/test_formatters.py | 111 ++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 9 deletions(-) diff --git a/monitorstack/cli.py b/monitorstack/cli.py index 06ab0de..324c577 100755 --- a/monitorstack/cli.py +++ b/monitorstack/cli.py @@ -82,7 +82,8 @@ class MonitorStackCLI(click.MultiCommand): VALID_OUTPUT_FORMATS = [ 'json', 'line', - 'telegraf' + 'telegraf', + 'rax-maas' ] @@ -107,7 +108,7 @@ def cli(ctx, output_format, verbose): def process_result(result, output_format, verbose): """Render the output into the proper format.""" module_name = 'monitorstack.common.formatters' - method_name = 'write_{}'.format(output_format) + method_name = 'write_{}'.format(output_format.replace('-', '_')) output_formatter = getattr( importlib.import_module(module_name), method_name diff --git a/monitorstack/common/formatters.py b/monitorstack/common/formatters.py index 6f5ea8f..6737905 100644 --- a/monitorstack/common/formatters.py +++ b/monitorstack/common/formatters.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Output methods.""" + import json import time @@ -38,18 +39,62 @@ def _current_time(): return int(time.time() * 1000000000) +def _get_value_types(value, measurement_type=None): + """Return the value and the measurement type. + + This method will evaluate a given value and cast it to the + appropriate type. If the parameter `measurement_type` is + not provided the method will assign the type based on the + key set in the types list. + + :param value: item to evaluate. + :param measurement_type: name of the measurement + """ + def _check_value(c_type, c_value): + try: + c_value = c_type(c_value) + except ValueError: + return False, c_value + else: + return True, c_value + + success = False + if isinstance(value, str) and '.' in value: + success, value = _check_value( + c_type=float, + c_value=value + ) + + elif isinstance(value, float): + success = True + + if success: + _measurement_type = 'float' + else: + _, value = _check_value( + c_type=int, + c_value=value + ) + if isinstance(value, int): + if value > 2147483647: + _measurement_type = 'int64' + else: + _measurement_type = 'int32' + else: + _measurement_type = 'string' + + if not measurement_type: + measurement_type = _measurement_type + + return value, measurement_type + + def _telegraf_line_format(sets, quote=False): """Return a comma separated string.""" store = list() for k, v in sets.items(): k = k.replace(' ', '_') - for v_type in [int, float]: - try: - v = v_type(v) - except ValueError: - pass # v was not a int, float, or long - else: - break + v, _ = _get_value_types(value=v) if not isinstance(v, (int, float, bool)) and quote: store.append('{}="{}"'.format(k, v)) else: @@ -72,3 +117,27 @@ def write_telegraf(result): click.echo(' '.join(resultant)) return True + + +def write_rax_maas(result): + """Output in Rackspace Monitoring as a Service format.""" + status = ['status'] + if result['exit_code'] == 0: + status.append('okay') + else: + status.append('error') + + status.append(result['message']) + click.echo(' '.join(status)) + + for key, value in result['variables'].items(): + value, measurement_type = _get_value_types( + value=value, + measurement_type=result.get('measurement_type') + ) + metric = ['metric', key, measurement_type, str(value)] + if 'measurement_units' in result: + metric.append(result['measurement_units']) + click.echo(' '.join(metric)) + + return True diff --git a/tests/test_formatters.py b/tests/test_formatters.py index e25c53b..3cdf785 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -28,6 +28,40 @@ SAMPLE_RESULT = { } } +SAMPLE_RESULT_ERROR = { + 'exit_code': 1, + 'message': 'uptime failed', + 'measurement_name': 'system_uptime', + 'meta': {}, + 'variables': {} +} + +SAMPLE_RESULT_MEASUREMENT_TYPE = { + 'exit_code': 0, + 'message': 'uptime is ok', + 'measurement_name': 'system_uptime', + 'measurement_type': 'testType', + 'meta': { + 'platform': 'example_platform', + }, + 'variables': { + 'uptime': '29587.75' + } +} + +SAMPLE_RESULT_MEASUREMENT_UNITS = { + 'exit_code': 0, + 'message': 'uptime is ok', + 'measurement_name': 'system_uptime', + 'measurement_units': 'testUnits', + 'meta': { + 'platform': 'example_platform', + }, + 'variables': { + 'uptime': '29587.75' + } +} + SAMPLE_RESULT_NO_META = { 'exit_code': 0, 'message': 'uptime is ok', @@ -56,6 +90,54 @@ class TestFormatters(object): assert isinstance(result, int) assert result > 0 + def test__get_value_types_int32(self): + """Test _get_value_types() with int.""" + value, m_type = formatters._get_value_types(1) + assert value == 1 + assert m_type == 'int32' + + def test__get_value_types_int32_str(self): + """Test _get_value_types() with int.""" + value, m_type = formatters._get_value_types('1') + assert value == 1 + assert m_type == 'int32' + + def test__get_value_types_int64(self): + """Test _get_value_types() with int.""" + value, m_type = formatters._get_value_types(9999999999) + assert value == 9999999999 + assert m_type == 'int64' + + def test__get_value_types_int64_str(self): + """Test _get_value_types() with int.""" + value, m_type = formatters._get_value_types('9999999999') + assert value == 9999999999 + assert m_type == 'int64' + + def test__get_value_types_float(self): + """Test _get_value_types() with float.""" + value, m_type = formatters._get_value_types(1.1) + assert value == 1.1 + assert m_type == 'float' + + def test__get_value_types_float_str(self): + """Test _get_value_types() with float.""" + value, m_type = formatters._get_value_types('1.1') + assert value == 1.1 + assert m_type == 'float' + + def test__get_value_types_set_m_type(self): + """Test _get_value_types() with float.""" + value, m_type = formatters._get_value_types('1.1', 'double') + assert value == 1.1 + assert m_type == 'double' + + def test__get_value_types_string(self): + """Test _get_value_types() with str.""" + value, m_type = formatters._get_value_types('TestString') + assert value == 'TestString' + assert m_type == 'string' + def test_write_json(self, capsys): """Test write_json() module.""" formatters.write_json(SAMPLE_RESULT) @@ -95,3 +177,32 @@ class TestFormatters(object): assert isinstance(result, str) assert 'othervar=3' in result assert 'platform="example_platform"' in result + + def test_write_rax_maas(self, capsys): + """Test write_telegraf() module.""" + formatters.write_rax_maas(SAMPLE_RESULT) + out, err = capsys.readouterr() + assert SAMPLE_RESULT['message'] in out + assert 'metric uptime float 29587.75' in out + + def test_write_rax_maas_with_types(self, capsys): + """Test write_telegraf() module.""" + formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_TYPE) + out, err = capsys.readouterr() + assert SAMPLE_RESULT['message'] in out + assert 'metric uptime testType 29587.75' in out + + def test_write_rax_maas_with_units(self, capsys): + """Test write_telegraf() module.""" + formatters.write_rax_maas(SAMPLE_RESULT_MEASUREMENT_UNITS) + out, err = capsys.readouterr() + out_split = out.splitlines() + assert [i for i in out_split if SAMPLE_RESULT['message'] in i] + assert 'metric uptime float 29587.75 testUnits' in out_split + + def test_write_rax_maas_with_error(self, capsys): + """Test write_telegraf() module.""" + formatters.write_rax_maas(SAMPLE_RESULT_ERROR) + out, err = capsys.readouterr() + out_split = out.splitlines() + assert [i for i in out_split if 'status error' in i]