Skip to content

Commit

Permalink
Ensure label order is preserved when parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
braedon committed Sep 22, 2018
1 parent 15ccda5 commit 01cc9ab
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 28 deletions.
5 changes: 3 additions & 2 deletions prometheus_es_exporter/__init__.py
Expand Up @@ -8,6 +8,7 @@
import sys
import time

from collections import OrderedDict
from elasticsearch import Elasticsearch
from elasticsearch.exceptions import ConnectionTimeout
from functools import partial
Expand Down Expand Up @@ -51,8 +52,8 @@ def group_metrics(metrics):
metric_dict = {}
for (name_list, label_dict, value) in metrics:
metric_name = format_metric_name(name_list)
label_dict = {format_label_key(k): format_label_value(v)
for k, v in label_dict.items()}
label_dict = OrderedDict([(format_label_key(k), format_label_value(v))
for k, v in label_dict.items()])

if metric_name not in metric_dict:
metric_dict[metric_name] = (tuple(label_dict.keys()), {})
Expand Down
17 changes: 13 additions & 4 deletions prometheus_es_exporter/cluster_health_parser.py
@@ -1,12 +1,18 @@
from .utils import merge_dicts
from collections import OrderedDict
from .utils import merge_dicts_ordered

singular_forms = {
'indices': 'index',
'shards': 'shard'
}


def parse_block(block, metric=[], labels={}):
def parse_block(block, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

# Green is 0, so if we add statuses of mutiple blocks together
Expand All @@ -33,12 +39,15 @@ def parse_block(block, metric=[], labels={}):
else:
singular_key = key
for n_key, n_value in value.items():
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts(labels, {singular_key: [n_key]})))
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {singular_key: [n_key]})))

return result


def parse_response(response, metric=[]):
def parse_response(response, metric=None):
if metric is None:
metric = []

result = []

# Create a shallow copy as we are going to modify it
Expand Down
23 changes: 16 additions & 7 deletions prometheus_es_exporter/indices_stats_parser.py
@@ -1,4 +1,5 @@
from .utils import merge_dicts
from collections import OrderedDict
from .utils import merge_dicts_ordered

singular_forms = {
'fields': 'field'
Expand All @@ -10,7 +11,12 @@
bucket_list_keys = {}


def parse_block(block, metric=[], labels={}):
def parse_block(block, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

for key, value in block.items():
Expand All @@ -26,27 +32,30 @@ def parse_block(block, metric=[], labels={}):
else:
singular_key = key
for n_key, n_value in value.items():
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts(labels, {singular_key: [n_key]})))
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {singular_key: [n_key]})))
else:
result.extend(parse_block(value, metric=metric + [key], labels=labels))
elif isinstance(value, list) and key in bucket_list_keys:
bucket_name_key = bucket_list_keys[key]

for n_value in value:
bucket_name = n_value[bucket_name_key]
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts(labels, {bucket_name_key: [bucket_name]})))
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {bucket_name_key: [bucket_name]})))

return result


def parse_response(response, parse_indices=False, metric=[]):
def parse_response(response, parse_indices=False, metric=None):
if metric is None:
metric = []

result = []

if '_shards' not in response or not response['_shards']['failed']:
if parse_indices:
for key, value in response['indices'].items():
result.extend(parse_block(value, metric=metric, labels={'index': [key]}))
result.extend(parse_block(value, metric=metric, labels=OrderedDict({'index': [key]})))
else:
result.extend(parse_block(response['_all'], metric=metric, labels={'index': ['_all']}))
result.extend(parse_block(response['_all'], metric=metric, labels=OrderedDict({'index': ['_all']})))

return result
30 changes: 22 additions & 8 deletions prometheus_es_exporter/nodes_stats_parser.py
@@ -1,4 +1,5 @@
from .utils import merge_dicts
from collections import OrderedDict
from .utils import merge_dicts_ordered

singular_forms = {
'pools': 'pool',
Expand All @@ -20,7 +21,12 @@
}


