企业最不想要的就是被称为不可靠且性能不佳的服务,特别是如果点击几下即可获得类似的解决方案。因此,必须了解 WebRTC 应用程序或任何其他软件解决方案的性能,以避免将来出现问题。一个解决方案可以由有经验的人开发并在发布前进行测试,但即便如此并不意味着性能下降永远不会出现。尽早发现问题并采取相应的行动可以帮助用户获得更好的体验。
想象一下,您的视频通话应用程序在发生数据包丢失时出现问题。数据包丢失会导致音频断断续续、视频冻结或视频帧丢失,使用户难以理解通话内容或查看通话中发生的情况。高丢包率还会使应用程序难以建立和维持连接,从而导致通话或视频聊天中断。如果您和您的团队在开发应用程序时没有遇到数据包丢失的情况,那么您可以通过一种方法找出问题所在——失望的客户可以告知您他们的不愉快经历。当然,最好在用户体验之前找出问题并修复它。
这就是为什么监控是一种非常有益的做法——随着时间的推移定期观察产品的性能和行为,可以让您对出现的任何问题做出快速反应,并保持可靠和高性能的服务。这不仅可以改善用户体验,从而提高客户满意度和忠诚度,还可以为您的企业带来市场竞争优势。
定期应用程序监控的好处:
Loadero 是一种负载和性能测试工具,能够通过使用 Web 自动化技术(例如“Javascript + Nightwatch”、“Java + TestUI”或“Python + Py-TestUI”)模拟真实的用户行为和与网站的交互。除此之外,它还为您提供所有必要的 WebRTC 和机器统计数据,例如 FPS、比特率、抖动、往返时间等,这些数据在测试运行期间收集并能够断言它们。
由于监控需要持续观察,例如在我们的案例中需要持续测试运行,因此我们还需要定期触发测试运行。为此,CI/CD 管道是一个很好的选择,因为它很灵活,而且为我们的任务配置起来并不复杂。除了定期运行测试外,管道还可以用于在每次部署后自动测试以确保性能良好。
为了开始使用 Loadero 监控您的 WebRTC 解决方案,需要一个由 3 个部分组成的设置:
让我们从为我们的监控设置示例设置 Loadero 测试开始。如果您已经在 Loadero 中进行了测试,可以启动它来检查一些性能指标,您可以跳过这部分并跳转到关于通过管道启动测试的部分。如果这是您第一次在 Loadero 中进行测试,可以在这篇博文中找到有关如何在 Loadero 中创建测试的完整分步指南。如果您已经在其他服务中进行了 WebRTC 性能测试,请在此处查看如何将您的测试迁移到 Loadero。在我们的例子中,我们将进行“Javascript + Nightwatch”测试。它的场景将是Jitsi中的一分钟长的一对一通话。
在测试中,两名参与者将加入一个通话并停留一分钟,中途截取一些屏幕截图以进行额外的连接验证。
参与者将从美国俄勒冈州连接,将使用最新的 Google Chrome 版本(撰写此博客时为 109v),并将使用默认视频 + 音频来模拟输出信号。
我们还将使用 Loadero 的运行后断言。它们允许您在测试中为 WebRTC 和/或机器指标指定“通过”标准,例如“如果平均 FPS ≥ 10,则通过”。运行完成后,会自动为每个参与者计算断言结果,以检查给定值是否符合通过标准。如果参与者断言失败,则该参与者在结果报告中的状态也将是“失败”。
通过断言,我们将评估音频和视频以及 FPS 的传入和传出比特率和数据包。
我们测试的配置是这样的:
在这个例子中,我们有一个用 Javascript + Nightwatch 编写的 Loadero 测试脚本,但同样可以用 Java + TestUI 或 Python + Py-TestUI 来完成。
client => { client // Open the page .url(`https://meet.jit.si/LoaderoTests`) // Wait until the username field is visible // And enter the username .waitForElementVisible('[placeholder]', 30 * 1000) .sendKeys('[placeholder]', 'User') // Wait until the "Join" button is visible // And join the call by pressing the button .waitForElementVisible('[aria-label="Join meeting"]', 10 * 1000) .click('[aria-label="Join meeting"]') // Another thing you can do is to take screenshots during the test // Which could help you to identify the cause of a failure // And give visual feeback about the test run .takeScreenshot('pre_call_screenshot.png') // Stay in the call for half a minute .pause(30 * 1000) // Take a mid-call screenshot .takeScreenshot('mid_call_screenshot.png') // Stay in the call for another half a minute .pause(30 * 1000) // Take a post-call screenshot .takeScreenshot('post_call_screenshot.png'); }
每个 WebRTC 应用程序都是不同的,并且性能会略有不同。在这里,我们的目标是定义我们希望在未达到性能预期时立即收到通知的阈值,即使这种情况发生在夜间。为此,我们将为 WebRTC 指标使用一组 Loadero 的运行后断言,如果 WebRTC 性能指标值不如我们希望看到的那么好,这将使整个测试运行失败。因此,不应将目标值设置在狭窄的范围内,但我们应该允许测试偶尔比我们理想中的情况稍差,但仍在合理的性能范围内。我们在此处设置的值是示例,您可能希望根据应用程序的具体情况对其进行调整(例如,如果您优先考虑高帧率而不是网络带宽,您可能希望增加您希望看到的最小帧率并降低比特率限制)。作为起点,您可以使用下面的断言列表:
以下是我们为此示例测试设置的断言列表及其值:
webrtc/video/fps/out/25th >= 10
webrtc/video/fps/in/25th >= 10
webrtc/video/fps/out/stddev < 2
webrtc/video/fps/in/stddev < 2
webrtc/audio/packets/out/avg > 40/sec
webrtc/video/packets/out/avg > 100/sec
webrtc/audio/packets/in/avg > 40/sec
webrtc/video/packets/in/avg > 100/sec
webrtc/audio/bitrate/out/95th <= 25 kbit/sec
webrtc/video/bitrate/out/95th <= 1000 kbit/sec
webrtc/audio/bitrate/in/95th <= 25 kbit/sec
webrtc/video/bitrate/in/95th <= 1000 kbit/sec
最后,我们必须配置测试参与者。我们将有 1 组参与者,其中 2 名具有相同配置的参与者将加入通话。参与者的设置如下:
提示:如果您希望有许多具有完全相同配置的参与者,则在配置菜单中增加
Count
数值。为了节省您的时间,我们建议仅在需要不同配置时单独创建参与者。
对于测试配置就是这样。但由于我们打算定期运行测试,因此在继续配置监控设置之前,运行测试并通过检查参与者在测试运行期间拍摄的 Selenium 日志和屏幕截图来验证脚本没有错误。如果这是您第一次在 Loadero 中启动测试,或者您只是不确定它是否配置正确,请使用此博客文章检查您的测试是否已准备好启动。
如前所述,如果 WebRTC 指标断言失败并且成功率可能不是 100%,参与者仍可能会失败。这也发生在我们的测试中。
这并不一定意味着测试有问题,只是应用程序不符合您设置的断言标准。但是,如果您的测试由于其他原因而不是断言集而失败,这篇博文将介绍一些调试测试的方法。
我们设置的断言只是一个通用基线,并不适合我们测试的应用程序。您可以使用该列表并从这些值开始,但您测试的应用程序可能不同,指标结果也可能非常不同。
提示:设置您自己的断言的一种好方法是进行 5 次测试运行,查看参与者指标并评估合理的目标。
设置这些断言并分析结果以找出断言失败的原因本身就是一项复杂的任务,因此我们不会在这篇博文中详细介绍。此外,我们的示例测试由于断言失败而失败的事实恰好模拟了我们需要的情况,当测试失败并发送有关失败的通知时,因此我们将保持原样。如果 Selenium 日志显示您没有遇到任何错误并且屏幕截图确认已采取所有必要的操作,那么测试仍然可以进行。
我们的团队已经准备了一些关于如何在您的开发管道中集成测试的博客文章: 使用 JS和Loadero 的 Python 客户端。因此,在这里我们将依赖它们。对于我们的设置,我们将使用博客文章中建议的内容,该文章使用 Python、GitHub 及其 CI/CD 实现 – 工作流以及 Loadero Python 客户端。
注意:某些信息可能会被跳过。有关深入说明,请参阅原始Loadero Python 博客文章。
对于我们的工作流程,我们需要:
在 GitHub 上创建新存储库或使用现有存储库。
在.github/workflows
目录中的存储库中创建一个notify-on-fail.yml
文件。该文件将包含有关如何设置环境和启动 Loadero 测试的说明。
让我们通过在notify-on-fail.yml
中指定触发器来开始定义工作流。
on: schedule: - cron: '0 9-18 * * *' workflow_dispatch:
schedule
允许您在预定的时间触发工作流。因此,它是您定义测试运行频率的地方。在我们的示例中,我们已将时间表设置为从上午 9 点到下午 6 点每小时运行一次测试,因为这是团队最有可能对失败做出反应的时间。如果您可能需要在整个昼夜循环中运行测试,您可能更愿意每 4 小时左右运行一次。因此,即使没有人醒着,您也可以监控应用程序。请注意,该schedule
使用了一种特定的语法,您可以在此处了解更多信息。
提示:设置测试频率时,请考虑测试运行之间的时间间隔应长于测试持续时间,因为您的测试可能会相互干扰。
第二个触发器workflow_dispatch
允许在需要时通过 GitHub Web 应用程序手动触发管道。
在脚本的jobs
部分,我们指定了环境和我们将使用的所有依赖项。对于我们的目的,来自Loadero Python 博客文章的配置非常适合,所以请不要犹豫,复制粘贴它。
jobs: notify-on-fail: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.10" - run: pip install loadero-python - run: python run.py env: LOADERO_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} LOADERO_PROJECT_ID: ${{ secrets.PROJECT_ID }} LOADERO_TEST_ID: ${{ secrets.TEST_ID }}
重要提示:注意,在行
- run: python run.py
中,在 python 之后我们有相对于存储库根目录的run.py
文件的路径。在我的例子中,文件结构如下所示:
另一件需要注意的事情是凭据。首先,您可以在 Loadero 中找到测试和项目 ID,但您还需要一个项目 API 访问令牌——目前,项目访问令牌是通过向我们的支持团队请求获得的。其次,凭据用作 GitHub Actions 机密。这使您的 Loadero 访问令牌保持私密。秘密可以在存储库设置 -> 安全 -> 秘密和变量 -> 操作 -> 新存储库秘密中配置。
有关秘密的详细分步说明,请参阅前面提到的博客文章。
所以现在的工作流程配置应该是这样的:
name: Notify about failing tests in Loadero on: schedule: - cron: '0 9-18 * * *' workflow_dispatch: jobs: notify-on-fail: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.10" - run: pip install loadero-python - run: python run.py env: LOADERO_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} LOADERO_PROJECT_ID: ${{ secrets.PROJECT_ID }} LOADERO_TEST_ID: ${{ secrets.TEST_ID }}
现在让我们将 Python 脚本添加到我们的设置中以与 Loadero 交互。再次强调,来自Loadero Python 博客的脚本是一个很好的起点,因此我们将使用它并在稍后根据需要修改它。
import os from loadero_python.api_client import APIClient from loadero_python.resources.test import Test project_id = os.environ.get("LOADERO_PROJECT_ID", None) access_token = os.environ.get("LOADERO_ACCESS_TOKEN", None) test_id = os.environ.get("LOADERO_TEST_ID", None) if project_id is None or access_token is None or test_id is None: raise Exception( "Please set the " "LOADERO_PROJECT_ID and LOADERO_ACCESS_TOKEN AND LOADERO_TEST_ID " "environment variables." ) APIClient( project_id=project_id, access_token=access_token, ) run = Test(test_id=test_id).launch().poll() print(run) for result in run.results()[0]: print(result) if run.params.success_rate != 1: raise Exception("Test failed")
现在是时候修改我们的 Python 脚本以实现我们的通知了。在此示例中,警报将发送到 Discord 频道。虽然您实现通知的方式完全是可选的,因为它取决于您的个人喜好。
让我们通过导入请求库、 ResultStatus
和AssertStatus
类来更新我们的 Python 文件。
import requests import os from loadero_python.api_client import APIClient from loadero_python.resources.test import Test from loadero_python.resources.classificator import ResultStatus, AssertStatus
以及更新我们的 YAML 文件以安装请求库。
- run: pip install loadero-python - run: pip install requests - run: python run.py
如果管道配置错误并且任何凭证的值为 None,我们应该通过发送带有错误消息的 POST 请求来通知我们的 Discord 频道。
missing_credentials_message = ( "Please set the " "LOADERO_PROJECT_ID and LOADERO_ACCESS_TOKEN AND LOADERO_TEST_ID " "environment variables." ) def send_notification(message): requests.post( "https://discordapp.com/api/webhooks/{id}", data={"content": message}, ) if project_id is None or access_token is None or test_id is None: send_notification(missing_credentials_message) raise Exception(missing_credentials_message)
如果测试失败,我们想知道原因。这里我们添加参与者的验证。如果参与者失败,我们将发送具有以下结构的错误消息:
此外,我们应该摆脱初始脚本的异常,因为它在这里过多
run_failure_message = "" for result in run.results()[0]: result.params.run_id = run.params.run_id result.read() if ( result.params.selenium_result.value != ResultStatus.RS_PASS or result.params.status.value != ResultStatus.RS_PASS ): run_failure_message += ( f"{result.params.participant_details.participant_name}:\n" f"-Selenium result: {result.params.selenium_result.value}\n" f"-Participant status: {result.params.status.value}\n" ) if result.params.asserts: run_failure_message += "-Failing asserts:\n" for assertion in result.params.asserts: if assertion.status != AssertStatus.AS_PASS: run_failure_message += f"--{assertion.path.value}\n" run_failure_message += "\n" run_failure_message += f"Run status: {run.params.status.value}" if run.params.success_rate != 1: send_notification(run_failure_message)
并且让我们发送成功测试运行的消息:
if run.params.success_rate != 1: send_notification(run_failure_message) else: send_notification(f"The {run.params.test_name} test has been finished successfully")
作为最后的验证,我们应该将整个 API 调用包装到try-except
中,以防与 Loadero API 的连接出现故障。最终的 python 脚本看起来像这样:
import os import requests from loadero_python.api_client import APIClient from loadero_python.resources.test import Test from loadero_python.resources.classificator import ResultStatus, AssertStatus project_id = os.environ.get("LOADERO_PROJECT_ID", None) access_token = os.environ.get("LOADERO_ACCESS_TOKEN", None) test_id = os.environ.get("LOADERO_TEST_ID", None) missing_credentials_message = ( "Please set the " "LOADERO_PROJECT_ID and LOADERO_ACCESS_TOKEN AND LOADERO_TEST_ID " "environment variables." ) def send_notification(message): requests.post( "https://discordapp.com/api/webhooks/{id}", data={"content": message}, ) if project_id is None or access_token is None or test_id is None: send_notification(missing_credentials_message) raise Exception(missing_credentials_message) try: APIClient( project_id=project_id, access_token=access_token, ) run = Test(test_id=test_id).launch().poll() print(run) run_failure_message = "" for result in run.results()[0]: result.params.run_id = run.params.run_id result.read() if ( result.params.selenium_result.value != ResultStatus.RS_PASS or result.params.status.value != ResultStatus.RS_PASS ): run_failure_message += ( f"{result.params.participant_details.participant_name}:\n" f"-Selenium result: {result.params.selenium_result.value}\n" f"-Participant status: {result.params.status.value}\n" ) if result.params.asserts: run_failure_message += "-Failing asserts:\n" for assertion in result.params.asserts: if assertion.status != AssertStatus.AS_PASS: run_failure_message += f"--{assertion.path.value}\n" run_failure_message += "\n" run_failure_message += f"Run status: {run.params.status.value}" if run.params.success_rate != 1: send_notification(run_failure_message) else: send_notification( f"The {run.params.test_name} test has been finished successfully" ) except Exception as err: send_notification(f"Error while running Loadero test: {err}")
现在,我们的测试从上午 9 点到下午 6 点每小时自动运行一次,并评估应用程序是否按预期执行。
测试执行完成后,如果测试失败,您会收到有关失败的通知。
在我们的案例中,由于 FPS 和数据包不符合我们的标准,Jitsi 测试失败了,这可以在测试结果的 Asserts 选项卡中看到。断言结果也可供每个参与者单独访问,因此您可以验证问题是否发生在所有参与者身上,还是只发生在其中的一部分参与者身上。
上面为断言提供的值可以作为开始参考,在我们的例子中它们似乎与 Jitsi 的目标性能不一致。因此,请不要犹豫,探索您的应用程序如何执行以及断言什么最适合您的应用程序,以确保监控过程是最佳的。
注意:只要查看我们测试的断言,我们就可以注意到在整个测试运行过程中有整个部分输出零,这最终会影响测试结果。
提示:如果您的测试失败,您可以导航到 WebRTC 统计信息选项卡,您可以在其中找到包含数据的各种图表,并获取有关导致失败的指标的更多信息。
在这篇博文中,我们提供了一个示例,说明您如何在 Loadero 和 GitHub 工作流的帮助下监控您的 WebRTC 应用程序。通过使用监控,您可以更好地处理应用程序中可能出现的任何问题。创建具有不同条件的其他场景以更全面地了解应用程序的性能也可能是明智的。设置此类监控可能是一项相当复杂的任务。您希望 Loadero 团队为您创建类似的监控设置吗?请随时通过[email protected]与我们联系,让我们讨论如何帮助您定期自动监控您的 WebRTC 解决方案。
也发布在这里。