Skip to content

Instantly share code, notes, and snippets.

@glorpen
Last active June 24, 2019 12:51
Show Gist options
  • Save glorpen/10ada52c363f7bac5392314368a87ff3 to your computer and use it in GitHub Desktop.
Save glorpen/10ada52c363f7bac5392314368a87ff3 to your computer and use it in GitHub Desktop.
Prometheus LVM Metrics for textfile collector.
#!/usr/bin/env python3
"""
Prometheus LVM Metrics for textfile collector.
Shows LVM stats: LV/VG sizes, health states and LVM RAID data.
There is no cache, LVM raport will be created every scrape/cron execution.
Requires Python3.
@author: Arkadiusz Dzięgiel <arkadiusz.dziegiel@glorpen.pl>
"""
#
# Prometheus LVM Metrics for textfile collector.
# Copyright (C) 2019 Arkadiusz Dzięgiel
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import sys
import json
import subprocess
import collections
class StatGroup(object):
GAUGE = 'gauge'
def __init__(self, name, type, help, conv=int, allow_empty=False):
super(StatGroup, self).__init__()
self.stats = []
self.converter = conv
self.allow_empty = allow_empty
self.help = help
self.type = type
self.name = name
def add(self, value, **tags):
self.stats.append((value, tags))
def render_single(self, value, tags):
tags = ",".join(('%s="%s"' % (k,v)) for k,v in tags.items())
if value == "" and self.allow_empty:
value = self.converter(value)
else:
value = "NaN" if value == "" else self.converter(value)
return "%s{%s} %s" % (self.name, tags, value)
def render(self):
lines = []
lines.append("# HELP %s %s" % (self.name, self.help))
lines.append("# TYPE %s %s" % (self.name, self.type))
for stat in self.stats:
lines.append(self.render_single(*stat))
return "\n".join(lines)
class LvmStats(object):
def __init__(self):
super(LvmStats, self).__init__()
self.groups = collections.OrderedDict()
def _run(self):
command = ["/sbin/lvm", "fullreport", "--noheadings", "--reportformat", "json", "--units", "b", "--nosuffix"]
return subprocess.run(
command, stdout=subprocess.PIPE, check=True, env={"LC_ALL": "C"}
).stdout.decode('utf-8')
def _get_stats(self):
return json.loads(self._run())['report']
def create_group(self, name, stat_name, tags = {}, **kwargs):
group = StatGroup(name, **kwargs)
self.groups[name] = group
def adder(stats):
stat_tags = dict((k, stats[v]) for k,v in tags.items())
group.add(stats[stat_name], **stat_tags)
return adder
def _conv_enum(self, map, default=None):
def wrapper(value):
return map.get(value, default)
return wrapper
def get_normalized(self):
#lv_copy_percent = StatGroup("lvm_lv_copy_percent", StatGroup.GAUGE, "For Cache, RAID, mirrors and pvmove, current percentage in-sync.", conv=float)
lv_tags = {'name':'lv_full_name'}
lv_stats = []
lv_stats.append(self.create_group("lvm_lv_snap_percent", "snap_percent", conv=float, tags=lv_tags,
type=StatGroup.GAUGE, help="For snapshots, the percentage full if LV is active."))
lv_stats.append(self.create_group("lvm_lv_copy_percent", "copy_percent", conv=float, tags=lv_tags,
type=StatGroup.GAUGE, help="For Cache, RAID, mirrors and pvmove, current percentage in-sync."))
lv_stats.append(self.create_group("lvm_lv_metadata_percent", "metadata_percent", conv=float, tags=lv_tags,
type=StatGroup.GAUGE, help="For cache and thin pools, the percentage of metadata full if LV is active."))
lv_stats.append(self.create_group("lvm_lv_data_percent", "data_percent", conv=float, tags=lv_tags,
type=StatGroup.GAUGE, help="For snapshot, cache and thin pools and volumes, the percentage full if LV is active."))
lv_stats.append(self.create_group("lvm_lv_raid_mismatch_count", "raid_mismatch_count", tags=lv_tags,
type=StatGroup.GAUGE, help="For RAID, number of mismatches found or repaired."))
lv_stats.append(self.create_group("lvm_lv_size_bytes", "lv_size", tags=lv_tags,
type=StatGroup.GAUGE, help="Size of LV in current units."))
lv_stats.append(self.create_group("lvm_lv_metadata_size_bytes", "lv_metadata_size", tags=lv_tags,
type=StatGroup.GAUGE, help="For thin and cache pools, the size of the LV that holds the metadata."))
lv_stats.append(self.create_group("lvm_lv_active", "lv_active", tags=lv_tags,
type=StatGroup.GAUGE, help="Active state of the LV.", conv=self._conv_enum({"active":1}, 0)))
lv_stats.append(self.create_group("lvm_lv_raid_sync_action", "raid_sync_action", tags=lv_tags,
type=StatGroup.GAUGE, help="For RAID, the current synchronization action being performed.", conv=self._conv_enum({"check":1, "repair":2, "idle":0}, 9)))
lv_stats.append(self.create_group("lvm_lv_health_status", "lv_health_status", tags=lv_tags,
type=StatGroup.GAUGE, help="LV health status.", conv=self._conv_enum({"partial":1, "refresh needed":2, "mismatches exist":3, "":0}, 9), allow_empty=True))
vg_stats = []
vg_tags = {'name':'vg_name'}
vg_stats.append(self.create_group("lvm_vg_lv_count", "lv_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Number of LVs."))
vg_stats.append(self.create_group("lvm_vg_pv_count", "pv_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Number of PVs in VG."))
vg_stats.append(self.create_group("lvm_vg_missing_pv_count", "vg_missing_pv_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Number of PVs in VG which are missing."))
vg_stats.append(self.create_group("lvm_vg_snap_count", "snap_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Number of snapshots."))
vg_stats.append(self.create_group("lvm_vg_size_bytes", "vg_size", tags=vg_tags,
type=StatGroup.GAUGE, help="Total size of VG."))
vg_stats.append(self.create_group("lvm_vg_free_bytes", "vg_free", tags=vg_tags,
type=StatGroup.GAUGE, help="Total amount of free space."))
vg_stats.append(self.create_group("lvm_vg_extent_count", "vg_extent_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Total number of Physical Extents."))
vg_stats.append(self.create_group("lvm_vg_free_count", "vg_free_count", tags=vg_tags,
type=StatGroup.GAUGE, help="Total number of unallocated Physical Extents."))
vg_stats.append(self.create_group("lvm_vg_extent_size_bytes", "vg_extent_size", tags=vg_tags,
type=StatGroup.GAUGE, help="Size of Physical Extents."))
for lvm_stats in self._get_stats():
for lv_stat in lvm_stats['lv']:
for i in lv_stats:
i(lv_stat)
for vg_stat in lvm_stats['vg']:
for i in vg_stats:
i(vg_stat)
return self.groups.values()
def render_stats(self):
stats = self.get_normalized()
return "\n\n".join(s.render() for s in stats)
sys.stdout.write(LvmStats().render_stats()+"\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment