lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [day] [month] [year] [list]
Date:   Thu, 05 Aug 2021 20:54:04 +0800
From:   "何惟禹(百奎)" <heweiyu.hwy@...baba-inc.com>
To:     "peterz" <peterz@...radead.org>, "mingo" <mingo@...hat.com>,
        "will.deacon" <will.deacon@....com>
Cc:     "何惟禹(百奎)" <heweiyu.hwy@...baba-inc.com>,
        "wuyihao" <wuyihao@...ux.alibaba.com>,
        "linux-kernel" <linux-kernel@...r.kernel.org>
Subject: [RFC PATCH] tools/accounting: add lock_contention.py tool

Add a drgn-based tool to count some spinlocks(eg. runqueues.lock, task->pi_lock) contention

Currently supports locks:
        1. rq_lock
        2. task->pi_lock
        3. zone->lock
        4. pgdat->lru_lock
        5. cfs_b->lock

Output example:
$ ./tools/accounting/lock_contention.py lock rq_lock
17:52:23  	rq_lock contention:            3/52 [A]
17:52:24  	rq_lock contention:            1/52 [A]
17:52:25  	rq_lock contention:            0/52 [A]
17:52:26  	rq_lock contention:            1/52 [A]
17:52:27  	rq_lock contention:            1/52 [A]
17:52:28  	rq_lock contention:            2/52 [A]
17:52:29  	rq_lock contention:            1/52 [A]
17:52:30  	rq_lock contention:            1/52 [A]
17:52:32  	rq_lock contention:            0/52 [A]
17:52:33  	rq_lock contention:            1/52 [A]
17:52:34  	rq_lock contention:            2/52 [A]
17:52:35  	rq_lock contention:            2/52 [A]
17:52:36  	rq_lock contention:            1/52 [A]
17:52:37  	rq_lock contention:            0/52 [A]
17:52:38  	rq_lock contention:            0/52 [A]
17:52:39  	rq_lock contention:            0/52 [A]
17:52:40  	rq_lock contention:            0/52 [A]
17:52:41  	rq_lock contention:            0/52 [A]
17:52:42  	rq_lock contention:            0/52 [A]
17:52:43  	rq_lock contention:            0/52 [A]
17:52:44  	rq_lock contention:            0/52 [A]
17:52:45  	rq_lock contention:            0/52 [A]
17:52:46  	rq_lock contention:            0/52 [A]
17:52:47  	rq_lock contention:            0/52 [A]
17:52:48  	rq_lock contention:            0/52 [A]
17:52:49  	rq_lock contention:            0/52 [A]
17:52:50  	rq_lock contention:            0/52 [A]
17:52:51  	rq_lock contention:            1/52 [A]
17:52:52  	rq_lock contention:            0/52 [A]
17:52:53  	rq_lock contention:            1/52 [A]
17:52:54  	rq_lock contention:            0/52 [A]
17:52:55  	rq_lock contention:            1/52 [A]
17:52:56  	rq_lock contention:            2/52 [A]

Signed-off-by: Weiyu <heweiyu.hwy@...baba-inc.com>
---
 tools/accounting/lock_contention.py | 214 ++++++++++++++++++++++++++++
 1 file changed, 214 insertions(+)
 create mode 100755 tools/accounting/lock_contention.py

