first draft
This commit is contained in:
commit
d623efde64
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
*.sw[pomnqrst]
|
||||||
|
*.gem
|
||||||
|
Gemfile.lock
|
||||||
|
.rake_tasks
|
5
Gemfile
Normal file
5
Gemfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
gem 'ruby-lxc', git: 'https://git.denkn.at/deac/ruby-lxc'
|
||||||
|
gem 'prometheus-client'
|
||||||
|
gem 'puma'
|
||||||
|
gem 'rack'
|
14
config.ru
Normal file
14
config.ru
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require 'rack'
|
||||||
|
require_relative 'pvect-exporter'
|
||||||
|
require 'prometheus/client/formats/text'
|
||||||
|
|
||||||
|
run lambda {|env|
|
||||||
|
req = Rack::Request.new env
|
||||||
|
case req.path
|
||||||
|
when "/metrics"
|
||||||
|
collector = PveCtCollector.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
|
||||||
|
}
|
327
pvect-exporter.rb
Normal file
327
pvect-exporter.rb
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
#!/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 = {}
|
||||||
|
#=begin
|
||||||
|
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
|
||||||
|
#=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 FFI::Library
|
||||||
|
#ffi_lib 'c'
|
||||||
|
#attach_function :setns_without_exception_handling, :setns, %i[int int], :int
|
||||||
|
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_without_exception_handling fd.to_i, t.to_i
|
||||||
|
#SystemCallError.new "setns(#{fd.to_i})", FFI::LastError.error if -1 == r
|
||||||
|
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
|
||||||
|
|
||||||
|
=begin
|
||||||
|
LxcStartExe = Pathname.new "/usr/bin/lxc-start"
|
||||||
|
def get_running_ct_pids &block
|
||||||
|
return to_enum( __method__) unless block_given?
|
||||||
|
|
||||||
|
cts = []
|
||||||
|
Pathname.new( "/proc").each_child do |fn|
|
||||||
|
next if /\D/ =~ fn.basename.to_s
|
||||||
|
exepath = fn + 'exe'
|
||||||
|
next unless exepath.exist? and LxcStartExe == exepath.readlink
|
||||||
|
pid = fn.basename.to_s.to_i
|
||||||
|
cts.push pid
|
||||||
|
end
|
||||||
|
|
||||||
|
Pathname.new( "/proc").each_child do |fn|
|
||||||
|
next if /\D/ =~ fn.basename.to_s
|
||||||
|
statuspath = fn + 'status'
|
||||||
|
ppid = statuspath.read.match( /^PPid:\s+(\d+)$/)[1].to_i
|
||||||
|
next unless cts.include? ppid
|
||||||
|
yield fn.basename.to_s.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
pids = get_running_ct_pids
|
||||||
|
=end
|
||||||
|
|
||||||
|
class PveCtCollector
|
||||||
|
def fetch
|
||||||
|
LXC.list_containers.map {|n|LXC::Container.new n}.select( &:running?).each do |ct|
|
||||||
|
pid = ct.init_pid
|
||||||
|
mis = MountInfo.of pid
|
||||||
|
NS.change pid, :pid, :mnt do
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize registry = nil
|
||||||
|
@prometheus ||= Prometheus::Client::Registry.new
|
||||||
|
@info = Prometheus::Client::Gauge.new :pvect_filesystem_info, labels: %i[id mountpoint name fstype], docstring: 'CT Info'
|
||||||
|
@totalb = Prometheus::Client::Gauge.new :pvect_filesystem_total_bytes, labels: %i[id mountpoint], docstring: 'Total bytes in filesystem'
|
||||||
|
@freeb = Prometheus::Client::Gauge.new :pvect_filesystem_free_bytes, labels: %i[id mountpoint], docstring: 'Free bytes in filesystem'
|
||||||
|
@availb = Prometheus::Client::Gauge.new :pvect_filesystem_available_bytes, labels: %i[id mountpoint], docstring: 'Free bytes available to unprivileged user'
|
||||||
|
@totalf = Prometheus::Client::Gauge.new :pvect_filesystem_total_files, labels: %i[id mountpoint], docstring: 'Total files in filesystem'
|
||||||
|
@freef = Prometheus::Client::Gauge.new :pvect_filesystem_free_files, labels: %i[id mountpoint], docstring: 'Free files in filesystem'
|
||||||
|
[@info, @totalb, @freeb, @availb, @totalf, @freef].each {|g| @prometheus.register g }
|
||||||
|
end
|
||||||
|
|
||||||
|
def collect
|
||||||
|
rd, wr = IO.pipe
|
||||||
|
pid = fork do
|
||||||
|
rd.close
|
||||||
|
fetch do |ct, mp, st|
|
||||||
|
next unless st.fstypename and 0 < st.blocks
|
||||||
|
wr.puts st.to_h.merge( mp: mp, id: ct.name, name: ct.config_item( 'lxc.uts.name')).to_json
|
||||||
|
end
|
||||||
|
wr.close
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
wr.close
|
||||||
|
rd.each_line do |l|
|
||||||
|
st = OpenStruct.new JSON.parse( l)
|
||||||
|
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
|
||||||
|
end
|
||||||
|
rd.close
|
||||||
|
Process.wait pid
|
||||||
|
@prometheus
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue