| --[[ |
| |
| Example lua script demonstrating the SLURM proctrack/lua interface. |
| |
| This script implements a very simple job step container using CPUSETs. |
| |
| --]] |
| |
| require "posix" |
| |
| --########################################################################-- |
| -- |
| -- SLURM proctrack/lua interface: |
| -- |
| --########################################################################-- |
| |
| local use_release_agent = false |
| |
| function slurm_container_create (job) |
| local id = cpuset_id_create (job) |
| local cpu_list = cpumap:convert_ids (job.JobCPUs) |
| |
| log_verbose ("slurm_container_create: job=%u.%u CPUs=%s (%s) cpuset=%d", |
| job.jobid, job.stepid, job.JobCPUs, cpu_list, id) |
| if not cpuset_create (id, cpu_list) then return nil end |
| return id |
| end |
| |
| function slurm_container_add (job, id, pid) |
| log_verbose ("slurm_container_add(%d, %d)\n", id, pid) |
| return cpuset_add_pid (id, pid) |
| end |
| |
| function slurm_container_signal (id, signo) |
| log_verbose ("slurm_container_signal(%d, %d)\n", id, signo) |
| cpuset_kill (id, signo) |
| return slurm.SUCCESS |
| end |
| |
| function slurm_container_destroy (id) |
| log_verbose ("slurm_container_destroy (id=%d)\n", id) |
| return (cpuset_destroy (id)) and 0 or -1 |
| end |
| |
| function slurm_container_find (pid) |
| log_verbose ("slurm_container_find (pid=%d)\n", pid) |
| for i, id in ipairs (posix.dir (cpuset_dir)) do |
| path = string.format ("%s/%s", cpuset_dir, id) |
| st = posix.stat (path) |
| if st.type == "directory" and cpuset_has_pid (id) then |
| return id |
| end |
| end |
| return slurm.FAILURE |
| end |
| |
| function slurm_container_has_pid (id, pid) |
| log_verbose ("slurm_container_has_pid (id=%d, pid=%d)\n", id, pid) |
| return cpuset_has_pid (id, pid) |
| end |
| |
| function slurm_container_wait (id) |
| local s = 1 |
| |
| if not cpuset_exists (id) then return 0 end |
| |
| log_verbose ("slurm_container_wait (id=%d)\n", id) |
| while not cpuset_destroy (id) do |
| cpuset_kill (id, 9) |
| log_debug ("Waiting %ds for cpuset id=%d\n", s, id) |
| posix.sleep (s) |
| s = (2*s <= 30) and 2*s or 30 -- Wait a max of 30s |
| end |
| return slurm.SUCCESS |
| end |
| |
| function slurm_container_get_pids (id) |
| log_debug ("slurm_container_get_pids (id=%d)\n", id) |
| return cpuset_pids (id) |
| end |
| |
| |
| --########################################################################-- |
| -- |
| -- Internal lua functions: |
| -- |
| --########################################################################-- |
| |
| root_cpuset = {} |
| |
| function split (line) |
| local t = {} |
| for word in line:gmatch ('%S+') do table.insert(t, word) end |
| return t |
| end |
| |
| function get_cpuset_dir () |
| for line in io.lines ("/proc/mounts") do |
| local t = split (line) |
| if t[3] == "cpuset" then return t[2] end |
| end |
| return nil |
| end |
| |
| function cpuset_exists (id) |
| local path = cpuset_dir .. "/" .. id |
| local s = posix.stat (path) |
| return (s ~= nil and s.type == "directory") |
| end |
| |
| -- Set cpus |
| function cpuset_set_f (path, name, val) |
| local f, msg = io.open (path .."/".. name, "w") |
| if f == nil then |
| log_err ("open (%s/%s): %s\n", path, name, msg) |
| return nil |
| end |
| |
| -- |
| -- Write value to cpuset if [val] was passed to this function, |
| -- if not use the value from the root cpuset: |
| -- |
| f:write (val or root_cpuset[name]) |
| f:close () |
| end |
| |
| function cpuset_create (name, cpus) |
| local mask = posix.umask() |
| local path = cpuset_dir .. "/" .. name |
| |
| posix.umask ("077") |
| local d, s = posix.mkdir (path) |
| if (d == nil and s ~= "File exists") then |
| log_err ("cpuset_create: %s: %s\n", path, s or "msg") |
| return false |
| end |
| posix.umask (mask) |
| cpuset_set_f (path, "cpus", cpus) |
| cpuset_set_f (path, "mems") |
| if (use_release_agent == true) then |
| cpuset_set_f (path, "notify_on_release", 1) |
| end |
| return true |
| end |
| |
| function cpuset_destroy (name) |
| if (not cpuset_exists (name)) then return true end |
| |
| local path = cpuset_dir .. "/" .. name |
| return (posix.rmdir (path) ~= 0) or false; |
| end |
| |
| function cpuset_add_pid (name, pid) |
| if (not cpuset_exists (name)) then return -1 end |
| |
| local path = cpuset_dir .. "/" .. name |
| local f = io.open (path.."/tasks", "w") |
| f:write (pid) |
| f:close() |
| log_debug ("Added pid=%d to cpuset %s", pid, name) |
| end |
| |
| function cpuset_kill (name, signo) |
| if (not cpuset_exists (name)) then return end |
| |
| local path = string.format ("%s/%s/tasks", cpuset_dir, name) |
| |
| local path_fh = io.open(path) |
| if path_fh then |
| while true do |
| local pid = path_fh:read() |
| if pid == nil then break end |
| log_debug ("Sending signal %d to pid %d", signo, pid) |
| posix.kill (pid, signo) |
| end |
| end |
| end |
| |
| function cpuset_read (path, name) |
| local f = assert (io.open (path .. "/" .. name)) |
| val = f:read("*all") |
| f:close() |
| return val |
| end |
| |
| -- |
| -- lua doesn't have bitwise operators, fun. |
| -- |
| function truncate_to_n_bits (n, bits) |
| local result = 0 |
| for i = 1, bits do |
| local l = math.mod (n, 2) |
| if (l == 1) then |
| result = result + (2 ^ (i-1)) |
| end |
| n = (n - l) / 2 |
| end |
| return result |
| end |
| |
| -- |
| -- Create a unique identifier from the job step in [job] |
| -- to be used as the name of the resulting cpuset |
| -- |
| function cpuset_id_create (job) |
| local id = job.jobid |
| |
| -- Simulate a left shift by 16 (I think): |
| for i = 0, 16 do |
| id = id*2 |
| end |
| |
| -- Add the lower 16 bits of the stepid: |
| id = id + truncate_to_n_bits (job.stepid, 16) |
| |
| -- Must truncate result to 32bits until SLURM's job container |
| -- id is no longer represented by uint32_t : |
| return truncate_to_n_bits (id, 32) |
| end |
| |
| function cpuset_has_pid (id, process_id) |
| if (not cpuset_exists (id)) then return false end |
| |
| local path = string.format ("%s/%s/tasks", cpuset_dir, id) |
| |
| -- Force pid to be a number |
| local pid = tonumber (process_id) |
| |
| for task in io.lines (path) do |
| -- again, ensure task is represented as a lua number for comparison: |
| if tonumber(task) == pid then return true end |
| end |
| return false |
| end |
| |
| function pid_is_thread (process_id) |
| local pid_status_path = string.format ("/proc/%d/status",process_id) |
| local pid_status_fh = io.open(pid_status_path) |
| |
| if pid_status_fh then |
| while true do |
| local pid_status_line=pid_status_fh:read() |
| if pid_status_line == nil then break end |
| if string.match(pid_status_line,'^Tgid:%s+' .. process_id .. '$') then return false end |
| end |
| end |
| return true |
| end |
| |
| function cpuset_pids (id) |
| local pids = {} |
| if (cpuset_exists (id)) then |
| local path = string.format ("%s/%s/tasks", cpuset_dir, id) |
| local path_fh = io.open(path) |
| if path_fh then |
| while true do |
| local task=path_fh:read() |
| if task == nil then break end |
| if not ( pid_is_thread(task) ) then |
| table.insert (pids, task) |
| end |
| end |
| end |
| end |
| return pids |
| end |
| |
| -- |
| -- cpumap_create() creates an object whose sole purpose is to |
| -- convert a list of physical CPU ids, which are given relative |
| -- to physical location, back to the logical cpu d map of the |
| -- current host. |
| -- |
| function cpumap_create () |
| |
| function cpuset_list_create (s) |
| local cpus = {} |
| for c in s:gmatch ('[^,]+') do |
| local s, e = c:match ('([%d]+)-?([%d]*)') |
| if e == "" then e = s end |
| for cpu = s, e do |
| table.insert (cpus, cpu) |
| end |
| end |
| return cpus |
| end |
| |
| function read_cpu_topology_member (id, name) |
| local val |
| local cpudir = "/sys/devices/system/cpu" |
| local path = string.format ("%s/cpu%d/topology/%s", cpudir, id, name) |
| local f, err = io.open (path, "r") |
| if f == nil then |
| print (err) |
| return f, err |
| end |
| val = f:read ("*all") |
| f:close() |
| return val |
| end |
| |
| function cpu_info_create (id) |
| local cpuinfo = {} |
| local cpudir = "/sys/devices/system/cpu" |
| cpuinfo.id = id |
| cpuinfo.pkgid = read_cpu_topology_member (id, "physical_package_id") |
| cpuinfo.coreid = read_cpu_topology_member (id, "core_id") |
| return cpuinfo |
| end |
| |
| function list_id (self, i) |
| return self.cpu_list[i+1].id |
| end |
| |
| local function cmp_cpu_info (a,b) |
| if a.pkgid == b.pkgid then |
| return a.coreid < b.coreid |
| else |
| return a.pkgid < b.pkgid |
| end |
| end |
| |
| local function convert_cpu_ids (self, s) |
| local l = {} |
| for i, id in ipairs (cpuset_list_create (s)) do |
| table.insert (l, list_id (self, id)) |
| end |
| return table.concat (l, ",") |
| end |
| |
| local cpu_map = { |
| cpu_list = {}, |
| ncpus = 0, |
| get_id = list_id, |
| convert_ids = convert_cpu_ids |
| } |
| |
| for i, dir in ipairs (posix.dir ("/sys/devices/system/cpu")) do |
| local id = string.match (dir, 'cpu([%d]+)') |
| if id then |
| table.insert (cpu_map.cpu_list, cpu_info_create (id)) |
| end |
| end |
| |
| cpu_map.ncpus = #cpu_map.cpu_list |
| |
| table.sort (cpu_map.cpu_list, cmp_cpu_info) |
| |
| return cpu_map |
| end |
| |
| --########################################################################-- |
| -- |
| -- Initialization code: |
| -- |
| --########################################################################-- |
| |
| log_msg = slurm.log_info |
| log_verbose = slurm.log_verbose |
| log_debug = slurm.log_debug |
| log_err = slurm.error |
| |
| |
| cpuset_dir = get_cpuset_dir () |
| if cpuset_dir == nil then |
| print "cpuset must be mounted" |
| return 0 |
| end |
| |
| root_cpuset.cpus = cpuset_read (cpuset_dir, "cpus") |
| root_cpuset.mems = cpuset_read (cpuset_dir, "mems") |
| |
| cpumap = cpumap_create () |
| |
| log_msg ("initialized: root cpuset = %s\n", cpuset_dir) |
| |
| return slurm.SUCCESS |
| |
| -- vi: filetype=lua ts=4 sw=4 expandtab |