251 lines
8.2 KiB
Ruby
Executable file
251 lines
8.2 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
require 'pathname'
|
|
require 'prometheus/client'
|
|
require 'lxc'
|
|
require 'ostruct'
|
|
require 'pmap'
|
|
|
|
require_relative 'ns'
|
|
require_relative 'file-statfs'
|
|
require_relative 'cborio'
|
|
require_relative 'lxc_extend'
|
|
require_relative 'lxc-exporter/os_release'
|
|
require_relative 'lxc-exporter/mount_info'
|
|
|
|
class LxcCollector
|
|
include Prometheus::Client
|
|
|
|
CTStates = { aborting: -1, stopped: 0, starting: 0.75, stopping: 0.25, running: 1 }
|
|
|
|
def statfs ct, mis
|
|
mis.each do |mi|
|
|
next if %w[devpts devtmpfs fuse.lxcfs].include? mi.type
|
|
statfs = File::Statfs.new mi.mp
|
|
yield ct, mi.mp, statfs
|
|
end
|
|
end
|
|
|
|
def initialize registry = nil
|
|
@prometheus ||= Registry.new
|
|
@up = Gauge.new :lxc_up, labels: %i[id name], docstring: 'Is CT running?'
|
|
@state = Gauge.new :lxc_state, labels: %i[id name],
|
|
docstring: "State of CT (#{CTStates.map{|n,i|"#{i}=#{n}"}.join', '})"
|
|
@info = Gauge.new :lxc_filesystem_info,
|
|
labels: %i[id mountpoint name fstype], docstring: 'CT Info'
|
|
@totalb = Gauge.new :lxc_filesystem_total_bytes,
|
|
labels: %i[id mountpoint], docstring: 'Total bytes in filesystem'
|
|
@freeb = Gauge.new :lxc_filesystem_free_bytes,
|
|
labels: %i[id mountpoint], docstring: 'Free bytes in filesystem'
|
|
@availb = Gauge.new :lxc_filesystem_available_bytes,
|
|
labels: %i[id mountpoint], docstring: 'Free bytes available to unprivileged user'
|
|
@totalf = Gauge.new :lxc_filesystem_total_files,
|
|
labels: %i[id mountpoint], docstring: 'Total files in filesystem'
|
|
@freef = Gauge.new :lxc_filesystem_free_files,
|
|
labels: %i[id mountpoint], docstring: 'Free files in filesystem'
|
|
@os_release = Gauge.new :lxc_os_release_info,
|
|
labels: %i[id name os_id os_name os_pretty_name os_version_codename],
|
|
docstring: 'OS release info'
|
|
@os_version = Gauge.new :lxc_os_version_id,
|
|
labels: %i[id], docstring: 'OS release (distribution) version'
|
|
@pkgs_upgradable = Gauge.new :lxc_packages_upgradable,
|
|
labels: %i[id], docstring: 'Count of upgradable packages'
|
|
@pkgs_last_update = Gauge.new :lxc_packages_last_update,
|
|
labels: %i[id], docstring: 'Last successfull source update'
|
|
[
|
|
@up, @state,
|
|
@info, @totalb, @freeb, @availb, @totalf, @freef,
|
|
@os_release, @os_version,
|
|
@pkgs_upgradable, @pkgs_last_update
|
|
].each {|g| @prometheus.register g }
|
|
end
|
|
|
|
AptLastUpdateFile = Pathname.new '/var/cache/apt/pkgcache.bin'
|
|
|
|
def forkedo wr
|
|
LXC.running_containers do |ct|
|
|
pid = ct.init_pid
|
|
mis = MountInfo.of pid
|
|
NS.change pid, :pid, :mnt do
|
|
name = ct.config_item( 'lxc.uts.name')
|
|
|
|
# Filesystems
|
|
statfs ct, mis do |ct, mp, st|
|
|
next unless st.fstypename and 0 < st.blocks
|
|
st = st.to_h.merge mp: mp, id: ct.name, name: name
|
|
wr.write [:mountpoint, st]
|
|
end
|
|
|
|
# OS / Distribution
|
|
osr = OsRelease.read
|
|
if osr
|
|
wr.write [:os_release, {
|
|
id: ct.name, name: name,
|
|
os_id: osr[:id],
|
|
os_name: osr[:name],
|
|
os_pretty_name: osr[:pretty_name],
|
|
os_version_id: osr[:version_id].to_i,
|
|
os_version_codename: osr[:version_codename]
|
|
}]
|
|
|
|
case osr[:id].to_sym
|
|
# Special things for Debian(-based) distributions
|
|
when :debian
|
|
upgradable = nil
|
|
pid =
|
|
IO.popen %w[apt list --upgradable], err: "/dev/null" do |apt|
|
|
upgradable = apt.readlines.length - 1
|
|
end
|
|
wr.write [:pkgs, {
|
|
id: ct.name, name: name, upgradable: upgradable,
|
|
last_update: AptLastUpdateFile.exist? ? AptLastUpdateFile.stat.mtime.to_f : -1,
|
|
}]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
wr.close
|
|
exit 0
|
|
end
|
|
|
|
def collecto
|
|
LXC.containers do |ct|
|
|
labels = { id: ct.name, name: ct.config_item( 'lxc.uts.name') }
|
|
@up.set ct.running? ? 1 : 0, labels: labels
|
|
@state.set CTStates[ct.state], labels: labels
|
|
end
|
|
pid, rd = CBORIO.popen( &method( :forked))
|
|
rd.each do |l|
|
|
case l[0].to_sym
|
|
|
|
when :mountpoint
|
|
st = OpenStruct.new l[1]
|
|
labels = { id: st.id, mountpoint: st.mp }
|
|
@info.set 1, labels: labels.merge( name: st.name, fstype: st.fstypename)
|
|
@totalb.set st.bsize*st.blocks, labels: labels
|
|
@freeb.set st.bsize*st.bfree, labels: labels
|
|
@availb.set st.bsize*st.bavail, labels: labels
|
|
@totalf.set st.files, labels: labels
|
|
@freef.set st.ffree, labels: labels
|
|
|
|
when :os_release
|
|
osr = OpenStruct.new l[1]
|
|
@os_release.set 1, labels: {
|
|
id: osr.id, name: osr.name,
|
|
os_id: osr.os_id,
|
|
os_name: osr.os_name,
|
|
os_pretty_name: osr.os_pretty_name,
|
|
os_version_codename: osr.os_version_codename
|
|
}
|
|
@os_version.set osr.os_version_id, labels: { id: osr.id }
|
|
|
|
when :pkgs
|
|
as = OpenStruct.new l[1]
|
|
@pkgs_last_update.set as.last_update, labels: { id: as.id }
|
|
@pkgs_upgradable.set as.upgradable, labels: { id: as.id }
|
|
end
|
|
end
|
|
rd.close
|
|
Process.wait pid
|
|
@prometheus
|
|
end
|
|
|
|
def forked2 io, ct
|
|
pid = ct.init_pid
|
|
mis = MountInfo.of pid
|
|
id, name = ct.name, ct.config_item( 'lxc.uts.name')
|
|
NS.enter pid, keep_open: [io, STDERR, STDOUT]
|
|
|
|
# Filesystems
|
|
statfs ct, mis do |ct, mp, st|
|
|
next unless st.fstypename and 0 < st.blocks
|
|
st = st.to_h.merge mp: mp, id: id, name: name
|
|
io.write [:mountpoint, st]
|
|
end
|
|
|
|
# OS / Distribution
|
|
osr = OsRelease.read
|
|
unless osr.nil?
|
|
io.write [:os_release, {
|
|
id: id, name: name,
|
|
os_id: osr[:id],
|
|
os_name: osr[:name],
|
|
os_pretty_name: osr[:pretty_name],
|
|
os_version_id: osr[:version_id].to_i,
|
|
os_version_codename: osr[:version_codename]
|
|
}]
|
|
|
|
case osr[:id].to_sym
|
|
# Special things for Debian(-based) distributions
|
|
when :debian
|
|
upgradable = nil
|
|
pid =
|
|
IO.popen %w[apt list --upgradable], err: "/dev/null" do |apt|
|
|
upgradable = apt.readlines.length - 1
|
|
end
|
|
io.write [:pkgs, {
|
|
id: id, name: name, upgradable: upgradable,
|
|
last_update: AptLastUpdateFile.stat.mtime.to_f,
|
|
}]
|
|
end
|
|
end
|
|
rescue Object
|
|
STDERR.puts "#{$!} (#{$!.class})", $!.backtrace.map {|x| " #{x}"}
|
|
end
|
|
|
|
def collect2
|
|
LXC.containers do |ct|
|
|
labels = { id: ct.name, name: ct.config_item( 'lxc.uts.name') }
|
|
@up.set ct.running? ? 1 : 0, labels: labels
|
|
@state.set CTStates[ct.state], labels: labels
|
|
end
|
|
|
|
LXC.running_containers.peach do |ct|
|
|
pid, io = CBORIO.popen(:r) {|io| forked2 io, ct }
|
|
begin
|
|
io.map do |l|
|
|
case l[0].to_sym
|
|
|
|
when :mountpoint
|
|
st = OpenStruct.new l[1]
|
|
labels = { id: st.id, mountpoint: st.mp }
|
|
@info.set 1, labels: labels.merge( name: st.name, fstype: st.fstypename)
|
|
@totalb.set st.bsize*st.blocks, labels: labels
|
|
@freeb.set st.bsize*st.bfree, labels: labels
|
|
@availb.set st.bsize*st.bavail, labels: labels
|
|
@totalf.set st.files, labels: labels
|
|
@freef.set st.ffree, labels: labels
|
|
|
|
when :os_release
|
|
osr = OpenStruct.new l[1]
|
|
@os_release.set 1, labels: {
|
|
id: osr.id, name: osr.name,
|
|
os_id: osr.os_id,
|
|
os_name: osr.os_name,
|
|
os_pretty_name: osr.os_pretty_name,
|
|
os_version_codename: osr.os_version_codename
|
|
}
|
|
@os_version.set osr.os_version_id, labels: { id: osr.id }
|
|
|
|
when :pkgs
|
|
as = OpenStruct.new l[1]
|
|
@pkgs_last_update.set as.last_update, labels: { id: as.id }
|
|
@pkgs_upgradable.set as.upgradable, labels: { id: as.id }
|
|
end
|
|
end
|
|
ensure
|
|
io.close
|
|
Process.wait pid
|
|
end
|
|
end
|
|
@prometheus
|
|
end
|
|
|
|
alias forked forked2
|
|
alias collect collect2
|
|
end
|
|
|
|
if Pathname.new( __FILE__).expand_path == Pathname.new( $0).expand_path
|
|
require 'prometheus/client/formats/text'
|
|
puts Prometheus::Client::Formats::Text.marshal( LxcCollector.new.collect2)
|
|
end
|