Python - 使用"可疑"对日志文件进行二进制搜索时间

时间:2016-02-29 10:23:48

标签: python search logging

有没有办法对"可疑时间进行有效的二进制搜索"在使用Python的日志文件中?

我有一个日志文件,其条目如下所示:

02:38:18  0  RcvTxData - 11 : Telegram received and process completed - MCP35 Tx -24239
02:38:20  0  RcvNewTxNo - 3 : MCP36 Set receive trigger
02:38:21  0  RcvNewTxNo - 1 : 
02:38:21  0  RcvNewTxNo - 1 : MCP35 get new Tx 24241
02:38:23  0  RcvTxData - 11 : Telegram received and process completed - MCP36 Tx -13918
02:38:23  0  RcvNewTxNo - 3 : MCP36 Set receive trigger
02:38:24  0  RcvNewTxNo - 1 : 
02:38:24  0  RcvTxData - 11 : Telegram received and process completed - MCP35 Tx -24241
02:38:24  0  RcvNewTxNo - 3 : MCP35 Set receive trigger
02:38:27  0  RcvNewTxNo - 1 : 
02:38:27  0  RcvNewTxNo - 1 : MCP36 get new Tx 13920
09:44:54  0  RcvNewTxNo - 1 : 
09:44:54  0  RcvNewTxNo - 1 : MCP24 get new Tx 17702
09:44:54  0  RcvNewTxNo - 2 : MCP24 Read last Tx before new Tx 17702
09:44:56  0  RcvNewTxNo - 1 : 
09:45:00  0  RcvTxData - 7 :MCP24 Prepare normal TxData to DB
09:45:01  0  RcvTxData - 8 :MCP24 complete call GetTxData
09:45:02  0  RcvTxData - 11 : Telegram received and process completed - MCP10 Tx -9008
09:45:02  0  RcvNewTxNo - 3 : MCP10 Set receive trigger
09:45:04  0  RcvNewTxNo - 1 : 
09:45:04  0  RcvNewTxNo - 3 : MCP24 Set receive trigger
09:45:16  0  RcvNewTxNo - 1 : 
09:45:16  0  RcvNewTxNo - 1 : MCP19 get new Tx 9133
09:45:16  0  RcvNewTxNo - 2 : MCP19 Read last Tx before new Tx 9133
09:45:17  0  RcvTxData - 1 :MCP19 gwTx-9133 lastTx-9131 newTx-0
09:45:17  0  RcvTxData - 4 :MCP19 Adjusted newTxNo_Val-9132
09:45:17  0  RcvTxData - 4.1 :MCP19 FnCode PF
09:45:23  0  RcvTxData - 1 :MCP24 gwTx-17706 lastTx-17704 newTx-0

从上面的示例中可以看出,日志的时间没有减少,并且可能会突然跳跃:

02:38:27  0  RcvNewTxNo - 1 : MCP36 get new Tx 13920
09:44:54  0  RcvNewTxNo - 1 : #there is a big jump here

我的目标是检测这条可疑线,返回它的线和索引。

我已经创建了一个功能来检测这个"可疑时间"。但是,日志文件的大小各约为22,00044,000行。因此,我的算法非常慢,因为我从一行到另一行:

f = open(fp, "r")
notEmpty = True
oldTime = None
while(notEmpty): #this can be executed 22,000 - 44,000 times
    l = f.readline()
    notEmpty = l != ""
    if not notEmpty:
        break
    t = datetime.datetime.strptime(l[0:8], fmt)
    if oldTime is None:
        oldTime = t
    else:
        tdelta = t - oldTime
        if tdelta.seconds > 3600: #more than 1 hour is suspicious
            print("suspicious time: " + str(t) + "\told time: " + str(oldTime))
        oldTime = t

有没有办法在Python的日志文件中通过二进制搜索等方式加快搜索速度?

(注意:建议任何替代搜索以外的二分搜索,只要它比强力搜索更好也同样赞赏)

修改

我已部分实施Torxed的解决方案(并修复了一些错误):

with open(fp, 'rb') as fh:
    prev_time = datetime.datetime.strptime(fh.readline()[0:5].decode('utf-8'), '%H:%M')
    index = 0
    for line in fh:
        if len(line) == 0: continue
        index += 1
        t = datetime.datetime.strptime(line[0:4].decode('utf-8'), '%H:%M')
        if (t - prev_time).total_seconds() > 3600:
            print('\tSuspicious time:', t, '\told time:', prev_time, '\tat: ', str(index))
        prev_time = t

