开源软件的性能
Talos

Clint Talbert和Joel Maher

在Mozilla,我们最早的自动化系统之一是一个我们称为Talos的性能测试框架。Talos自2007年创建以来一直忠实地维护,没有进行实质性的修改,即使Talos背后的许多最初假设和设计决策在工具所有权易手时都丢失了。

在2011年夏季,我们终于开始对Talos数字中的噪音和差异表示怀疑,我们开始思考如何对系统进行一些小的修改来开始改进它。我们不知道我们即将打开潘多拉的魔盒。

在本章中,我们将详细说明我们在剥离该软件一层又一层时发现了什么,发现了什么问题,以及我们采取了哪些步骤来解决这些问题,希望您能从我们的错误和成功中学习。

概述

让我们分解Talos的不同部分。Talos的核心是一个简单的测试框架,它创建一个新的Firefox配置文件,初始化配置文件,校准浏览器,运行指定的测试,最后报告测试结果的摘要。测试位于Talos存储库中,并且有两种类型:一个报告单个数字的单个页面(例如,通过网页的onload处理程序启动时间)或循环浏览的一组页面以测量页面加载时间。在内部,使用Firefox扩展来循环页面并收集诸如内存和页面加载时间等信息,强制垃圾回收以及测试不同的浏览器模式。最初的目标是创建一个尽可能通用的框架,以允许框架执行各种测试并测量测试本身定义的性能属性集合。

为了报告其数据,Talos框架可以将JSON发送到图形服务器:一个内部图形Web应用程序,只要该数据满足每个测试、值、平台和配置的特定预定义格式,它就会接受Talos数据。图形服务器还充当调查趋势和性能回归的界面。测试运行期间,标准Apache Web服务器的本地实例提供页面。

Talos的最后一个组件是回归报告工具。对于对Firefox存储库的每次签入,都会运行几个Talos测试,这些测试将其数据上传到图形服务器,另一个脚本从图形服务器使用数据并确定是否发生了回归。如果发现回归(即,脚本的分析表明签入的代码使该测试的性能显着下降),则脚本会向邮件列表以及签入有问题的代码的个人发送邮件。

虽然这种架构(在图8.1中总结)看起来相当简单,但随着Mozilla添加新的平台、产品和测试,Talos的每个部分都随着时间的推移而发生了变化。由于对整个系统作为端到端解决方案的监督最少,Talos最终需要进行一些认真改进。

Figure 8.1 - Talos architecture

图8.1 - Talos架构

在2011年夏季对Talos框架进行黑客攻击以添加对新平台和测试的支持时,我们遇到了Jan Larres硕士论文的结果,其中他调查了Talos测试中出现的大量噪音。他分析了各种因素,包括硬件、操作系统、文件系统、驱动程序和Firefox,这些因素可能会影响Talos测试的结果。在这些工作的基础上,Stephen Lewchuk致力于在他的实习期间尝试统计性地减少我们在这些测试中看到的噪音。

根据他们的工作和兴趣,我们开始制定一项消除或减少Talos测试中噪音的计划。我们召集了框架黑客来处理框架本身,召集了Web开发人员来更新图形服务器,并召集了统计学家来确定运行每个测试以产生可预测结果和最小噪音的最佳方法。

了解您正在测量的对象

在进行性能测试时,拥有有用的测试对于产品开发人员来说非常重要,并且可以帮助客户了解该产品在特定条件下的性能。拥有可重复的环境也很重要,以便您根据需要重现结果。但是,最重要的是了解您拥有什么测试以及您从这些测试中测量什么。

在项目开始几周后,我们都了解了更多关于整个系统的知识,并开始尝试使用不同的参数以不同的方式运行测试。一个反复出现的问题是“这些数字意味着什么?”这不容易回答。许多测试已经存在多年了,几乎没有文档。

更糟糕的是,无法在本地生成与自动化测试运行报告相同的的结果。很明显,框架本身执行了计算(它会删除每个页面的最高值,然后报告其余循环的平均值),图形服务器也执行了计算(删除最高页面值,然后将页面平均到一起)。最终结果是没有历史数据可以提供太多价值,也没有人理解我们正在运行的测试。

我们确实对一项特定测试有一些了解。我们知道此测试获取了某个时间点快照的前100个网站,并逐个加载每个页面,重复10次。Talos加载页面,等待mozAfterPaint事件(Firefox绘制网页画布时触发的标准事件),然后记录从加载页面到接收此事件的时间。查看从单个测试运行产生的1000个数据点,没有明显的模式。想象一下,将这10,000个点缩减为一个数字,并随着时间的推移跟踪该数字。如果我们使CSS解析更快,但图像加载速度更慢怎么办?我们如何检测到?如果所有其他99个页面保持不变,是否可以看出第17页速度变慢?为了展示在Talos的原始版本中如何计算值,请考虑以下数字。

