按需创建 Web 图表

作者:Mark Pruett

我在弗吉尼亚电力公司工作,这是一家为美国东南部大片地区提供服务的电力公司。我们小组的部分工作是收集和存档来自现场数千台设备的数据,例如电力变压器和电路。我们使用 Linux 收集这些数据本身就是一个故事(参见 1995 年 1 月和 2 月刊的 Linux Journal 中的“SCADA—Linux 仍在努力工作”)。

为了使公司内部能够访问这些海量数据,我们小组构建了一套 Intranet 应用程序。公司网络中的任何人都可以使用标准 Web 浏览器访问这些应用程序。

使用这些应用程序中的一个,我们的用户可以从我们的数据库中生成数据的图形图表。例如,工程师可能想查看特定变压器在一个月内的电力负载图。我们的程序可以从我们的数据库中提取数据,创建该数据的图表,并在几秒钟内通过用户的 Web 浏览器显示图形。

使用大多数标准 Linux 发行版附带的工具,您也可以按需构建基于 Web 的图表。您需要的工具包括 Perl、gnuplot 图形软件包和 NetPBM 图形转换工具。我假设您已经有一个正在运行的 Web 服务器。我们使用 Apache Web 服务器,但这里描述的技术也适用于其他 Web 服务器。

在向您展示如何将这些工具结合在一起之前,您需要获取并安装 gnuplot 和 NetPBM 软件包。有关您可以在哪里获取这些软件包的 Internet 站点和 Linux 发行版的信息,请参阅“资源”。

什么是 gnuplot?

gnuplot 是一种设备无关的绘图软件包,多年来一直适用于各种 Unix 以及其他平台。它具有自己的语言,用于创建和显示二维和三维图表。命令可以一次交互式输入一个,也可以放在 gnuplot 脚本文件中一起运行作为批处理作业。

gnuplot 支持各种输出设备。这些设备中最有用的是 X 窗口。您可以从 X Windows 下的 xterm 运行 gnuplot,它可以将您的数据绘制到单独的 X 窗口中。对于那些不熟悉 gnuplot 的人来说,这是一个了解各种命令的好方法。

由于 gnuplot 可以从脚本文件获取其命令,并从简单的文本文件获取其数据,因此它是自动化生成 Web 服务器请求的图形的完美工具。

绘制 access_log 数据

我怀疑您对绘制电力变压器的负载曲线有多大兴趣,因此我将提供一个更通用的示例。该示例是一个 CGI Perl 脚本,用于绘制给定一天内 Web 服务器的点击次数。

该脚本需要对 Perl 有一定的了解;所需的一小部分 CGI 知识将根据需要进行解释。

首先,让我们描述一下脚本需要做什么。每次有人从 Apache Web 服务器请求信息时,该请求都会记录到一个名为 access_log 的文件中。此日志位于 Web 服务器的日志目录中。此目录的确切位置取决于您如何设置 Web 服务器。日志的每一行代表一次 Web 点击。来自 Apache 服务器的 access_log 文件的一行看起来像这样

foobar.mydomain.org
- - [01/Sep/1997:17:14:31 -0400]
"GET /images/gnuplot_10270.gif HTTP/1.0" 200 9538

此行指示 foobar.mydomain.org 上的 Web 浏览器请求在 9 月 1 日下午 5:14 将文件 /images/gnuplot_10270.gif 发送给它。日志中的所有行都具有相同的格式,因此我们应该能够仅提取与我们正在寻找的日期匹配的那些行。

我们还可以知道访问的小时和分钟。我们希望记录目标日期的每分钟访问次数。此数据将发送到 gnuplot,以便它可以创建我们的图表。我们希望创建数据的简单 x-y 线图,其中 x 轴表示一天中的时间,y 轴表示点击次数。

绘制图表后,我们需要将其转换为可以使用 Web 浏览器显示的图形格式。最后,我们需要构建一个 HTML 页面发送回浏览器。此页面将包含一个图像标签,该标签指向我们刚刚创建的图形文件。

如果这看起来像很多步骤要完成,请不要担心。所有这些都可以在七个简短的步骤中完成,代码少于 100 行。完整的 Perl 脚本显示在 清单 1 中。代码分为编号部分,方便参考。让我们一次查看脚本的一个部分。

第 1 节:文件位置

清单 1 的第一部分几乎不需要解释。我只是为稍后将使用的文件名和目录分配变量。如果您的 Web 服务器目录与我的不同,您可能会更改此部分。

第 2 节:要搜索的日期

Perl 脚本旨在在 Web 浏览器请求时启动。换句话说,它是一个 CGI 程序。公共网关接口 (CGI) 程序提供了一种标准机制,用于通过 Internet 提供“交互式内容”。Web 浏览器不是请求静态 HTML 文档,而是要求 Web 服务器运行一个程序,该程序将动态创建 HTML 文档。CGI 程序可以用任何语言编写,但 Perl 是当今 CGI 编程中最常见的语言。