然而,正如他提出的一些" hacks"在答案中,我还想添加一些文件的其他特性,这些特征可能值得"黑客攻击"为了增加性能的好处:

  1. 整个文件的时间戳,如果没有可疑时间,从第一个条目到最后一个条目的持续时间不超过6小时
  2. 在两个时间戳之间,如果没有可疑时间,则差异不超过1小时
  3. 可疑时间很可能发生在第20,000行之后和第30,000行之前(因此很可能会跳过其他一些行)。
  4. 有没有办法进一步实施" hack"这里吗?

1 个答案:

答案 0 :(得分:4)

这一切都归结为重新分解代码以使其高效(从硬件+缓存的角度来看)。
我会考虑一些设计更改并优化代码,以便在执行读取操作时不创建或调用任何不必要的东西。

prev_time = None
with open(fp, 'rb') as fh:
    prev_time = datetime.datetime.strptime(fh.readline()[0:5].decode('utf-8'), '%H:%M')

    for line in fh:
        if len(line) == 0: continue

        t = datetime.datetime.strptime(line[0:5].decode('utf-8'), '%H:%M')
        if (t - prev_time).total_seconds() > 3600:
            print('Suspicious time:', t, '\told time:', prev_time)
        prev_time = t

首先,我们不是尝试逻辑是old_time无?,而是在我们进入大for ...循环之前,我们只需获取第一行并在其中加入时间。这样我们为每行读取节省了几微秒,最后捆绑了很多。

然后我们也使用with open只是因为我们不希望在结尾处打开任何文件句柄。如果您要浏览大量文件,这很重要。

我们还使用is not notEmpty跳过is this line empty, if so continue 3行逻辑。

我们还缩短了时间转换为不包括秒,这是一个小编辑,但最终可能节省了大量时间,因为我们只使用了2/3的数据总数为了您的运营。

最后一项改进是我们将文件作为二进制对象打开,这意味着我们跳过可能在Python代码中完成的任何自动binary -> hex/ascii转换。这将对处理速度产生巨大影响,唯一的缺点是strptime将需要像对象一样的字符串。我的计算(我没有巨大的文本文件源atm)是5个字母的转换将比将文档数据从二进制转换为字符串数据的python内部的整体速度更快。 我可能在这里错了。

希望这会稍微改善你的时间 哦,记住这只是一种方式,这意味着如果时差向后变化,你会得到一个负值(它可能不会以顺序时间日志格式......但你永远不会知道)

编辑:

寻求黑客

如果你可以预测每条线长度的粗略估计,那么它实际上会更快:

data = fh.read(5)
t = datetime.datetime ...
fh.seek(128) # Skip 128 bytes, hopefully this is enough to find a new line.:
data = fh.read(5) # again
                              # This just shows you the idea, obviously not perfect working code here hehe.

要获取00:00时间戳,显然需要对此逻辑进行更多处理,例如,您需要监控是否实际通过了行标记\r\n等,但是看到了Python不知道一行是多长,但是寻找\r\n标记与你粗略地知道并且能够跳过大部分数据是时间寻求的巨大优势。所以考虑一下这个,因为跳过大部分数据与使用广义的操作功能总是会更快。

请注意,我们在这里追求微秒,所以每个坚果的想法和体力劳动都可能在这里得到回报。

Additiona黑客使用搜索

假设您知道在大堆中有足够的类似时间戳,您可以轻松地跳过几行:

for line in fh:
    if len(line) == 0: continue
    # Check the line

    fh.seek(56 * 10000) # Average length of a line is 56 characters (calculated this over a few of your lines, so give or take +-10 here)
                                     # And we multiply this with 10000, essentially skipping ~10k lines

如果此处有 BIG 跳跃,您可以这样做:

    if diff > 3600:
        fh.seek(fh.tell() - 5000)

然后跳回5000行并检查时差是否仍然像10k线那样大,那么也许你确实有时差。你也可以用它来缩小发生时差的地方(但我会把它留给你,有最简单的方法可以用最少的手工劳动来找到它,而不会占用处理能力)。

基本上,这可以归结为在最好的世界中寻求~4并通过检查新的行结尾等手动执行for line in fh。这样的事情:

from functools import partial
import datetime
jump_gap = 56 * 10000 # average row length * how many rows you want to jump

def f_jump(fh, jump_size):
    fh.seek(fh.tell() + jump_size)
    while fh.read(1) not in ('\n', ''):
        continue
    return True