对于以下页面加载值

首先,Talos框架本身会删除第一个值并计算中位数

这些值将提交到图形服务器。图形服务器将删除最高值并使用这些每个页面的值计算平均值,它将报告该值。

(565.5 + 675) / 2 = 620.25

此最终值将随时间推移绘制图表,并且如您所见,它生成一个近似值,除了对性能进行粗略评估之外,它对其他任何事情都没有好处。此外,如果使用这样的值检测到回归,则很难回溯并查看哪些页面导致了回归,以便开发人员可以针对特定问题进行修复。

我们决心证明我们可以减少此100页测试中数据的噪音。由于测试测量加载页面的时间,因此我们首先需要将测试与系统中的其他影响(如缓存)隔离。我们更改了测试,使其一遍又一遍地加载同一页面,而不是在页面之间循环,以便测量已大部分缓存的页面的加载时间。虽然这种方法不能说明最终用户实际浏览网页的方式,但它减少了记录数据中的一些噪音。不幸的是,仅查看给定页面的10个数据点不是有用的样本量。

通过改变我们的样本量并测量许多测试运行中页面加载值的标准偏差,我们确定如果我们至少加载页面20次,噪音就会减少。经过多次实验,这种方法在25次加载并忽略前5次加载的情况下找到了一个最佳点。换句话说,通过查看多个页面加载值的标准偏差,我们发现95%的嘈杂结果发生在前五次加载中。即使我们不使用这前5个数据点,我们也会存储它们,以便如果我们希望将来更改我们的统计计算。

所有这些实验导致我们对Talos正在执行的数据收集提出了一些新要求

将这些新要求应用于整个Talos系统是正确的做法,但考虑到围绕Talos发展起来的生态系统,切换到这种新模型将是一项重大的工作。我们必须做出决定,是重构还是重写系统。

重写与重构

鉴于我们对Talos必须更改内容的研究,我们知道我们将进行一些重大更改。但是,Mozilla对Talos的所有历史更改始终都担心“破坏数字”。Talos的许多部分都是多年来由善意的贡献者构建的,他们的添加在当时是有意义的,但没有文档或对工具链方向的监督,它已成为一个代码拼凑而成,不容易测试、修改或理解。

鉴于我们对代码库中未记录的暗物质的担忧,以及我们需要将新的测量结果与旧的测量结果进行验证的问题,我们开始了一项重构工作,以修改 Talos 和 Graph Server。然而,很快发现,如果没有对数据库模式进行大规模的重新架构,Graph Server 系统将永远无法摄取性能测试的完整原始数据集。此外,我们没有干净的方法将我们新研究的统计方法应用到 Graph Server 的后端。因此,我们决定从头开始重写 Graph Server,创建了一个名为 Datazilla 的项目。这不是一个轻率的决定,因为其他开源项目已经为他们自己的性能自动化分叉了 Graph Server 代码库。在 Talos 测试框架方面,我们也从头开始进行了原型设计。我们甚至有一个可以运行简单测试的工作原型,代码量减少了大约 2000 行。

当我们从头开始重写 Graph Server 时,我们担心继续推进新的 Talos 测试运行器原型。我们的担忧是,我们可能会失去以“旧方式”运行数字的能力,以便将新方法与旧方法进行比较。因此,我们放弃了原型,并对 Talos 测试框架本身进行了逐段修改,将其转变为数据生成器,同时保留了执行平均值以上传到旧 Graph Server 系统的现有部分。这是一个非常糟糕的决定。我们应该构建一个单独的测试框架,然后将新的测试框架与旧的进行比较。

尝试支持原始数据流和每个页面数据的新测量方法被证明是困难的。从积极方面来看,它迫使我们重构框架内部的大部分代码,并简化了许多内容。但是,我们必须在运行的自动化部分逐段执行所有这些操作,这导致我们的持续集成工具出现了一些头痛问题。

最好是从头开始并行开发 Talos 框架及其报告系统 Datazilla,并将所有旧代码抛在后面。尤其是在登台方面,在不尝试将即将推出的 Datazilla 系统的开发数据生成连接到正在运行的自动化的情况下,对新系统进行登台会容易得多。我们认为有必要这样做,以便我们可以使用真实的构建和真实的负载生成测试数据,以确保我们的设计能够正确扩展。最终,这些构建数据不值得修改生产系统带来的复杂性。如果我们当时知道这是一个为期一年的项目,而不是我们预计的六个月的项目,我们将从头开始重写 Talos 和结果框架。

