SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|

2023-01-22(Sun) dockerのコンテナ内でもっとアレしたい

  年末に「今年のまとめ」としてアレコレ書きたかったのだが、ほかにもヤリたいことだらけで、まとめられないまま1月も下旬になってしまった。まぁ、反省や抱負も大事だが、ヤリたいことがあるうちは、それをヤルことが優先である。反省や抱負は一段落してからにしよう。

  去年から今年にかけてプログラムをする機会が多い。公私ともに。仕事環境は劣化の一途でいっそヤメたろうかとまで思っているが、割と好きなことをできているので、まさにイタしカユしである。

  最近は、コンテナで動かすことを前提にしたプログラムをすることが多く、ちょうど1年前くらいに作ったdockerのコンテナ内でアレしたいのツール群が大活躍中なのであるが、活躍しているからこそ、先に面倒だから実装を省いた「プロセスの起動時間やCPU時間」が見たくなってきてしまった。

  というわけでサクッと実装である。

# docker_ps ldap-
# INTO [ldap-alpha].
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:44 -        00:00:00 /sbin/init 
root          26       1  0 11:44 -        00:00:00 /usr/lib/systemd/systemd-journald 
root          36       1  0 11:44 -        00:00:00 /usr/lib/systemd/systemd-homed 
dbus          44       1  0 11:44 -        00:00:00 /usr/bin/dbus-broker-launch --scope system --audit 
dbus          53      44  0 11:44 -        00:00:00 dbus-broker --log 4 --controller 9 --machine-id a6cd51e94fd74eb98afdf11c55965591 --max-bytes 536870912 --max-fds 4096 --max-matches 16384 --audit 
ldap          54       1  0 11:44 -        00:00:00 /usr/sbin/slapd -u ldap -h ldap:/// ldaps:/// ldapi:/// 
root        1287       0  0       -                 
# docker exec ldap-alpha ps -efww
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 11:44 ?        00:00:00 /sbin/init
root          26       1  0 11:44 ?        00:00:00 /usr/lib/systemd/systemd-journald
root          36       1  0 11:44 ?        00:00:00 /usr/lib/systemd/systemd-homed
dbus          44       1  0 11:44 ?        00:00:00 /usr/bin/dbus-broker-launch --scope system --audit
dbus          53      44  0 11:44 ?        00:00:00 dbus-broker --log 4 --controller 9 --machine-id a6cd51e94fd74eb98afdf11c55965591 --max-bytes 536870912 --max-fds 4096 --max-matches 16384 --audit
ldap          54       1  0 11:44 ?        00:00:00 /usr/sbin/slapd -u ldap -h ldap:/// ldaps:/// ldapi:///
root        1356       0  0 12:07 ?        00:00:00 ps -efww

  で、以下がdocker_execのコードである。

#!/usr/bin/env ruby
# coding: utf-8
 
class String
	def to_ipaddr
		r = self.scan(/.{8}/).map {|h8|
			h8.scan(/../).map {|hh|
				hh.to_i(16)
			}.reverse
		}.join('.')
		self.size < 9 ? r : r.gsub(/(0\.){3,}0?/, '::')
	end
end
 
states = [
	'', 'ESTABLISHED', 'SYN_SENT', 'SYN_RECV', 'FIN_WAIT1', 'FIN_WAIT2',
	'TIME_WAIT', 'CLOSE', 'CLOSE_WAIT', 'LAST_ACK', 'LISTEN', 'CLOSING',
]
 
$0 =~ /docker_(.+)/ and subcmd = $1
 
ps = `docker ps`
targets = []; ps.split(/\n/)[1..-1].each {|l|
	(it = l.split(/\s+/).last) =~ /#{ARGV.last}/ and targets << it
}
 
if(targets.size == 0)
	puts('# NOT MATCH...')
 
