1414// limitations under the License.
1515//
1616
17+ use std:: path:: PathBuf ;
1718use std:: process;
1819
1920use nix:: sched:: { sched_getaffinity, CpuSet } ;
2021use nix:: unistd:: Pid ;
2122
2223use ptools:: {
2324 cpu_to_node, enumerate_tids_from, numa_node_cpus, numa_online_nodes, parse_pid_spec,
24- LiveProcess , ProcSource ,
25+ CoredumpSource , LiveProcess , ProcSource ,
2526} ;
2627
28+ enum Operand {
29+ Live ( ptools:: PidSpec ) ,
30+ Core ( Box < dyn ProcSource > ) ,
31+ }
32+
2733/// Get the CPU number a thread is currently running on (field 39 of /proc/PID/task/TID/stat).
2834fn get_thread_cpu ( source : & dyn ProcSource , tid : u64 ) -> Option < u32 > {
2935 let stat = source. read_tid_stat ( tid) . ok ( ) ?;
@@ -117,14 +123,31 @@ fn thread_has_affinity_for_node(affinity: &CpuSet, node: u32) -> bool {
117123 . any ( |& cpu| affinity. is_set ( cpu as usize ) . unwrap_or ( false ) )
118124}
119125
126+ /// Check whether a list of allowed CPUs includes any CPU on the given node.
127+ fn cpus_include_node ( allowed : & [ u32 ] , node : u32 ) -> bool {
128+ let Ok ( cpus) = numa_node_cpus ( node) else {
129+ return false ;
130+ } ;
131+ cpus. iter ( ) . any ( |cpu| allowed. contains ( cpu) )
132+ }
133+
134+ /// Extract Cpus_allowed_list from /proc status and parse it.
135+ fn get_affinity_from_status ( source : & dyn ProcSource , tid : u64 ) -> Option < Vec < u32 > > {
136+ let status = source. read_tid_status ( tid) . ok ( ) ?;
137+ let line = status
138+ . lines ( )
139+ . find_map ( |l| l. strip_prefix ( "Cpus_allowed_list:" ) ) ?;
140+ ptools:: parse_list_format ( line. trim ( ) ) . ok ( )
141+ }
142+
120143struct Args {
121144 affinity_nodes : Option < Vec < u32 > > ,
122145 had_bad_nodes : bool ,
123- specs : Vec < ptools :: PidSpec > ,
146+ operands : Vec < Operand > ,
124147}
125148
126149fn print_usage ( ) {
127- eprintln ! ( "Usage: plgrp [-a node_list] pid[/tid] ..." ) ;
150+ eprintln ! ( "Usage: plgrp [-a node_list] [ pid[/tid] | core ] ..." ) ;
128151 eprintln ! ( "Display home NUMA node and thread affinities." ) ;
129152 eprintln ! ( ) ;
130153 eprintln ! ( "Options:" ) ;
@@ -139,7 +162,7 @@ fn parse_args() -> Args {
139162 let mut args = Args {
140163 affinity_nodes : None ,
141164 had_bad_nodes : false ,
142- specs : Vec :: new ( ) ,
165+ operands : Vec :: new ( ) ,
143166 } ;
144167 let mut parser = lexopt:: Parser :: from_env ( ) ;
145168
@@ -176,10 +199,18 @@ fn parse_args() -> Args {
176199 Value ( val) => {
177200 let s = val. to_string_lossy ( ) ;
178201 match parse_pid_spec ( & s) {
179- Ok ( spec) => args. specs . push ( spec) ,
180- Err ( e) => {
181- eprintln ! ( "plgrp: {e}" ) ;
182- process:: exit ( 2 ) ;
202+ Ok ( spec) => args. operands . push ( Operand :: Live ( spec) ) ,
203+ Err ( _) => {
204+ let path = PathBuf :: from ( & * s) ;
205+ match CoredumpSource :: from_corefile ( & path) {
206+ Ok ( source) => {
207+ args. operands . push ( Operand :: Core ( Box :: new ( source) ) ) ;
208+ }
209+ Err ( e) => {
210+ eprintln ! ( "plgrp: {}: {e}" , path. display( ) ) ;
211+ process:: exit ( 2 ) ;
212+ }
213+ }
183214 }
184215 }
185216 }
@@ -190,38 +221,65 @@ fn parse_args() -> Args {
190221 }
191222 }
192223
193- if args. specs . is_empty ( ) {
194- eprintln ! ( "plgrp: at least one pid[/tid] required" ) ;
224+ if args. operands . is_empty ( ) {
225+ eprintln ! ( "plgrp: at least one pid[/tid] or core required" ) ;
195226 process:: exit ( 2 ) ;
196227 }
197228 args
198229}
199230
200- fn print_thread ( source : & dyn ProcSource , tid : u64 , affinity_nodes : & Option < Vec < u32 > > ) -> bool {
231+ fn print_thread (
232+ source : & dyn ProcSource ,
233+ tid : u64 ,
234+ affinity_nodes : & Option < Vec < u32 > > ,
235+ is_coredump : bool ,
236+ ) -> bool {
201237 let pid = source. pid ( ) ;
202- let Some ( cpu) = get_thread_cpu ( source, tid) else {
203- eprintln ! ( "plgrp: cannot read CPU for {}/{}" , pid, tid) ;
204- return false ;
238+ let home = if is_coredump {
239+ "?" . to_string ( )
240+ } else {
241+ match get_thread_cpu ( source, tid) {
242+ Some ( cpu) => cpu_to_node ( cpu)
243+ . map ( |n| n. to_string ( ) )
244+ . unwrap_or_else ( || "?" . to_string ( ) ) ,
245+ None => {
246+ eprintln ! ( "plgrp: cannot read CPU for {}/{}" , pid, tid) ;
247+ return false ;
248+ }
249+ }
205250 } ;
206- let home = cpu_to_node ( cpu)
207- . map ( |n| n. to_string ( ) )
208- . unwrap_or_else ( || "?" . to_string ( ) ) ;
209251
210252 let pid_tid = format ! ( "{}/{}" , pid, tid) ;
211253 if let Some ( nodes) = affinity_nodes {
212- let affinity = get_thread_affinity ( tid) ;
213- let aff_str: String = nodes
214- . iter ( )
215- . map ( |& n| {
216- let label = match & affinity {
217- Some ( cpuset) if thread_has_affinity_for_node ( cpuset, n) => "bound" ,
218- Some ( _) => "none" ,
219- None => "?" ,
220- } ;
221- format ! ( "{}/{}" , n, label)
222- } )
223- . collect :: < Vec < _ > > ( )
224- . join ( "," ) ;
254+ let aff_str: String = if is_coredump {
255+ let allowed = get_affinity_from_status ( source, tid) ;
256+ nodes
257+ . iter ( )
258+ . map ( |& n| {
259+ let label = match & allowed {
260+ Some ( cpus) if cpus_include_node ( cpus, n) => "bound" ,
261+ Some ( _) => "none" ,
262+ None => "?" ,
263+ } ;
264+ format ! ( "{}/{}" , n, label)
265+ } )
266+ . collect :: < Vec < _ > > ( )
267+ . join ( "," )
268+ } else {
269+ let affinity = get_thread_affinity ( tid) ;
270+ nodes
271+ . iter ( )
272+ . map ( |& n| {
273+ let label = match & affinity {
274+ Some ( cpuset) if thread_has_affinity_for_node ( cpuset, n) => "bound" ,
275+ Some ( _) => "none" ,
276+ None => "?" ,
277+ } ;
278+ format ! ( "{}/{}" , n, label)
279+ } )
280+ . collect :: < Vec < _ > > ( )
281+ . join ( "," )
282+ } ;
225283 println ! ( "{:>14} {:>4} {}" , pid_tid, home, aff_str) ;
226284 } else {
227285 println ! ( "{:>14} {:>4}" , pid_tid, home) ;
@@ -240,22 +298,56 @@ fn main() {
240298 }
241299
242300 let mut error = false ;
243- for spec in & args. specs {
244- let source = LiveProcess :: new ( spec. pid ) ;
245- if let Some ( tid) = spec. tid {
246- if !print_thread ( & source, tid, & args. affinity_nodes ) {
247- error = true ;
248- }
249- } else {
250- let tids = enumerate_tids_from ( & source) ;
251- if tids. is_empty ( ) {
252- eprintln ! ( "plgrp: cannot read threads for PID {}" , spec. pid) ;
253- error = true ;
254- continue ;
301+ for operand in & args. operands {
302+ match operand {
303+ Operand :: Live ( spec) => {
304+ let source = LiveProcess :: new ( spec. pid ) ;
305+ if let Some ( tid) = spec. tid {
306+ if !print_thread ( & source, tid, & args. affinity_nodes , false ) {
307+ error = true ;
308+ }
309+ } else {
310+ let tids = enumerate_tids_from ( & source) ;
311+ if tids. is_empty ( ) {
312+ eprintln ! ( "plgrp: cannot read threads for PID {}" , spec. pid) ;
313+ error = true ;
314+ continue ;
315+ }
316+ for tid in tids {
317+ if !print_thread ( & source, tid, & args. affinity_nodes , false ) {
318+ error = true ;
319+ }
320+ }
321+ }
255322 }
256- for tid in tids {
257- if !print_thread ( & source, tid, & args. affinity_nodes ) {
323+ Operand :: Core ( source) => {
324+ let tids = enumerate_tids_from ( source. as_ref ( ) ) ;
325+ if tids. is_empty ( ) {
326+ eprintln ! ( "plgrp: cannot read threads for PID {}" , source. pid( ) ) ;
258327 error = true ;
328+ continue ;
329+ }
330+ // Warn if the process had more threads than are available.
331+ if let Ok ( status) = source. read_status ( ) {
332+ if let Some ( n) = status
333+ . lines ( )
334+ . find_map ( |l| l. strip_prefix ( "Threads:" ) )
335+ . and_then ( |v| v. trim ( ) . parse :: < usize > ( ) . ok ( ) )
336+ {
337+ if n > tids. len ( ) {
338+ eprintln ! (
339+ "warning: process had {} threads but only {} available; \
340+ output may be incomplete",
341+ n,
342+ tids. len( )
343+ ) ;
344+ }
345+ }
346+ }
347+ for tid in tids {
348+ if !print_thread ( source. as_ref ( ) , tid, & args. affinity_nodes , true ) {
349+ error = true ;
350+ }
259351 }
260352 }
261353 }
0 commit comments