解析多行日志文件,其中包含上下文更改行

时间:2016-12-20 19:06:28

标签: python parsing

问题陈述

我运行一个创建大型纯文本日志文件的程序。在那里有很多 我希望提取的数字块用于进一步分析。 不幸的是,那里的结构并不多。一开始就有 一个块,其中包含有关问题大小和数量的一些信息 使用的节点:

Lattice initialized:
  problem size = 16 16 16 32
  layout size = 8 16 16 32
  logical machine size = 1 1 2 4
  subgrid size = 16 16 8 8
  total number of nodes = 8
  total volume = 131072
  subgrid volume = 16384

要获得这些数字,我只是遍历文件中的每一行 然后看看我是否可以在那里使用正则表达式模式total number of nodes = (\d+)。这很有效。

程序进行模拟,需要解决多个线性系统 单个更新的等式。每个都有任意数量的更新 日志文件。它们中的每一个都是由这样的一条线引入的:

Doing Update: 29 warm_up_p = 1

到目前为止,我刚刚提取了整体的性能数据(下面更多) 文件,忽略它们所属的更新。我现在想改变它 在更新的上下文中具有性能,并查看它是否在减速 下来。

使用了不同的求解器,每个求解器都有不同的输出:

invcg:

QDP:FlopCount:invcg2 Performance/CPU: t=2.577605(s) Flops=19473629184 => 7554.93149027877 Mflops/cpu.
QDP:FlopCount:invcg2 Total performance:  7554.93149027877 Mflops = 7.55493149027877 Gflops = 0.00755493149027877 Tflops
CG_SOLVER: 37 iterations. Rsd = 2.06699506385389e-09 Relative Rsd = 5.77136063168358e-13
CG_SOLVER_TIME: 2.72206 sec

minvcg:

MInvCG2: 36 iterations
QDP:FlopCount:minvcg Performance/CPU: t=2.998556(s) Flops=21837643776 => 7282.72000789713 Mflops/cpu.
QDP:FlopCount:minvcg Total performance:  7282.72000789713 Mflops = 7.28272000789713 Gflops = 0.00728272000789713 Tflops

QPhiX Clover CG:

QPHIX_CLOVER_CG_SOLVER: 29 iters,  rsd_sq_final=8.70199859880196e-11
QPHIX_CLOVER_CG_SOLVER: || r || / || b || = 8.70199859880196e-11
QPHIX_CLOVER_CG_SOLVER: Solver Time=0.0646071434020996 (sec)  Performance=239.823066739962 GFLOPS
QPHIX_MDAGM_SOLVER: total time: 0.079926 (sec)

QPhiX Clover Multi-Shift:

QPHIX_CLOVER_MULTI_SHIFT_CG_MDAGM_SOLVER: Iters=30 Solver Time=0.0672321319580078 (sec)  Performance=253.535273917039 GFLOPS
QPHIX_CLOVER_MULTI_SHIFT_CG_MDAGM_SOLVER: total time: 0.225874 (sec)

QPhiX Clover BICGSTAB:

QPHIX_CLOVER_BICGSTAB_SOLVER: 8 iters,  rsd_sq_final=6.82060929874834e-09 ||r||/||b|| (acc) = 9.48324202036213e-08
QPHIX_CLOVER_BICGSTAB_SOLVER: Solver Time=0.114965915679932 (sec)  Performance=35.4751782550446 GFLOPS
QPHIX_CLOVER_BICGSTAB_SOLVER: total_iters=17 || r || / || b || = 8.2259136270501e-08

Gflop / s的表现总是在一条线上。所以我能够使用 像这样的正则表达式来提取数据:

QDP:FlopCount:(\S+) Total performance:  ([\d.]+) Mflops = ([\d.]+) Gflops = ([\d.]+) Tflops

我也希望在上下文中有解算器中的迭代次数 更新号码。最后,我想分析像这些每个解算器一样的东西 算法

  • 迭代次数与更新次数
  • 效果与更新编号
  • 性能与迭代
  • 性能与节点数

解算器的多个行块可以用多行解析 我认为正则表达式。但是因为可以有多个求解器块 对于每个更新号,我需要一些具有状态的解析器。我从来没用过 正确的解析库,但我觉得这是使用它的重点。

编写Python(或C ++,如果库)是一个明智的起点 是更好的程序从这个日志文件中提取数据?

pyparsing Attempt

与此同时,我尝试了几次听过的pyparsing。这是我的尝试,实际上有点工作:

import argparse
import pprint
import textwrap

from pyparsing import Word, Optional, OneOrMore, Group, ParseException, Suppress, SkipTo, ZeroOrMore, Combine

caps = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
lowers = caps.lower()
digits = "0123456789"