with open('log.txt', 'rb') as fh:
    prev_time = datetime.datetime.strptime(fh.read(5).decode('utf-8'), '%H:%M')
    f_jump(fh, jump_gap) # Jump to the next jump since we got a starting time

    for chunk in iter(partial(fh.read, 5), ''): # <-- Note here! We only read 5 bytes
                                                                 # there for it's very important we check for new
                                                                 # rows manually and set the pointed at the start
                                                                 # of a new line, this is what `f_jump()` does later.
        if chunk == '':
            break # we clearly hit rock bottom

        t = datetime.datetime.strptime(chunk.decode('utf-8'), '%H:%M')
        if (t - prev_time).total_seconds() > 3600:
            print('\tSuspicious time:', t, '\ttold time:', prev_time, '\tat: ', fh.tell())

        prev_time = t
        f_jump(fh, jump_gap)

免责声明:一个限制,我从不计算行数。但我确实向您展示了日志文件中发生这种情况的位置。

这很有用,因为你可以这样做:

('\tSuspicious time:', datetime.datetime(1900, 1, 1, 9, 44), '\ttold time:', datetime.datetime(1900, 1, 1, 2, 38), '\tat: ', 636)

你取636这是档案位置,
你输入tail就像这样:

[user@firefox ~]$ tail -c 636 log.txt 
ete call GetTxData
09:45:02  0  RcvTxData - 11 : Telegram received and process completed - MCP10 Tx -9008
09:45:02  0  RcvNewTxNo - 3 : MCP10 Set receive trigger

这显示了发生问题的 ish 行,我现在可以回溯这些内容了。
或者我可以去香蕉并抛出一些Linux忍者魔法并做:

 x=`tail -c 636 log.txt -n 1`; grep -B 20 -A 3 "$x" log.txt

这给了我确切的数据,其中发生的事情也发生了20行,所以我可以稍微回溯一下。

既然您想要行号(可能是您的老板或同事),您可以将-n添加到grep命令并以此方式获取:

x=`tail -c 636 log.txt -n 1`; grep -B 20 -A 3 -n "$x" log.txt

[user@firefox ~]$ x=`tail -c 636 log.txt -n 1`; grep -B 20 -A 3 -n "$x" log.txt
8-02:38:24  0  RcvTxData - 11 : Telegram received and process completed - MCP35 Tx -24241
9-02:38:24  0  RcvNewTxNo - 3 : MCP35 Set receive trigger
10-02:38:27  0  RcvNewTxNo - 1 : 
11-02:38:27  0  RcvNewTxNo - 1 : MCP36 get new Tx 13920
12-09:44:54  0  RcvNewTxNo - 1 : 
13-09:44:54  0  RcvNewTxNo - 1 : MCP24 get new Tx 17702
14-09:44:54  0  RcvNewTxNo - 2 : MCP24 Read last Tx before new Tx 17702
15-09:44:56  0  RcvNewTxNo - 1 : 
16-09:45:00  0  RcvTxData - 7 :MCP24 Prepare normal TxData to DB
17-09:45:01  0  RcvTxData - 8 :MCP24 complete call GetTxData
18-09:45:02  0  RcvTxData - 11 : Telegram received and process completed - MCP10 Tx -9008
19-09:45:02  0  RcvNewTxNo - 3 : MCP10 Set receive trigger
20-09:45:04  0  RcvNewTxNo - 1 : 
21-09:45:04  0  RcvNewTxNo - 3 : MCP24 Set receive trigger
22-09:45:16  0  RcvNewTxNo - 1 : 
23-09:45:16  0  RcvNewTxNo - 1 : MCP19 get new Tx 9133
24-09:45:16  0  RcvNewTxNo - 2 : MCP19 Read last Tx before new Tx 9133
25-09:45:17  0  RcvTxData - 1 :MCP19 gwTx-9133 lastTx-9131 newTx-0
26-09:45:17  0  RcvTxData - 4 :MCP19 Adjusted newTxNo_Val-9132
27-09:45:17  0  RcvTxData - 4.1 :MCP19 FnCode PF
28:09:45:23  0  RcvTxData - 1 :MCP24 gwTx-17706 lastTx-17704 newTx-0

由于seek() hack的性质,细粒度这可能有点困难,但在这个例子中,我得到row 28的命中,这不是问题的实际行,但它给了我一个近距离展示,并且tail + grep我可以快速回溯它,row 12是错误的时差。

我希望这会有所帮助。