From nobody Tue Oct 7 14:10:01 2025 Received: from mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2801B22DA0B; Wed, 9 Jul 2025 09:08:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052115; cv=none; b=ems1x4UyybA0y/ym7BhULmkjJ6OLBKqeNMV6VXiB1fwBuRDNA7TZ1rrmR1gpT5M7aPpZ7zThnTVTe56nOfQ3W7lfEJSIzmdlxKvhIOz+oGs9mLnUkQVs11ism74Z1XuT8qCCFPR20S4k4QZVj+iuGWqwJwqbMz5nO+iJKsi/oUo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052115; c=relaxed/simple; bh=coIuyt+97yKL1i0Yaf+t+cLYvf7z/aHGft61ucHTU7w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=k9o3NFgxxxcf2k3gVecEYUeWFJmumpdc21ig368ghiqVwdcSYfUVtwJyFgOWBXzbTGCMqYfiW5TKAfVeOKroVhfpi9mN/g50K8rzn/fekhEEzfKcVVf7Y75p6h4VRh4MQb84LB05bWH33uXUsjFwQUk+Gqj3lgv7sPJV8JHLfJM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.218.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-ae0b6532345so138756966b.1; Wed, 09 Jul 2025 02:08:32 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752052111; x=1752656911; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=9IhsES15K+/vF99i1FysT8pf+kVRIfbOBsOi518P2bw=; b=fH8DkuHV8yMDHc7He0kflcM3Hg7yfRn8GHLwufvSrY9wyVCgREZ2dzhUxn7HtyOlu7 TjL8+/Snmo8j3o9WS0YUojkk90tXlXrmB6xX4zMvOXYdNZwVc4tK7YYl9t8v7Sgv5hXm 6Nwp5Dl0Qy1eWwbrXE2tpnAgNjtXyDLLi12nmDRnt5aV9T6CA/3kEHQIiOOHKsKgI/Un 4dELZ8LiqOeQqo18kh2hsUxq4jegU8ZJUsr1hPuZT9LuZnSOqbQymLd5CmH/sy6ntz05 /XAwMddqohPYSrgv54hMVRaKM1qCeCFz+3G3vA4WDvQKUHF3NuR7C6E3DiY28bonnfSo KheA== X-Forwarded-Encrypted: i=1; AJvYcCXFGi80QbqTkqBHIfdg0chxmCxtyvyxDoSa9dQSUhDi1dKupMs4x+w31nkb3y5vugM4M4CrJfOQPWfm2qRt6nNK@vger.kernel.org, AJvYcCXdrO24LktG/Sh6Vq/KJvRABLb5MsR5bLph7GSz0zgwEuRxx3sD9kQ+RMIbjntHO7ZtVoY=@vger.kernel.org, AJvYcCXdwNvs2xjnfbTl5fnrh3zWzI9wfupmgibUm/QBIrijB3HDCV/i7xyj2z9AySp1tcvuPRlGnt3g@vger.kernel.org X-Gm-Message-State: AOJu0YwtyJ0SE3Kly3KtKb0Mr14GtrJWNBmsytsvrz+aCpVe0A5iy30e ME9J/qt2MO9ncD8WXQNno5BLAMxbZPXeIMJLdfpb5ytS8Ivxog2Q/gAY X-Gm-Gg: ASbGncsdvt1aw27/U5/dqKUvGOZLy7YkYNc9EEEnscC2nV3HhutuZ+oupw/yNWOfFpg NC4zlHelzUNfNkFaBlUwuz7p34IfByk1S+pu+9JY9mxdUCIdaUp5u6xT5q3mPISd+ZQrVKjfOLG VB+mE2doyZwDN2BMcWNwtS+ueAAL4M5TP/fFvbyuEuhWlCVrQGJ7UlyJBGMzV7qozSDABavcz17 aFRxSIltZdknaWfvQsxxoVPP7a4s7nQoKsXC4gKOJoeU0M/b12oJoj4pkXBP67H903VYTulOugf 1F3KbK0LpSGwE51LcniDpWXslo03mfVUAIpxBSfmhaVl2CmLON/nxQ== X-Google-Smtp-Source: AGHT+IFm6izYbxWfDI5W78fP968wsUlWmaAR2DToUnTqsRqC4PtBDYkHebHKTcg2xVmMMzPz7AW10Q== X-Received: by 2002:a17:907:3f0a:b0:ad8:91e4:a931 with SMTP id a640c23a62f3a-ae6d149a87bmr148528766b.26.1752052110962; Wed, 09 Jul 2025 02:08:30 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:71::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae3f692d101sm1079608666b.54.2025.07.09.02.08.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 02:08:30 -0700 (PDT) From: Breno Leitao Date: Wed, 09 Jul 2025 02:08:15 -0700 Subject: [PATCH net-next v5 1/3] selftests: drv-net: add helper/wrapper for bpftrace Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250709-netpoll_test-v5-1-b3737895affe@debian.org> References: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> In-Reply-To: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Shuah Khan , Simon Horman Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, Willem de Bruijn , bpf@vger.kernel.org, kernel-team@meta.com, Breno Leitao X-Mailer: b4 0.15-dev-dd21f X-Developer-Signature: v=1; a=openpgp-sha256; l=3171; i=leitao@debian.org; h=from:subject:message-id; bh=Hh2QFmCQNBJJwPZAOp0hdcYrJqy0Em3ByaIHm5C9iTs=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBobjGLglh3KfoXOLMo2JTVl00yUfLkWC+F9P++t 69u+PupwxWJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaG4xiwAKCRA1o5Of/Hh3 bWqBD/9gqxOd6U8IcejxZEaNCpBpbXURKl1763CTR/kjLDNIbkBay++NKgyk5YWlonvyIcjrJdS dyBKxE1SnW7czCk6St2r3jqbbqpefcttGgLUONroAL2De8fDmBz/3YYkChxA9tr1+g6w0STMAIv EZ/iX8mkLLQ+Ah4fnddatHMnGzWJQlip11SvSAv/Gcd5j1xjEj959MUKs/5UyUQXu22iy2yCuCk /w5O2o/CxPCY+aArKOAJmK8bJ1KuSEneCfVhZo9cvnKfgrUg/sG9ZVA/yr7uFORZ92NPCuINS1s UP21F4ZdHZOEUsfd1GbLXSaICjwY+vJ8j/1i5PG/i+vwsNcHhNuUtFkjENFnLpG3DYqCPd6Cxkj itIFsO66BnWN0b+l/1Q82v22CdDLzh2yHx1V2vS/BimpaEIr/IMakTG+/nSJqfKV0zuezZ3ZzLr auk872EN3HgTloaJ1ck8E4v6zpEeE8kJgPSYxYIcY9eJeyu5c7tbtBlHOJngjrQUvdD+PuqzOgx ykCtGAuFYlvByIVZDOW/p2PwzNH1Bf99sel1L4D9Xj/wGKv7eh2haKOwtAGcA4cxAzqUqOeb1iq OoCO0tEDCReZ+RiHZg3F0f73sJ6HmSR7L11pdnuPtHh0oTEEfAxr4ePX3mwq3JfLIPcTU1CjDtI Axn2YilHRD3KqAw== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D From: Jakub Kicinski bpftrace is very useful for low level driver testing. perf or trace-cmd would also do for collecting data from tracepoints, but they require much more post-processing. Add a wrapper for running bpftrace and sanitizing its output. bpftrace has JSON output, which is great, but it prints loose objects and in a slightly inconvenient format. We have to read the objects line by line, and while at it return them indexed by the map name. Signed-off-by: Jakub Kicinski Reviewed-by: Breno Leitao Signed-off-by: Breno Leitao --- .../selftests/drivers/net/lib/py/__init__.py | 3 +- tools/testing/selftests/net/lib/py/utils.py | 33 ++++++++++++++++++= ++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools= /testing/selftests/drivers/net/lib/py/__init__.py index fce5d9218f1d6..4e76fbb76b659 100644 --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -14,7 +14,8 @@ try: from net.lib.py import EthtoolFamily, NetdevFamily, NetshaperFamily, \ NlError, RtnlFamily, DevlinkFamily from net.lib.py import CmdExitFailure - from net.lib.py import bkg, cmd, defer, ethtool, fd_read_timeout, ip, \ + from net.lib.py import bkg, cmd, bpftrace, defer, ethtool, \ + fd_read_timeout, ip, \ rand_port, tool, wait_port_listen from net.lib.py import fd_read_timeout from net.lib.py import KsftSkipEx, KsftFailEx, KsftXfailEx diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/se= lftests/net/lib/py/utils.py index 34470d65d871a..760ccf6fccccc 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -185,6 +185,39 @@ def ethtool(args, json=3DNone, ns=3DNone, host=3DNone): return tool('ethtool', args, json=3Djson, ns=3Dns, host=3Dhost) =20 =20 +def bpftrace(expr, json=3DNone, ns=3DNone, host=3DNone, timeout=3DNone): + """ + Run bpftrace and return map data (if json=3DTrue). + The output of bpftrace is inconvenient, so the helper converts + to a dict indexed by map name, e.g.: + { + "@": { ... }, + "@map2": { ... }, + } + """ + cmd_arr =3D ['bpftrace'] + # Throw in --quiet if json, otherwise the output has two objects + if json: + cmd_arr +=3D ['-f', 'json', '-q'] + if timeout: + expr +=3D ' interval:s:' + str(timeout) + ' { exit(); }' + cmd_arr +=3D ['-e', expr] + cmd_obj =3D cmd(cmd_arr, ns=3Dns, host=3Dhost, shell=3DFalse) + if json: + # bpftrace prints objects as lines + ret =3D {} + for l in cmd_obj.stdout.split('\n'): + if not l.strip(): + continue + one =3D _json.loads(l) + if one.get('type') !=3D 'map': + continue + for k, v in one["data"].items(): + ret[k] =3D v + return ret + return cmd_obj + + def rand_port(type=3Dsocket.SOCK_STREAM): """ Get a random unprivileged port. --=20 2.47.1 From nobody Tue Oct 7 14:10:01 2025 Received: from mail-ed1-f50.google.com (mail-ed1-f50.google.com [209.85.208.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6E665284662; Wed, 9 Jul 2025 09:08:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052116; cv=none; b=HarPIfMHNdZpdTKIjFMj31lrv63qUlYph3Q2YKBoaoycX147AOVeAY+0pHcNRKdqtILouxlfYLtJOFjEosJd2BKCFijE9uWnMIEUVN9biJhcpmCpX6BcFRXCj87XsDNkcM9soqPHPkPwd2keJqDHYkxnzJWYv3dXdJiwjdBeVHQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052116; c=relaxed/simple; bh=5e7Gu2hSYoF78NHOOQ4u3fblm9IpAmXxrCi48b0pb34=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MO9HVXX7+ItQ2BcpFvL2fj1QO3AmGU/8gLUDkRNMfW1UrQPe+AlsFCerE9v6yKFTVSfLN0RbR59S6fLIOXjL74bTuyyC4wvSJttqYhDr9YZu1GLlyhaNxhS99OIVqtXbKwqO+KWs1BzJm8wj3p8qABzuYV0GykKANi0CqCUv6ds= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.208.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ed1-f50.google.com with SMTP id 4fb4d7f45d1cf-60c9d8a16e5so9820571a12.0; Wed, 09 Jul 2025 02:08:34 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752052113; x=1752656913; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Gaqp+BTPfvuWnqLQlzPe4IbE5DQFSHBJTK+OlPf0tvE=; b=gsgI6KUZCVbRERJcgNgU2G6sRWL+Tv/5HywNCDOk3CnRmlRVeDx+rxoO5/qAXyluHp 9ZSq+jrubBGFwvp+JTY7zaClMOp5fyIOoZ5xffDJkctBFNEZOa1Iw5SGtDP706dlaIb0 vmE8IVZPFr7thMtMQPbhs5hFqkGjXkntZj477PbeoWCtdDwOPqzcFDJ7gi75QKaSkfrh G51u9pG1TxLFiiURE7EDQB0tH8kIGqTO7Z1FpudAeFsANH1gMcBbI1O5Xk3jsJtD7H6Z 14nKhiZTtXpQ2pAUCvQiISeTDTFNxAU/CocufZeXZIhAPMwmjSeGBiXEhqVxc6u96pD7 wU3A== X-Forwarded-Encrypted: i=1; AJvYcCUn1Fx4lVqVAkR+ttXHXry+reqRup95zv0t6MGkQAqN9Q6UHln/0q6d4GHQxvjRM5JvIrA=@vger.kernel.org, AJvYcCVRJKO8HSZ7KPmU/F3i/qTyehQeYMznJ5eTc6I3UtfHumPNViBWqVm5yxugqOX+zlUU9B8TFEp+ibcdtC6m9Rnw@vger.kernel.org, AJvYcCVWIw/nCb8eg5TbF3sykOXloScTbKpYa8afrA+EF0JN8DWm9yqedf2jsUpEHfaKVydN93PfXFSd@vger.kernel.org X-Gm-Message-State: AOJu0YzUksF/b+QF+EKpsIKePWdF9Sut8hyVlOg1hey0klAUS4uQZOs5 82Ldzca4b/dLg2+cQk74sra7r7YBhD/tE8zoM8IelVQSXTsPo3oibdRT X-Gm-Gg: ASbGncvafkvj0+bcf90gpIBd31f8I5PpJbO8V8kiLYPy5dF63RIn81Wch0EznMHXKj/ 4c2stByrojFHizr9Wf2FvjNrVeWihnnLLO6FVly2LXmp39zg7nrXwDhaIAIruMyR/9tVxDol1Mq W8Kwo4zXX8AMIWJnsxRhmVZ4X4RSpkoc+Fei7IooCrzzO5bbWkQHKGw3ho7kSgb3rfTpx5m09tN qm5uGgwiutx1RYSnV6WtAo1T8Gwif5gtDNTJKy0e3MTgdE8wG2CY2RLLE/rDu1mmYVNDrwcY1uO /1EcYDy3lm4w0rGb7opqo97Jfop5a2XPpnEawyVl2NO2xYWEU7i9bg== X-Google-Smtp-Source: AGHT+IHxOBQRhV3LQjYlKP2QRVe/mJ1jkHEpgMBTz4l2wSmjG32vKc1CypZqn3cCxXVOu3hprVAxXQ== X-Received: by 2002:a17:906:6a03:b0:ae3:a3f7:ad8e with SMTP id a640c23a62f3a-ae6cf5e017cmr184962666b.25.1752052112359; Wed, 09 Jul 2025 02:08:32 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:74::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae3f6bb3af6sm1052753766b.185.2025.07.09.02.08.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 02:08:31 -0700 (PDT) From: Breno Leitao Date: Wed, 09 Jul 2025 02:08:16 -0700 Subject: [PATCH net-next v5 2/3] selftests: drv-net: Strip '@' prefix from bpftrace map keys Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250709-netpoll_test-v5-2-b3737895affe@debian.org> References: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> In-Reply-To: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Shuah Khan , Simon Horman Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, Willem de Bruijn , bpf@vger.kernel.org, kernel-team@meta.com, Breno Leitao X-Mailer: b4 0.15-dev-dd21f X-Developer-Signature: v=1; a=openpgp-sha256; l=1053; i=leitao@debian.org; h=from:subject:message-id; bh=5e7Gu2hSYoF78NHOOQ4u3fblm9IpAmXxrCi48b0pb34=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBobjGLeLogJZU40OubI2N+HX+rpTwVsH2KBI8+q Vz8hEAt5g2JAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaG4xiwAKCRA1o5Of/Hh3 bQheD/4juYvrnmo1L6yKWY7pfMQ2gVr4aZ8Fa9Bk+ZSx9poM3I/4EL6pdyxocKFoAyXldti/i0m 29i1XY3KQFrKE66WQRPdzrABOs4CeuiZ2OzaARieIsuTNsGo90hwD87jLTcpwR1T0YvQH8yD9Jb x8DidB13plNwe7kq3cFNpUrgSTSxrQhEtuvEVGFc8KgF5WCXeXxfPYJefghmX6aDOw8rC4OO9oH vwH+haljrwnwTmV1Ob6gj0dLTx43H2tCAluNi3Cr6J2L3B/hyvMuLKaaUKr9rXnjK51jk+zJloN zUVvyiA6fcuknUY+T1RcjMJfaKS93aaHdDRGqYbR/ZrLCBKgoUwLOYvoiH3jSoCQ7y3h9yD06Dd DrAbah/8Wsz/VBXkMhH50FzW4KK8SjUooP7PjlKtVxnGGF3apCZVz0V8bFOWiy6Oqu2P0kjEglx 2+qFgtBAFwe7RTeCuz4/DXzUU4uZSdMsiG9RwkENMzSCLQt8dlIDjLBCgFUEY34UIXHsxcnan1d oFigLBlzXdiP3BZqtvJNtou7eLFxXIyshcnlQEbyk7x9wRzVRN1XTSVPcIx7lYPQprXQQD9NSIo dRO8i01cHR0iYPqnc3KZ/k/CgaF7q1hgydFkUUOSuAXwrT3XwPfwwX4AyxyWMBSx+xnwrXHtZWC /fGJ0iWRq19hM0g== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D The '@' prefix in bpftrace map keys is specific to bpftrace and can be safely removed when processing results. This patch modifies the bpftrace utility to strip the '@' from map keys before storing them in the result dictionary, making the keys more consistent with Python conventions. Signed-off-by: Breno Leitao --- tools/testing/selftests/net/lib/py/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/net/lib/py/utils.py b/tools/testing/se= lftests/net/lib/py/utils.py index 760ccf6fccccc..33c23a928ed1d 100644 --- a/tools/testing/selftests/net/lib/py/utils.py +++ b/tools/testing/selftests/net/lib/py/utils.py @@ -213,6 +213,8 @@ def bpftrace(expr, json=3DNone, ns=3DNone, host=3DNone,= timeout=3DNone): if one.get('type') !=3D 'map': continue for k, v in one["data"].items(): + if k.startswith('@'): + k =3D k.lstrip('@') ret[k] =3D v return ret return cmd_obj --=20 2.47.1 From nobody Tue Oct 7 14:10:01 2025 Received: from mail-ed1-f41.google.com (mail-ed1-f41.google.com [209.85.208.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3635F285052; Wed, 9 Jul 2025 09:08:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052118; cv=none; b=G08ofJVB4PibGBE+CVgFo9zuss2ud6JDUtP3wkDbgCMQAdhdklH2AHFEdBjQOFlD18jC00YK6sqQPhfcHrPXVE8eq62FCVtAr5IQ0OZhs2NyM4HRtdD1Y0P4OVxsTz7kyaOu/oni1SgC5i047he3S6+thBllTeozhxeIAk199j0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752052118; c=relaxed/simple; bh=w2tJfdiqwcE8bfYuAAwLU5bNM/pPk/9kXBOSGad1awY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=bo271z4gcIYivRpaNHiEX5eOWKZTPcZYpdEFf6rD4t42nYADdhKWcc69r9Sm5iWxcJywgknd+xN+oMeRX2Dd+VJzTuCKtiWkzeIWx85geDxHIClUrNRsKtAO5i0oCVjU7yTzoSPpP2Up5b042l04tYjJMd27PjiwZr30HDD8jo0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.208.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ed1-f41.google.com with SMTP id 4fb4d7f45d1cf-608acb0a27fso7302389a12.0; Wed, 09 Jul 2025 02:08:35 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752052114; x=1752656914; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=V0x+TC4q1w9EwhE5JrMj+8XsQkTRjt/FHW7puo36KoI=; b=VCguidqlUavQ/ZAAe3GFA3hheK6r9l+4EnQ1FNODVZw2mmw+ckKB+eHl48G6Sa9xMF wSmPwNTfjjHg4vqEvgIM5gQJNLbXotEScVBxLLTZyR8glvwVA5aw6SmpcSHASMgInxnJ q1p+EX5VzULqzKe8ePj0haffDkP61k5IzI6oJtqvaG+4dInHlnXEgjxHycHkTDGS2pvp sb4XhmTlHLVT7tpLdqeHXRYex7v5ymZhvoBaSm+jAbeegDQQZl9LTgWbFKj1Z715wtvF nUfsrFqHElM8jO8sxQgEPXXeUTwtHPBZm15arFt5Fdx/xolzFS0TL3+BUxI3eZDMkxhd hWCQ== X-Forwarded-Encrypted: i=1; AJvYcCUQsW4bjJSNjuCHsvGs+fJWFEL1gIJ+nk7dYYZiMsqaMlHeVl5a+BapFEQqgLJhZtQdtAlH5vtQ54mXarZihmvO@vger.kernel.org, AJvYcCVFqeQxvBVT/L1v2oG8J90FoEV/7mMuXiYH1GY8aEHMqMW6d6Qgwh3tRN73X97bHf/0+eU=@vger.kernel.org, AJvYcCW+h6cooe/MITuEvv+c3mS8u/PytoIONz/WytK2zIDt3ZL5UShudruK+5kGlg0AcJkAqeSYVoW0@vger.kernel.org X-Gm-Message-State: AOJu0YwnZcRL7QJdBPbTSVwj1TINDTCA8QgFZZ+25suUR6P/Scp4CxWI QZPlNLlgjxpoV6Pc2iX7bSMm/dtHLO3l4emhuBDWFBdHhxgEK61EP7vJ X-Gm-Gg: ASbGncsiD77o/HABgByIPdcRIgVzNFnZpDgdxUReDk9HlNYTiUKP4h49Q4IdjvFRh/D UjCGnRDANihJL1CeEqNs+WSHiNKUw5JzN5x8lTONtKM5YwOfxFUZWNWwVCvpXCsBTxXCWBEU84o u2ZZxa7wQz/NrM53SfNALicCJvnvHb68AX1ssLCrloa5BskC9V0jbsMcMivYUt3GcM/7J++yQs8 eSJldpx6O0buS/InowIisRtMzxewCcGTRAC/L/VM4QQhzNBohyMbC9J0okuA8wRySpbL6FW7tYq MsVfA/cfYk97ifK24RwErzU4Kl1wKmECH08TIeOJq4ZIm4i0rn/F X-Google-Smtp-Source: AGHT+IHeW1eWnWp6des3hroAzDw/idgot1hOXn+ykQnPznaja7UVB8ycNjsYIGjepAUiEgp6TE0q7w== X-Received: by 2002:a17:906:6a10:b0:add:f0a2:d5d8 with SMTP id a640c23a62f3a-ae6cf577101mr171853566b.11.1752052114028; Wed, 09 Jul 2025 02:08:34 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:2::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae412020b5fsm812870566b.163.2025.07.09.02.08.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 09 Jul 2025 02:08:33 -0700 (PDT) From: Breno Leitao Date: Wed, 09 Jul 2025 02:08:17 -0700 Subject: [PATCH net-next v5 3/3] selftests: net: add netpoll basic functionality test Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250709-netpoll_test-v5-3-b3737895affe@debian.org> References: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> In-Reply-To: <20250709-netpoll_test-v5-0-b3737895affe@debian.org> To: Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Shuah Khan , Simon Horman Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org, linux-kselftest@vger.kernel.org, Willem de Bruijn , bpf@vger.kernel.org, kernel-team@meta.com, Breno Leitao , Willem de Bruijn X-Mailer: b4 0.15-dev-dd21f X-Developer-Signature: v=1; a=openpgp-sha256; l=15038; i=leitao@debian.org; h=from:subject:message-id; bh=w2tJfdiqwcE8bfYuAAwLU5bNM/pPk/9kXBOSGad1awY=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBobjGLTGYDug8ClHNKt4DXZr3xzq+jBLKIo0Fi6 1hnRYU0ML+JAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaG4xiwAKCRA1o5Of/Hh3 bYHUEAChE7Sqnd2kMwyr+7+vAhRJeGzaywcADGCpAC5G0fA/g50VTOW511NfDb6v5SvDmewivSi J8iD0UiPVDgYcnUNXnjJLf3k40n45Y2IVrNGEzyegRBPl7OrQ6Gx6CPTEZUcTmxkpTts9vbmloe 4ybEcjT7r53aUjA++jAUX1RrMkKyt7t60ivI211s7XfSnFIDdQ2tpi8XvnFv9poepZn4j0idUah i/fOL/7YV86KzaVCIb8gFmBKVxxXrTs+FA9wCP9bU5yofTYC0aBHRyYU9pFFQi7PPQuJvzVk8ST ShvFUtgKSmSLpAibGgBYNjvJJOHDY7ZqLxzRcOdXJAdN65oZlrFSCGcZu7ygH47jlmUGUbL8eTu tzHI9ZzyrQnsgE0w9/CqBo3ZDtmN7mwI4AcSHHmOvnYvKv4achiVlx1FLnQ8xb54lpP8YKsKTYt lLNDR68QbJdS+fo+S1qO8WI6pZlt27SkRZpZII0MilrsTLMwo0I2n3l94rpN64/FKa47J0HS1ZI lM4jW9r0EOJjWzx8pieywNtuJf6l65nQep0xwJSkLY88vh4pI93aH3wV8EarR1rFOAR7/44v9O4 Y+uQHDEpUrOQANN9FyocFtnyU293nLeG+4ZjYAzhQUrytPFZXpHjoVLa6EFf9abz0iPRGiTtaqW xsUv/GFUVBhYBpQ== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D Add a basic selftest for the netpoll polling mechanism, specifically targeting the netpoll poll() side. The test creates a scenario where network transmission is running at maximum speed, and netpoll needs to poll the NIC. This is achieved by: 1. Configuring a single RX/TX queue to create contention 2. Generating background traffic to saturate the interface 3. Sending netconsole messages to trigger netpoll polling 4. Using dynamic netconsole targets via configfs 5. Delete and create new netconsole targets after some messages 6. Start a bpftrace in parallel to make sure netpoll_poll_dev() is called 7. If bpftrace exists and netpoll_poll_dev() was called, stop. The test validates a critical netpoll code path by monitoring traffic flow and ensuring netpoll_poll_dev() is called when the normal TX path is blocked. This addresses a gap in netpoll test coverage for a path that is tricky for the network stack. Signed-off-by: Breno Leitao Reviewed-by: Willem de Bruijn --- tools/testing/selftests/drivers/net/Makefile | 1 + .../testing/selftests/drivers/net/netpoll_basic.py | 366 +++++++++++++++++= ++++ 2 files changed, 367 insertions(+) diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/s= elftests/drivers/net/Makefile index bd309b2d39095..9bd84d6b542e5 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -16,6 +16,7 @@ TEST_PROGS :=3D \ netcons_fragmented_msg.sh \ netcons_overflow.sh \ netcons_sysdata.sh \ + netpoll_basic.py \ ping.py \ queues.py \ stats.py \ diff --git a/tools/testing/selftests/drivers/net/netpoll_basic.py b/tools/t= esting/selftests/drivers/net/netpoll_basic.py new file mode 100755 index 0000000000000..6017b71f154b2 --- /dev/null +++ b/tools/testing/selftests/drivers/net/netpoll_basic.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Author: Breno Leitao +""" + This test aims to evaluate the netpoll polling mechanism (as in + netpoll_poll_dev()). It presents a complex scenario where the network + attempts to send a packet but fails, prompting it to poll the NIC from wi= thin + the netpoll TX side. + + This has been a crucial path in netpoll that was previously untested. Jak= ub + suggested using a single RX/TX queue, pushing traffic to the NIC, and then + sending netpoll messages (via netconsole) to trigger the poll. + + In parallel, bpftrace is used to detect if netpoll_poll_dev() was called.= If + so, the test passes, otherwise it will be skipped. This test is very depe= ndent on + the driver and environment, given we are trying to trigger a tricky scena= rio. +""" + +import errno +import logging +import os +import random +import string +import threading +import time +from typing import Optional + +from lib.py import ( + bpftrace, + defer, + ip, + ethtool, + GenerateTraffic, + ksft_exit, + ksft_pr, + ksft_run, + KsftFailEx, + KsftSkipEx, + NetDrvEpEnv, + KsftXfailEx, +) + +# Configure logging +logging.basicConfig( + level=3Dlogging.INFO, + format=3D"%(asctime)s - %(levelname)s - %(message)s", +) + +NETCONSOLE_CONFIGFS_PATH: str =3D "/sys/kernel/config/netconsole" +NETCONS_REMOTE_PORT: int =3D 6666 +NETCONS_LOCAL_PORT: int =3D 1514 + +# Max number of netcons messages to send. Each iteration will setup +# netconsole and send MAX_WRITES messages +ITERATIONS: int =3D 20 +# Number of writes to /dev/kmsg per iteration +MAX_WRITES: int =3D 40 +# MAPS contains the information coming from bpftrace it will have only one +# key: "hits", which tells the number of times netpoll_poll_dev() was call= ed +MAPS: dict[str, int] =3D {} +# Thread to run bpftrace in parallel +BPF_THREAD: Optional[threading.Thread] =3D None +# Time bpftrace will be running in parallel. +BPFTRACE_TIMEOUT: int =3D 10 + + +def ethtool_read_rx_tx_queue(interface_name: str) -> tuple[int, int]: + """ + Read the number of RX and TX queues using ethtool. This will be used + to restore it after the test + """ + rx_queue =3D 0 + tx_queue =3D 0 + + try: + ethtool_result =3D ethtool(f"-g {interface_name}").stdout + for line in ethtool_result.splitlines(): + if line.startswith("RX:"): + rx_queue =3D int(line.split()[1]) + if line.startswith("TX:"): + tx_queue =3D int(line.split()[1]) + except IndexError as exception: + raise KsftSkipEx( + f"Failed to read RX/TX queues numbers: {exception}. Not going = to mess with them." + ) from exception + + if not rx_queue or not tx_queue: + raise KsftSkipEx( + "Failed to read RX/TX queues numbers. Not going to mess with t= hem." + ) + return rx_queue, tx_queue + + +def ethtool_set_rx_tx_queue(interface_name: str, rx_val: int, tx_val: int)= -> None: + """Set the number of RX and TX queues to 1 using ethtool""" + try: + # This don't need to be reverted, since interfaces will be deleted= after test + ethtool(f"-G {interface_name} rx {rx_val} tx {tx_val}") + except Exception as exception: + raise KsftSkipEx( + f"Failed to configure RX/TX queues: {exception}. Ethtool not a= vailable?" + ) from exception + + +def netcons_generate_random_target_name() -> str: + """Generate a random target name starting with 'netcons'""" + random_suffix =3D "".join(random.choices(string.ascii_lowercase + stri= ng.digits, k=3D8)) + return f"netcons_{random_suffix}" + + +def netcons_create_target( + config_data: dict[str, str], + target_name: str, +) -> None: + """Create a netconsole dynamic target against the interfaces""" + logging.debug("Using netconsole name: %s", target_name) + try: + os.makedirs(f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}", exist_ok= =3DTrue) + logging.debug( + "Created target directory: %s/%s", NETCONSOLE_CONFIGFS_PATH, t= arget_name + ) + except OSError as exception: + if exception.errno !=3D errno.EEXIST: + raise KsftFailEx( + f"Failed to create netconsole target directory: {exception= }" + ) from exception + + try: + for key, value in config_data.items(): + path =3D f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}/{key}" + logging.debug("Writing %s to %s", key, path) + with open(path, "w", encoding=3D"utf-8") as file: + # Always convert to string to write to file + file.write(str(value)) + + # Read all configuration values for debugging purposes + for debug_key in config_data.keys(): + with open( + f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}/{debug_key}", + "r", + encoding=3D"utf-8", + ) as file: + content =3D file.read() + logging.debug( + "%s/%s/%s : %s", + NETCONSOLE_CONFIGFS_PATH, + target_name, + debug_key, + content.strip(), + ) + + except Exception as exception: + raise KsftFailEx( + f"Failed to configure netconsole target: {exception}" + ) from exception + + +def netcons_configure_target( + cfg: NetDrvEpEnv, interface_name: str, target_name: str +) -> None: + """Configure netconsole on the interface with the given target name""" + config_data =3D { + "extended": "1", + "dev_name": interface_name, + "local_port": NETCONS_LOCAL_PORT, + "remote_port": NETCONS_REMOTE_PORT, + "local_ip": cfg.addr_v["4"] if cfg.addr_ipver =3D=3D "4" else cfg.= addr_v["6"], + "remote_ip": ( + cfg.remote_addr_v["4"] if cfg.addr_ipver =3D=3D "4" else cfg.r= emote_addr_v["6"] + ), + "remote_mac": "00:00:00:00:00:00", # Not important for this test + "enabled": "1", + } + + netcons_create_target(config_data, target_name) + logging.debug( + "Created netconsole target: %s on interface %s", target_name, inte= rface_name + ) + + +def netcons_delete_target(name: str) -> None: + """Delete a netconsole dynamic target""" + target_path =3D f"{NETCONSOLE_CONFIGFS_PATH}/{name}" + try: + if os.path.exists(target_path): + os.rmdir(target_path) + except OSError as exception: + raise KsftFailEx( + f"Failed to delete netconsole target: {exception}" + ) from exception + + +def netcons_load_module() -> None: + """Try to load the netconsole module""" + os.system("modprobe netconsole") + + +def bpftrace_call() -> None: + """Call bpftrace to find how many times netpoll_poll_dev() is called. + Output is saved in the global variable `maps`""" + + # This is going to update the global variable, that will be seen by the + # main function + global MAPS # pylint: disable=3DW0603 + + # This will be passed to bpftrace as in bpftrace -e "expr" + expr =3D "kprobe:netpoll_poll_dev { @hits =3D count(); }" + + MAPS =3D bpftrace(expr, timeout=3DBPFTRACE_TIMEOUT, json=3DTrue) + logging.debug("BPFtrace output: %s", MAPS) + + +def bpftrace_start(): + """Start a thread to call `call_bpf` in a parallel thread""" + global BPF_THREAD # pylint: disable=3DW0603 + + BPF_THREAD =3D threading.Thread(target=3Dbpftrace_call) + BPF_THREAD.start() + if not BPF_THREAD.is_alive(): + raise KsftSkipEx("BPFtrace thread is not alive. Skipping test") + + +def bpftrace_stop() -> None: + """Stop the bpftrace thread""" + if BPF_THREAD: + BPF_THREAD.join() + + +def bpftrace_any_hit(join: bool) -> bool: + """Check if netpoll_poll_dev() was called by checking the global varia= ble `maps`""" + if not BPF_THREAD: + raise KsftFailEx("BPFtrace didn't start") + + if BPF_THREAD.is_alive(): + if join: + # Wait for bpftrace to finish + BPF_THREAD.join() + else: + # bpftrace is still running, so, we will not check the result = yet + return False + + logging.debug("MAPS coming from bpftrace =3D %s", MAPS) + if "hits" not in MAPS.keys(): + raise KsftFailEx(f"bpftrace failed to run!?: {MAPS}") + + logging.debug("Got a total of %d hits", MAPS["hits"]) + return MAPS["hits"] > 0 + + +def do_netpoll_flush_monitored(cfg: NetDrvEpEnv, ifname: str, target_name:= str) -> None: + """Print messages to the console, trying to trigger a netpoll poll""" + # Start bpftrace in parallel, so, it is watching + # netpoll_poll_dev() while we are sending netconsole messages + bpftrace_start() + defer(bpftrace_stop) + + do_netpoll_flush(cfg, ifname, target_name) + + if bpftrace_any_hit(join=3DTrue): + ksft_pr("netpoll_poll_dev() was called. Success") + return + + raise KsftXfailEx("netpoll_poll_dev() was not called during the test..= .") + + +# toggle the interface up and down, to cause some congestion +def toggle_interface(ifname: str) -> None: + """Toggle the interface up and down""" + logging.debug("Toggling interface %s", ifname) + try: + ip(f"link set dev {ifname} down") + # Send a message while the interface is down, just to + # cause more test scenarios. Netconsole should be + # going down here as well, giving the link was lost + with open("/dev/kmsg", "w", encoding=3D"utf-8") as kmsg: + kmsg.write("netcons test while interface down\n") + + ip(f"link set dev {ifname} up") + except Exception as exception: + raise KsftFailEx(f"Failed to toggle interface: {exception}") from = exception + + +def do_netpoll_flush(cfg: NetDrvEpEnv, ifname: str, target_name: str) -> N= one: + """Print messages to the console, trying to trigger a netpoll poll""" + netcons_configure_target(cfg, ifname, target_name) + retry =3D 0 + + for i in range(int(ITERATIONS)): + if not BPF_THREAD.is_alive() or bpftrace_any_hit(join=3DFalse): + # bpftrace is done, stop sending messages + break + + msg =3D f"netcons test #{i}" + with open("/dev/kmsg", "w", encoding=3D"utf-8") as kmsg: + for j in range(MAX_WRITES): + try: + kmsg.write(f"{msg}-{j}\n") + except OSError as exception: + # in some cases, kmsg can be busy, so, we will retry + time.sleep(1) + retry +=3D 1 + if retry < 5: + logging.info("Failed to write to kmsg. Retrying") + # Just retry a few times + continue + raise KsftFailEx( + f"Failed to write to kmsg: {exception}" + ) from exception + + # Every 5 iterations, toggle the network and netconsole + toggle_interface(ifname) + netcons_delete_target(target_name) + netcons_configure_target(cfg, ifname, target_name) + # If we sleep here, we will have a better chance of triggering + # This number is based on a few tests I ran while developing this = test + time.sleep(0.4) + + +def test_netpoll(cfg: NetDrvEpEnv) -> None: + """ + Test netpoll by sending traffic to the interface and then sending + netconsole messages to trigger a poll + """ + + target_name =3D netcons_generate_random_target_name() + ifname =3D cfg.dev["ifname"] + traffic =3D None + original_queues =3D ethtool_read_rx_tx_queue(ifname) + + try: + # Set RX/TX queues to 1 to force congestion + ethtool_set_rx_tx_queue(ifname, 1, 1) + + traffic =3D GenerateTraffic(cfg) + do_netpoll_flush_monitored(cfg, ifname, target_name) + finally: + if traffic: + traffic.stop() + + # Revert RX/TX queues + ethtool_set_rx_tx_queue(ifname, original_queues[0], original_queue= s[1]) + netcons_delete_target(target_name) + + +def test_check_dependencies() -> None: + """Check if the dependencies are met""" + if not os.path.exists(NETCONSOLE_CONFIGFS_PATH): + raise KsftSkipEx( + f"Directory {NETCONSOLE_CONFIGFS_PATH} does not exist. CONFIG_= NETCONSOLE_DYNAMIC might not be set." # pylint: disable=3DC0301 + ) + + +def main() -> None: + """Main function to run the test""" + netcons_load_module() + test_check_dependencies() + with NetDrvEpEnv(__file__, nsim_test=3DTrue) as cfg: + ksft_run( + [test_netpoll], + args=3D(cfg,), + ) + ksft_exit() + + +if __name__ =3D=3D "__main__": + main() --=20 2.47.1