我的 CGI 脚本实际上将作为命令行中的独立程序运行,尽管以这种方式运行时它不是特别有用。作为 CGI 程序运行时,它获得了一个重要的方面:访问 QUERY_STRING 环境变量。QUERY_STRING 环境变量是将信息传递给 CGI 程序的一种方式。如果已定义 QUERY_STRING,则我们的程序希望它包含一个日期。此日期应为 dd/mmm/yyyy 格式(例如,12/Sep/1997)。这与 Apache 用于记录其日期的格式相同。

如果未定义 QUERY_STRING,我们将假设我们应该查找今天的访问数据。在任何一种情况下,我们正在寻找的日期都存储在变量 $date 中。

第 3 节:搜索 access_log 文件

在清单 1 的第 3 节中,我们打开 Apache 访问日志并开始查找包含我们目标日期的行。当我们找到匹配的日期时,我们提取小时和分钟并将它们放在变量 $inhour$inmin 中。

在这一点上,我们遇到了一个轻微的概念性问题。我们想在图表的 x 轴上绘制一天中的时间,但我们实际拥有的是小时和分钟。x 轴上将绘制什么值?我们想要的是从凌晨 12:01 到午夜的时间线。如果我们只是将时间转换为整数,我们将得到像上午 10:24 的 1024 这样的值。gnuplot 可以绘制这个,但由于一小时只有 60 分钟,因此在每个绘制的小时结束时都会有一个间隙。有很多方法可以解决这个问题。我使用的方法只是将分钟 0 到 59 缩放到 0 到 99 之间的值。然后将小时乘以 100,并将转换后的分钟添加到其中。因此,下午 1:30 的时间变为整数值 1350。我们的图表将仅具有小时的刻度线,因此没有人会注意到我们的小转换。

因此,在本节代码中,我们将小时和分钟转换为整数值,并将其用作名为 $accesses 的计数器数组的数组索引。

第 4 节:准备 gnuplot 的数据

gnuplot 可以绘制它从文本文件中读取的数据。我们需要为 gnuplot 编写一个文件,其中文件的每一行包含两个整数值。第一个值将是我们的时间值(x 轴),第二个值将是该时间的 Web 点击次数(y 轴)。该文件将包含 2401 个值。以下是示例文件中的几行

0808  1
0809  0
0810  1
0811  2
0812  0
0813  21
0814  0
0815  12

此文件显示了 8 点钟小时内的几分钟。我们通过打印出 $accesses 数组中的数据来创建文件。在这里,我们遇到了另一个小“陷阱”。Perl 创建了称为“稀疏数组”的东西。这只是意味着数组元素是在定义时创建的。换句话说,如果上午 8:12 没有访问,如我们的示例数据中所示,则不会创建相应的数组元素。它是未定义的。

通常在 Perl 中,您可以使用 foreach 循环遍历数组。但在我们的例子中,我们需要输出所有时间间隔的数据,即使是那些没有发生访问的时间。我们需要这样做,以便我们有一个连续的数据集供 gnuplot 绘制图形。

这实际上很容易做到。在清单 1 的第 4 节中,我们设置了一个简单的 for 循环。使用 Perl 的 defined 函数,我们确定数组元素是否存在。如果存在,我们只需打印索引值作为我们的 x 值,并将访问计数作为我们的 y 值。如果未定义数组元素,我们知道该时间没有访问,因此我们打印出零作为我们的 y 值。

第 5 节:构建 gnuplot 命令文件

gnuplot 将自动使用来自我们数据集的值标记 x 轴和 y 轴。如果数据集中任何时间间隔的最大 Web 点击次数为 48,则 gnuplot 可能会在 y 轴上创建刻度线,分别为 0、10、20、30、40 和 50。这对于 y 轴来说很好,但请记住,x 轴包含一个计算的时间值,对于阅读我们图表的人来说可能没有多大意义。

幸运的是,我们可以覆盖默认刻度线并创建我们自己的刻度线。这是第五节开头 for 循环的工作。我们创建一个文本字符串,该字符串将嵌入到我们即将创建的 gnuplot 命令文件中。

gnuplot 基于您提供给它的一组命令创建图表。实际上,gnuplot 只有两个用于绘制数据的命令,plotsplot,我们将在程序中使用其中较简单的一个。我们将使用的另一个命令是 set,用于启用和禁用 gnuplot 中的特定选项和功能。清单 1 第五节中的 Perl print 语句处理将 gnuplot 命令文件写入磁盘。

那些不太熟悉 Perl 的人可能会发现 print 语句的这种变体有点令人困惑。让我们看看那行

print GPFILE <<EOM;

此 print 命令告诉 Perl 将其后的 Perl 脚本中的每一行写入使用 GPFILE 文件句柄打开的文件。当 Perl 遇到脚本中仅由字母 EOM 组成的行时,它停止将行打印到文件。因此,第五节中 print 语句之后并一直到 EOM 行的所有行根本不是 Perl 语句,而是 gnuplot 命令,这些命令将由 Perl 写入 gnuplot 命令文件。