elsif(targets.size == 1)
 
	def get_ps(target)
		uids = {}; progs = {}; sockets = {}
		IO.popen(['docker', 'exec', target, 'ls', '-lR', '/proc'], :err => [:child, :out]) {|io|
			pid = nil; io.each {|line|
				if(!pid and line =~ /^d.*?(\d+)$/)
					uids[$1] = line.split(/\s+/)[2]
				end
				if(line =~ /^\/proc\/(\d+):/)
					progs[pid = $1] ||= []
				end
				if(line =~ / exe -> (.+)/)
					progs[pid] << $1
				end
				if(line =~ / -> socket:\[(\d+)\]/)
					sockets[$1] ||= []
					sockets[$1] << pid
				end
			}
		}
		[uids, progs, sockets]
	end
 
	def get_ps2(target)
		ppids = {}; cmdlines = {}
		IO.popen(['docker', 'exec', target, '/bin/bash', '-c',
			'ls /proc | grep -e "^[1-9]" | sed "s/\(.*\)/cat \/proc\/\\1\/status/" | sh 2>/dev/null'], :err => [:child, :out]) {|io|
			pid = nil; io.each {|line|
				line =~ /^Pid:\s*(\d+)/ and pid = $1
				line =~ /^PPid:\s*(\d+)/ and ppids[pid] = $1
			}
		}
		IO.popen(['docker', 'exec', target, '/bin/bash', '-c',
			'ls /proc | grep -e "^[1-9]" | sed "s/\(.*\)/echo -n \\1:; cat \/proc\/\\1\/cmdline; echo/" | sh 2>/dev/null'], :err => [:child, :out]) {|io|
			io.each {|line|
				line =~ /^(\d+):(.*)/ and cmdlines[$1] = $2.gsub(/\u0000/, ' ')
			}
		}
		[ppids, cmdlines]
	end
 
	def get_ps3(target)
		start_times = {}; cpu_times = {}
		btime = nil
		IO.popen(['/bin/bash', '-c', 'cat /proc/stat'], :err => [:child, :out]) {|io|
			io.each {|line|
				line =~ /^btime\s+(\d+)/ and btime = $1.to_i and break
			}
		}
		now_a = Time.now.to_a
		IO.popen(['docker', 'exec', target, '/bin/bash', '-c',
			'ls /proc | grep -e "^[1-9]" | sed "s/\(.*\)/cat \/proc\/\\1\/stat/" | sh 2>/dev/null'], :err => [:child, :out]) {|io|
			io.each {|line|
				if(line =~ /(\d+)\s+\([^)]+\)\s+(.+)/)
					pid = $1
					stats = $2.split(/\s/)
					cpu_time = (stats[11].to_i + stats[12].to_i) / 100	# 11:utime + 12:stime
					days = cpu_time / 86400
					cpu_times[pid] = (days > 0 ? '%2d-' % days : '') + Time.at(cpu_time).utc.strftime('%H:%M:%S')
					start_time = Time.at(btime + stats[19].to_i / 100)	# 19:start_time
					start_time_a = start_time.to_a
					if(now_a[5] != start_time_a[5])
						start_times[pid] = '%5d' % start_time.year
					elsif(now_a[7] != start_time_a[7])
						start_times[pid] = start_time.strftime('%b%d')
					else
						start_times[pid] = start_time.strftime('%H:%M')
					end
				end
			}
		}
		[start_times, cpu_times]
	end
 
	puts('# INTO [%s].' % targets[0])
	if(subcmd == 'exec')
		# TODO
		system('docker exec -it %s %s /bin/bash' % [ARGV[0..-2].join(' '), targets[0]])
 
	elsif(subcmd == 'ps')
		uids, progs, sockets = get_ps(targets[0])
		ppids, cmdlines = get_ps2(targets[0])
		start_times, cpu_times = get_ps3(targets[0])
		puts('UID          PID    PPID  C STIME TTY          TIME CMD')
		progs.keys.sort {|a, b|
			a.to_i <=> b.to_i
		}.each {|pid|
			puts('%-8s %7d %7d %2d %5s %-8s %8s %s' % [uids[pid], pid, ppids[pid] || 0, 0, start_times[pid], '-', cpu_times[pid], cmdlines[pid]])
		}
 
	elsif(subcmd == 'netstat')
		uids, progs, sockets = get_ps(targets[0])
		puts('Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name')
		['tcp', 'tcp6', 'udp', 'udp6'].each {|prot|
			IO.popen(['docker', 'exec', targets[0], 'cat', '/proc/net/' + prot]) {|io|
				io.each {|line|
					ls = line.split(/[:\s]+/)
					ls[1] =~ /^\d/ or next
					laddr = ls[2].to_ipaddr + ':' + ls[3].to_i(16).to_s
					faddr = ls[4].to_ipaddr + ':' + ls[5].to_i(16).to_s
					prog = '-'; (it = sockets[ls[14]]) and prog = '%s/%s' % [it[0], progs[it[0]][0].split('/').last]
					puts('%-5s %6d %6d %-23s %-23s %-11s %-s' % [prot, ls[8].to_i(16), ls[7].to_i(16), laddr, faddr, states[ls[6].to_i(16)], prog])
				}
			}
			(it = $?.exitstatus and it == 0) or raise 'failed.'
		}
 
	else
		system('docker %s %s %s' % [subcmd, ARGV[0..-2].join(' '), targets[0]])
	end
 
else
	puts('# MULTIPLE MATCH...')
	puts(targets.join("\n"))
end
 
__END__

  結局、去年書いたコードに特段のバグは見つからず、ほぼ純粋に処理を追加しただけで実装できた。こういうのが、なんとも気分がいいんだよなぁ。