From nobody Wed Oct 8 12:33:56 2025 Received: from mail-ej1-f42.google.com (mail-ej1-f42.google.com [209.85.218.42]) (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 8EC1E2D3EFC; Fri, 27 Jun 2025 17:03:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043814; cv=none; b=Dc8w0VpD58u8YxVw1nKffwDg5edIJNMIV1I2Z2kOQ+F5kS/ubtL3NXpeIPH3nygggRhBgSpdNxvZpdoNMdMnokukZZQoI2EinNt7V+OeJ3rAp1II1WSQBNposz3H9QCvZh0b/FKMPimdyB5q+s2l4T5h+lwkxWDWQ388pkJRgDU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043814; c=relaxed/simple; bh=/aNBC0XBfGvwuRnaC4+TsJVqjIgdi1gtra/gd2Kk8+s=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jkjEDdkAARtDZoZSV5280KZX1sEO9FvxiFRL676yD69vUw9P12ZOFlF0T37HyAhXC31xIBP+0EOMJwB4j4zEoJktHNtgO9AUsofzJKy1CUdz/mw6o//dzzP7CDRhIJz5QxzwPYCFr/QBkQxKuDLvpZ6RDw1+vF6RqlsgNYq8G4w= 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.42 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-f42.google.com with SMTP id a640c23a62f3a-ade76b8356cso4073466b.2; Fri, 27 Jun 2025 10:03:32 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751043811; x=1751648611; 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=I3HuCBWhl7sYzAryoc8j2NnrheIMmb5LJG1G+EC5ub8=; b=q6nu0oQE8ma2mXFUk5+GSVYP+meFbDT46TKNYPdD/NhZ/4FEMCqPl6mJ2fy8kcqyMk 8JCH/24wz3wl8v21wrkcipr7gVvfF0xjwePBA1LjMYEzjnqxlTYwSNCkZKCtGwUW/Tgx 8OUreqiaf0Tgw38Argbrn/3yFxdydAeMNH5YmJSV3KpWbzjLYnw5M0pBzZMkuEuIxfRU DRrwFKeIhlTWcXiys/0c5+5s8zPW/JReRKAaKPK1XGBO4sdAzgOdC3qWl7ubkzb7O8rO 3+2joWrP7fz5fXdPaWF0M5CumgVOZSfNwnXDngZ0CEIExQmv6p1G9O1zdymUAQwjTP2f qI4A== X-Forwarded-Encrypted: i=1; AJvYcCWZtqejyB9RfTfnFXUFyVzTu979Nh3/S5PagVyME4nYXHsD1/8wwKlUQVaeW53x5eJScmU=@vger.kernel.org, AJvYcCX3E6HjeaGOIfBEW8tcbhDS4MQQfXW2BCUuoF6KrDjNjCsi5fC/4qMW5gavGSFAHGUjeq1Oul8p@vger.kernel.org, AJvYcCXeHCanNGssaYAWxV2mLj7THye6BTixV+aq18BykCfRqlMB1vTn1+oC5LTR7T6xsZg2V3y+t9f8FQejhSsjXC4H@vger.kernel.org X-Gm-Message-State: AOJu0YyCTgCVj3ULc1paQwUO/u7BG611rjEQSug2T2Kipn4iDgL7qwJZ JxAdK7nZ6QRIq+bM8RRNRzBEDDAeGUKjHV99o6sc2CXE8HjTsyijz6oxOhskjg== X-Gm-Gg: ASbGncsdZKgZMisWUTATqqmdWfdk8yteOwo4KLKpvhFa003US/Q3uza1elJKcMoaljv C2HzDNBL8DMXxHRsoi6Zdd/+8gUSIoGx6/we/k4ut/B/0M+lV5tJA6Ex0dSvplnCVlGBW3gEgVs 8x0XsnsvaG3ygrcQ42fcFB+TWp0cQ0yG6V/R0arttWRXE4N75fXx80CgXRAqH+VvwIDXNtuThvd EV/10BnIQu83bKEvhBNjS0j8sQzvqGu5yse2vLWemKTt7TZK/IDPu9crI3Zj1y7dSs6+Qhm7mPt 3kwyGMmJac4awlDD/fcnnXqrkaUdSpgX9FdD34fRWvXYboBRVnNB X-Google-Smtp-Source: AGHT+IENsGlGcMgnMWYwPxvWkQX4RMe6V4a7dkEgE/WtU7Ma5WEOnkwSn70xiLohRqrgGr0MAS3UhQ== X-Received: by 2002:a17:907:ea6:b0:ae0:a463:f4da with SMTP id a640c23a62f3a-ae35015c01amr403406266b.42.1751043810567; Fri, 27 Jun 2025 10:03:30 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:9::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae36327ce4asm82044066b.163.2025.06.27.10.03.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Jun 2025 10:03:30 -0700 (PDT) From: Breno Leitao Date: Fri, 27 Jun 2025 10:03:09 -0700 Subject: [PATCH net-next v3 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: <20250627-netpoll_test-v3-1-575bd200c8a9@debian.org> References: <20250627-netpoll_test-v3-0-575bd200c8a9@debian.org> In-Reply-To: <20250627-netpoll_test-v3-0-575bd200c8a9@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, ast@kernel.org, Breno Leitao X-Mailer: b4 0.15-dev-dd21f X-Developer-Signature: v=1; a=openpgp-sha256; l=3109; i=leitao@debian.org; h=from:subject:message-id; bh=2B3HspaUtkLq/87GHVo3MgVog4C2o2TFROWRoUVy5GY=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBoXs7f/8E6BG65o3jhrLh4koQgcCH/Whdkftybo aoE86vMT7KJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaF7O3wAKCRA1o5Of/Hh3 bbC2D/oCPCBueLOi1qmmxeWym8rqsVryET+A2t5F3oX11d9j0Uusbp/FVrrHL2PDfWPDuM5VMk6 +quzanGXbRkKTldTqAZTTsd92rhhwnSsZ3zxFn67MAZ5XZKaN5D189V40hYCN4AAogqAIabgQB3 /rjykwZdMYZO43yMRX8FVOzTR8PRQfgdT7euHwUaw189Bvoj6c0HdCg2+GLD5SjG5QVe36NvRnu CON7yBLfjIXnpk5tGB6xEF0Mpm9HMG+VJRh1GnSd8XJhCAJomUUJ0tDfaDXbOpH2AiOlGsoIOOF LPtiszx4Qw75KfoaVYA/BJ1Y7SW+Q4IGIsvMUNLmXSSC4wNatV3iwNkPxvklJgQXklZmut2Tz2K qC/40tBfIyZX35+9cbf5ywHGWCTCO5DVt4Lc2oOkizNL0VFhJRGgu/oAmwGOqMt6qBopXnKgG7i IWG7SpQIih/2Yf1WcC0qDUalyDR0eU4RUxKurxEMqUD0ZSn0NxE2CWknuhA58+6157+QDG+vA9N SPImwTTxtFFhxnVYbwbUJm5YhabA6g0ahTTLOubrSWGSRcJCbUkfZ7LnGvnrnHXyMAFyVs0RGSe ad7nKggbQVcDzdiL4W8+ewIl4P+M/+YLMxig8VGHtDvRdGJIqo/BAZ11yskcg9/RusylpMrGnCf IEFCbY24flbcflw== 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 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 9ed1d8f70524a..98829a0f7a02c 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 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 Wed Oct 8 12:33:56 2025 Received: from mail-ed1-f48.google.com (mail-ed1-f48.google.com [209.85.208.48]) (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 1E8382EB5A3; Fri, 27 Jun 2025 17:03:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043815; cv=none; b=CuBw0jG5JwtBbYZs65qwS+6kcpJbN7njQAqYkPgjnTEBDN3SLP2fdq7WFxJwznTKupzFadV4a2wRBKxTjE9mtXaoT/76DKeJ0X+V5MpPL8WTtZlKn4nEHSEYtzgtlpRO0aw0XQELOrMTwFu9gdXbZvg1Xpp8v+fmO4Hj+za+l60= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043815; c=relaxed/simple; bh=5e7Gu2hSYoF78NHOOQ4u3fblm9IpAmXxrCi48b0pb34=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=RGUnb3eIfcoR5inYV0dA0HynXGLY+jVUfxjsK5NaVUjp7bYqDcuxRTZ7MgqSjybrzMzB92wTKfvG8AxBQRUnIOG++D8W+7gUijB8su6t/409D9Fq9NM35AFQSJLbFidlCTcOZNkyxNahD3JZma6pYILhBBHxJBKniUuISXy4VyE= 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.48 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-f48.google.com with SMTP id 4fb4d7f45d1cf-60bfcada295so88339a12.1; Fri, 27 Jun 2025 10:03:33 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751043812; x=1751648612; 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=C8VRk8MzP7fAvjb3f8uZbzOOZMgQQVH7iuwhbACM3PcmM1ENmiPxopXJGA62uiC4gj pXfrD6U9tyRofxZ23zfyurLICFDB55h1gD/v9Y4rBKEWLU0SAySEy3WX6QoZyjPjm8s2 kkbx3QbfTf+Sd/7Q7/Q5XzsdPt5/Cp32mppQ+xjandwGlABJ2efh+KbjlMq04jJj7r9x dAfzN7N6Ja2vYRMgT8y3XArS0QurGWjjVo3HIhHXePyidEGVjf+b+iMIyjUsCWpo7NE+ qAefcoPNuVZJCCBH0jsATitn9ijZMPSwwV+bXPvXZFCbnI46IsDk/bKB5Q6KTeAi9ts4 nLmg== X-Forwarded-Encrypted: i=1; AJvYcCUPKKxtc7xKewAWYSNyvlhJSLlX2kHr/1zKlm0xnXu8KRlEIFF8x/JSncPwFjzM1ybPGsQk1OD3@vger.kernel.org, AJvYcCViAculXkI9T/k5NjOQccE4JrH+RVNqolpDoA2U7hckefyvC2I1+46Hu9Ugu6RrxyShNK6TOlrr/SISLTvaZsYi@vger.kernel.org, AJvYcCWnZTs4Te0FPTw8lfjoBG4PRHTF4qIkyYX/T/++O6VTaoi2sk5POIa4BnErY0AmQXJelUM=@vger.kernel.org X-Gm-Message-State: AOJu0Ywxl4Oe4CqFRttRsZd7TK2nMQcnJewD41h4hWP7/blxHVkx6gXd /HJmGcbp2WQXcXWFGjFuEPwt/wXHr+Yj5mLLCjI2WZUu5dFSxIcs92h0 X-Gm-Gg: ASbGncvk7kb02s6fK6ny4Bt+dSIalxYs4NRu3riCNnsfe+F/tYm0NCErnguauAIzdFT X6PXVeOOuVkDBVGNCucen0T8zmneiSmuSTfYnBK5CCzJquK+sFzuwpVssh0wqQQNmS2i525uGzu a399YoZS6J6bVCZkkcRm18Dyvo3/Z7rcnVmAXIb35q5x3NMnPWuE6ifFcun4/Di4RIBmG+n/46O w408oYngAOVFnRhhqHafnfo5vMuwQaW8/HaP+I1YiFv7M7SwwNtLxpA7TbbM3QRu6WJBLFSvBBV uBRau94Wemht7wiFg9APPEUptHl53mn97VYyrhdiBRC/WIrVBxdT X-Google-Smtp-Source: AGHT+IGcpd2RBzfznvsMkkE7dTrxjGkTpCXn9DI4b+VvJ+kG+66wytl7b3Z+kpXzv/bAyZ8RG0Y81w== X-Received: by 2002:a17:907:fd16:b0:ae3:51ac:12b5 with SMTP id a640c23a62f3a-ae351ac18c6mr337729966b.46.1751043811989; Fri, 27 Jun 2025 10:03:31 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:3::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae353c6bc08sm153290666b.131.2025.06.27.10.03.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Jun 2025 10:03:31 -0700 (PDT) From: Breno Leitao Date: Fri, 27 Jun 2025 10:03:10 -0700 Subject: [PATCH net-next v3 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: <20250627-netpoll_test-v3-2-575bd200c8a9@debian.org> References: <20250627-netpoll_test-v3-0-575bd200c8a9@debian.org> In-Reply-To: <20250627-netpoll_test-v3-0-575bd200c8a9@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, ast@kernel.org, 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/8eHdtAcsmYgBoXs7fB7F/EbtkQdJXy/APfEtmfP4ZgyvryZszh OmJjBPnZ4mJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaF7O3wAKCRA1o5Of/Hh3 bSA6D/sEXSVWe/YK7PWbCg95iQPABEp2H/M+G0OAizbLnRbhL6xd69mj74nWCGK8Iw/7D/s1K9g GiFQzBJx5CIrQ68veRrYdTIj6CAmOChrfLMHnjS6bE1Ykh5rDD2zf83B3qoYPBuwqOq9iscqspE dj/zboUo8Zu4ymundsLqNwYpV9W5fwRmnuYQ7pBkwW3MPQajgJzVD4UzmL72CaSBSX1VV/ETz+b jKg8mWKGlEdJQk4ZcrY8MpFP6Fprq/QeQ6teeDHMZ0QUDETmXnS3Rj0dc52InG9e7FrO44qtDtu oDG0OH0hL/aYT6M1Mlxi3mYOLzSAamrTh+BBKIUTi9VPQnbuT/Lyi/LXWir8icatTzcPJlASUKg 34nmMeMHOkjuHFAbqaDIb3eXLGIhiXfF80eaktqTFW9w8Ynm3/WzoGfApzOxg9Tj6o56t/3T/Mi UKg8dyprhLOnfBOOElyjiMELHHQ5phICIohV/2Jgo7j515FCwmC+mCdx58QmC7CUvpvuIIJT2bq ixspnSpu6prvmwG9yR1sOZLw4Itg638Qt85y7B5HpOY9RR5qSPizcbitBkMSLuRk0fUkQ8g0Cvh X1aiz7aqjZbSFH4coWaag7VU6c++hgdlVNq/fNdKfqFhl8THnngRohPsPqq5Uoc9RKMIxDFTQ8s 6ksD7pvEE8uCTmA== 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 Wed Oct 8 12:33:56 2025 Received: from mail-ej1-f42.google.com (mail-ej1-f42.google.com [209.85.218.42]) (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 E26592EBDD7; Fri, 27 Jun 2025 17:03:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043818; cv=none; b=FNDir6LWr1OjLwmaQ5gp/v2NhIzlyJU1wp1jSsbottfUoC69cGRCVdrZqCO9TgOX1O5wGQO0cqAQ9HYA7pDxPDQkaWjkezldJyNy8ntd6E7jqdAljhx/OX5hDzEojhiN26nSJRZOWfeDgdh1tROol3YjMfbftR2whTsnK5nqqNY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751043818; c=relaxed/simple; bh=T2m9FFDSS2NveVwVaZZ11Z51W0+7UfcIzqPpQSpOnCQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=CK/Ip9o2NfIEw5ADeytB9AVFCQpi8xu2Cu0KAwSAS7/nwYMcrKvEJif1UV5kb0OeFGIy9ojzXKMNbEWJj0DfQ6HicOCFxWy7oaJRAnzdiSqgFY6DEakHcrVY4uA7JNHanDXn46hBYAsjdynlwCm/5Vi2aGIANIRo1M7KH1DANGo= 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.42 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-f42.google.com with SMTP id a640c23a62f3a-ad56cbc7b07so6430066b.0; Fri, 27 Jun 2025 10:03:35 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1751043814; x=1751648614; 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=bRE5JMrl+AHADSewX7znwCNdFZigkW0b6fCb+g9m094=; b=d058LgK78qvCgd+br7IWmIJundYVRJfE8D3mt7MdR0Yi4J8oKja3qnmGU0pg8El5Re Efupp3109DwKglyc+M+QYi3vN0MElygQB1fd6GEJYUTR7XrCvjY6NyFiW6Q/LYXu1cx1 FL+K0bZqf0snBqG0kv+bxQKcAihL3xUbCA7A8d/+tRo3f4DHAFuSw2QEWMcZhNYA/Za4 N/8ODH16zo3xwmcwdyNefvFAjd7hxlzSYAXPSecwEeH48c/0KCON3aIwy6DtxuXuzxrl xSR4FYkdKRwg0810Vzk4KBB/aDN69HF3+NPqdPlEHRZSRbrP/B4JdWSnVVAJmEkERCFJ MMNQ== X-Forwarded-Encrypted: i=1; AJvYcCVARjVEscpyqXulCI+1zqMsGVWpQJPW76RuUKIbHp29K1ToHI0i+3vgP1pj9SKu111K9RM=@vger.kernel.org, AJvYcCVtBxDRlkxmQmzPYmZtxjQWXsLJ+FSIBOvB7ZHHIYicwuGhqWTAkTu5ce4MHp8YFK5tivkvzQDvwdC46FH34JEL@vger.kernel.org, AJvYcCXKT+YZ3B3cqYDXpuq2mY/YTJjse1ueoBOu6gVBii2cyxrBBBvp2xP3MIDeq2FDEyWMUguNg4Et@vger.kernel.org X-Gm-Message-State: AOJu0YyYfnjVW3CoqmYrtAHmgVQxRbKjhDSw0CS7BphIxoTGZsOn9co1 79PgBwLBYwL7+FKAoGpptAFQpCeF2GcLrVY0YeJAVo+xNY5ndqqtxVLd X-Gm-Gg: ASbGnctK+UO6qKHhLQV8+lz9e73JhbLLGdBfFmFWt9+jzXV00XWQvMCJiQXBB2OE8s4 0ufjfiLXKKwzOxrg4p4GvJk3KLEsjn7BinT9ga/+FjMhMREEYm6BNmp6/ysnEZo1XOtdDPxdBgH X+kj+EvqdJt9f/k9WSeSrj9Gigwl4kJbSYPblrEM172z4iTBmX7KwWyk3DiSYualBN2d1w+oatb tTIxYYnIwQq08SOLkD4nywikUJdY+onna6cEDqR54jWMpmBSU2hPBV/YTG3utwdiWZmyTK6rMxe ZsKkewiC/Q6nTcZBJKLk6F8TT/h4KQ9To/7N1NSor+Ha5lekeLP40w== X-Google-Smtp-Source: AGHT+IH25kAdjvhDrubYsrBCmZET07g41DhxNurnndN7GT34toNvN4zWnG80gP0nzs+Izn1aYkL/nw== X-Received: by 2002:a17:907:7292:b0:ae0:bbd2:68ce with SMTP id a640c23a62f3a-ae3500b8e7bmr332248466b.32.1751043813651; Fri, 27 Jun 2025 10:03:33 -0700 (PDT) Received: from localhost ([2a03:2880:30ff:74::]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ae353ca1619sm153616866b.149.2025.06.27.10.03.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Jun 2025 10:03:33 -0700 (PDT) From: Breno Leitao Date: Fri, 27 Jun 2025 10:03:11 -0700 Subject: [PATCH net-next v3 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: <20250627-netpoll_test-v3-3-575bd200c8a9@debian.org> References: <20250627-netpoll_test-v3-0-575bd200c8a9@debian.org> In-Reply-To: <20250627-netpoll_test-v3-0-575bd200c8a9@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, ast@kernel.org, Breno Leitao X-Mailer: b4 0.15-dev-dd21f X-Developer-Signature: v=1; a=openpgp-sha256; l=14118; i=leitao@debian.org; h=from:subject:message-id; bh=T2m9FFDSS2NveVwVaZZ11Z51W0+7UfcIzqPpQSpOnCQ=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBoXs7fZj9BtVY8JscDok9NwWzMkTW+JJ3baVE/L zGfAbtJUqyJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCaF7O3wAKCRA1o5Of/Hh3 bSl2D/954/gIVuJi4NfZPWBDg6KikgEKH5yTQ8eSLhxCNBuJZI6kpphVNt8QF4SgPxW7Wm/oDWP tIxtNYtGrUBgk45+fLmPLs65gMgSjtuI8dZglmT2KCu3Acx8+EQlPXar50921ONvGGoArplwjOD OTJSi42dn5vnjStwNx+YMUsTpON/yBQ1Vy44XihjWcyGJAx3bhKvVWF8ql1tDPygZYnFt8p+OJZ Zlhps6/bWjVluabyewv+nemHJ5zs5o8yjImdF0FOIFkw+4hFfTMhWgLQg+PpVd2qXukkfFJvRRn DirpLQ/j7AtQf3QqgEEDW4oLADjOAzh2KcZp/8qkGs96jVsxXxRe7og/dDIrtocpvP9x6eal02X jrdguVp4tp+2oIlaHCXsy2n/dBrbEJzcwxCGEFSwx/mu8o07EoDKV23ESAqeLS0uVb60rFy4QMu qlxYN6es+2umRKZ7F3jxOwK2Tr4Qcug6HTkqo9CX7Hp3i2EC4+xNPPkutITmosLRndvT4CCvd0a cCQpHAKxgOBavNw8cmjcIdbIRzKxznMEgAP+2kFMVsNJx4PuBTv5lR5RyIRl1DKKVaMq9pqLxuV 5X1cZpncjC+ZM9jiEeNJAs2jg1pCj1pnjPOXtf5451pH86myWglcN4+hRD7yarIzM4ea4k9Q+Zr /HX6Bs1y2Z6OfUw== 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 --- tools/testing/selftests/drivers/net/Makefile | 1 + .../testing/selftests/drivers/net/netpoll_basic.py | 345 +++++++++++++++++= ++++ 2 files changed, 346 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..f523d5a1c707e --- /dev/null +++ b/tools/testing/selftests/drivers/net/netpoll_basic.py @@ -0,0 +1,345 @@ +#!/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 lib.py import ( + bpftrace, + ethtool, + GenerateTraffic, + ksft_exit, + ksft_pr, + ksft_run, + KsftFailEx, + KsftSkipEx, + NetDrvEpEnv, +) + +# 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 10 messages +ITERATIONS: int =3D 20 +# MAPS contains the information coming from bpftrace +# it will have only one key: @hits, which tells the number of times +# netpoll_poll_dev() was called +MAPS: dict[str, int] =3D {} +# Thread to run bpftrace in parallel +BPF_THREAD: threading.Thread =3D None +# Time bpftrace will be running in parallel. +BPFTRACE_TIMEOUT: int =3D 15 + + +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)) + file.close() + + # 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, + ) + + 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 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}") + + 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() + + do_netpoll_flush(cfg, ifname, target_name) + + if bpftrace_any_hit(join=3DTrue): + ksft_pr("netpoll_poll_dev() was called. Success") + return + + raise KsftSkipEx("netpoll_poll_dev() was not called. Skipping 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(): + # 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(10): + 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 + + if bpftrace_any_hit(join=3DFalse): + # Check if netpoll_poll_dev() was called, but do not wait = for it + # to finish. + ksft_pr("netpoll_poll_dev() was called. Success") + return + + # Every 5 iterations, toggle netconsole + 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) + bpftrace_stop() + + +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