print 命令还将用它们的值替换 Perl 变量引用,因此该行

set title "Web Server Accesses $mon $day, $year"

可能会写入文件为

set title "Web Server Accesses Aug 12, 1997"
第 6 节:创建图表图形

现在我们有了一个数据文件和一个 gnuplot 命令文件,我们如何获得我们的图表?我们如何向用户展示它?这实际上是容易的部分。

首先,我们需要更多地谈谈 gnuplot。我之前提到过 gnuplot 可以显示到各种设备,包括 X 终端。但是 gnuplot 也可以以可移植像素图格式 (PPM) 写入文件。在 Perl 脚本的第五节中,请注意我们编写了 gnuplot 命令

set term pbm color

到 gnuplot 命令文件。这告诉 gnuplot 将输出写入文件。

但是,这只完成了我们的一部分工作。Web 浏览器不知道如何处理 PPM 文件;它们通常需要 GIF 或 JPEG 文件。这就是 NetPBM 软件包的用武之地。这是一组命令行实用程序,用于从一种格式转换为另一种格式。其中一个工具正是我们正在寻找的:恰如其名 ppmtogifppmtogif 是一个简单的过滤器程序。它从标准输入中获取 PPM 图像文件,并将 GIF 文件写入标准输出。由于大多数 Web 浏览器都支持 GIF,因此 ppmtogif 非常适合我们的需求。

因此,第六节结果几乎是微不足道的。我们使用 Perl 系统调用来运行我们的 UNIX 命令,然后使用我们在第五节中创建的命令文件运行 gnuplot(反过来,它将读取我们在第四节中构建的数据文件)。gnuplot 以 PPM 格式将图形发送到标准输出,ppmtogif 过滤器将其转换为 GIF 文件,该文件被重定向到磁盘文件。我们现在在磁盘上有了 GIF 格式的图表。

第 7 节:在浏览器中显示图表

最后一步(清单 1 的第 7 节)是在用户的 Web 浏览器中显示图表。请记住,这是一个 CGI 程序,因此调用 CGI Perl 脚本的浏览器正在等待从我们的 Web 服务器接收某些内容。我们将提供的是一个简短的 HTML 页面,其中包含一个 HTML 图像标签,该标签指向我们刚刚创建的 GIF 文件。

我们使用相同的技术,即多行 print 语句,来创建浏览器将接收的 HTML。请注意,我们必须在发送回浏览器的数据前加上“content-type”前导码。这让浏览器知道它应该期望的是 HTML 页面,而不是其他类型的数据。

用户通过在其浏览器中键入 CGI 脚本的 URL 来生成图表。如果服务器名为 myserver 并且我们的脚本在 Web 服务器的 cgi-bin 目录中另存为 usage_graph_cgi,那么他们将键入

http://myserver/cgi-bin/usage_graph_cgi

要指定今天以外的日期,用户将日期附加到 URL 的末尾,并用问号分隔

http://myserver/cgi-bin/usage_graph_cgi?11/Sep/1997
如果一切顺利,并且没有理由不顺利,用户应该看到一个看起来类似于图 1 的页面。
Creating Web Plots on Demand

图 1. 在 Web 上显示的图表

清理 GIF 文件

还剩下一个小问题。每次运行我们的 Perl 程序时,我们都会创建一个新的 GIF 文件,因此我们必须在不再需要它们时将其删除。

在我的情况下,每天晚上我只是在 crontab 下运行一个脚本,该脚本删除白天创建的任何 GIF 文件。由于我们的大多数应用程序都在白天运行,因此删除我仍然需要的 GIF 文件的机会非常小。

这种方案可能并非在每种情况下都适用,因此您可能需要设计一种不同的方法来清理 Web 服务器上收集的 GIF 文件。[find 命令将非常出色地实现此目的—Ed]

步骤回顾

虽然我提供了一个按需绘图的具体示例,但所使用的技术可以应用于您可能想要绘制的任何类型的数据。如果您可以将数据提取到简单的文本文件中,并且如果数据适合二维或三维绘图,则可以将其交付到 Web。基本步骤始终相同

  • 构建一个包含要绘制数据的文本文件。

  • 构建一个 gnuplot 命令文件。

  • 运行 gnuplot 以构建 PPM 格式的图表。

  • 使用 ppmtogif 将图表转换为 GIF 格式。

  • 构建一个带有图像标签的 HTML 页面并将其发送到浏览器。

将 gnuplot 和 NetPBM 等工具结合在一起以快速构建有用的程序表明,软件不一定必须打包在最新的面向对象组件中,并与 ActiveX 或 CORBA 捆绑在一起。通常,好的可靠工具、文本文件和一点 Perl 就足以完成工作。

资源

Creating Web Plots on Demand
Mark Pruett 在弗吉尼亚联邦大学获得计算机科学硕士学位。Mark 是一位程序员,撰写有关编程的文章。他希望有一天成为一名作家,撰写有关如何编写程序文档的文章。可以通过 pruettm@vancpower.com 与他联系。
加载 Disqus 评论