def print_results(results):
    lines = []
    #lines.append(str(type(results)))
    if isinstance(results, str):
        lines.append(results)
    else:
        for key, val in sorted(results.items()):
            lines.append(key)
            lines.append(textwrap.indent(print_results(val), '  '))

    return '\n'.join(lines)



def main(options):
    pp = pprint.PrettyPrinter()

    with open(options.logfile) as f:
        content = f.read()

    all_letters = ''.join(sorted(set(content)))

    g_integer = Word(digits)
    g_float = Word(digits + '+-.eE')

    g_subgrid_volume = Suppress('subgrid volume =') + g_integer('subgrid_volume')

    g_invcg = Combine(Suppress('QDP:FlopCount:invcg2 Performance/CPU: t=')
                      + Suppress(g_float)
                      + Suppress('(s) Flops=')
                      + Suppress(g_float)
                      + Suppress(' => ')
                      + Suppress(g_float)
                      + Suppress(' Mflops/cpu.\nQDP:FlopCount:invcg2 Total performance:  ')
                      + Suppress(g_float)
                      + Suppress(' Mflops = ')
                      + g_float('gflops')
                      + Suppress(' Gflops = ')
                      + Suppress(g_float)
                      + Suppress(' Tflops\nCG_SOLVER: ')
                      + g_integer('iterations')
                      + Suppress(' iterations. Rsd = ')
                      + Suppress(g_float)
                      + Suppress(' Relative Rsd = ')
                      + Suppress(g_float)
                      + Suppress('\nCG_SOLVER_TIME: ')
                      + Suppress(g_float)
                      + Suppress(' sec')
                     )

    g_minvcg = Combine(Suppress('MInvCG2: ')
                       + g_integer('iterations')
                       + Suppress(' iterations\nQDP:FlopCount:minvcg Performance/CPU: t=')
                       + Suppress(g_float)
                       + Suppress('(s) Flops=')
                       + Suppress(g_float)
                       + Suppress(' => ')
                       + Suppress(g_float)
                       + Suppress(' Mflops/cpu.\nQDP:FlopCount:minvcg Total performance:  ')
                       + Suppress(g_float)
                       + Suppress(' Mflops = ')
                       + g_float('gflops')
                       + Suppress(' Gflops = ')
                       + Suppress(g_float)
                       + Suppress(' Tflops')
                      )

    g_qphix_clover_cg = Combine(
        'QPHIX_CLOVER_CG_SOLVER: '
        + g_integer('iterations')
        + ' iters,  rsd_sq_final='
        + Suppress(g_float)
        + '\nQPHIX_CLOVER_CG_SOLVER: || r || / || b || = '
        + Suppress(g_float)
        + '\nQPHIX_CLOVER_CG_SOLVER: Solver Time='
        + Suppress(g_float)
        + ' (sec)  Performance='
        + g_float('gflops')
        + ' GFLOPS\nQPHIX_MDAGM_SOLVER: total time: '
        + Suppress(g_float)
        + ' (sec)'
    )

    g_update = Suppress('Doing Update:') + g_integer('update_no')
    g_before_update = SkipTo(g_update)
    g_solver_block = (g_invcg('invcg') | g_minvcg('minvcg') | g_qphix_clover_cg('qphix_clover_cg'))
    g_solver_blocks = Suppress(SkipTo(g_solver_block)) + g_solver_block
    g_update_block = Suppress(g_before_update) + Group(g_update + OneOrMore(g_solver_blocks))('update_block')
    g_logfile = Suppress(SkipTo(g_subgrid_volume)) + g_subgrid_volume + OneOrMore(g_update_block)

    results = g_logfile.parseString(content)

    print(print_results(results))

对于一个小文件只需几秒钟,并提供所需的输出:

subgrid_volume
  16384
update_block
  qphix_clover_cg
    gflops
      375.885935327638
    iterations
      20
  update_no
    1

典型文件有274480行,需要261秒才能解析。不太酷。这不是一个真正的问题,因为生成数据的过程会运行三到六个小时。

1 个答案:

答案 0 :(得分:1)

除非您处理大量数据并且性能是一个很大的问题,否则我的感觉是没有理由对此过于复杂。通过更新行拆分日志文件

update_lines = {}
with open(log_path, 'r') as fh:
  update = None
  for line in fh.readlines():
    if update is None:
      continue
    match = re.match('^Doing Update: (\d+) warm_up_p = (\d+)', line)
    if match:
      update = match.group(0)
      update_lines[update] = []
      continue
    update_lines[update].append(line)

然后,通过一系列解析特定解算器的函数为每个更新提供行列表

for update, lines in update_lines.items():
  invcg_info = parse_invcg(lines)
  minvcg_info = parse_minvcg(lines)

  # etc, etc.

如果您受到文件/速度大小的限制,但有限,则可以将解析链接到单个函数中,以避免一遍又一遍地遍历相同的行。我的两分钱,但我觉得任何基于EBNF的解析都可能有点过分。