Skip to content

collect more apt metrics (Closes: #220) #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 91 additions & 10 deletions apt_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@

import apt
import apt_pkg
import argparse
import collections
import logging
import os
import sys
from prometheus_client import CollectorRegistry, Gauge, generate_latest

_UpgradeInfo = collections.namedtuple("_UpgradeInfo", ["labels", "count"])
Expand All @@ -35,8 +38,13 @@ def _convert_candidates_to_upgrade_infos(candidates):
changes_dict = collections.defaultdict(lambda: collections.defaultdict(int))

for candidate in candidates:
# The 'now' archive only shows that packages are not installed. We tend
# to filter the candidates on those kinds of conditions before reaching
# here so here we don't want to include this information in order to
# reduce noise in the data.
origins = sorted(
{f"{o.origin}:{o.codename}/{o.archive}" for o in candidate.origins}
{f"{o.origin}:{o.codename}/{o.archive}" for o in candidate.origins
if o.archive != 'now'}
)
changes_dict[",".join(origins)][candidate.architecture] += 1

Expand All @@ -53,10 +61,18 @@ def _convert_candidates_to_upgrade_infos(candidates):
return changes_list


def _write_pending_upgrades(registry, cache):
def _write_pending_upgrades(registry, cache, exclusions):
candidates = {
p.candidate for p in cache if p.is_upgradable
p.candidate
for p in cache
if p.is_upgradable and not p.phasing_applied and p.name not in exclusions
}
for candidate in candidates:
logging.debug(
"pending upgrade: %s / %s",
candidate.package,
candidate.architecture,
)
upgrade_list = _convert_candidates_to_upgrade_infos(candidates)

if upgrade_list:
Expand All @@ -66,11 +82,22 @@ def _write_pending_upgrades(registry, cache):
g.labels(change.labels['origin'], change.labels['arch']).set(change.count)


def _write_held_upgrades(registry, cache):
def _write_held_upgrades(registry, cache, exclusions):
held_candidates = {
p.candidate for p in cache
if p.is_upgradable and p._pkg.selected_state == apt_pkg.SELSTATE_HOLD
if (
p.is_upgradable
and p._pkg.selected_state == apt_pkg.SELSTATE_HOLD
and not p.phasing_applied
and p.name not in exclusions
)
}
for candidate in held_candidates:
logging.debug(
"held upgrade: %s / %s",
candidate.package,
candidate.architecture,
)
upgrade_list = _convert_candidates_to_upgrade_infos(held_candidates)

if upgrade_list:
Expand All @@ -80,13 +107,58 @@ def _write_held_upgrades(registry, cache):
g.labels(change.labels['origin'], change.labels['arch']).set(change.count)


def _write_autoremove_pending(registry, cache):
autoremovable_packages = {p for p in cache if p.is_auto_removable}
def _write_obsolete_packages(registry, cache, exclusions):
# This corresponds to the apt filter "?obsolete"
obsoletes = [p for p in cache if p.is_installed and (
p.candidate is None or
not p.candidate.origins or
(len(p.candidate.origins) == 1 and
p.candidate.origins[0].origin in ['', "/var/lib/dpkg/status"])
and p.name not in exclusions
)]
for package in obsoletes:
if package.candidate is None:
logging.debug("obsolete package with no candidate: %s", package)
else:
logging.debug(
"obsolete package: %s / %s",
package,
package.candidate.architecture,
)

g = Gauge('apt_packages_obsolete_count', "Apt packages which are obsolete",
registry=registry)
g.set(len(obsoletes))


def _write_autoremove_pending(registry, cache, exclusions):
autoremovable_packages = {
p.candidate
for p in cache
if p.is_auto_removable and p.name not in exclusions
}
for candidate in autoremovable_packages:
logging.debug(
"autoremovable package: %s / %s",
candidate.package,
candidate.architecture,
)
g = Gauge('apt_autoremove_pending', "Apt packages pending autoremoval.",
registry=registry)
g.set(len(autoremovable_packages))


def _write_installed_packages_per_origin(registry, cache):
installed_packages = {p.candidate for p in cache if p.is_installed}
per_origin = _convert_candidates_to_upgrade_infos(installed_packages)

if per_origin:
g = Gauge('apt_packages_per_origin_count', "Number of packages installed per origin.",
['origin', 'arch'], registry=registry)
for o in per_origin:
g.labels(o.labels['origin'], o.labels['arch']).set(o.count)


def _write_cache_timestamps(registry):
g = Gauge('apt_package_cache_timestamp_seconds', "Apt update last run time.", registry=registry)
apt_pkg.init_config()
Expand All @@ -113,12 +185,21 @@ def _write_reboot_required(registry):


def _main():
if os.getenv('DEBUG'):
logging.basicConfig(level=logging.DEBUG)

parser = argparse.ArgumentParser()
parser.add_argument("--exclude", nargs='*', default=[])
args = parser.parse_args(sys.argv[1:])

cache = apt.cache.Cache()

registry = CollectorRegistry()
_write_pending_upgrades(registry, cache)
_write_held_upgrades(registry, cache)
_write_autoremove_pending(registry, cache)
_write_pending_upgrades(registry, cache, args.exclude)
_write_held_upgrades(registry, cache, args.exclude)
_write_obsolete_packages(registry, cache, args.exclude)
_write_autoremove_pending(registry, cache, args.exclude)
_write_installed_packages_per_origin(registry, cache)
_write_cache_timestamps(registry)
_write_reboot_required(registry)
print(generate_latest(registry).decode(), end='')
Expand Down