创建性能文化

作为一个开源项目,我们需要接受来自其他个人和项目的意见和批评。没有开发主管来指示事情如何运作。为了获得尽可能多的信息并做出正确的决定,需要从许多不同的团队中调动很多人。该项目最初由两名 Talos 框架开发人员、两名 Datazilla/Graph Server 开发人员和两名来自我们的指标团队的统计学家启动。我们从一开始就向我们的志愿者开放了这个项目,并吸引了许多新面孔加入 Mozilla,以及其他使用 Graph Server 和一些 Talos 测试用于他们自己项目的个人。当我们一起工作,逐渐了解测试运行的哪些排列会给我们带来更少的噪声结果时,我们伸出援手,将几位 Mozilla 开发人员纳入项目。我们与他们第一次会面可以理解地很艰难,因为我们提议进行的重大变更。对于许多非常关心性能的开发人员来说,“Talos”的神秘性使得这项工作难以推广。

需要一段时间才能沉淀下来的重要信息是,为什么重写系统的大部分组件是一个好主意,以及为什么我们不能简单地“就地修复”。最常见的反馈是,对现有系统进行一些小的更改,但提出这些建议的每个人都不知道底层系统是如何工作的。我们做了很多演示,邀请很多人参加我们的会议,举办特别的一次性会议,写博客,发帖,发推文等等。我们尽一切努力宣传。因为唯一比完成所有这些工作来创建一个更好的系统更糟糕的事情是,完成所有这些工作却没有人使用它。

自从我们第一次审查 Talos 噪声问题已经过去一年了。开发人员期待着我们发布的内容。Talos 框架已经过重构,因此它具有清晰的内部结构,并且可以同时向 Datazilla 和旧的 Graph Server 报告。我们已经验证了 Datazilla 可以处理我们正在处理的数据规模(每六个月 1 TB 的数据),并审查了我们的指标计算结果。最令人兴奋的是,我们找到了一种方法,可以针对 Mozilla 代码库中的每次更改,实时提供回归/改进分析,这对开发人员来说是一个巨大的胜利。

因此,现在当有人将更改推送到 Firefox 时,Talos 会执行以下操作

结果会实时返回到 Talos 测试框架,然后 Talos 可以向构建脚本报告是否存在性能回归。所有这些都在每分钟完成 10-20 次 Talos 运行(因此产生了 1 TB 的数据)的同时更新计算和存储的统计信息。

要将此从一个工作解决方案转变为替换现有解决方案,需要并行运行这两个系统以完成 Firefox 的完整发布。此过程确保我们查看原始 Graph Server 报告的所有回归,并确保它们也是真实的,并且 Datazilla 也报告了它们。由于 Datazilla 基于每个页面而不是在测试套件级别进行报告,因此需要对新的 UI 和我们报告回归的方式进行一些必要的适应。

回顾过去,如果一开始就替换旧的 Talos 测试框架会更快。但是,通过重构它,Mozilla 为 Talos 项目带来了许多新的贡献者。重构还迫使我们更好地理解测试,这转化为修复了许多损坏的测试并关闭了几乎没有价值的测试。因此,在考虑是重写还是重构时,花费的总时间不是唯一需要审查的指标。

结论

在过去的一年里,我们深入研究了 Mozilla 性能测试自动化的各个方面。我们分析了测试框架、报告工具以及生成结果的统计可靠性。在这一年中,我们利用学到的知识使 Talos 框架更易于维护、更易于运行、更易于设置、更易于测试实验性补丁,并且更不容易出错。我们创建了 Datazilla 作为可扩展的系统,用于存储和检索来自 Talos 和任何未来的性能自动化的所有性能指标。我们重新启动了我们的性能统计分析,并创建了统计上可行的、每次推送的回归/改进检测。我们使所有这些系统更易于使用和更开放,以便任何贡献者在任何地方都可以查看我们的代码,甚至可以尝试在我们性能数据上使用新的统计分析方法。我们始终如一地致力于在项目的每个里程碑再次审查数据,以及我们愿意丢弃被证明不确定或无效的数据,这帮助我们在推动这个巨大的项目前进时保持了专注。吸引 Mozilla 各个团队的人员以及许多新的志愿者帮助提高了这项工作的有效性,并帮助在 Mozilla 多个领域的性能监控和数据分析方面实现了复兴,从而形成了更加数据驱动、以性能为中心的文化。

  1. https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/ttest.py.

  2. https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/fdr.py.

  3. https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/data_smoothing.py.