diff --git a/.github/workflows/stress.yml b/.github/workflows/stress.yml index e8e6f7d16..ce4fd3901 100644 --- a/.github/workflows/stress.yml +++ b/.github/workflows/stress.yml @@ -47,7 +47,15 @@ jobs: matrix: python-version: [3.7] go-version: [1.18] - suite: ["network-forming", "commissioning", "connectivity", "network-latency", "multicast-performance", "otns-performance", "network-limits"] + suite: + - "network-forming" + - "commissioning" + - "connectivity" + - "network-latency" + - "multicast-performance" + - "otns-performance" + - "network-limits" + - "long-duration" runs-on: ubuntu-20.04 env: HOMEBREW_NO_AUTO_UPDATE: 1 diff --git a/pylibs/stress_tests/long_lived.py b/pylibs/stress_tests/long_lived.py new file mode 100644 index 000000000..8f4be2441 --- /dev/null +++ b/pylibs/stress_tests/long_lived.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, The OTNS Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# OTNS Long Duration Stress test: +# Simulate 4x8 nodes at max speed without injected traffic or failure for 1h, measure the execution (real) time. +# Topology: +# Router 4x8 +# Fault Injections: +# None +# Pass Criteria: +# Execution time <= 30s +# +import random +import time +import os + +from BaseStressTest import BaseStressTest + +RADIO_RANGE = 200 +XMAX = 300 +YMAX = 300 + +PACKET_LOSS_RATIO = 0.0 +TOTAL_SIMULATION_TIME = 10 * 86400 * int(os.getenv("STRESS_LEVEL", "1")) +MOVE_INTERVAL = 3600 +PING_INTERVAL = 3 +PING_DATA_SIZE = 64 + + +class LongDurationStressTest(BaseStressTest): + SUITE = 'long-duration' + + def __init__(self): + super(LongDurationStressTest, self).__init__("Long-Duration stress test", + ['Simulation Time', 'Execution Time', 'Speed Up']) + self._cur_time = 0 + self._last_ping_succ_time = {} + + def rand_pos(self): + return random.randint(0, XMAX), random.randint(0, YMAX) + + def run(self): + ns = self.ns + ns.packet_loss_ratio = PACKET_LOSS_RATIO + + router1 = ns.add("router", *self.rand_pos(), radio_range=RADIO_RANGE) + router1_addr = self.expect_node_mleid(router1, 10) + + router2 = ns.add("router", *self.rand_pos(), radio_range=RADIO_RANGE) + med = ns.add("med", *self.rand_pos(), radio_range=RADIO_RANGE) + sed = ns.add("sed", *self.rand_pos(), radio_range=RADIO_RANGE) + + ns.set_poll_period(sed, 60) + + for nodeid in (med, sed): + self._last_ping_succ_time[nodeid] = 0 + ns.ping(nodeid, router1_addr, datasize=PING_DATA_SIZE, count=TOTAL_SIMULATION_TIME // PING_INTERVAL, + interval=PING_INTERVAL) + + t0 = time.time() + + for _ in range(TOTAL_SIMULATION_TIME // MOVE_INTERVAL): + self.ns.go(MOVE_INTERVAL) + self._cur_time += MOVE_INTERVAL + + self._collect_pings() + + for nodeid in (router1, router2, med, sed): + self.ns.move(nodeid, *self.rand_pos()) + + duration = time.time() - t0 + + self.result.append_row('%ds' % TOTAL_SIMULATION_TIME, '%ds' % duration, + '%d' % (TOTAL_SIMULATION_TIME / duration)) + self.result.fail_if(TOTAL_SIMULATION_TIME / duration < 3000, "Speed Up < 3000") + self.result.fail_if(self._last_ping_succ_time[med] < self._cur_time - 3600, "MED not connected for a long time") + self.result.fail_if(self._last_ping_succ_time[sed] < self._cur_time - 3600, "SED not connected for a long time") + + def _collect_pings(self): + for srcid, dstaddr, _, delay in self.ns.pings(): + if delay >= 10000: + # ignore failed pings + self._ping_fail_count += 1 + continue + + self._ping_succ_count += 1 + self._last_ping_succ_time[srcid] = self._cur_time + + +if __name__ == '__main__': + LongDurationStressTest().run() diff --git a/script/test b/script/test index cf56ecf63..bc7d07a6f 100755 --- a/script/test +++ b/script/test @@ -99,6 +99,7 @@ build_openthread() "-DOT_SERVICE=ON" "-DOT_COAP=ON" "-DOT_THREAD_VERSION=${THREAD_VERSION:-1.2}" + "-DOT_UPTIME=ON" ) local COVERAGE=${COVERAGE:-0}