refactoring files and classes.
This commit is contained in:
parent
e225cc96db
commit
23a5d2c729
11
config.ru
11
config.ru
|
@ -1,13 +1,20 @@
|
|||
require 'rack'
|
||||
require_relative 'lxc-exporter'
|
||||
require_relative 'lib/lxc_collector'
|
||||
require_relative 'lib/authorized_keys_collector'
|
||||
require 'prometheus/client/formats/text'
|
||||
|
||||
run lambda {|env|
|
||||
req = Rack::Request.new env
|
||||
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
|
||||
[200, {"Content-Type" => "text/plain"}, [Prometheus::Client::Formats::Text.marshal( collector.collect)]]
|
||||
|
||||
else
|
||||
[404, {"Content-Type" => "text/plain"}, ["Not found\nYou want to try /metrics?\n"]]
|
||||
end
|
||||
|
|
140
lib/file-statfs.rb
Normal file
140
lib/file-statfs.rb
Normal 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
|
25
lib/lxc-exporter/mount_info.rb
Normal file
25
lib/lxc-exporter/mount_info.rb
Normal 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
|
38
lib/lxc-exporter/os_release.rb
Normal file
38
lib/lxc-exporter/os_release.rb
Normal 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
170
lib/lxc_collector.rb
Executable 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
22
lib/lxc_extend.rb
Normal 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
61
lib/ns.rb
Normal 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
|
||||
|
||||
|
412
lxc-exporter.rb
412
lxc-exporter.rb
|
@ -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
|
Loading…
Reference in a new issue