def parse_block(block, metric=[], labels={}):
def parse_block(block, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

for key, value in block.items():
Expand All @@ -36,30 +42,38 @@ def parse_block(block, metric=[], labels={}):
else:
singular_key = key
for n_key, n_value in value.items():
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts(labels, {singular_key: [n_key]})))
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {singular_key: [n_key]})))
else:
result.extend(parse_block(value, metric=metric + [key], labels=labels))
elif isinstance(value, list) and key in bucket_list_keys:
bucket_name_key = bucket_list_keys[key]

for n_value in value:
bucket_name = n_value[bucket_name_key]
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts(labels, {bucket_name_key: [bucket_name]})))
result.extend(parse_block(n_value, metric=metric + [key], labels=merge_dicts_ordered(labels, {bucket_name_key: [bucket_name]})))

return result


def parse_node(node, metric=[], labels={}):
labels = merge_dicts(labels, node_name=[node['name']])
def parse_node(node, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

labels = merge_dicts_ordered(labels, node_name=[node['name']])

return parse_block(node, metric=metric, labels=labels)


def parse_response(response, metric=[]):
def parse_response(response, metric=None):
if metric is None:
metric = []

result = []

if '_nodes' not in response or not response['_nodes']['failed']:
for key, value in response['nodes'].items():
result.extend(parse_node(value, metric=metric, labels={'node_id': [key]}))
result.extend(parse_node(value, metric=metric, labels=OrderedDict({'node_id': [key]})))

return result
29 changes: 25 additions & 4 deletions prometheus_es_exporter/parser.py
@@ -1,4 +1,12 @@
def parse_buckets(agg_key, buckets, metric=[], labels={}):
from collections import OrderedDict


def parse_buckets(agg_key, buckets, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

for index, bucket in enumerate(buckets):
Expand All @@ -23,7 +31,12 @@ def parse_buckets(agg_key, buckets, metric=[], labels={}):
return result


def parse_buckets_fixed(agg_key, buckets, metric=[], labels={}):
def parse_buckets_fixed(agg_key, buckets, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

for bucket_key, bucket in buckets.items():
Expand All @@ -39,7 +52,12 @@ def parse_buckets_fixed(agg_key, buckets, metric=[], labels={}):
return result


def parse_agg(agg_key, agg, metric=[], labels={}):
def parse_agg(agg_key, agg, metric=None, labels=None):
if metric is None:
metric = []
if labels is None:
labels = OrderedDict()

result = []

for key, value in agg.items():
Expand All @@ -55,7 +73,10 @@ def parse_agg(agg_key, agg, metric=[], labels={}):
return result


def parse_response(response, metric=[]):
def parse_response(response, metric=None):
if metric is None:
metric = []

result = []

if not response['timed_out']:
Expand Down
7 changes: 5 additions & 2 deletions prometheus_es_exporter/utils.py
@@ -1,4 +1,7 @@
def merge_dicts(*dict_args, **extra_entries):
from collections import OrderedDict


def merge_dicts_ordered(*dict_args, **extra_entries):
"""
Given an arbitrary number of dictionaries, merge them into a
single new dictionary. Later dictionaries take precedence if
Expand All @@ -7,7 +10,7 @@ def merge_dicts(*dict_args, **extra_entries):
Extra entries can also be provided via kwargs. These entries
have the highest precedence.
"""
res = {}
res = OrderedDict()

for d in dict_args + (extra_entries,):
res.update(d)
Expand Down
2 changes: 1 addition & 1 deletion tests/utils.py
Expand Up @@ -25,7 +25,7 @@ def format_metrics(metric_name, label_keys, value_dict):

# Converts the parse_response() result into a psuedo-prometheus format
# that is useful for comparing results in tests.
# Uses the 'grop_metrics()' function used by the exporter, so effectively
# Uses the 'group_metrics()' function used by the exporter, so effectively
# tests that function.
def convert_result(result):
metric_dict = group_metrics(result)
Expand Down

0 comments on commit 01cc9ab

Please sign in to comment.