diff --git a/tools/accounting/lock_contention.py b/tools/accounting/lock_contention.py
new file mode 100755
index 000000000000..984cb415cee3
--- /dev/null
+++ b/tools/accounting/lock_contention.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env drgn
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2021 Weiyu He <heweiyu.hwy@...baba-inc.com>
+# Copyright (C) 2021 Alibaba Inc
+
+from collections import namedtuple
+import argparse
+import sys
+import time
+import multiprocessing
+
+from drgn.helpers.linux import css_for_each_descendant_pre, per_cpu, for_each_task
+from drgn import container_of, sizeof
+
+
+DESC = """
+This is a drgn script to provide lock contention statistics for spinlock.
+"""
+
+def err(s):
+    print('lock_contention.py: error: %s' % s, file=sys.stderr, flush=True)
+    sys.exit(1)
+
+def for_each_set_bit(bitmap, size):
+    """
+    Iterate over all set (one) bits in a bitmap.
+    :param bitmap: ``unsigned long *``
+    :param size: Size of *bitmap* in bits.
+    """
+    size = int(size)
+    word_bits = 8 * sizeof(bitmap.type_.type)
+    for i in range((size + word_bits - 1) // word_bits):
+        word = bitmap[i].value_()
+        for j in range(min(word_bits, size - word_bits * i)):
+            if word & (1 << j):
+                yield (word_bits * i) + j
+
+def for_each_online_node(prog):
+    """Iterate over all online NUMA nodes."""
+    mask = prog["node_states"][prog["N_ONLINE"]]
+    try:
+        nr_node_ids = mask.prog_["nr_node_ids"].value_()
+    except KeyError:
+        nr_node_ids = 1
+    return for_each_set_bit(mask.bits, nr_node_ids)
+
+def is_vaild_bit(bits):
+    return any(int(x) for x in bits)
+
+def split_counter(counter_bits):
+    if sys.byteorder == "little":
+        locked = counter_bits[24:]
+        pending = counter_bits[16:24]
+        tail = counter_bits[:16]
+    else:
+        locked = counter_bits[:8]
+        pending = counter_bits[8:16]
+        tail = counter_bits[16:]
+    return (locked, pending, tail)
+
+def computer_spinlock_waiter(val):
+    """
+    @Return: (is_accurate[Bool], n_locked + n_waiter [int])
+
+    counter: 32 bits {31, 30, ...., 2, 1, 0}
+    locked: 8bits [0, 7]
+    pending: 8 bits [8, 15]
+    tail: 16bits [16, 32]
+    """
+    counter = val.counter.value_()
+    counter_bits = f'{counter:032b}'
+    locked, pending, tail = split_counter(counter_bits)
+
+    Result = namedtuple('waiter', ['ac', 'num'])
+
+    res = Result(True, 0)
+
+    if is_vaild_bit(tail):
+        res = Result(False, 3) # scenario.1 more than two waiter
+    elif is_vaild_bit(pending):
+        if not is_vaild_bit(locked):
+            res = Result(True, 1.5) # special: no locked, but one pending is regarded as 1.5
+        else:
+            res = Result(True, 2)  # scenario.2 only one waiter
+    elif is_vaild_bit(locked):
+        res = Result(True, 1) # scenario.1 only one locked
+
+    return res
+
+def get_raw_spinlock_waiter(raw_spinlock):
+    return computer_spinlock_waiter(raw_spinlock.raw_lock.val)
+
+def get_spinlock_waiter(spinlock):
+    return get_raw_spinlock_waiter(spinlock.rlock)
+
+def for_each_task_group(prog):
+    """
+        bug:
+            An error may be reported if the cgroup is deleted during the traversal
+    """
+    cpu_cgrp_id = prog["cpu_cgrp_id"]
+    root_tg = prog["root_task_group"].address_of_()
+    for pos in css_for_each_descendant_pre(root_tg.css.address_of_()):
+        cgroup = pos.cgroup
+        task_group = container_of(cgroup.subsys[cpu_cgrp_id], "struct task_group", "css")
+        yield task_group
+
+def lock_contention_show(name, ac, sum):
+    now = time.localtime(time.time())
+    now_str = "{}:{}:{}".format(now.tm_hour, now.tm_min, now.tm_sec)
+
+    flag = "A"
+    if not ac:
+        flag = "NA"
+    counter_msg = "{}/{} [{}]".format(sum, NR_CPUS, flag)
+
+    print("{:10}\t{} contention: {:^30}".format(now_str, name, counter_msg))
+
+def for_each_rq_lock():
+    for cpu in range(NR_CPUS):
+        yield get_raw_spinlock_waiter(per_cpu(prog["runqueues"], cpu).lock)
+
+def for_each_cb_lock():
+    for tg in for_each_task_group(prog):
+        yield get_raw_spinlock_waiter(tg.cfs_bandwidth.lock)
+
+def for_each_pi_lock():
+    """
+        Performance issues:
+            Problem with too many tasks (too slow)
+    """
+    for task in for_each_task(prog):
+        yield get_raw_spinlock_waiter(task.pi_lock)
+
+def for_each_zone_lock():
+    node_data = prog["node_data"]
+    for i in for_each_online_node(prog):
+        zone_num = prog["__MAX_NR_ZONES"].value_()
+        for j in range(zone_num):
+            yield get_spinlock_waiter(node_data[i].node_zones[j].lock)
+
+def for_each_lru_lock():
+    node_data = prog["node_data"]
+    for i in for_each_online_node(prog):
+        yield get_spinlock_waiter(node_data[i].lru_lock)
+
+def for_each_lock(lock_name):
+    return LOCKS[lock_name]()
+
+def lock_list_show(args):
+    for i, lock_name in enumerate(LOCKS.keys()):
+        print("{}. {:20}".format(i+1, lock_name))
+
+def lock_contention(args):
+    lock_name = args.lock
+    interval = int(args.interval)
+    count = int(args.count)
+
+    if not lock_name in LOCKS:
+        err("this \"{}\" lock is not support".format(lock_name))
+
+    exiting = 0
+    while(1):
+        try:
+            time.sleep(float(interval))
+
+            is_accurate = True
+            contention_sum = 0
+            for lock_info in for_each_lock(lock_name):
+                is_accurate &= lock_info.ac
+                contention_sum += lock_info.num
+            lock_contention_show(lock_name, is_accurate, contention_sum)
+
+        except KeyboardInterrupt:
+            exiting = 1
+
+        count -= 1
+        if exiting or count == 0:
+            exit()
+
+LOCKS = {
+    "rq_lock" : for_each_rq_lock,
+    "pi_lock" : for_each_pi_lock,
+    "zone_lock" : for_each_zone_lock,
+    "lru_lock" : for_each_lru_lock,
+    "cb_lock" : for_each_cb_lock,
+}
+
+NR_CPUS = multiprocessing.cpu_count()
+
+def main():
+    parser = argparse.ArgumentParser(description=DESC,
+                                     formatter_class=
+                                     argparse.RawTextHelpFormatter)
+
+    subparsers = parser.add_subparsers()
+
+    lock_parser = subparsers.add_parser("lock", help="statistics lock contention")
+    lock_parser.add_argument('lock', metavar='lockname',
+                        help='Target lock name (e.g. rq_lock)')
+    lock_parser.add_argument("interval", nargs="?", default=1,
+                        help="Output interval, in seconds")
+    lock_parser.add_argument("count", nargs="?", default=99999999,
+                        help="Number of outputs")
+    lock_parser.set_defaults(func=lock_contention)
+
+    list_parser = subparsers.add_parser("list", help="list support locks")
+    list_parser.set_defaults(func=lock_list_show)
+
+
+    args = parser.parse_args()
+    args.func(args)
+
+main()
\ No newline at end of file
-- 
2.27.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