blob: 3ccf1e37da27ecf89fc8d758a0859125b6d73d30 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package wifi
import (
"context"
"fmt"
"sort"
"time"
"go.chromium.org/tast-tests/cros/common/perf"
"go.chromium.org/tast-tests/cros/common/shillconst"
"go.chromium.org/tast-tests/cros/common/tbdep"
tdreq "go.chromium.org/tast-tests/cros/common/testdevicerequirements"
"go.chromium.org/tast-tests/cros/common/wifi/security"
"go.chromium.org/tast-tests/cros/common/wifi/security/wpa"
"go.chromium.org/tast-tests/cros/remote/bundles/cros/wifi/wifiutil/perfmanager"
"go.chromium.org/tast-tests/cros/remote/network/iperf"
remoteiw "go.chromium.org/tast-tests/cros/remote/wifi/iw"
"go.chromium.org/tast-tests/cros/remote/wificell"
"go.chromium.org/tast-tests/cros/remote/wificell/dutcfg"
"go.chromium.org/tast-tests/cros/remote/wificell/hostapd"
"go.chromium.org/tast-tests/cros/remote/wificell/router/common/support"
"go.chromium.org/tast-tests/cros/remote/wificell/tethering"
"go.chromium.org/tast/core/ctxutil"
"go.chromium.org/tast/core/errors"
"go.chromium.org/tast/core/testing"
"go.chromium.org/tast/core/testing/hwdep"
)
type sapPerfTestThreshold struct {
throughput iperf.BitRate
jitter time.Duration
lost float64
}
type sapPerfTestcase struct {
tetheringOpts []tethering.Option
secConfFac security.ConfigFactory
useWpaCliAPI bool
powerSave bool
}
var (
jitterThreshold time.Duration = 20 * time.Millisecond
lostThreshold float64 = 5
perfTestTypes = []perfmanager.TestType{
perfmanager.TestTypeTCPTx,
perfmanager.TestTypeTCPRx,
perfmanager.TestTypeUDPTx,
perfmanager.TestTypeUDPRx,
perfmanager.TestTypeTCPBidirectional,
perfmanager.TestTypeUDPBidirectional,
perfmanager.TestTypeUDPTxSmall,
perfmanager.TestTypeUDPRxSmall,
}
)
func init() {
testing.AddTest(&testing.Test{
Func: SAPPerf,
Desc: "Verifies that AP can handle throughput without losses",
Contacts: []string{
"chromeos-wifi-champs@google.com", // WiFi oncall rotation
"jsiuda@google.com", // Test author
"jintaolin@google.com",
},
BugComponent: "b:893827", // ChromeOS > Platform > Connectivity > WiFi
Attr: []string{"group:wificell_cross_device", "wificell_cross_device_sap", "wificell_cross_device_unstable"},
TestBedDeps: []string{tbdep.Wificell, tbdep.PeripheralWifiStateWorking},
ServiceDeps: []string{wificell.ShillServiceName},
Fixture: wificell.FixtureID(wificell.TFFeaturesCompanionDUT | wificell.TFFeaturesSelfManagedAP),
Requirements: []string{tdreq.WiFiGenSupportWiFi, tdreq.WiFiProcPassFW, tdreq.WiFiProcPassAVL, tdreq.WiFiProcPassAVLBeforeUpdates},
Timeout: 20 * time.Minute,
HardwareDeps: hwdep.D(hwdep.WifiSAP()),
Params: []testing.Param{
{
Name: "open",
Val: []sapPerfTestcase{{
tetheringOpts: []tethering.Option{tethering.Band(tethering.Band2p4g), tethering.NoUplink(true)},
useWpaCliAPI: true,
powerSave: false,
}},
},
{
Name: "wpa2",
Val: []sapPerfTestcase{{
tetheringOpts: []tethering.Option{tethering.Band(tethering.Band5g), tethering.NoUplink(true),
tethering.SecMode(wpa.ModePureWPA2)},
secConfFac: wpa.NewConfigFactory(
"chromeos", wpa.Mode(wpa.ModePureWPA2), wpa.Ciphers2(wpa.CipherCCMP),
),
powerSave: false,
}},
},
{
Name: "wpa3",
Val: []sapPerfTestcase{{
tetheringOpts: []tethering.Option{tethering.Band(tethering.Band2p4g), tethering.NoUplink(true),
tethering.SecMode(wpa.ModePureWPA3)},
secConfFac: wpa.NewConfigFactory(
"chromeos", wpa.Mode(wpa.ModePureWPA3), wpa.Ciphers2(wpa.CipherCCMP),
),
powerSave: false,
}},
},
{
Name: "wpa3mixed",
Val: []sapPerfTestcase{{
tetheringOpts: []tethering.Option{tethering.Band(tethering.Band5g), tethering.NoUplink(true),
tethering.SecMode(wpa.ModeMixedWPA3)},
secConfFac: wpa.NewConfigFactory(
"chromeos", wpa.Mode(wpa.ModeMixedWPA3), wpa.Ciphers2(wpa.CipherCCMP),
),
powerSave: false,
}},
},
},
})
}
func SAPPerf(ctx context.Context, s *testing.State) {
/*
This test checks throughput performance of the chromebook by using
the following steps:
1- Disable the station interface on the main DUT.
2- Configures the main DUT as a soft AP.
3- Configures the Companion DUT as a STA.
4- Connects the the STA to the soft AP.
5- Disable bgscan and power save on STA.
6- Measure the throughput performance on different traffic types/directions.
7- Deconfigure the STA.
8- Deconfigure the soft AP.
9- Re-enable the station interface on the main DUT.
*/
tf := s.FixtValue().(*wificell.TestFixture)
if tf.NumberOfDUTs() < 2 {
s.Fatal("Test requires at least 2 DUTs to be declared. Only have ", tf.NumberOfDUTs())
}
pv := perf.NewValues()
defer func() {
if err := pv.Save(s.OutDir()); err != nil {
s.Error("Failed to save perf data: ", err)
}
}()
// Add a cascading timeout to let test store perf report, but don't impact test teardown if it takes too long.
ctx, cancel := ctxutil.Shorten(ctx, 10*time.Second)
defer cancel()
logPerfValues := func(label, unit string, value float64, dir perf.Direction) {
pv.Set(perf.Metric{
Name: label,
Unit: unit,
Direction: dir,
}, value)
s.Logf("%s: %v", label, value)
}
// Disable background scan which causes severe udp_rx throughput drops that can cause
// a disconnection during perf tests on the companion DUT. Refer to b/315880821.
ctx, restoreBgAndFg, err := tf.DUTWifiClient(wificell.PeerDUT1).TurnOffBgAndFgscan(ctx)
if err != nil {
s.Fatal("Failed to turn off the background and/or foreground scan: ", err)
}
defer func() {
if err := restoreBgAndFg(); err != nil {
s.Error("Failed to restore the background and/or foreground scan config: ", err)
}
}()
// Verify that performance test result passes requirements and save the result.
verifyResults := func(ctx context.Context, testType perfmanager.TestType, pr *iperf.Result) {
// TODO(b/316207256) Retrieve PHY spec from connection status.
expectedThroughput, err := perfmanager.ExpectedThroughputWiFi(support.SoftAPT, testType, hostapd.Mode80211axMixed, hostapd.ChWidth20)
if err != nil {
s.Fatal("Failed to get expected throughput")
}
if float64(pr.Throughput/iperf.Mbps) < expectedThroughput.Must {
s.Fatalf("Unacceptable throughput in performance test. Wanted > %v Mbps, got %v Mbps", expectedThroughput.Must, pr.Throughput/iperf.Mbps)
}
// Keep jitter and packet lost informational for UDP small packet tests.
// TODO(b/316207256): Set jitter and lost criteria for these tests based on test results.
if testType == perfmanager.TestTypeUDPTx || testType == perfmanager.TestTypeUDPRx {
if pr.PercentLoss > lostThreshold {
s.Fatalf("Unacceptable loss in performance test. Wanted <= %f%%, got %f%%", lostThreshold, pr.PercentLoss)
}
}
if testType == perfmanager.TestTypeUDPTx || testType == perfmanager.TestTypeUDPRx {
if len(pr.Jitter) == 0 {
s.Fatal("No jitter results")
}
sort.Slice(pr.Jitter, func(i, j int) bool {
return pr.Jitter[i] < pr.Jitter[j]
})
// Pick 90th percentile value. We want to exclude top 10% of recorded jitters,
// so we can be pretty convinced that 90% of our traffic fits under the maximum acceptable jitter threshold.
jitter := pr.Jitter[(len(pr.Jitter)-1)*9/10]
s.Logf("90th percentile jitter: %vus", jitter.Microseconds())
if jitter >= jitterThreshold {
s.Fatalf("Unacceptable jitter in performance test. Wanted < %dus, got %dus", jitterThreshold, jitter.Microseconds())
}
}
}
testOnce := func(ctx context.Context, s *testing.State, tc sapPerfTestcase) {
tf.UseWpaCliAPI(tc.useWpaCliAPI)
apPrimIface, err := tf.DUTClientInterface(ctx, wificell.DefaultDUT)
if err != nil {
s.Fatal("DUT: failed to get the client WiFi interface, err: ", err)
}
tetheringConf, _, err := tf.StartTethering(ctx, wificell.DefaultDUT, append([]tethering.Option{tethering.PriIface(apPrimIface)}, tc.tetheringOpts...), tc.secConfFac)
if err != nil {
s.Fatal("Failed to start tethering session on DUT, err: ", err)
}
defer func(ctx context.Context) {
if _, err := tf.StopTethering(ctx, wificell.DefaultDUT, tetheringConf); err != nil {
s.Error("Failed to stop tethering session on DUT, err: ", err)
}
}(ctx)
ctx, cancel := tf.ReserveForStopTethering(ctx)
defer cancel()
// Create apConfigTag which is used in the keyval
apConfigDesc := tetheringConf.PerfDesc()
s.Logf("Tethering session started with config: %s", apConfigDesc)
_, err = tf.ConnectWifiFromDUT(ctx, wificell.PeerDUT1, tetheringConf.SSID, dutcfg.ConnSecurity(tetheringConf.SecConf))
if err != nil {
s.Fatal("Failed to connect to Soft AP, err: ", err)
}
defer func(ctx context.Context) {
if err := tf.DisconnectDUTFromWifi(ctx, wificell.PeerDUT1); err != nil {
s.Error("Failed to disconnect from Soft AP, err: ", err)
}
}(ctx)
ctx, cancel = tf.ReserveForDisconnect(ctx)
defer cancel()
s.Log("Connected")
staIface, err := tf.DUTClientInterface(ctx, wificell.PeerDUT1)
if err != nil {
s.Fatal("DUT: failed to get the STA WiFi interface, err: ", err)
}
// Disable power save on the companion DUT
iwr := remoteiw.NewRemoteRunner(tf.DUTConn(wificell.PeerDUT1))
psMode, err := iwr.PowersaveMode(ctx, staIface)
if err != nil {
s.Error("Failed to get the powersave mode of the WiFi interface, err: ", err)
}
defer func(ctx context.Context) error {
if err := iwr.SetPowersaveMode(ctx, staIface, psMode); err != nil {
s.Errorf("Failed to set the powersave mode %t: %v", psMode, err)
}
return nil
}(ctx)
ctx, cancel = ctxutil.Shorten(ctx, 2*time.Second)
defer cancel()
if err := iwr.SetPowersaveMode(ctx, staIface, tc.powerSave); err != nil {
s.Fatalf("Failed to set the powersave mode %t: %v", tc.powerSave, err)
}
apIP, err := tf.DUTIfaceIPv4Addrs(ctx, wificell.DefaultDUT, shillconst.ApInterfaceName)
if err != nil || len(apIP) == 0 {
s.Fatal("Failed to get SoftAP IP address, err: ", err)
}
staIP, err := tf.DUTIfaceIPv4Addrs(ctx, wificell.PeerDUT1, staIface)
if err != nil || len(staIP) == 0 {
s.Fatal("Failed to get STA IP address, err: ", err)
}
// 2-way performance setup
manager, err := perfmanager.NewTestManager(ctx, tf.DUTConn(wificell.PeerDUT1), tf.DUTConn(wificell.DefaultDUT), nil, support.SoftAPT, staIP[0].String(), apIP[0].String(), staIface)
if err != nil {
s.Fatal("Failed to get performance test manager, err: ", err)
}
defer func(ctx context.Context) error {
if err := manager.Close(ctx); err != nil {
s.Error("Failed to Close Perf Test Manager, err: ", err)
}
return nil
}(ctx)
ctx, cancel = ctxutil.Shorten(ctx, 2*time.Second)
doRun := func(ctx context.Context) error {
for _, testType := range perfTestTypes {
s.Logf("Performing [[ %s ]]", testType)
config, err := manager.Config(support.SoftAPT, testType, 0)
if err != nil {
return errors.Wrapf(err, "failed to get the iperf/netperf configuration for test type %s", testType)
}
session, err := manager.Session(ctx, testType)
if err != nil {
return errors.Wrapf(err, "failed to get the iperf/netperf session for test type %s", testType)
}
pr, _, err := session.Run(ctx, config)
if err != nil {
return errors.Wrap(err, "failed to run session")
}
logPerfValues(fmt.Sprintf("%s.%s", apConfigDesc, testType), "Mbps", float64(pr.Throughput/iperf.Mbps), perf.BiggerIsBetter)
verifyResults(ctx, testType, pr)
}
return nil
}
if err := tf.AssertNoDisconnect(ctx, wificell.PeerDUT1, doRun); err != nil {
s.Error("Failed run performance test, err: ", err)
}
s.Log("Deconfiguring")
}
testcases := s.Param().([]sapPerfTestcase)
for i, tc := range testcases {
subtest := func(ctx context.Context, s *testing.State) {
testOnce(ctx, s, tc)
}
s.Run(ctx, fmt.Sprintf("Testcase #%d/%d", i+1, len(testcases)), subtest)
}
s.Log("Tearing down")
}