Skip to main content

scx_lavd/
cpu_order.rs

1// SPDX-License-Identifier: GPL-2.0
2//
3// Copyright (c) 2025 Valve Corporation.
4// Author: Changwoo Min <changwoo@igalia.com>
5
6// This software may be used and distributed according to the terms of the
7// GNU General Public License version 2.
8
9use anyhow::anyhow;
10use anyhow::Result;
11use combinations::Combinations;
12use itertools::iproduct;
13use scx_utils::CoreType;
14use scx_utils::Cpumask;
15use scx_utils::EnergyModel;
16use scx_utils::PerfDomain;
17use scx_utils::PerfState;
18use scx_utils::Topology;
19use scx_utils::NR_CPU_IDS;
20use std::cell::Cell;
21use std::cell::RefCell;
22use std::collections::BTreeMap;
23use std::collections::BTreeSet;
24use std::collections::HashSet;
25use std::fmt;
26use std::hash::{Hash, Hasher};
27use tracing::debug;
28
29#[derive(Debug, Clone)]
30pub struct CpuId {
31    // - *_adx: an absolute index within a system scope
32    // - *_rdx: a relative index under a parent
33    //
34    // - numa_adx: a NUMA domain within a system
35    // - pd_adx: a performance domain (CPU frequency domain) within a system
36    //   - llc_rdx: an LLC domain (CCX) under a NUMA domain
37    //   - llc_kernel_id: physical LLC domain ID provided by the kernel
38    //     - core_rdx: a core under a LLC domain
39    //       - cpu_rdx: a CPU under a core
40    pub numa_adx: usize,
41    pub pd_adx: usize,
42    pub llc_adx: usize,
43    pub llc_rdx: usize,
44    pub llc_kernel_id: usize,
45    pub core_rdx: usize,
46    pub cpu_rdx: usize,
47    pub cpu_adx: usize,
48    pub smt_level: usize,
49    pub cache_size: usize,
50    pub cpu_cap: usize,
51    pub big_core: bool,
52    pub turbo_core: bool,
53    pub cpu_sibling: usize,
54}
55
56#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone)]
57pub struct ComputeDomainId {
58    pub numa_adx: usize,
59    pub llc_adx: usize,
60    pub llc_rdx: usize,
61    pub llc_kernel_id: usize,
62    pub is_big: bool,
63}
64
65#[derive(Debug, Clone)]
66pub struct ComputeDomain {
67    pub cpdom_id: usize,
68    pub cpdom_alt_id: Cell<usize>,
69    pub cpu_ids: Vec<usize>,
70    pub neighbor_map: RefCell<BTreeMap<usize, RefCell<Vec<usize>>>>,
71}
72
73#[derive(Debug, Clone)]
74#[allow(dead_code)]
75pub struct PerfCpuOrder {
76    pub perf_cap: usize,                 // performance in capacity
77    pub perf_util: f32,                  // performance in utilization, [0, 1]
78    pub cpus_perf: RefCell<Vec<usize>>,  // CPU adx order within the performance range by @perf_cap
79    pub cpus_ovflw: RefCell<Vec<usize>>, // CPU adx order beyond @perf_cap
80}
81
82#[derive(Debug)]
83#[allow(dead_code)]
84pub struct CpuOrder {
85    pub all_cpus_mask: Cpumask,
86    pub cpuids: Vec<CpuId>,
87    pub perf_cpu_order: BTreeMap<usize, PerfCpuOrder>,
88    pub cpdom_map: BTreeMap<ComputeDomainId, ComputeDomain>,
89    pub nr_cpus: usize,
90    pub nr_cores: usize,
91    pub nr_cpdoms: usize,
92    pub nr_llcs: usize,
93    pub nr_numa: usize,
94    pub smt_enabled: bool,
95    pub has_biglittle: bool,
96    pub has_energy_model: bool,
97}
98
99impl CpuOrder {
100    /// Build a cpu preference order with optional topology configuration.
101    /// When @no_use_em is set, ignore the energy model even if the kernel
102    /// provides one, so the CPU preference order is built as on a machine
103    /// without an energy model.
104    pub fn new(
105        topology_args: Option<&scx_utils::TopologyArgs>,
106        no_use_em: bool,
107    ) -> Result<CpuOrder> {
108        let ctx = CpuOrderCtx::new(topology_args, no_use_em)?;
109        let cpus_pf = ctx.build_topo_order(false).unwrap();
110        let cpus_ps = ctx.build_topo_order(true).unwrap();
111        let cpdom_map = CpuOrderCtx::build_cpdom(&cpus_pf).unwrap();
112        let perf_cpu_order = if ctx.em.is_ok() {
113            let em = ctx.em.unwrap();
114            EnergyModelOptimizer::get_perf_cpu_order_table(&em, &cpus_pf)
115        } else {
116            EnergyModelOptimizer::get_fake_perf_cpu_order_table(&cpus_pf, &cpus_ps)
117        };
118
119        let nr_cpdoms = cpdom_map.len();
120        Ok(CpuOrder {
121            all_cpus_mask: ctx.topo.span,
122            cpuids: cpus_pf,
123            perf_cpu_order,
124            cpdom_map,
125            nr_cpus: ctx.topo.all_cpus.len(),
126            nr_cores: ctx.topo.all_cores.len(),
127            nr_cpdoms,
128            nr_llcs: ctx.topo.all_llcs.len(),
129            nr_numa: ctx.topo.nodes.len(),
130            smt_enabled: ctx.smt_enabled,
131            has_biglittle: ctx.has_biglittle,
132            has_energy_model: ctx.has_energy_model,
133        })
134    }
135}
136
137/// CpuOrderCtx is a helper struct used to build a CpuOrder
138struct CpuOrderCtx {
139    topo: Topology,
140    em: Result<EnergyModel>,
141    smt_enabled: bool,
142    has_biglittle: bool,
143    has_energy_model: bool,
144}
145
146impl CpuOrderCtx {
147    fn new(topology_args: Option<&scx_utils::TopologyArgs>, no_use_em: bool) -> Result<Self> {
148        let topo = match topology_args {
149            Some(args) => Topology::with_args(args)?,
150            None => Topology::new()?,
151        };
152
153        let em = if no_use_em {
154            Err(anyhow!("energy model disabled (--no-use-em)"))
155        } else {
156            EnergyModel::new()
157        };
158        let smt_enabled = topo.smt_enabled;
159        let has_biglittle = topo.has_little_cores();
160        let has_energy_model = em.is_ok();
161
162        debug!("{:#?}", topo);
163        debug!("{:#?}", em);
164
165        Ok(CpuOrderCtx {
166            topo,
167            em,
168            smt_enabled,
169            has_biglittle,
170            has_energy_model,
171        })
172    }
173
174    /// Build a CPU preference order based on its optimization target
175    fn build_topo_order(&self, prefer_powersave: bool) -> Option<Vec<CpuId>> {
176        let mut cpu_ids = Vec::new();
177        let smt_siblings = self.topo.sibling_cpus();
178
179        // Build a vector of cpu ids.
180        for (&numa_adx, node) in self.topo.nodes.iter() {
181            for (llc_rdx, (&llc_adx, llc)) in node.llcs.iter().enumerate() {
182                for (core_rdx, (_core_adx, core)) in llc.cores.iter().enumerate() {
183                    for (cpu_rdx, (cpu_adx, cpu)) in core.cpus.iter().enumerate() {
184                        let cpu_adx = *cpu_adx;
185                        let pd_adx = Self::get_pd_id(&self.em, cpu_adx, llc_adx);
186                        let cpu_id = CpuId {
187                            numa_adx,
188                            pd_adx,
189                            llc_adx,
190                            llc_rdx,
191                            core_rdx,
192                            cpu_rdx,
193                            cpu_adx,
194                            smt_level: cpu.smt_level,
195                            cache_size: cpu.cache_size,
196                            cpu_cap: cpu.cpu_capacity,
197                            big_core: cpu.core_type != CoreType::Little,
198                            turbo_core: cpu.core_type == CoreType::Big { turbo: true },
199                            cpu_sibling: smt_siblings[cpu_adx] as usize,
200                            llc_kernel_id: llc.kernel_id,
201                        };
202                        cpu_ids.push(RefCell::new(cpu_id));
203                    }
204                }
205            }
206        }
207
208        // Convert a vector of RefCell to a vector of plain cpu_ids
209        let mut cpu_ids2 = Vec::new();
210        for cpu_id in cpu_ids.iter() {
211            cpu_ids2.push(cpu_id.borrow().clone());
212        }
213        let mut cpu_ids = cpu_ids2;
214
215        // Sort the cpu_ids
216        match (prefer_powersave, self.has_biglittle) {
217            // 1. powersave,      no  big/little
218            //     * within the same LLC domain
219            //         - numa_adx, llc_rdx,
220            //     * prefer more capable CPU with higher capacity
221            //       and larger cache
222            //         - ^cpu_cap (chip binning), ^cache_size,
223            //     * prefer the SMT core within the same performance domain
224            //         - pd_adx, core_rdx, ^smt_level, cpu_rdx
225            (true, false) => {
226                cpu_ids.sort_by(|a, b| {
227                    a.numa_adx
228                        .cmp(&b.numa_adx)
229                        .then_with(|| a.llc_rdx.cmp(&b.llc_rdx))
230                        .then_with(|| b.cpu_cap.cmp(&a.cpu_cap))
231                        .then_with(|| b.cache_size.cmp(&a.cache_size))
232                        .then_with(|| a.pd_adx.cmp(&b.pd_adx))
233                        .then_with(|| a.core_rdx.cmp(&b.core_rdx))
234                        .then_with(|| b.smt_level.cmp(&a.smt_level))
235                        .then_with(|| a.cpu_rdx.cmp(&b.cpu_rdx))
236                        .then_with(|| a.cpu_adx.cmp(&b.cpu_adx))
237                });
238            }
239            // 2. powersave,      yes big/little
240            //     * within the same LLC domain
241            //         - numa_adx, llc_rdx,
242            //     * prefer energy-efficient LITTLE CPU with a larger cache
243            //         - cpu_cap (big/little), ^cache_size,
244            //     * prefer the SMT core within the same performance domain
245            //         - pd_adx, core_rdx, ^smt_level, cpu_rdx
246            (true, true) => {
247                cpu_ids.sort_by(|a, b| {
248                    a.numa_adx
249                        .cmp(&b.numa_adx)
250                        .then_with(|| a.llc_rdx.cmp(&b.llc_rdx))
251                        .then_with(|| a.cpu_cap.cmp(&b.cpu_cap))
252                        .then_with(|| b.cache_size.cmp(&a.cache_size))
253                        .then_with(|| a.pd_adx.cmp(&b.pd_adx))
254                        .then_with(|| a.core_rdx.cmp(&b.core_rdx))
255                        .then_with(|| b.smt_level.cmp(&a.smt_level))
256                        .then_with(|| a.cpu_rdx.cmp(&b.cpu_rdx))
257                        .then_with(|| a.cpu_adx.cmp(&b.cpu_adx))
258                });
259            }
260            // 3. performance,    no  big/little
261            // 4. performance,    yes big/little
262            //     * prefer the non-SMT core
263            //         - cpu_rdx,
264            //     * fill the same LLC domain first
265            //         - numa_adx, llc_rdx,
266            //     * prefer more capable CPU with higher capacity
267            //       (chip binning or big/little) and larger cache
268            //         - ^cpu_cap, ^cache_size, smt_level
269            //     * within the same power domain
270            //         - pd_adx, core_rdx
271            _ => {
272                cpu_ids.sort_by(|a, b| {
273                    a.cpu_rdx
274                        .cmp(&b.cpu_rdx)
275                        .then_with(|| a.numa_adx.cmp(&b.numa_adx))
276                        .then_with(|| a.llc_rdx.cmp(&b.llc_rdx))
277                        .then_with(|| b.cpu_cap.cmp(&a.cpu_cap))
278                        .then_with(|| b.cache_size.cmp(&a.cache_size))
279                        .then_with(|| a.smt_level.cmp(&b.smt_level))
280                        .then_with(|| a.pd_adx.cmp(&b.pd_adx))
281                        .then_with(|| a.core_rdx.cmp(&b.core_rdx))
282                        .then_with(|| a.cpu_adx.cmp(&b.cpu_adx))
283                });
284            }
285        }
286
287        Some(cpu_ids)
288    }
289
290    /// Build a list of compute domains
291    fn build_cpdom(cpu_ids: &Vec<CpuId>) -> Option<BTreeMap<ComputeDomainId, ComputeDomain>> {
292        // Note that building compute domain is independent to CPU order
293        // so it is okay to use any cpus_*.
294
295        // Create a compute domain map, where a compute domain is a CPUs that
296        // are under the same node and LLC (virtual and physical) and have the same core type.
297        let mut cpdom_id = 0;
298        let mut cpdom_map: BTreeMap<ComputeDomainId, ComputeDomain> = BTreeMap::new();
299        let mut cpdom_types: BTreeMap<usize, bool> = BTreeMap::new();
300        for cpu_id in cpu_ids.iter() {
301            let key = ComputeDomainId {
302                numa_adx: cpu_id.numa_adx,
303                llc_adx: cpu_id.llc_adx,
304                llc_rdx: cpu_id.llc_rdx,
305                llc_kernel_id: cpu_id.llc_kernel_id,
306                is_big: cpu_id.big_core,
307            };
308            let value = cpdom_map.entry(key.clone()).or_insert_with(|| {
309                let val = ComputeDomain {
310                    cpdom_id,
311                    cpdom_alt_id: Cell::new(cpdom_id),
312                    cpu_ids: Vec::new(),
313                    neighbor_map: RefCell::new(BTreeMap::new()),
314                };
315                cpdom_types.insert(cpdom_id, key.is_big);
316
317                cpdom_id += 1;
318                val
319            });
320            value.cpu_ids.push(cpu_id.cpu_adx);
321        }
322
323        // Build a neighbor map for each compute domain, where neighbors are
324        // ordered by core type, node, and LLC.
325        for ((from_k, from_v), (to_k, to_v)) in iproduct!(cpdom_map.iter(), cpdom_map.iter()) {
326            if from_k == to_k {
327                continue;
328            }
329
330            let d = Self::dist(from_k, to_k);
331            let mut map = from_v.neighbor_map.borrow_mut();
332            match map.get(&d) {
333                Some(v) => {
334                    v.borrow_mut().push(to_v.cpdom_id);
335                }
336                None => {
337                    map.insert(d, RefCell::new(vec![to_v.cpdom_id]));
338                }
339            }
340        }
341
342        // Circular sort compute domains within the same distance to preserve
343        // proximity between domains.
344        //
345        // Suppose that domains 0, 1, 2, 3, 4, 5, 6, 7 are at the same distance.
346        //            0
347        //         7     1
348        //       6         2
349        //         5     3
350        //            4
351        //
352        // We want to traverse the domains from 0. The circular-sorted order
353        // starting from domain 0 is 0, 1, 7, 2, 6, 3, 5, 4. Similarly,
354        // the order starting from domain 1 is 1, 0, 2, 3, 7, 4, 6, 5.
355        // The one from 7 is 7, 0, 6, 1, 5, 2, 4, 3. As follows, circularly
356        // sorted orders in task stealing preserve proximity between domains
357        // (e.g., 0, 1, 7 in the example), so we can achieve less cacheline
358        // bouncing than with random-ordered task stealing.
359        for (_, cpdom) in cpdom_map.iter() {
360            for (_, neighbors) in cpdom.neighbor_map.borrow_mut().iter() {
361                let mut neighbors_csorted =
362                    Self::circular_sort(cpdom.cpdom_id, &neighbors.borrow_mut().to_vec());
363                neighbors.borrow_mut().clear();
364                neighbors.borrow_mut().append(&mut neighbors_csorted);
365            }
366        }
367
368        // Fill up cpdom_alt_id for each compute domain.
369        for (k, v) in cpdom_map.iter() {
370            let mut key = k.clone();
371            key.is_big = !k.is_big;
372
373            if let Some(alt_v) = cpdom_map.get(&key) {
374                // First, try to find an alternative domain
375                // under the same node/LLC.
376                v.cpdom_alt_id.set(alt_v.cpdom_id);
377            } else {
378                // If there is no alternative domain in the same node/LLC,
379                // choose the closest one.
380                //
381                // Note that currently, the idle CPU selection (pick_idle_cpu)
382                // is not optimized for this kind of architecture, where big
383                // and LITTLE cores are in different node/LLCs.
384                'outer: for (_dist, ncpdoms) in v.neighbor_map.borrow().iter() {
385                    for ncpdom_id in ncpdoms.borrow().iter() {
386                        if let Some(is_big) = cpdom_types.get(ncpdom_id) {
387                            if *is_big == key.is_big {
388                                v.cpdom_alt_id.set(*ncpdom_id);
389                                break 'outer;
390                            }
391                        }
392                    }
393                }
394            }
395        }
396
397        Some(cpdom_map)
398    }
399
400    /// Circular sorting of a list from a starting point
401    fn circular_sort(start: usize, the_rest: &Vec<usize>) -> Vec<usize> {
402        // Create a full list including 'start'
403        let mut list = the_rest.clone();
404        list.push(start);
405        list.sort();
406
407        // Get the index of 'start'
408        let s = list
409            .binary_search(&start)
410            .expect("start must appear exactly once");
411
412        // Get the circularly sorted index list.
413        let n = list.len();
414        let dist = |x: usize| {
415            let d = (x + n - s) % n;
416            d.min(n - d)
417        };
418        let mut order: Vec<usize> = (0..n).collect();
419        order.sort_by_key(|&x| (dist(x), x));
420
421        // Rearrange the full list
422        // according to the circularly sorted index list.
423        let list_csorted: Vec<_> = order.iter().map(|&i| list[i]).collect();
424
425        // Drop 'start' from the rearranged full list.
426        list_csorted[1..].to_vec()
427    }
428
429    /// Get the performance domain (i.e., CPU frequency domain) ID for a CPU.
430    /// If the energy model is not available, use LLC ID instead.
431    fn get_pd_id(em: &Result<EnergyModel>, cpu_adx: usize, llc_adx: usize) -> usize {
432        match em {
433            Ok(em) => em.get_pd_by_cpu_id(cpu_adx).unwrap().id,
434            Err(_) => llc_adx,
435        }
436    }
437
438    /// Calculate distance from two compute domains
439    fn dist(from: &ComputeDomainId, to: &ComputeDomainId) -> usize {
440        let mut d = 0;
441        // core type > numa node > llc
442        if from.is_big != to.is_big {
443            d += 100;
444        }
445        if from.numa_adx != to.numa_adx {
446            d += 10;
447        } else {
448            if from.llc_rdx != to.llc_rdx {
449                d += 1;
450            }
451            if from.llc_kernel_id != to.llc_kernel_id {
452                d += 1;
453            }
454        }
455        d
456    }
457}
458
459#[derive(Debug)]
460struct EnergyModelOptimizer<'a> {
461    // Energy model of performance domains
462    em: &'a EnergyModel,
463
464    // CPU preference order in a performance mode purely based on topology
465    cpus_topological_order: Vec<usize>,
466
467    // CPU preference order within a performance domain
468    pd_cpu_order: BTreeMap<usize, RefCell<Vec<usize>>>,
469
470    // Total performance capacity of the system
471    tot_perf: usize,
472
473    // All possible combinations of performance domains & states
474    // indexed by performance.
475    pdss_infos: RefCell<BTreeMap<usize, RefCell<HashSet<PDSetInfo<'a>>>>>,
476
477    // Performance domains and states to achieve a certain performance level,
478    // which is derived from @pdss_infos.
479    perf_pdsi: RefCell<BTreeMap<usize, PDSetInfo<'a>>>,
480
481    // CPU orders indexed by performance
482    perf_cpu_order: RefCell<BTreeMap<usize, PerfCpuOrder>>,
483}
484
485#[derive(Debug, Clone, Eq, Hash, Ord, PartialOrd)]
486struct PDS<'a> {
487    pd: &'a PerfDomain,
488    ps: &'a PerfState,
489}
490
491#[derive(Debug, Clone, Eq, Hash, Ord, PartialOrd)]
492struct PDCpu<'a> {
493    pd: &'a PerfDomain, // performance domain
494    cpu_vid: usize,     // virtual ID of a CPU on the performance domain
495}
496
497#[derive(Debug, Clone, Eq)]
498struct PDSetInfo<'a> {
499    performance: usize,
500    power: usize,
501    pdcpu_set: BTreeSet<PDCpu<'a>>,
502    pd_id_set: BTreeSet<usize>, // pd:id:0, pd:id:1
503}
504
505const PD_UNIT: usize = 100_000_000;
506const CPU_UNIT: usize = 100_000;
507const LOOKAHEAD_CNT: usize = 10;
508
509impl<'a> EnergyModelOptimizer<'a> {
510    fn new(em: &'a EnergyModel, cpus_pf: &'a Vec<CpuId>) -> EnergyModelOptimizer<'a> {
511        let tot_perf = em.perf_total();
512
513        let pdss_infos: BTreeMap<usize, RefCell<HashSet<PDSetInfo<'a>>>> = BTreeMap::new();
514        let pdss_infos = pdss_infos.into();
515
516        let perf_pdsi: BTreeMap<usize, PDSetInfo<'a>> = BTreeMap::new();
517        let perf_pdsi = perf_pdsi.into();
518
519        let mut pd_cpu_order: BTreeMap<usize, RefCell<Vec<usize>>> = BTreeMap::new();
520        let mut cpus_topological_order: Vec<usize> = vec![];
521        for cpuid in cpus_pf.iter() {
522            match pd_cpu_order.get(&cpuid.pd_adx) {
523                Some(v) => {
524                    let mut v = v.borrow_mut();
525                    v.push(cpuid.cpu_adx);
526                }
527                None => {
528                    let v = vec![cpuid.cpu_adx];
529                    pd_cpu_order.insert(cpuid.pd_adx, v.into());
530                }
531            }
532            cpus_topological_order.push(cpuid.cpu_adx);
533        }
534
535        let perf_cpu_order: BTreeMap<usize, PerfCpuOrder> = BTreeMap::new();
536        let perf_cpu_order = perf_cpu_order.into();
537
538        debug!("# pd_cpu_order");
539        debug!("{:#?}", pd_cpu_order);
540
541        EnergyModelOptimizer {
542            em,
543            cpus_topological_order,
544            pd_cpu_order,
545            tot_perf,
546            pdss_infos,
547            perf_pdsi,
548            perf_cpu_order,
549        }
550    }
551
552    fn get_perf_cpu_order_table(
553        em: &'a EnergyModel,
554        cpus_pf: &'a Vec<CpuId>,
555    ) -> BTreeMap<usize, PerfCpuOrder> {
556        let emo = EnergyModelOptimizer::new(em, &cpus_pf);
557        emo.gen_perf_cpu_order_table();
558        let perf_cpu_order = emo.perf_cpu_order.borrow().clone();
559
560        perf_cpu_order
561    }
562
563    fn get_fake_perf_cpu_order_table(
564        cpus_pf: &'a Vec<CpuId>,
565        cpus_ps: &'a Vec<CpuId>,
566    ) -> BTreeMap<usize, PerfCpuOrder> {
567        let tot_perf: usize = cpus_pf.iter().map(|cpuid| cpuid.cpu_cap).sum();
568
569        let pco_pf = Self::fake_pco(tot_perf, cpus_pf, false);
570        let pco_ps = Self::fake_pco(tot_perf, cpus_ps, true);
571
572        let mut perf_cpu_order: BTreeMap<usize, PerfCpuOrder> = BTreeMap::new();
573        perf_cpu_order.insert(pco_pf.perf_cap, pco_pf);
574        perf_cpu_order.insert(pco_ps.perf_cap, pco_ps);
575
576        perf_cpu_order
577    }
578
579    fn fake_pco(tot_perf: usize, cpuids: &'a Vec<CpuId>, powersave: bool) -> PerfCpuOrder {
580        let perf_cap;
581
582        if powersave {
583            perf_cap = cpuids[0].cpu_cap;
584        } else {
585            perf_cap = tot_perf;
586        }
587
588        let perf_util: f32 = (perf_cap as f32) / (tot_perf as f32);
589        let cpus: Vec<usize> = cpuids.iter().map(|cpuid| cpuid.cpu_adx).collect();
590        let cpus_perf: Vec<usize> = cpus[..1].iter().map(|&cpuid| cpuid).collect();
591        let cpus_ovflw: Vec<usize> = cpus[1..].iter().map(|&cpuid| cpuid).collect();
592        PerfCpuOrder {
593            perf_cap,
594            perf_util,
595            cpus_perf: cpus_perf.clone().into(),
596            cpus_ovflw: cpus_ovflw.clone().into(),
597        }
598    }
599
600    /// Generate the performance versus CPU preference order table based on
601    /// the system's CPU topology and energy model. The table consists of the
602    /// following information (PerfCpuOrder):
603    ///
604    ///   - PerfCpuOrder::perf_cap: The upper bound of the performance
605    ///     capacity covered by this tuple.
606    ///
607    ///   - PerfCpuOrder::cpus_perf: Primary CPUs to be used is ordered
608    ///     by preference.
609    ///
610    ///   - PerfCpuOrder::cpus_ovrflw: When the system load goes beyond
611    ///     @perf_cap, the list of CPUs to be used is ordered by preference.
612    fn gen_perf_cpu_order_table(&'a self) {
613        // First, generate all possible combinations of CPUs (e.g., two CPUs
614        // in performance domain 0 and three CPUs in performance domain 1) to
615        // achieve the possible performance capacities with minimal energy
616        // consumption. We assume a reasonable load balancer, so the
617        // utilization of the used CPUs is similar.
618        self.gen_all_pds_combinations();
619
620        // Then, from all the possible combinations of performance versus
621        // CPU sets, select a list of combinations that minimize the number of
622        // active performance domains and reduce the number of performance
623        // domain switches when changing performance levels.
624        self.gen_perf_pds_table();
625
626        // Finally, assign CPUs (@cpu_adx) to the virtual CPU ID (@cpu_vid) of
627        // a performance domain.
628        self.assign_cpu_vids();
629    }
630
631    /// Generate a CPU order table for each performance range.
632    fn assign_cpu_vids(&'a self) {
633        // Generate CPU order within the performance range (@cpus_perf).
634        for (&perf_cap, pdsi) in self.perf_pdsi.borrow().iter() {
635            let mut cpus_perf: Vec<usize> = vec![];
636
637            for pdcpu in pdsi.pdcpu_set.iter() {
638                let pd_id = pdcpu.pd.id;
639                let cpu_vid = pdcpu.cpu_vid;
640                let cpu_order = self.pd_cpu_order.get(&pd_id).unwrap().borrow();
641                let cpu_adx = cpu_order[cpu_vid];
642                cpus_perf.push(cpu_adx);
643            }
644
645            let perf_util: f32 = (perf_cap as f32) / (self.tot_perf as f32);
646            let cpus_perf = self.sort_cpus_by_topological_order(&cpus_perf);
647            let cpus_ovflw: Vec<usize> = vec![];
648
649            let mut perf_cpu_order = self.perf_cpu_order.borrow_mut();
650            perf_cpu_order.insert(
651                perf_cap,
652                PerfCpuOrder {
653                    perf_cap,
654                    perf_util,
655                    cpus_perf: cpus_perf.clone().into(),
656                    cpus_ovflw: cpus_ovflw.clone().into(),
657                },
658            );
659        }
660
661        // Generate CPU order beyond the performance range (@cpus_ovflw).
662        let perf_cpu_order = self.perf_cpu_order.borrow();
663        let perf_caps: Vec<_> = self.perf_pdsi.borrow().keys().cloned().collect();
664        for o in 1..perf_caps.len() {
665            // Gather all @cpus_perf from the upper performance ranges.
666            let ovrflw_perf_caps = &perf_caps[o..];
667            let mut ovrflw_cpus_all: Vec<usize> = vec![];
668            for perf_cap in ovrflw_perf_caps.iter() {
669                let cpu_order = perf_cpu_order.get(perf_cap).unwrap();
670                let cpus_perf = cpu_order.cpus_perf.borrow();
671                ovrflw_cpus_all.extend(cpus_perf.iter().cloned());
672            }
673
674            // Filter out already taken CPUs from the @ovrflw_cpus_all,
675            // and build @cpus_ovrflw.
676            let mut cpu_set = HashSet::<usize>::new();
677            let perf_cap = perf_caps[o - 1];
678            let cpu_order = perf_cpu_order.get(&perf_cap).unwrap();
679            let cpus_perf = cpu_order.cpus_perf.borrow();
680            for &cpu_adx in cpus_perf.iter() {
681                cpu_set.insert(cpu_adx);
682            }
683
684            let mut cpus_ovflw: Vec<usize> = vec![];
685            for &cpu_adx in ovrflw_cpus_all.iter() {
686                if cpu_set.get(&cpu_adx).is_none() {
687                    cpus_ovflw.push(cpu_adx);
688                    cpu_set.insert(cpu_adx);
689                }
690            }
691
692            // Inject the constructed @cpus_ovrflw to the table.
693            let mut v = cpu_order.cpus_ovflw.borrow_mut();
694            v.extend(cpus_ovflw.iter().cloned());
695        }
696
697        // Debug print of the generated table
698        debug!("## gen_perf_cpu_order_table");
699        debug!("{:#?}", perf_cpu_order);
700    }
701
702    /// Sort the CPU IDs by topological order (@self.cpus_topological_order).
703    fn sort_cpus_by_topological_order(&'a self, cpus: &Vec<usize>) -> Vec<usize> {
704        let mut sorted: Vec<usize> = vec![];
705        for &cpu_adx in self.cpus_topological_order.iter() {
706            if let Some(_) = cpus.iter().find(|&&x| x == cpu_adx) {
707                sorted.push(cpu_adx);
708            }
709        }
710        sorted
711    }
712
713    /// Generate a table of performance vs. performance domain sets
714    /// (@self.perf_pdss) from all the possible performance domain & state
715    /// combinations (@self.pdss_infos).
716    ///
717    /// An example result is as follows:
718    ///     PERF: [_, 300]
719    ///             pd:id: 0 -- cpu_vid: 0
720    ///             pd:id: 0 -- cpu_vid: 1
721    ///     PERF: [_, 1138]
722    ///             pd:id: 0 -- cpu_vid: 0
723    ///             pd:id: 0 -- cpu_vid: 1
724    ///             pd:id: 1 -- cpu_vid: 0
725    ///             pd:id: 1 -- cpu_vid: 1
726    ///     PERF: [_, 3386]
727    ///             pd:id: 1 -- cpu_vid: 0
728    ///             pd:id: 1 -- cpu_vid: 1
729    ///             pd:id: 1 -- cpu_vid: 2
730    ///             pd:id: 2 -- cpu_vid: 0
731    ///             pd:id: 2 -- cpu_vid: 1
732    ///     PERF: [_, 3977]
733    ///             pd:id: 0 -- cpu_vid: 0
734    ///             pd:id: 1 -- cpu_vid: 0
735    ///             pd:id: 1 -- cpu_vid: 1
736    ///             pd:id: 1 -- cpu_vid: 2
737    ///             pd:id: 2 -- cpu_vid: 0
738    ///             pd:id: 2 -- cpu_vid: 1
739    ///     PERF: [_, 4508]
740    ///             pd:id: 0 -- cpu_vid: 0
741    ///             pd:id: 0 -- cpu_vid: 1
742    ///             pd:id: 1 -- cpu_vid: 0
743    ///             pd:id: 1 -- cpu_vid: 1
744    ///             pd:id: 1 -- cpu_vid: 2
745    ///             pd:id: 2 -- cpu_vid: 0
746    ///             pd:id: 2 -- cpu_vid: 1
747    ///     PERF: [_, 5627]
748    ///             pd:id: 0 -- cpu_vid: 0
749    ///             pd:id: 0 -- cpu_vid: 1
750    ///             pd:id: 1 -- cpu_vid: 0
751    ///             pd:id: 1 -- cpu_vid: 1
752    ///             pd:id: 1 -- cpu_vid: 2
753    ///             pd:id: 2 -- cpu_vid: 0
754    ///             pd:id: 2 -- cpu_vid: 1
755    ///             pd:id: 3 -- cpu_vid: 0
756    fn gen_perf_pds_table(&'a self) {
757        let utils = vec![0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
758
759        // Find the best performance domains for each system utilization target.
760        for &util in utils.iter() {
761            let mut best_pdsi: Option<PDSetInfo<'a>>;
762            let mut del_pdsi: Option<PDSetInfo<'a>> = None;
763
764            match self.perf_pdsi.borrow().last_key_value() {
765                Some((_, base)) => {
766                    best_pdsi = self.find_perf_pds_for(util, Some(base));
767
768                    // If the next performance level (@best_pdsi) is subsumed
769                    // by the previous level (@base), extend the base to the
770                    // next level. To this end, insert the extended base (with
771                    // updated performance and power values) and delete the old
772                    // base.
773                    if let Some(ref best) = best_pdsi {
774                        if best.pdcpu_set.is_subset(&base.pdcpu_set) {
775                            let ext_pdcpu = PDSetInfo {
776                                performance: best.performance,
777                                power: best.power,
778                                pdcpu_set: base.pdcpu_set.clone(),
779                                pd_id_set: base.pd_id_set.clone(),
780                            };
781                            best_pdsi = Some(ext_pdcpu);
782                            del_pdsi = Some(base.clone());
783                        }
784                    }
785                }
786                None => {
787                    best_pdsi = self.find_perf_pds_for(util, None);
788                }
789            };
790
791            if let Some(best_pdsi) = best_pdsi {
792                self.perf_pdsi
793                    .borrow_mut()
794                    .insert(best_pdsi.performance, best_pdsi);
795            }
796
797            if let Some(del_pdsi) = del_pdsi {
798                self.perf_pdsi.borrow_mut().remove(&del_pdsi.performance);
799            }
800        }
801
802        // Debug print of the generated table
803        debug!("## gen_perf_pds_table");
804        for (perf, pdsi) in self.perf_pdsi.borrow().iter() {
805            debug!("PERF: [_, {}]", perf);
806            for pdcpu in pdsi.pdcpu_set.iter() {
807                debug!(
808                    "        pd:id: {:?} -- cpu_vid: {}",
809                    pdcpu.pd.id, pdcpu.cpu_vid
810                );
811            }
812        }
813    }
814
815    fn find_perf_pds_for(
816        &'a self,
817        util: f32,
818        base: Option<&PDSetInfo<'a>>,
819    ) -> Option<PDSetInfo<'a>> {
820        let target_perf = (util * self.tot_perf as f32) as usize;
821        let mut lookahead = 0;
822        let mut min_dist: usize = usize::MAX;
823        let mut best_pdsi: Option<PDSetInfo<'a>> = None;
824
825        let pdss_infos = self.pdss_infos.borrow();
826        for (&pdsi_perf, pdsi_set) in pdss_infos.iter() {
827            if pdsi_perf >= target_perf {
828                let pdsi_set_ref = pdsi_set.borrow();
829                for pdsi in pdsi_set_ref.iter() {
830                    let dist = pdsi.dist(base);
831                    if dist < min_dist {
832                        min_dist = dist;
833                        best_pdsi = Some(pdsi.clone());
834                    }
835                }
836                lookahead += 1;
837                if lookahead >= LOOKAHEAD_CNT {
838                    break;
839                }
840            }
841        }
842
843        best_pdsi
844    }
845
846    /// Generate all possible performance domain & state combinations,
847    /// @self.pdss_infos. Each combination represents a set of performance
848    /// domains (and their corresponding performance states) that achieve the
849    /// requested performance with minimal power consumption.
850    ///
851    /// We assume a 'reasonable load balancer,' so the CPU utilization of all
852    /// the involved CPUs is similar.
853    ///
854    /// An example result is as follows:
855    ///
856    ///     PERF: [_, 5135]
857    ///         perf: 5135 -- power: 5475348
858    ///             pd:id: 0 -- cpu_vid: 0
859    ///             pd:id: 1 -- cpu_vid: 0
860    ///             pd:id: 1 -- cpu_vid: 1
861    ///             pd:id: 1 -- cpu_vid: 2
862    ///             pd:id: 2 -- cpu_vid: 0
863    ///             pd:id: 2 -- cpu_vid: 1
864    ///             pd:id: 3 -- cpu_vid: 0
865    ///     PERF: [_, 5187]
866    ///         perf: 5187 -- power: 4844969
867    ///             pd:id: 0 -- cpu_vid: 0
868    ///             pd:id: 0 -- cpu_vid: 1
869    ///             pd:id: 1 -- cpu_vid: 0
870    ///             pd:id: 1 -- cpu_vid: 1
871    ///             pd:id: 1 -- cpu_vid: 2
872    ///             pd:id: 2 -- cpu_vid: 0
873    ///             pd:id: 2 -- cpu_vid: 1
874    ///             pd:id: 3 -- cpu_vid: 0
875    ///     PERF: [_, 5195]
876    ///         perf: 5195 -- power: 5924606
877    ///             pd:id: 1 -- cpu_vid: 0
878    ///             pd:id: 1 -- cpu_vid: 1
879    ///             pd:id: 1 -- cpu_vid: 2
880    ///             pd:id: 2 -- cpu_vid: 0
881    ///             pd:id: 2 -- cpu_vid: 1
882    ///             pd:id: 3 -- cpu_vid: 0
883    ///     PERF: [_, 5217]
884    ///         perf: 5217 -- power: 4894911
885    ///             pd:id: 0 -- cpu_vid: 0
886    ///             pd:id: 0 -- cpu_vid: 1
887    ///             pd:id: 1 -- cpu_vid: 0
888    ///             pd:id: 1 -- cpu_vid: 1
889    ///             pd:id: 1 -- cpu_vid: 2
890    ///             pd:id: 2 -- cpu_vid: 0
891    ///             pd:id: 2 -- cpu_vid: 1
892    ///             pd:id: 3 -- cpu_vid: 0
893    ///     PERF: [_, 5225]
894    ///         perf: 5225 -- power: 5665770
895    ///             pd:id: 0 -- cpu_vid: 0
896    ///             pd:id: 1 -- cpu_vid: 0
897    ///             pd:id: 1 -- cpu_vid: 1
898    ///             pd:id: 1 -- cpu_vid: 2
899    ///             pd:id: 2 -- cpu_vid: 0
900    ///             pd:id: 2 -- cpu_vid: 1
901    ///             pd:id: 3 -- cpu_vid: 0
902    ///     PERF: [_, 5316]
903    ///         perf: 5316 -- power: 5860568
904    ///             pd:id: 0 -- cpu_vid: 0
905    ///             pd:id: 1 -- cpu_vid: 0
906    ///             pd:id: 1 -- cpu_vid: 1
907    ///             pd:id: 1 -- cpu_vid: 2
908    ///             pd:id: 2 -- cpu_vid: 0
909    ///             pd:id: 2 -- cpu_vid: 1
910    ///             pd:id: 3 -- cpu_vid: 0
911    fn gen_all_pds_combinations(&'a self) {
912        // Start from the min (0%) and max (100%) CPU utilizations
913        let pdsi_vec = self.gen_pds_combinations(0.0);
914        self.insert_pds_combinations(&pdsi_vec);
915
916        let pdsi_vec = self.gen_pds_combinations(100.0);
917        self.insert_pds_combinations(&pdsi_vec);
918
919        // Then dive into the range between the min and max.
920        self.gen_perf_cpuset_table_range(0, 100);
921
922        // Debug print performance table
923        debug!("## gen_all_pds_combinations");
924        for (perf, pdss_info) in self.pdss_infos.borrow().iter() {
925            debug!("PERF: [_, {}]", perf);
926            for pdsi in pdss_info.borrow().iter() {
927                debug!("    perf: {} -- power: {}", pdsi.performance, pdsi.power);
928                for pdcpu in pdsi.pdcpu_set.iter() {
929                    debug!(
930                        "        pd:id: {:?} -- cpu_vid: {}",
931                        pdcpu.pd.id, pdcpu.cpu_vid
932                    );
933                }
934            }
935        }
936    }
937
938    fn gen_perf_cpuset_table_range(&'a self, low: isize, high: isize) {
939        if low > high {
940            return;
941        }
942
943        // If there is a new performance point in the middle,
944        // let's further explore. Otherwise, stop it here.
945        let mid: isize = low + (high - low) / 2;
946        let pdsi_vec = self.gen_pds_combinations(mid as f32);
947        let found_new = self.insert_pds_combinations(&pdsi_vec);
948        if found_new {
949            self.gen_perf_cpuset_table_range(mid + 1, high);
950            self.gen_perf_cpuset_table_range(low, mid - 1);
951        }
952    }
953
954    fn gen_pds_combinations(&'a self, util: f32) -> Vec<PDSetInfo<'a>> {
955        let mut pdsi_vec = Vec::new();
956
957        let pds_set = self.gen_pds_set(util);
958        let n = pds_set.len();
959        for k in 1..n {
960            let pdss = pds_set.clone();
961            let pds_cmbs: Vec<_> = Combinations::new(pdss, k)
962                .map(|cmb| PDSetInfo::new(cmb.clone()))
963                .collect();
964            pdsi_vec.extend(pds_cmbs);
965        }
966
967        let pdsi = PDSetInfo::new(pds_set.clone());
968        pdsi_vec.push(pdsi);
969
970        pdsi_vec
971    }
972
973    fn insert_pds_combinations(&self, new_pdsi_vec: &Vec<PDSetInfo<'a>>) -> bool {
974        // For the same performance, keep the PDS combinations with the lowest
975        // power consumption. If there are more than one lowest, keep them all
976        // to choose one later when assigning CPUs from the selected
977        // performance domains.
978        let mut found_new = false;
979
980        for new_pdsi in new_pdsi_vec.iter() {
981            let mut pdss_infos = self.pdss_infos.borrow_mut();
982            let v = pdss_infos.get(&new_pdsi.performance);
983            match v {
984                // There are already PDSetInfo in the list.
985                Some(v) => {
986                    let mut v = v.borrow_mut();
987                    let pdsi = &v.iter().next().unwrap();
988                    if pdsi.power == new_pdsi.power {
989                        // If the power consumptions are the same, keep both.
990                        if v.insert(new_pdsi.clone()) {
991                            found_new = true;
992                        }
993                    } else if pdsi.power > new_pdsi.power {
994                        // If the new one takes less power, keep the new one.
995                        v.clear();
996                        v.insert(new_pdsi.clone());
997                        found_new = true;
998                    }
999                }
1000                // This is the first for the performance target.
1001                None => {
1002                    // Let's add it and move on.
1003                    let mut v: HashSet<PDSetInfo<'a>> = HashSet::new();
1004                    v.insert(new_pdsi.clone());
1005                    pdss_infos.insert(new_pdsi.performance, v.into());
1006                    found_new = true;
1007                }
1008            }
1009        }
1010        found_new
1011    }
1012
1013    /// Get a vector of (performance domain, performance state) to achieve
1014    /// the given CPU utilization, @util.
1015    fn gen_pds_set(&self, util: f32) -> Vec<PDS<'_>> {
1016        let mut pds_set = vec![];
1017        for (_, pd) in self.em.perf_doms.iter() {
1018            let ps = pd.select_perf_state(util).unwrap();
1019            let pds = PDS::new(pd, ps);
1020            pds_set.push(pds);
1021        }
1022        self.expand_pds_set(&mut pds_set);
1023        pds_set
1024    }
1025
1026    /// Expand a PDS vector such that a performance domain with X CPUs
1027    /// has N elements in the vector. This is purely for generating
1028    /// combinations easy.
1029    fn expand_pds_set(&self, pds_set: &mut Vec<PDS<'_>>) {
1030        let mut xset = vec![];
1031        // For a performance domain having nr_cpus, add nr_cpus-1 more
1032        // PDS to make the PDS nr_cpus in the vector.
1033        for pds in pds_set.iter() {
1034            let nr_cpus = pds.pd.span.weight();
1035            for _ in 1..nr_cpus {
1036                xset.push(pds.clone());
1037            }
1038        }
1039        pds_set.append(&mut xset);
1040
1041        // Sort the pds_set for easy comparison.
1042        pds_set.sort();
1043    }
1044}
1045
1046impl<'a> PDS<'_> {
1047    fn new(pd: &'a PerfDomain, ps: &'a PerfState) -> PDS<'a> {
1048        PDS { pd, ps }
1049    }
1050}
1051
1052impl PartialEq for PDS<'_> {
1053    fn eq(&self, other: &Self) -> bool {
1054        self.pd == other.pd && self.ps == other.ps
1055    }
1056}
1057
1058impl<'a> PDCpu<'_> {
1059    fn new(pd: &'a PerfDomain, cpu_vid: usize) -> PDCpu<'a> {
1060        PDCpu { pd, cpu_vid }
1061    }
1062}
1063
1064impl PartialEq for PDCpu<'_> {
1065    fn eq(&self, other: &Self) -> bool {
1066        self.pd == other.pd && self.cpu_vid == other.cpu_vid
1067    }
1068}
1069
1070impl fmt::Display for PDS<'_> {
1071    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1072        write!(
1073            f,
1074            "pd:id:{}/pd:weight:{}/ps:cap:{}/ps:power:{}",
1075            self.pd.id,
1076            self.pd.span.weight(),
1077            self.ps.performance,
1078            self.ps.power,
1079        )?;
1080        Ok(())
1081    }
1082}
1083
1084impl<'a> PDSetInfo<'_> {
1085    fn new(pds_set: Vec<PDS<'a>>) -> PDSetInfo<'a> {
1086        // Create a pd_id_set and calculate performance and power.
1087        let mut performance = 0;
1088        let mut power = 0;
1089        let mut pd_id_set: BTreeSet<usize> = BTreeSet::new();
1090
1091        for pds in pds_set.iter() {
1092            performance += pds.ps.performance;
1093            power += pds.ps.power;
1094            pd_id_set.insert(pds.pd.id);
1095        }
1096
1097        // Create a pdcpu_set, so first gather the same PDS entries.
1098        let mut pds_map: BTreeMap<PDS<'a>, RefCell<Vec<PDS<'a>>>> = BTreeMap::new();
1099
1100        for pds in pds_set.iter() {
1101            let v = pds_map.get(&pds);
1102            match v {
1103                Some(v) => {
1104                    let mut v = v.borrow_mut();
1105                    v.push(pds.clone());
1106                }
1107                None => {
1108                    let mut v: Vec<PDS<'a>> = Vec::new();
1109                    v.push(pds.clone());
1110                    pds_map.insert(pds.clone(), v.into());
1111                }
1112            }
1113        }
1114        // Then assign cpu virtual ids to pdcpu_set.
1115        let mut pdcpu_set: BTreeSet<PDCpu<'a>> = BTreeSet::new();
1116        let pds_map = pds_map;
1117
1118        for (_, v) in pds_map.iter() {
1119            for (cpu_vid, pds) in v.borrow().iter().enumerate() {
1120                let pdcpu = PDCpu::new(pds.pd, cpu_vid);
1121                pdcpu_set.insert(pdcpu);
1122            }
1123        }
1124
1125        PDSetInfo {
1126            performance,
1127            power,
1128            pdcpu_set,
1129            pd_id_set,
1130        }
1131    }
1132
1133    /// Calculate the distance from @base to @self. We minimize the number of
1134    /// performance domains involved to reduce the leakage power consumption.
1135    /// We then maximize the overlap between the previous (i.e., base)
1136    /// performance domains and the new one for a smooth transition to the new
1137    /// cpuset with higher cache locality. Finally, we minimize the number of
1138    /// CPUs involved, thereby reducing the chance of contention for shared
1139    /// hardware resources (e.g., shared cache).
1140    fn dist(&self, base: Option<&PDSetInfo<'a>>) -> usize {
1141        let nr_pds = self.pd_id_set.len();
1142        let nr_pds_overlap = match base {
1143            Some(base) => self.pd_id_set.intersection(&base.pd_id_set).count(),
1144            None => 0,
1145        };
1146        let nr_cpus = self.pdcpu_set.len();
1147
1148        ((nr_pds - nr_pds_overlap) * PD_UNIT) +         // # non-overlapping PDs
1149        ((*NR_CPU_IDS - nr_cpus) * CPU_UNIT) +          // # of CPUs
1150        (*NR_CPU_IDS - self.pd_id_set.first().unwrap()) // PD ID as a tiebreaker
1151    }
1152}
1153
1154impl PartialEq for PDSetInfo<'_> {
1155    fn eq(&self, other: &Self) -> bool {
1156        self.performance == other.performance
1157            && self.power == other.power
1158            && self.pdcpu_set == other.pdcpu_set
1159    }
1160}
1161
1162impl Hash for PDSetInfo<'_> {
1163    fn hash<H: Hasher>(&self, state: &mut H) {
1164        // We don't need to hash performance, power, and pd_id_set
1165        // since they are a kind of cache for pds_set.
1166        self.pdcpu_set.hash(state);
1167    }
1168}
1169
1170impl PartialEq for PerfCpuOrder {
1171    fn eq(&self, other: &Self) -> bool {
1172        self.perf_cap == other.perf_cap
1173    }
1174}
1175
1176impl fmt::Display for PerfCpuOrder {
1177    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1178        write!(
1179            f,
1180            "capacity bound:  {} ({}%)\n",
1181            self.perf_cap,
1182            self.perf_util * 100.0
1183        )?;
1184        write!(f, "  primary CPUs:  {:?}\n", self.cpus_perf.borrow())?;
1185        write!(f, "  overflow CPUs: {:?}", self.cpus_ovflw.borrow())?;
1186        Ok(())
1187    }
1188}