refactoring files and classes.

This commit is contained in:
Denis Knauf 2024-01-20 23:22:16 +01:00 committed by root
parent e225cc96db
commit 23a5d2c729
8 changed files with 465 additions and 414 deletions

View file

@ -1,13 +1,20 @@
require 'rack' require 'rack'
require_relative 'lxc-exporter' require_relative 'lib/lxc_collector'
require_relative 'lib/authorized_keys_collector'
require 'prometheus/client/formats/text' require 'prometheus/client/formats/text'
run lambda {|env| run lambda {|env|
req = Rack::Request.new env req = Rack::Request.new env
case req.path case req.path
when "/metrics"
when '/authorized_keys'
collector = AuthorizedKeysCollector.new
[200, {"Content-Type" => "text/javascript"}, [collector.collect.to_json]}]
when '/metrics'
collector = LxcCollector.new collector = LxcCollector.new
[200, {"Content-Type" => "text/plain"}, [Prometheus::Client::Formats::Text.marshal( collector.collect)]] [200, {"Content-Type" => "text/plain"}, [Prometheus::Client::Formats::Text.marshal( collector.collect)]]
else else
[404, {"Content-Type" => "text/plain"}, ["Not found\nYou want to try /metrics?\n"]] [404, {"Content-Type" => "text/plain"}, ["Not found\nYou want to try /metrics?\n"]]
end end

140
lib/file-statfs.rb Normal file
View file

@ -0,0 +1,140 @@
require 'fiddle'
require 'fiddle/import'
class File
module Statfs
Magics = {}
Line = <<~EOF
ADFS 0xadf5
AFFS 0xadff
AFS 0x5346414F
AUTOFS 0x0187
CODA 0x73757245
CRAMFS 0x28cd3d45
CRAMFS_WEND 0x453dcd28
DEBUGFS 0x64626720
SECURITYFS 0x73636673
SELINUX 0xf97cff8c
SMACK 0x43415d53
RAMFS 0x858458f6
TMPFS 0x01021994
HUGETLBFS 0x958458f6
SQUASHFS 0x73717368
ECRYPTFS 0xf15f
EFS 0x414A53
EROFS_V1 0xE0F5E1E2
EXT2 0xEF53
EXT3 0xEF53
XENFS 0xabba1974
EXT4 0xEF53
BTRFS 0x9123683E
NILFS 0x3434
F2FS 0xF2F52010
HPFS 0xf995e849
ISOFS 0x9660
JFFS2 0x72b6
XFS 0x58465342
PSTOREFS 0x6165676C
EFIVARFS 0xde5e81e4
HOSTFS 0x00c0ffee
OVERLAYFS 0x794c7630
MINIX 0x137F
MINIX2 0x138F
MINIX2 0x2468
MINIX22 0x2478
MINIX3 0x4d5a
MSDOS 0x4d44
NCP 0x564c
NFS 0x6969
OCFS2 0x7461636f
OPENPROM 0x9fa1
QNX4 0x002f
QNX6 0x68191122
AFS_FS 0x6B414653
REISERFS 0x52654973
REISERFS "ReIsErFs"
REISER2FS "ReIsEr2Fs"
REISER2FS_JR "ReIsEr3Fs"
SMB 0x517B
CGROUP 0x27e0eb
CGROUP2 0x63677270
RDTGROUP 0x7655821
STACK_END 0x57AC6E9D
TRACEFS 0x74726163
V9FS 0x01021997
BDEVFS 0x62646576
DAXFS 0x64646178
BINFMTFS 0x42494e4d
DEVPTS 0x1cd1
BINDERFS 0x6c6f6f70
FUTEXFS 0xBAD1DEA
PIPEFS 0x50495045
PROC 0x9fa0
SOCKFS 0x534F434B
SYSFS 0x62656572
USBDEVICE 0x9fa2
MTD_INODE_FS 0x11307854
ANON_INODE_FS 0x09041934
BTRFS_TEST 0x73727279
NSFS 0x6e736673
BPF_FS 0xcafe4a11
AAFS 0x5a3c69f0
ZONEFS 0x5a4f4653
UDF 0x15013346
BALLOON_KVM 0x13661366
ZSMALLOC 0x58295829
DMA_BUF 0x444d4142
DEVMEM 0x454d444d
Z3FOLD 0x33
PPC_CMM 0xc7571590
EOF
Line.each_line do |l|
n, v = l.split(/\s+/)[0..1]
v =
case v
when /^0x(\h+)$/ then $1.to_i 16
when /^"([^"]+)"$/ then $1
else raise "Unknown value: #$1"
end
Magics[v] = n.downcase.to_sym
end
extend Fiddle::Importer
dlload Fiddle.dlopen( nil)
Struct_statfs = struct <<~EOS
long type,
long bsize,
unsigned long blocks,
unsigned long bfree,
unsigned long bavail,
unsigned long files,
unsigned long ffree,
int fsid[2],
long namelen,
long frsize,
long flags,
long spare[4]
EOS
class Struct_statfs
def fstypename
Magics[type]
end
def to_h
{type: type, fstypename: fstypename, bsize: bsize, blocks: blocks, bfree: bfree, bavail: bavail, files: files, ffree: ffree, fsid: fsid, namelen: namelen, frsize: frsize, flags: flags, spare: spare}
end
end
StatfsFun = Fiddle::Function.new handler['statfs'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
#extern 'int statfs( const char *__file, struct statfs *__buf)'
def self.new file
buf = Struct_statfs.malloc
val = StatfsFun.call file, buf
raise SystemCallError.new( "statfs(#{file.inspect})", Fiddle.last_error) unless val == 0
buf
end
end
end

View file

@ -0,0 +1,25 @@
require 'pathname'
class MountInfo
attr_reader :major, :minor, :mp, :dev, :type, :opts
# 4979 4977 0:213 / /proc rw,nosuid,nodev,noexec,relatime shared:2416 - proc proc rw
def initialize *args
i = args.find_index {|x| '-' == x }
args2 = args[(i+1)..-1]
ma, mi = args[2].split ':'
@major, @minor, @mp, @dev, @type, @opts =
ma.to_i, mi.to_i, args[4], args2[1], args2[0], args2[2].split( ',')
end
class << self
def parse line
new *line.split( ' ')
end
def of pid
Pathname.new( "/proc/#{pid}/mountinfo").readlines.map {|l| parse l }
end
end
end

View file

@ -0,0 +1,38 @@
require 'pathname'
class OsRelease
Path = Pathname.new '/etc/os-release'
class <<self
def parse txt
r =
txt.each_line.
map do |l|
k, v =
*case l.chomp
when /^([^=]+)="(.*)"$/ then [$1, $2]
when /^([^=]+)=(.*)$/ then [$1,$2]
else next
end
[ k.downcase.to_sym, v.sub( /^"/, '').sub( /"$/, '') ]
end.
compact.
to_h
new r
end
def read() parse Path.read if Path.exist? end
end
def initialize( h) @h = h.freeze end
def to_h() @h.dup end
def []( n) @h[n] end
def missing_method n, *a
if a.empty? and @h.has_key?( n)
@h[n]
else
super n, *a
end
end
end

170
lib/lxc_collector.rb Executable file
View file

@ -0,0 +1,170 @@
#!/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

22
lib/lxc_extend.rb Normal file
View file

@ -0,0 +1,22 @@
require 'lxc'
module LXC
class <<self
def containers &exe
return to_enum __method__ unless block_given?
LXC.
list_containers.
map( &LXC::Container.method( :new)).
each &exe
end
def running_containers &exe
return to_enum __method__ unless block_given?
LXC.
list_containers.
map( &LXC::Container.method( :new)).
select( &:running?).
each &exe
end
end
end

61
lib/ns.rb Normal file
View file

@ -0,0 +1,61 @@
require 'fiddle'
require 'fiddle/import'
class NS
extend Fiddle::Importer
dlload Fiddle.dlopen( nil)
SetNS_Function = Fiddle::Function.new handler['setns'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
class <<self
def setns fd, t
r = SetNS_Function.call fd.to_i, t.to_i
SystemCallError.new "setns(#{fd.to_i})", Fiddle.last_error if -1 == r
r
end
def _open_nsfd pid, type
File.open "/proc/%d/ns/%s" % [pid, type]
end
def open pid, *types, &exe
ns = new pid, *types
if block_given?
begin yield ns
ensure ns.close
end
else ns
end
end
def change pid, *types, &exe
open pid, *types do |ns|
ns.change &exe
end
end
end
attr_reader :pid, :fds, :types
def initialize pid, *types
@pid, @fds = pid, {}
types.map {|t| @fds[t] = self.class._open_nsfd pid, t }
end
def close
@fds.each {|t, fd| fd.close }
end
def change &block
@owns = @fds.map {|t, _f| Own[t] }
begin
@fds.each {|type, fd| self.class.setns fd, 0 }
yield
ensure
@owns.each {|fd| self.class.setns fd, 0 }
end
end
Own = Hash.new {|h, ns| h[ns] = _open_nsfd $$, ns }
end

View file

@ -1,412 +0,0 @@
#!/usr/bin/env ruby
require 'pathname'
require 'fiddle'
require 'fiddle/import'
require 'json'
require 'lxc'
require 'prometheus/client'
=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 File
module Statfs
Magics = {}
Line = <<~EOF
ADFS 0xadf5
AFFS 0xadff
AFS 0x5346414F
AUTOFS 0x0187
CODA 0x73757245
CRAMFS 0x28cd3d45
CRAMFS_WEND 0x453dcd28
DEBUGFS 0x64626720
SECURITYFS 0x73636673
SELINUX 0xf97cff8c
SMACK 0x43415d53
RAMFS 0x858458f6
TMPFS 0x01021994
HUGETLBFS 0x958458f6
SQUASHFS 0x73717368
ECRYPTFS 0xf15f
EFS 0x414A53
EROFS_V1 0xE0F5E1E2
EXT2 0xEF53
EXT3 0xEF53
XENFS 0xabba1974
EXT4 0xEF53
BTRFS 0x9123683E
NILFS 0x3434
F2FS 0xF2F52010
HPFS 0xf995e849
ISOFS 0x9660
JFFS2 0x72b6
XFS 0x58465342
PSTOREFS 0x6165676C
EFIVARFS 0xde5e81e4
HOSTFS 0x00c0ffee
OVERLAYFS 0x794c7630
MINIX 0x137F
MINIX2 0x138F
MINIX2 0x2468
MINIX22 0x2478
MINIX3 0x4d5a
MSDOS 0x4d44
NCP 0x564c
NFS 0x6969
OCFS2 0x7461636f
OPENPROM 0x9fa1
QNX4 0x002f
QNX6 0x68191122
AFS_FS 0x6B414653
REISERFS 0x52654973
REISERFS "ReIsErFs"
REISER2FS "ReIsEr2Fs"
REISER2FS_JR "ReIsEr3Fs"
SMB 0x517B
CGROUP 0x27e0eb
CGROUP2 0x63677270
RDTGROUP 0x7655821
STACK_END 0x57AC6E9D
TRACEFS 0x74726163
V9FS 0x01021997
BDEVFS 0x62646576
DAXFS 0x64646178
BINFMTFS 0x42494e4d
DEVPTS 0x1cd1
BINDERFS 0x6c6f6f70
FUTEXFS 0xBAD1DEA
PIPEFS 0x50495045
PROC 0x9fa0
SOCKFS 0x534F434B
SYSFS 0x62656572
USBDEVICE 0x9fa2
MTD_INODE_FS 0x11307854
ANON_INODE_FS 0x09041934
BTRFS_TEST 0x73727279
NSFS 0x6e736673
BPF_FS 0xcafe4a11
AAFS 0x5a3c69f0
ZONEFS 0x5a4f4653
UDF 0x15013346
BALLOON_KVM 0x13661366
ZSMALLOC 0x58295829
DMA_BUF 0x444d4142
DEVMEM 0x454d444d
Z3FOLD 0x33
PPC_CMM 0xc7571590
EOF
Line.each_line do |l|
n, v = l.split(/\s+/)[0..1]
v =
case v
when /^0x(\h+)$/ then $1.to_i 16
when /^"([^"]+)"$/ then $1
else raise "Unknown value: #$1"
end
Magics[v] = n.downcase.to_sym
end
extend Fiddle::Importer
dlload Fiddle.dlopen( nil)
Struct_statfs = struct <<~EOS
long type,
long bsize,
unsigned long blocks,
unsigned long bfree,
unsigned long bavail,
unsigned long files,
unsigned long ffree,
int fsid[2],
long namelen,
long frsize,
long flags,
long spare[4]
EOS
class Struct_statfs
def fstypename
Magics[type]
end
def to_h
{type: type, fstypename: fstypename, bsize: bsize, blocks: blocks, bfree: bfree, bavail: bavail, files: files, ffree: ffree, fsid: fsid, namelen: namelen, frsize: frsize, flags: flags, spare: spare}
end
end
StatfsFun = Fiddle::Function.new handler['statfs'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
#extern 'int statfs( const char *__file, struct statfs *__buf)'
def self.new file
buf = Struct_statfs.malloc
val = StatfsFun.call file, buf
raise SystemCallError.new( "statfs(#{file.inspect})", Fiddle.last_error) unless val == 0
buf
end
end
end
class NS
extend Fiddle::Importer
dlload Fiddle.dlopen( nil)
SetNS_Function = Fiddle::Function.new handler['setns'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
class <<self
def setns fd, t
r = SetNS_Function.call fd.to_i, t.to_i
SystemCallError.new "setns(#{fd.to_i})", Fiddle.last_error if -1 == r
r
end
def _open_nsfd pid, type
File.open "/proc/%d/ns/%s" % [pid, type]
end
def open pid, *types, &exe
ns = new pid, *types
if block_given?
begin yield ns
ensure ns.close
end
else ns
end
end
def change pid, *types, &exe
open pid, *types do |ns|
ns.change &exe
end
end
end
attr_reader :pid, :fds, :types
def initialize pid, *types
@pid, @fds = pid, {}
types.map {|t| @fds[t] = self.class._open_nsfd pid, t }
end
def close
@fds.each {|t, fd| fd.close }
end
def change &block
@owns = @fds.map {|t, _f| Own[t] }
begin
@fds.each {|type, fd| self.class.setns fd, 0 }
yield
ensure
@owns.each {|fd| self.class.setns fd, 0 }
end
end
Own = Hash.new {|h, ns| h[ns] = _open_nsfd $$, ns }
end
class MountInfo
attr_reader :major, :minor, :mp, :dev, :type, :opts
# 4979 4977 0:213 / /proc rw,nosuid,nodev,noexec,relatime shared:2416 - proc proc rw
def initialize *args
i = args.find_index {|x| '-' == x }
args2 = args[(i+1)..-1]
ma, mi = args[2].split ':'
@major, @minor, @mp, @dev, @type, @opts =
ma.to_i, mi.to_i, args[4], args2[1], args2[0], args2[2].split( ',')
end
class << self
def parse line
new *line.split( ' ')
end
def of pid
Pathname.new( "/proc/#{pid}/mountinfo").readlines.map {|l| parse l }
end
end
end
class OsRelease
Path = Pathname.new '/etc/os-release'
class <<self
def parse txt
r =
txt.each_line.
map do |l|
k, v =
*case l.chomp
when /^([^=]+)="(.*)"$/ then [$1, $2]
when /^([^=]+)=(.*)$/ then [$1,$2]
else next
end
[ k.downcase.to_sym, v.sub( /^"/, '').sub( /"$/, '') ]
end.
compact.
to_h
new r
end
def read() parse Path.read if Path.exist? end
end
def initialize( h) @h = h.freeze end
def to_h() @h.dup end
def []( n) @h[n] end
def missing_method n, *a
if a.empty? and @h.has_key?( n)
@h[n]
else
super n, *a
end
end
end
class LxcCollector
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 ||= Prometheus::Client::Registry.new
@info = Prometheus::Client::Gauge.new :lxc_filesystem_info,
labels: %i[id mountpoint name fstype], docstring: 'CT Info'
@totalb = Prometheus::Client::Gauge.new :lxc_filesystem_total_bytes,
labels: %i[id mountpoint], docstring: 'Total bytes in filesystem'
@freeb = Prometheus::Client::Gauge.new :lxc_filesystem_free_bytes,
labels: %i[id mountpoint], docstring: 'Free bytes in filesystem'
@availb = Prometheus::Client::Gauge.new :lxc_filesystem_available_bytes,
labels: %i[id mountpoint], docstring: 'Free bytes available to unprivileged user'
@totalf = Prometheus::Client::Gauge.new :lxc_filesystem_total_files,
labels: %i[id mountpoint], docstring: 'Total files in filesystem'
@freef = Prometheus::Client::Gauge.new :lxc_filesystem_free_files,
labels: %i[id mountpoint], docstring: 'Free files in filesystem'
@os_release = Prometheus::Client::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 = Prometheus::Client::Gauge.new :lxc_os_version_id,
labels: %i[id], docstring: 'OS release (distribution) version'
@pkgs_upgradable = Prometheus::Client::Gauge.new :lxc_packages_upgradable,
labels: %i[id], docstring: 'Count of upgradable packages'
@pkgs_last_update = Prometheus::Client::Gauge.new :lxc_packages_last_update,
labels: %i[id], docstring: 'Last successfull source update'
[
@info, @totalb, @freeb, @availb, @totalf, @freef,
@os_release, @os_version,
@pkgs_upgradable, @pkgs_last_update
].each {|g| @prometheus.register g }
end
def running_containers &exe
return to_enum __method__ unless block_given?
LXC.
list_containers.
map {|n| LXC::Container.new n }.
select( &:running?).
each &exe
end
AptLastUpdateFile = Pathname.new '/var/cache/apt/pkgcache.bin'
def forked wr
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
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