From nobody Tue Oct 7 08:23:14 2025 Received: from mail-ej1-f45.google.com (mail-ej1-f45.google.com [209.85.218.45]) (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 5C4FF2EF2BA; Fri, 11 Jul 2025 16:05:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249926; cv=none; b=WT5lRU7OhOiXoX8HWhh5e9sslngblyJprc+sHM328zs4Im99aV7YoJl6nVqelq2uYaO0wfjVa9ZK8KVZc8UEAsjiTpdPG7aecJxSEMh3YCpgvC8TYqJWy38FPNdW+fe0xRnPZdPySIEr4Tng4i+X26hze8PjzLgqBOshOwmO36U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249926; 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=HhrS4xFeKzsWVV7l+NZo2ePEGFbJu8Prjdl6Z+TtcPyzy8sqEiYAS6wkowfPImRhwtm+eYNCYaXcPqxjArEXrTZoJrVnugVSeXxx38uQ+NpxjZGACnXxjgJqFZSeFctzC2heEQ7/TO1MBwf5A+tjBXrIjh4Q0amJ0hqTrxiNgyM= 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.45 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-f45.google.com with SMTP id a640c23a62f3a-ae3ec622d2fso396482466b.1; Fri, 11 Jul 2025 09:05:24 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752249923; x=1752854723; 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=Oc/itH8oOPexJaDFZworf2RpmU/cg/0HfT+MUI5LEhOyP8vtRILnN+miTCdhjmKTx7 vz2l76Dlac/QcgHLcNh0b4HoxBjk/BSQ/4ABRydemsCqQlEPPLX1E51waaWCCm9Vry4k 0LfKcabtWa/w6F+1HCcDX88fo1O1T/1bXquu4/Dba49V/TygX84hGxlr7HZWnUcAEl0R Y5uUn2pZK9h4FO7ZwmgM/H01IqbuVTUgGAy4OXdCD8HbCpq/PJCkX1eLw2GTggPKmVq0 pC3QsRO/GLsv1Bv4qx/DUyoNCe5w0QjdTP+YjJvDtqbHIWTuBwNunTsOgaNUczz6Kh/n JkiQ== X-Forwarded-Encrypted: i=1; AJvYcCUKrTcwOx3e2TXviwVSGSXjzcUn3GEXu8EAiZpwja/4sSeDLZnbhw1N0WAriKaST9P925I=@vger.kernel.org, AJvYcCW1j4MAzpYq7990JPTf+jHSBYgByb9AJOMwKKQLGZKCOSvidGk/AuKlcVQiS8C6h+rc/n6i+tWA@vger.kernel.org, AJvYcCWG2vr18kjJUDqtn/+8tUYN72F7PMa5bUxlOhxZvwDUn7HqJljXiFO+zyAgyuuPWvZ8BD1oF8Toh/6qNBk220N8@vger.kernel.org X-Gm-Message-State: AOJu0YwgWg9vLfMbgi296yGSJ/isaQcsAw8PMEajybeEXheNvTmTIKLz UCIUgu51Dpd5si+Krf0jJlO1YD8xB7JcD8yssY2IRRUO0mTwpBvcuW8r X-Gm-Gg: ASbGncuZui0UJ57jLPQMrJhEpTwnu+Zm+9UkfoBrQ3li5h6ItyuH3/8Vet6Ji8wEGCd QxD4IGdUGRSg+XBbF8VS0T8es+jA2aJ12X6RrAdfOA/zNaXnLSkBgbaXoXkWXNlWr6qcM17QHfq 7SPezD7LaNw28peF0JFmacqX0on3LrFNI794YzvyWrWCHEmTmAEf22trWEsz1n6pJMx/muymayg PpVPGoIi6YgFAy2Fsp7tV56rWLYTjdWFIKafWg8fSeQrOkPSk1mogHkUUujvnTKzI0oJOPkmY2y PayFItkpo3bpy/Y20Mka2+WtGzmlbc7gH85BqXp7XM3WwDxMwdsCpfUYUS+CJ0DJSumjCoY26kI QvOQiJmDkPhZD X-Google-Smtp-Source: AGHT+IFBlDtEH0RCsrlU3uTsnOYvjFtmKwYrM0BxvXNhln2S03J5tV3SGlXcZtDrbZrrQl6iD/1PmQ== X-Received: by 2002:a17:907:96a9:b0:ae0:bdc2:994d with SMTP id a640c23a62f3a-ae6fc3da7dcmr442875766b.55.1752249922076; Fri, 11 Jul 2025 09:05:22 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:9::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae6e7eec2a0sm318979866b.67.2025.07.11.09.05.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 11 Jul 2025 09:05:21 -0700 (PDT) From: Breno Leitao Date: Fri, 11 Jul 2025 09:05:09 -0700 Subject: [PATCH net-next v6 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: <20250711-netpoll_test-v6-1-130465f286a8@debian.org> References: <20250711-netpoll_test-v6-0-130465f286a8@debian.org> In-Reply-To: <20250711-netpoll_test-v6-0-130465f286a8@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/8eHdtAcsmYgBocTY+MxxHHPhqusTTtwGG709Jf5ErgCSIW/EBC ID69C6apXuJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaHE2PgAKCRA1o5Of/Hh3 bZ74EACRnIHXnpQrxCSOMAN/hal63q8013QLqCV3ZmTk2CHElinGUUsWcz7FWcT1iI97BHrUIi9 w93Jwr5OKix87yYxAgRnDe7+aVpAKjG65czEpHYA3/Hag6NOcgLz1w1wTQm+weg2+HnfjaEru+w cGejE1EnwXNJhsT2xxraQYMalotIjP/OqxojkZ134SXEScgVaMV9ECCI1lm0s9lfbgQN7V1mFiT K4h9q4dvW5uJCDVAgnIL+dvbOj8Vj5BSw1hkkxjBhGWa9RyXK17INi7UYVymn9HQTusv5W9ionn nSZ4cWap0dRFgSCfTQeRgc1AlvzpaMMGMOlxjY0VFhFBNfGuPF4Dd/7da3ae57uRcWYPBKwjhmB NhopaMjGJFE5BqBAbMdpdwfRcKIQ/HnSSAdE8BWOkqRhxQhyx3wRVsHvxllbujJD33KJOcNlXpL /2uVD1mwMRFufPJcyGdGLe44uWns39RS/B3r+YYMGQm42scYrRrQ0Z/0J4ajTjxjnmdbRWh7THP W+X81UMRmfsktzVkIYJuMaeu/h21J7q5AkXO9InxoOYRrIKbXMQ0Qz0eiWmeo43ezaSG+giobcE 9h3Ciw6l33sHfOVpnJ2GfBq6uY+9poxwUF3zJ5BJJEGkEa9w3g33bh9AmStiLAZfj30gkhWzNUA Gcsh/Hbo4tPf6Vw== 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 08:23:14 2025 Received: from mail-ej1-f54.google.com (mail-ej1-f54.google.com [209.85.218.54]) (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 AA47A2EF64E; Fri, 11 Jul 2025 16:05:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249927; cv=none; b=LmjJMEor6uOl+z4Xg4i1epeVBo+Dlv4JmUh7MTXVw2murauQ8YpWnx2TttVYw1W3h5RYA6asVN1o3kuv2/XZp2hcRk8dX9Q6M/kzPGLmxdsQuchlvjWWSW9vUsybg3tRLLxMHqy89aStyeNhBlJmakjPjxH+3Vdoe/d99LLX5+w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249927; c=relaxed/simple; bh=5e7Gu2hSYoF78NHOOQ4u3fblm9IpAmXxrCi48b0pb34=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ox8/L+Evu/CtsmrpHiXPB3/RITdjx9Hct5M7xLb9BE/mtlv4Yiy+ZgrGhXpAi0BnGQhE42oNdJ8ary6OLw0OkXyoQEYr3WiAELjhTF64yaCrbx4Formrap0NpXlI7sSRoGEKT7lp0y/hipFTPj+GcXNqPjr8DSfPG21yBJLAvak= 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.54 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-f54.google.com with SMTP id a640c23a62f3a-ae6fa02d8feso179003966b.0; Fri, 11 Jul 2025 09:05:25 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752249924; x=1752854724; 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=gowgm9d3KoOpmtVmAJavHVQDQcM90LQSGW9JXrp5bnOlec+rhGJGTshaLI8cEN4FHM tMRS9AnbEwGQpDuZ0cmaxXLcCESoATCeTcWpp7h4nPlR+qMVuE1L0o2RYzqSHhUOkWm7 MoBQuFr2K5pnZj61Ss9fR0HacK/6LX7yKyi7bzrieaXR+sEC46ZVOaL3ml5MSH+p9OGF 2p+vv8kF0xxLhRpS4hRBws0GJgNXEBBf1nhWfdrm63fMoIxq3eR7E+CYVn+j6y1J9oPW TB1hoLNOml2y7jIub8hbBo3p+/Vfte4DsHGXO/6xtXB1Jv6qwpSMdIYLndGlQX6bqSZi CNxQ== X-Forwarded-Encrypted: i=1; AJvYcCU4ZrmCNzqQpFI9auBi6TpN9Ypkn/QmVWqyRaPMiLHVzMvdUahjcb+jROFK49aGifkieIIQc6lgQs2ZsbZY2Iyj@vger.kernel.org, AJvYcCVJ9Caey5CpKHbzO16o0FWlCjSNdKBFtgbSUNQX0I5xT0QW7Zv8/KE986wE+QT7NEWGVVGcLgyp@vger.kernel.org, AJvYcCWj7sYgJhKub02h+13Oq4C+XTw/gzxSEwkl6EKrXJd27Ys/mlxTcfxkmA8o0sly7jzrzjQ=@vger.kernel.org X-Gm-Message-State: AOJu0YwqDGPyJ54cLUaaBQV+5uqBfspZJdwUUkmho/RG7eTkKMIvHdYO /X8zn0bZazJMouVtmkx5YJ4CfbNLrJUAnvpDAYjYyN4aajnnom+6gF1S X-Gm-Gg: ASbGncttgCPe3shaZhiFe6+M6Lq+HD/OowN1Eae7WEActADyN6fAkW0otjmWlh6+Woo K6gZaW125DPru9kZXK1cwUV7nl7cB2gjpiy35oVTNMQ4rioES30xDPHZSR2PYzA1F2uFKwRxoml C4PVPeh0jlfEJAzp/qzw9djUCMyg8K8h2d8O3DMzZDY4FV7GJC9f2D4zk/w3KqcrfRPUOVIO0V8 eb0plTmYfTjwSt/96n6GyxXWuf6ORxIvrBM6yR0KhW5rpsJVqO0pjXYhKeXGGC1+Luy3yTOTzlI 7QUXnUzzDANppuOWAwFWoQ/Es4ldYMqM9znS+mgOYyQRt74BIYjXrTXdNz/yRSCfsjgJU5mmjeu pQ8MZDDWzplPe X-Google-Smtp-Source: AGHT+IEOk8Txtw7wZrA6mgAZZ3zGVAl1tkX4/fAps4bB2HOYHCbHS9q01TOG2Zz1Z6XjlvyRYQwQJg== X-Received: by 2002:a17:907:94c8:b0:ae6:ddc2:f9f4 with SMTP id a640c23a62f3a-ae6fc9ff022mr405030066b.6.1752249923490; Fri, 11 Jul 2025 09:05:23 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:7::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae6e82635eesm313881966b.81.2025.07.11.09.05.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 11 Jul 2025 09:05:23 -0700 (PDT) From: Breno Leitao Date: Fri, 11 Jul 2025 09:05:10 -0700 Subject: [PATCH net-next v6 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: <20250711-netpoll_test-v6-2-130465f286a8@debian.org> References: <20250711-netpoll_test-v6-0-130465f286a8@debian.org> In-Reply-To: <20250711-netpoll_test-v6-0-130465f286a8@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/8eHdtAcsmYgBocTY+oEsQp+IO4aE31w+WTCng05k+3qn0r0LDS m8RygsELaiJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaHE2PgAKCRA1o5Of/Hh3 bafqD/0WXwdjs2NHHl5Ferwad32LyL06R2iMKwfNI63GyhPOd8/mezKSwvn/BE6nSSVpcQFQ3eo fCURY1a1J1lHUpHlcn3fWgo75nGW7fmgRSW0mIcBYw3JikQ/CoIhwoEE3IaIYQRGpvFz4ZCVPxl 0sLR+pYsxc56Q9Y5JaIx7F4A3vFOFBCNkISuJfb35Hb2+jfD2Mi3dbVUblKqW0lJ6PHrocMHc8v P+QZra0uvw/Nv0sdWX/Y6ixzV+Y3DaBCEXEAc5a7hlNZuuMKH/lOCj1dUhKtI6ohE7agjwUbCzs 3Nu2lQg5Skam+e2LXSG4WEBXoCN4F9PXyi4KP8bZPXVaiA1xyRg8wyIx4zWIaHeGGGUkDvEACkf BET2Yb9/+PdF2Mf+5nYMyMQNL6PtaOdRE8DdZfuX/uS8d5YziL+BrwCqxMeZP1ftJZGU43q+2OK GPwf6HRP8YRN13cUnpCb8Aqcty0/hG+dV1i1nUvBfLM8igpygS0QBnIQXQyAP7xGVPwJrUlAz2H SE4tNgeB6Aa8Ii+FBmHff8/bvmfWc3ZDXUkYEHrrKtp1xqdUf9V0UJkttrHNsJJMsLvKi0zjX5X BFnyAVMJrIaLQszuELZ6KOpWnUiksdog9ch3uGlbGk938JUCIvzbGRlGGkKuBmSNEik4rWYMw0M XuK6NJoE6QZfMVw== 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 08:23:14 2025 Received: from mail-ed1-f47.google.com (mail-ed1-f47.google.com [209.85.208.47]) (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 7682F2EF9DF; Fri, 11 Jul 2025 16:05:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249929; cv=none; b=JoyW5Ld+JW3ZPG/whp9G9G4Qc1np06E/vZvqFwf4viSmu6A8yFX3OO7GFehI6bx5MsTRSfjb+q4b6kKDvvRKOz8NpuZdPf9ZWEnmkNYyXtU30T8Gm5Ke+epA4jXvsZDwxDAIzW1EnYNkPvU4OMXyk2lqKoY+kOKTYS+rPBAakD4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1752249929; c=relaxed/simple; bh=HuTgKNiNzN7gfP3SZS19EdEf2Iptuunx9l7c6X91QSE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=utO1GDHxin8rdGhcRclHD0oPmRD9FLCc611RqsIGKEzemcQfj9OQSM40GoLkw35OmKWpPkgVHTN/lmcFyC0ct62IPn00ozhPKIZ/lSlAFon02siA1R3xJnmmtvXOzS20Ldua12nB3Y2CPvxZvvWvNCSgtx2b8XNFtxGaRQQxC6E= 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.47 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-f47.google.com with SMTP id 4fb4d7f45d1cf-60780d74c8cso3661862a12.2; Fri, 11 Jul 2025 09:05:27 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752249926; x=1752854726; 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=34tK3dA/g+Ec6FuI3ZK7zDdtJQ1B8nDAieH5jeaWgcI=; b=aTv5qOi7+Brx0E8QSgLXhr5cHudBr3hwMrh8oYRy4GGiYW7GO3N4nMuDgeXYWgDwbt MyOr/Dvb7kVG/ly1cJlFBeiLjiUxKPqmQcwUISrfLMGEBK+tr6MxFamNqcZDAQxBrNOm UR8UKDMTMVN1Tnlg2iGfU5zQVfUk5r9T/YkqSQ0PdmOhYUdICPSo6zgKlzRDQX+iTfqS UfufyA/a73g9kN0bDNg2NyWFjsAlPPOPbGqIj6yEsJholqBbtmx5e95SU/J6jk+WjJPr EYsZU/xD+My5xO8ZGjIdeLM0KG3vQrpHwnVz8VujIY+Na+ylSwmdkelUqixJjRk9+TlN G4oA== X-Forwarded-Encrypted: i=1; AJvYcCUTDdUyUG/R0sXd9JOXMargdAL2sBQAWNhuX+jYToqfpH45r2DfA6Fq/GcTB8B0jaW20P847PhU59zJOk2PIkYq@vger.kernel.org, AJvYcCUiEYyfmdf9q5mCPWlAodkzMu3BWvImtLWbdbKL8baRi7ROz7V3ZLvEr3t+12k9BiUum50pzedV@vger.kernel.org, AJvYcCWyPkC0bRbytrBDdfnipdAuRE+AyFjq7mntsPvdHnSe5p5KfMzDBHaayJlmXrOHbT6Bmn4=@vger.kernel.org X-Gm-Message-State: AOJu0Yzv4mZv0LMkbqA4CbuSRc0PiHFo7VfVzg9KVaRs2+OgRNumehhZ yb+Cz0j09BZsGSk7/K/zVZK9Cc7IReYv7yGHNRn4TqiLZiXLZtAkSlbh6ijcgA== X-Gm-Gg: ASbGnctrgxQmNR/w9qki8UE5ZjenW6toHrTxXsXh+r5hdzgK51Y5HihbmL7P8ooHuMR rOwiWPSfEm6hwHWrWIqBQR4tLHTIOV2mzSSI9BMMZlH3rkyz309Mel0naatrAQ+x2Kz5F/zgXOf EAfoNS0lrBoI+WVTTky3XErcArAEbxBN4LSu/OMLdXhCi8AJpi0Y2p7z9ZhE54FPtBrcq0E3GPG Rk7sUa2KL8B6hFT1RmgI7hjVmHySM6TI31UxatJtPDXGq7PId/iOe8da0a7q3gAlbs4kskB72g+ VATUqz76l/tyipq9nDFGjXPwE8tQGUNG9tFn3hElPYUPiQ/edTQIlAqK3fU/ijXx7JVDsGQYY2P 9UlS3BZ0rRqvbQQ== X-Google-Smtp-Source: AGHT+IFqGycNIsOAm/6c3PE+Kd/IgjqtCLwXepAYWiZyWmCLovjq6UFnyvZdkD84cMkMOklpwxU5uQ== X-Received: by 2002:a17:907:60d5:b0:ae3:a717:e90c with SMTP id a640c23a62f3a-ae6fc713cbcmr366869066b.23.1752249925032; Fri, 11 Jul 2025 09:05:25 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:74::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae6e7e94321sm321477366b.30.2025.07.11.09.05.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 11 Jul 2025 09:05:24 -0700 (PDT) From: Breno Leitao Date: Fri, 11 Jul 2025 09:05:11 -0700 Subject: [PATCH net-next v6 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: <20250711-netpoll_test-v6-3-130465f286a8@debian.org> References: <20250711-netpoll_test-v6-0-130465f286a8@debian.org> In-Reply-To: <20250711-netpoll_test-v6-0-130465f286a8@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=16141; i=leitao@debian.org; h=from:subject:message-id; bh=HuTgKNiNzN7gfP3SZS19EdEf2Iptuunx9l7c6X91QSE=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBocTY+iQs8eb0MaQfUmpukJdMqVS9JqrpTA1V0P qSnG9cwwTCJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaHE2PgAKCRA1o5Of/Hh3 bau3D/0SlGSq5C1p/d1T1cXinQTov000NXacCoFzxJt5spINlUkuAzlg8gFMgWD5vDjv9oK3DME YO/2vjNuGUGJw8G0ewK+DFjKoNdEyldssPuU/0h83NMfqqNZMu3xEF2kksHzOVHTY1K79wRP1ic 1dP0xW9hWM8NE9tQV9Z5ohyZap7eHGxjwVfGWMzWmKaqZZAK6H6/yN5igImJjXOQvH4QT6OjXmb vbNRdfdaOIwHC+B5aKyrOBnX+U1AEXxc6WfJ2uFZsfOWzXyYAvOeWSMRhJjJuopoq3hgQp03yaM ApTnJ3dfxy9Oz2TWqniEZ5rz5VyUrFE/1nH+9AeYXIZ4T/RZlaYwucsOd0yXtChRX8uV/kFP8mZ s5C2L64pNrld+RcRduhu418/px2yt4oA53kA/C0rFJcOlaVIxCa787hQ/g+6QgVt76YsCDwxLXt t5RN43F7shK9yZdwtdI5VQOYN44jrtI06VQuLCV+pFkpc/T51HwotdmLouceQczle9mdFp9guto fMmf+qo+C5y5RqYEFVAkFaI0EX963PTx+ycktOwT50wwHP5EPjgeCvz2vtP4xVbXNvq2fe13mYY R9CvmzHOd2IKYZt0V1wpMvo5gjbV2C7+Ot76hoqekUbJK/4Tt7d+52c1BmmCIru4vvxD43TvChh O8FQ1vbUF69Icdg== 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 | 411 +++++++++++++++++= ++++ 2 files changed, 412 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..5fe2d931f7569 --- /dev/null +++ b/tools/testing/selftests/drivers/net/netpoll_basic.py @@ -0,0 +1,411 @@ +#!/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, + CmdExitFailure, + defer, + 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 to_int(number: str) -> int: + """Convert str to int, or -1""" + try: + i =3D int(number) + except ValueError: + i =3D -1 + return i + + +def ethtool_get_ringsize(interface_name: str) -> tuple[int, int]: + """ + Read the ringsize using ethtool. This will be used to restore it after= the test + """ + try: + ethtool_result =3D ethtool(f"-g {interface_name}", json=3DTrue)[0] + rxq =3D ethtool_result["rx"] + txq =3D ethtool_result["tx"] + except (KeyError, IndexError) as exception: + raise KsftSkipEx( + f"Failed to read RX/TX ringsize: {exception}. Not going to mes= s with them." + ) from exception + + return rxq, txq + + +def ethtool_set_ringsize(interface_name: str, ring_size: tuple[int, int]) = -> bool: + """Try to the number of RX and TX ringsize.""" + rxs =3D ring_size[0] + txs =3D ring_size[1] + + logging.debug("Setting ring size to %d/%d", rxs, txs) + try: + ethtool(f"-G {interface_name} rx {rxs} tx {txs}") + except CmdExitFailure: + # This might fail on real device, retry with a higher value, + # worst case, keep it as it is. + return False + + return True + + +def ethtool_get_queues_cnt(interface_name: str) -> tuple[int, int, int]: + """Read the number of RX, TX and combined queues using ethtool""" + rxq =3D -1 + txq =3D -1 + combined =3D -1 + + try: + ethtool_result =3D ethtool(f"-l {interface_name}").stdout + for line in ethtool_result.splitlines(): + if line.startswith("RX:"): + rxq =3D to_int(line.split()[1]) + if line.startswith("TX:"): + txq =3D to_int(line.split()[1]) + if line.startswith("Combined:"): + combined =3D to_int(line.split()[1]) + except IndexError as exception: + raise KsftSkipEx( + f"Failed to read queues numbers: {exception}. Not going to mes= s with them." + ) from exception + + return rxq, txq, combined + + +def ethtool_set_queues_cnt(interface_name: str, queues: tuple[int, int, in= t]) -> None: + """Set the number of RX, TX and combined queues using ethtool""" + rxq, txq, combined =3D queues + + cmdline =3D f"-L {interface_name}" + + if rxq !=3D -1: + cmdline +=3D f" rx {rxq}" + if txq !=3D -1: + cmdline +=3D f" tx {txq}" + if combined !=3D -1: + cmdline +=3D f" combined {combined}" + + logging.debug("calling: ethtool %s", cmdline) + + try: + ethtool(cmdline) + except CmdExitFailure 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, + "remote_ip": cfg.remote_addr, + "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..= .") + + +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 + + 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 configure_network(ifname: str) -> None: + """Configure ring size and queue numbers""" + + # Set defined queues to 1 to force congestion + prev_queues =3D ethtool_get_queues_cnt(ifname) + logging.debug("RX/TX/combined queues: %s", prev_queues) + # Only set the queues to 1 if they exists in the device. I.e, they are= > 0 + ethtool_set_queues_cnt(ifname, tuple(1 if x > 0 else x for x in prev_q= ueues)) + defer(ethtool_set_queues_cnt, ifname, prev_queues) + + # Try to set the ring size to some low value. + # Do not fail if the hardware do not accepted desired values + prev_ring_size =3D ethtool_get_ringsize(ifname) + for size in [(1, 1), (128, 128), (256, 256)]: + if ethtool_set_ringsize(ifname, size): + # hardware accepted the desired ringsize + logging.debug("Set RX/TX ringsize to: %s from %s", size, prev_= ring_size) + break + defer(ethtool_set_ringsize, ifname, prev_ring_size) + + +def test_netpoll(cfg: NetDrvEpEnv) -> None: + """ + Test netpoll by sending traffic to the interface and then sending + netconsole messages to trigger a poll + """ + + ifname =3D cfg.ifname + configure_network(ifname) + target_name =3D netcons_generate_random_target_name() + traffic =3D None + + try: + traffic =3D GenerateTraffic(cfg) + do_netpoll_flush_monitored(cfg, ifname, target_name) + finally: + if traffic: + traffic.stop() + + # Revert RX/TX queues + 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__) as cfg: + ksft_run( + [test_netpoll], + args=3D(cfg,), + ) + ksft_exit() + + +if __name__ =3D=3D "__main__": + main() --=20 2.47.1