大家好,我是 Melanie Drake,JMP 开发运维部门的系统工程师。我们团队提供自动化流程,用于构建 JMP 可执行文件和安装程序、运行自动化测试以及执行许多其他任务,以支持 JMP 的研发工作。
今天,我将讲解我们如何从流程日志中收集信息,并使用 JMP 来查找问题并监控其中一个自动化流程的整体运行状况。流程会产生大量数据,这些数据可能来自仪器读数、生产线测量数据或状态日志。它们可以保存为纯文本格式,有时也可以保存为结构化文本格式,例如 XML、JSON 或 CSV。所有这些数据都为查找和解决问题提供了大量机会。
然而,这些数据通常被写入许多不断创建和更新的独立文件中。这使得发现和追踪错误变得非常困难。我将以每日 JSL 单元测试流为例进行说明。该流程每天都会生成大量日志文件。我们发现,追踪和排查特定错误非常困难。我们拥有海量数据,但使用起来却异常困难。
什么是 JSL 单元测试流?它是一套 JSL 回归测试,我们使用每天构建的 JMP 版本运行它。它每晚都会在所有正在开发的 JMP 版本上运行,并且支持多种 Windows 和 Macintosh 操作系统版本。测试人员利用这个夜间运行来查找 JMP 中的错误,以便开发人员能够快速解决这些问题。如果某个测试流运行失败,他们就无法获得相应的测试结果。
夏季的几周时间里,我们同时开发了四个版本的 JMP。这意味着我们每晚都要在不同的 JMP 版本和操作系统组合上运行 18 个测试流。整个测试流可能需要 8 到 12 个小时才能运行完毕。我们将每个 Windows 测试流拆分成三个并发运行的测试流,以便更快地完成所有 Windows 测试。这样,我们在北京的测试团队就可以在当天使用这些测试结果。
总的来说,我们每天要查看 36 条独立的控制台日志。我们花费了大量时间试图找出特定错误发生的位置和时间。管理分散数据的解决方案是将所有数据整合到一个地方。当然,就是 JMP 数据表。
在 JMP 中,您可以可视化和分析数据,并使用 JMP Live 与他人分享您的发现。在本例中,我们将日志收集到 JMP 数据表中。我们使用图表来跟踪和排查所有问题,并通过发布到 JMP Live 来持续监控系统运行状况。由于 Macintosh 和 Windows 的日志输出不同,我们最终创建了两个不同的数据表。虽然有些错误是通用的,但许多其他错误则特定于某个操作系统。
我首先概述一下整体工作流程,然后会逐步讲解脚本并演示我们的流程。第一步是确定数据的位置以及如何将其导入 JMP。日志文件可以保存到磁盘或服务器,也可以通过 Web 访问。或许您可以通过 API 甚至数据库查询来获取它们。在我们的案例中,我们通过 Jenkins 服务器的 Web 用户界面访问文本文件,我们所有的自动化流程都运行在该服务器上。我们使用 HTTP 请求获取信息,然后使用 Parse JSON 将信息转换成我们可以使用的格式。
第二步是将数据导入 JMP。这可以从日志文件开始,您可能还需要添加其他元数据。您可以将整个日志放在一个单元格中,也可以将日志分散到多个单元格、列甚至行中。这完全取决于您的数据类型以及您需要如何查看数据。在我们的示例中,每个流运行对应一个日志。我们将每个流运行记录放在一行中,并将整个日志放在一个单元格中。此外,我们还收集了许多其他元数据。
第三步是解析文本。您可以使用 Parse XML 或 Parse JSON 来获取原始信息。获取原始信息后,您可以使用正则表达式、JSL 模式匹配或 JSL 字符函数来解析信息。您可以使用公式列、指示符列或多个响应列将信息收集成可用于图表的格式。
在我们的案例中,我们几乎用到了以上所有方法。我们使用公式列来收集元数据列的特定信息,然后使用正则表达式和 JSL 字符函数来解析实际日志。我们还使用了指示器列和多个响应列来收集信息,以便从中生成漂亮的图表。
第四步是探索和绘制数据图表。JMP 提供了 Graph Builder 和许多其他绘图平台。您可以使用这些工具来查找错误模式,找出错误之间的相关性,并跟踪它们随时间的变化。在我们的案例中,我们大量使用了 Graph Builder。我们创建了热图来查看相关性。我们使用条形图和散点图来跟踪错误随时间、JMP 版本和操作系统版本的变化。我们还使用了堆叠条形图和热图来查看流状态和缺失运行随时间的变化。稍后我会展示一些示例。
最后,在第五步,您需要与同事分享图表。您可以向特定受众展示特定的发现,也可以分享图表以提高透明度、增强团队协作。此外,您还可以隐藏私人、个人或机密数据。在我们的案例中,我们将部分数据发布到内部 JMP Live 服务器。我们只发布图表所需的数据。图表发布后,在会议中讨论起来非常方便,也便于监控整个流程的运行状况。
JMP 19 新增了一项名为“列标签”的功能。我们发现它对本项目非常有用。我们使用了三个列标签:一个用于标记特定里程碑的时间戳,一个用于标记我们发现的错误,还有一个用于标记我们在图表中使用的列。稍后我会详细讲解它们的使用方法。
我们为什么要这样做?首先,它极大地简化了我们的 JSL 代码。我们可以使用特定的标签来引用列,而无需硬编码每个列名。这也意味着,例如,当我们更新发现的错误时,无需更新列名。我们只需标记新列,它就会自动生效。这使得筛选和发布必要的数据变得非常容易。
现在我们来演示一下,我会展示我们的 JSL 脚本。在正式开始之前,我们先运行几行代码进行设置。我们引入了一些包含函数和变量定义的其他脚本。我们设置了保存主表的文件夹路径。这些表包含了我们目前收集的所有数据。然后,我们打开一个包含配置信息的 CSV 文件。
我们实际的 CSV 文件包含的列比这多,但其中包含一些内部信息,为了演示,我已将其删除。每个项目所需的基本信息包括:每个测试流的名称,以及与操作系统相关的主表,该主表保存了我们目前为止从该测试流的所有操作系统收集到的所有数据。
现在我们需要读取数据。每个 JMP 版本和操作系统版本的组合都是一个独立的 Jenkins 项目,拥有自己的页面。每个测试流运行都是主页的一个子页面。我们将以第一个项目为例,即运行在 Sonoma 上的 JMP 19。我将迭代次数设置为 1。然后,我将读取每个作业的名称以及要导入的目标表。
我们首先使用 HTTP 请求获取主页的 JSON 数据,其中包含每次流运行的 URL 列表。我们将该请求发送到 Jenkins,获取 JSON 数据,并将其解析到我们的构建列表中。由于我无法在此处实时运行,因此我将创建一个列表。您可以看到它非常长。此列表包含迄今为止在 Macintosh Sonoma 上使用 JMP 19 运行的每一次测试流的构建 ID。
但是,主表中已经包含了大部分此类数据,我们不想浪费大量时间读取和处理已有的数据。为了解决这个问题,我们打开主表。主表包含了所有 Macintosh 的结果,而不仅仅是本项目的结果。然后,我们获取了本项目已有的构建 ID 列表。换句话说,就是主表中与 Sonoma 上的 JMP 19 匹配的行的 ID。我们将这两个构建 ID 列表转换为关联数组,以便从新列表中减去当前的 ID。关联数组的运算功能可以让我们得到一个仅包含尚未处理的运行记录的简短列表。处理 3 行数据比处理 395 行数据然后丢弃大部分数据要容易得多,也快得多。
第二步是将这些数据实际导入 JMP。如果列表不为空,我们会使用之前编写的函数,该函数从列表中每个流运行中获取所需数据,并将其导入到数据表中。该函数再次使用 Parse JSON 来提取所需的跟踪数据,并将其添加到新的数据表中,每个测试流对应一列和一行。
我打开一个示例表,方便您查看其结构。可以看到,我们已经获取了一些元数据。这些元数据包括:特定流的持续时间、名称、ID 号、构建状态(成功或失败)以及开始日期时间戳。最重要的是,我们获得了指向文本日志的 URL,所有信息都将从中获取。接下来,我们将向此表中添加一列,用于存储日志文件的内容,并使用“加载文本文件”函数从该 URL 读取日志文件并将其插入到表中。
第三步是解析这些行的元数据和控制台日志。我们添加了一些新的派生列。例如,我们将持续时间转换为以分钟为单位的运行时间,这样更容易理解。我们还添加了一个日期列,其中只包含日期,不包含时间戳。这使得我们的一些图表更加简洁。最后,我们添加的另一个列是星期几。我们也在一些图表中用到了它。
接下来,我们需要为解析日志做好准备。我们会添加列来收集特定里程碑的开始时间。我们为 Windows 和 Macintosh 定义了不同的开始时间列,并在添加这些列时,将它们标记为“开始时间”。您可以看到这里的绿色标签,它们表示所有这些列都标记了相同的标签。您可以在下方看到这些标签。
我们添加的下一列是“最后启动项”。如果测试流因错误而停止运行,我们可以获取最后一个成功的里程碑。它将是该行中带有“starts”标签且包含日期时间戳的最后一列。如果进程与虚拟机或服务器的连接断开,日志中将出现 JavaScript 错误。我们一定会收集这些错误。
最后,我们添加了一个多响应列来收集错误信息。稍后我会详细介绍。下一步是开始解析日志。我们使用一个循环来解析每一行的整个控制台输出,提取所需信息并将其添加到该行的所有新列中。控制台日志在每一行的单个单元格中。我们将其转换为字符串列表,其中日志的每一行都是一个字符串。
然后我们遍历该列表一次,并将每一行与我们正在寻找的特定项目(例如开始时间、错误等)进行比较。这比为要提取的每个项目解析一次日志要高效得多。我们首先查找的是 JavaScript 错误。如果找到错误,我们就将其获取并放入数据表中,然后继续处理下一行的控制台日志。一旦作业以这种方式停止,就不会再有任何信息需要获取。
在我们的 Jenkins 项目中,我们会查找特定的错误,这些查找结果会打印在日志末尾,称为文本查找器。当我们找到第一个文本查找器时,我们会收集日志直到末尾。因为日志到此结束,所以我们会停止循环。我们会将收集到的信息传递给一个函数,稍后我会演示这个函数。
除了以上两项之外,我们还会查找里程碑事件,并将每个里程碑事件的日期时间戳插入到表格中。最后,在循环结束时,我们会收集这些文本查找器。找到的每个文本查找器都会作为指示列添加到表格中,如果未找到错误则为 0,如果找到错误则为 1。
这样就遗漏了我们没有查找到的错误。例如,如果这是一个新错误,我们之前就没有查找过。每个文本查找列都被标记为“文本查找”。一旦这些指示列被填充,我们就获取每个标记为“文本查找”且在该行中值为 1 的列的名称,并将其添加到多响应列中,该列稍后用于绘制图表。
最后,我们启动最后一个任务,并将其添加到该行的表格中。后续行的循环会将找到的文本查找器与已有的列进行比较,并仅添加尚未包含的列。这里我将打开一个示例,展示运行所有操作后表格的样子。我们仍然保留了最初收集的所有元数据、日志文本以及所有派生数据。现在,所有起始列都包含日期时间戳,因此我们可以准确地知道每个任务的开始时间。
我们最后启动的进程收集到了最后一个成功收集的项目。我们没有遇到任何 Java 断开连接的错误,但如果有的话,错误信息应该会显示出来。
最后,我们添加了所有文本查找列。您会看到它们都被标记为红色。所有这些运行实际上都成功了,但我添加了一些错误结果,以便向您展示它们在多响应列中的显示方式。您可以看到,这个运行有两个失败项,并且它们之间用分号分隔。而这个运行只有一个失败项。
最后一步是将所有新信息添加到我们的主数据表中。这只是一个简单的连接操作。我们要将新增的三行数据添加到表中。最后一步是保存此表。保存时我们会压缩表格,因为这些日志有时长达数百甚至数千行。1333 行数据量非常大,这些表格可能会变得非常庞大。这部分是整个流程的核心,也是我们开发过程中耗时最长的部分。确定我们需要哪些信息是一个反复迭代的过程。
最后一步是探索数据并绘制图表。我们保存了很多自己设计的图表,这些图表可以帮助我们诊断问题。我将向您展示其中几个。这个图表显示了按日期和操作系统划分的数据流运行时长。
持续时间短的直播通常很快就会结束,我们很少能从中获得任何结果。持续时间很长的直播可能卡在某个地方,最终也会超时。这些都是我们需要进一步调查的问题。这次的直播运行非常稳定,这总是令人欣喜的。
另一个很有用的图表是这张,它显示了 JavaScript 错误与最后一次成功启动的操作之间的相关性。这对于诊断问题非常有用。最后这张图表可能是我最喜欢的。它是一张热图,显示了缺失运行中的构建状态。
您可以立即看到,我们每周运行五天这些测试流。周末不运行。您可以看到,此表中有很多缺失的运行记录,这些记录表明我们根本没有运行任何测试流。这些测试流虽然运行了,但出现了错误。所有这些绿色的运行记录都完全正常。以下是我们刚刚添加到此表中的三行数据。
第五步是共享图表。由于所有这些图表都已发布到 JMP Live 中,我们只需在 JMP Live 中更新表格和图表即可。我们创建与 JMP Live 实例的连接。这可以在“文件”>“发布”>“管理连接”中进行设置。我们打开数据表,然后运行子集脚本。
你可能会好奇我们为什么要这么做。你会注意到,我们原来的主表有 39 列,而子表只有 9 列,而且这 9 列还不包括庞大的控制台日志。这使得子表体积小得多,从而提升了 JMP Live 的性能。此外,我们还可以很轻松地选择不发布私有或专有数据,这一点也很棒。我们只发布用于生成图表所必需的数据。
我们将该子集表以及其他信息(例如 JMP Live 连接)发送到我们编写的一个函数,该函数会遍历表中的每个图表。然后,它会更新每个表。接着,它会更新 JMP Live 中每个图表的子集表,然后继续处理下一个表,在本例中是 Windows 表。这样就完成了。每天运行此程序,我们都会得到全新的结果。
以下是我们发布在 JMP Live 上的一些图表示例。通过我们的仪表盘,这些图表以及其他图表可以让我们快速概览,轻松查看总体趋势和问题所在。由于这是 JMP Live,我们可以单独查看每个图表并深入挖掘更多信息。我们发布的所有图表都带有本地数据筛选器。这使我们能够仅查看特定时间段内的特定测试流运行情况。例如,仅查看最近几周的 JMP 20 测试流运行情况。
最终,我们取得了什么成果?我们实现了洞察的自动化。我们只需查看图表即可了解情况。图表会自动更新。我们无需手动处理具体错误。它会自动发布。由于所有设置都已预先定义,因此流程每天都能正常运行。在处理数据的过程中,我们确定了需要跟踪的内容。我们修改了控制台日志的打印内容,例如添加了里程碑的起始时间。我们还确定了哪些图表最能有效地展示我们需要解决的问题。
当我们完成并运行这个脚本时,我们取得了重大胜利。我们解决了之前遇到的所有问题,而且由于测试流现在通常都能成功运行,我们的图表也不再那么令人兴奋了。谢谢。