From 52c72fd6700601fa1daf2ecab134e88863d3705e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 3 Sep 2024 16:22:47 +0200 Subject: [PATCH] collect2, fetch2 tests --- Gemfile | 2 + lib/lxc_collector.rb | 103 +++++++++++++++++++++++++++++++++++++++++-- lib/ns.rb | 71 ++++++++++++++++++++--------- 3 files changed, 151 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index 1572c41..514cb9f 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,5 @@ gem 'rack' gem 'puma' gem 'sd_notify' gem 'cbor' +gem 'io-extra' +gem 'ffi' diff --git a/lib/lxc_collector.rb b/lib/lxc_collector.rb index c605e4f..7a03f5d 100755 --- a/lib/lxc_collector.rb +++ b/lib/lxc_collector.rb @@ -3,6 +3,7 @@ require 'pathname' require 'prometheus/client' require 'lxc' +require 'ostruct' require_relative 'ns' require_relative 'file-statfs' @@ -60,7 +61,7 @@ class LxcCollector AptLastUpdateFile = Pathname.new '/var/cache/apt/pkgcache.bin' - def forked wr + def forkedo wr LXC.running_containers do |ct| pid = ct.init_pid mis = MountInfo.of pid @@ -106,7 +107,7 @@ class LxcCollector exit 0 end - def collect + 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 @@ -147,9 +148,105 @@ class LxcCollector 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.map do |ct| + Thread.new ct 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 + end.each &:join + @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.collect) + puts Prometheus::Client::Formats::Text.marshal( LxcCollector.new.collect2) end diff --git a/lib/ns.rb b/lib/ns.rb index defab21..6879554 100644 --- a/lib/ns.rb +++ b/lib/ns.rb @@ -1,5 +1,6 @@ require 'fiddle' require 'fiddle/import' +require 'io/extra' class NS extend Fiddle::Importer @@ -32,30 +33,56 @@ class 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 } + def enter pid, keep_open: nil + new( pid).enter keep_open: keep_open end end - Own = Hash.new {|h, ns| h[ns] = _open_nsfd $$, ns } + attr_reader :pid, :fds + + def initialize pid, *types + @pid, @fds = pid, {} + types = Dir.children "/proc/%d/ns" % pid if types.empty? or types.include?( :any) + types.each {|t| @fds[t.to_sym] = self.class._open_nsfd pid, t } + end + + def close_ios *keep_open + keep_open = + keep_open.flatten.map do |fd| + case fd + when Integer then fd + when IO then fd.to_i + when nil # ignore + else raise ArgumentError, "Unknown file descriptor for keeping opened: #{fd.inspect}" + end + end.sort + IO.fdwalk(0) {|fd| fd.close unless keep_open.include? fd.to_i } + end + + def enter keep_open: nil + close_ios keep_open, @fds.values + @fds.each {|_, fd| self.class.setns fd, 0 } + close + nil + end + + def close + @fds.each {|_, fd| fd.close } + end + + def change &block + if block_given? + @owns = @fds.map {|ns, _f| self.class._open_nsfd $$, ns } + begin + @fds.each {|type, fd| self.class.setns fd, 0 } + yield + ensure + @owns.each {|fd| self.class.setns fd, 0 } + end + else + @fds.each {|type, fd| self.class.setns fd, 0 } + nil + end + end end - -