#!/usr/bin/env ruby require 'pathname' require 'json' require 'prometheus/client' require 'lxc' require_relative 'ns' require_relative 'file-statfs' require_relative 'lxc-exporter/os_release' require_relative 'lxc-exporter/mount_info' require_relative 'lxc_extend' =begin class LXC::CT def initialize name extend Fiddle::Importer dlload Fiddle.dlopen( nil) extern 'lxc_container *lxc_container_new( const char* name, const char* config_path)' def initialize name, config = nil lxc_container_new name, config end end end =end class LxcCollector include Prometheus::Client CTStates = { aborting: -1, stopped: 0, starting: 1, stopping: 2, running: 3 } 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 forked 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') 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.puts [:mountpoint, st].to_json end osr = OsRelease.read if osr wr.puts [: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] }].to_json case osr[:id].to_sym when :debian upgradable = nil pid = IO.popen %w[apt list --upgradable], err: "/dev/null" do |apt| upgradable = apt.readlines.length - 1 end wr.puts [:pkgs, { id: ct.name, name: name, upgradable: upgradable, last_update: AptLastUpdateFile.stat.mtime.to_f, }].to_json end end end end wr.close exit 0 end def collect rd, wr = IO.pipe pid = fork do rd.close forked wr end wr.close 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 rd.each_line do |l| l = JSON.parse l, symbolize_names: true 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 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.collect) end