【问题标题】:Poor performance when writing large XML-based log file编写大型基于 XML 的日志文件时性能不佳
【发布时间】:2015-03-23 18:39:14
【问题描述】:

我对 Python 比较陌生,我已经为定义的应用程序列表编写了一个相当简单的脚本记录性能统计信息。该脚本每隔一段时间对进程进行采样(使用psutil)并返回各种统计信息,然后记录这些统计信息。为了方便以后用数据做有趣的事情,我使用了 XML 日志格式。

以下是日志结构的简化版本:

<?xml version="1.0" ?>
<data>
    <periodic>
        <sample name="2015-02-25_23-22-54">
            <cpu app="safari">10.5</cpu>
            <memory app="safari">1024</memory>
            <disk app="safari">60</disk>
            <network app="safari">720</network>
        </sample>
    </periodic>
</data>

我目前正在使用cElementTree 来解析和创建日志文件。采样循环的每次迭代都会解析现有日志文件,将最新数据追加到末尾,然后将新文件写入磁盘。

我的日志写入器类的简化版:

import xml.etree.cElementTree as etree
from xml.dom import minidom

logfile = 'path/to/logfile.xml'

class WriteXmlLog:
    # Parse the logfile.
    def __init__(self):
        self.root = etree.parse(logfile).getroot()
        self.periodic = list(self.root.iter('periodic'))[0]

    def __write_out(self, log_file):
        """Write log contents to file."""
        open(log_file, 'w').write(minidom.parseString(etree.tostring(self.root).replace('\n', '').replace('\t', '')).toprettyxml())

    def __create_timestamp(self):
        """Returns a timestamp for naming a process sample iteration."""
        return datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d_%H-%M-%S')

    def write_sample(self, sample_list):
        """Create or append sample to XML log file."""
        node_sample_time = etree.Element('sample')
        node_sample_time.set('time', self.__create_timestamp())
        for i in sample_list:
            app_dict = i.get('values')
            for a in app_dict:
                sample = etree.Element(a)
                app = str(i.get('appname')).lower()
                sample.set('app', app)
                sample.text = app_dict[a]
                node_sample_time.append(sample)
        self.periodic.append(node_sample_time)
        self.__write_out(logfile)

我遇到的问题是,虽然这个脚本在日志文件很小的情况下工作得很好,但它被用于我们必须每隔几秒对相同的进程进行采样的情况下,有时会运行几天。这可以生成最大 10 MB 的日志文件(此时它们会被轮换)。在这样大小的日志上运行脚本大约需要 15 秒,并且在整个持续时间内固定 1 个 CPU 内核,更不用说过多的内存使用和磁盘 I/O。

__write_out() 可能效率不高,因为它会运行两个搜索和替换操作(以去除导致toprettyxml 混乱的无关换行符和制表符),然后在每次迭代时通过 minidom 发送整个输出。这样做是因为cElementTree 不会自行缩进节点,从而使生成的文件难以阅读。然而,真正的问题似乎只是每次迭代解析和写入整个日志本质上是不可扩展的。

我的第一个想法是完全放弃使用cElementTree,“手动”将结果格式化为 XML 字符串,然后在每次迭代时将它们附加到日志文件的末尾(根本不解析现有文件)。这种方法的问题是生成的文件不是有效的 XML,因为根节点没有结束标记。我可以让记录器在完成后写入一个(它目前设计为无限循环直到SIGTERM,然后在退出时进行一些清理),但我希望日志文件在记录期间始终是有效的 XML。它也似乎有点笨拙。

总结:写入基于 XML 的日志文件的最佳方式是什么?该文件具有良好的性能和合理的资源使用率,可以扩展到大约 10 MB 的日志文件大小?

【问题讨论】:

  • 重写整个日志文件以追加记录对我来说似乎是一个糟糕的主意。日志文件应该很容易附加到。如果有机会摆脱 XML,或者将单文档日志文件替换为文件或数据库中的一系列文档?
  • OP 说他们正在使用 XML,“以便以后更容易地用数据做有趣的事情”——XML 是一个非常好的选择。因为最后可能不得不摆弄“”而放弃它会让尾巴摇摆不定。哦,JSON 也有完全相同的问题(只是最后一个“}”或“]”)。创建数以万计的小文件会带来其他问题,包括速度(对于许多文件系统)。

标签: python xml logging


【解决方案1】:

如果我的理解正确,您可以创建每个“周期性”元素,就好像它是一个完整的文档一样(因此您仍然可以使用 cElementTree 或类似的东西;或者只是手动将其创建为字符串)。

然后当需要写出这样一个(小)元素时,打开你的日志文件,并寻找到末尾减去“”的长度(7)。写入新的周期元素,然后重新写入“”,应该就可以了。

如果您要格外小心,请在移动到最后后读取最后 7 个字符以确保它们与预期一致,然后再次寻找以再次将文件定位在它们之前。

【讨论】:

  • 这几乎就是我最终所做的,而且效果很好。
猜你喜欢
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 2015-03-20
  • 2016-06-05
  • 2014-08-10
  • 1970-01-01
  • 2011-11-04
相关资源
最近更新 更多