Golang 按行解析日志测试 ldetool

比较早的时候写过一篇 Python Golang 解析web日志正则一例 当时发现 Golang的正则模块性能不是很好。最近在逛 reddit的时候发现了这样一篇文章 Fast log parsing with Go ,文中提到了一个基于行做半结构化解析的库ldetool, 于是又做了一些尝试和学习。

ldetool 这个工具更像一个DSL,通过一种语法来写解析的语法,然后通过 ldetool generate 命令来生成 golang 代码,这个代码就是针对某中格式单行的解析方法,详细的教程大家可以参考 https://github.com/sirkon/ldetool/blob/master/README.md ,下面是一个简单的测试过程的记录。

测试的环境 Mac Pro 8G4Core,粗略的看下性能,并不严谨

ldetool

安装

通过 go get就可以安装, 安装之后就是个命令

▶ go version
go version go1.11 darwin/amd64

go get -u github.com/sirkon/ldetool

▶ ldetool -v
ldetool version 0.0.0

场景

测试的样本是类似nginx的access log文件,格式如下

27.154.70.117 - - [20/Nov/2018:20:23:56 +0800] 2 "GET http://7img1.tianlaikge.com/tv/homeimage/201709/shengridangao.zip HTTP/1.1" 200 842186 841382 "-" "-" "Dalvik/2.1.0 (Linux; U; Android 7.1.2; vivo Y79A Build/N2G47H)" "-" 219082621 "HIT" 27.159.73.35

测试需求,解析30M左右的日志文件,大概12w行,计算总流量(流量字段为 842186)

分析

这种日志文件有几个特点

  • 每个记录就是一行文件
  • 虽然是文本,但是不是完全的无结构,算半结构化
  • 上下行基本无关,可以并行处理

通常的解析方案

  • 使用正则表达式(后面有个python版本的使用正则来分析)
  • 使用split方法,根据特征分隔符来分割,例如空格,双引号
  • 使用DSL,本质还是类似split,不过是通过代码生成器自动生成解析代码(上面提到的 ldetool 命令啦)

测试

源代码在 https://github.com/orangle/snippets/tree/master/golang/log_parse ,主要是对比split方式和ldetool生成的代码的性能(因为之前测试regex效果并不好,这里没有加入测试)

直接执行函数,使用 time 来测量的结果(多次测试下来,下面两个算是平均值)

# split
▶ time go run main.go nginx_lde.go
total:  96130191409
go run main.go nginx_lde.go  1.80s user 0.25s system 106% cpu 1.914 total

# ldetool
▶ time go run main.go nginx_lde.go
total:  96130191409
go run main.go nginx_lde.go  1.36s user 0.23s system 108% cpu 1.464 total

使用 golang test benckmark 测量的结果

▶ go test -bench=. -benchmem
total:  96130191409
goos: darwin
goarch: amd64
pkg: log_parse
BenchmarkSplit-4     	       1	1549334472 ns/op	512189744 B/op	 1629989 allocs/op
total:  96130191409
BenchmarkLdetool-4   	       1	1052007947 ns/op	 2996064 B/op	   24805 allocs/op
PASS
ok  	log_parse	2.608s

总的来看,ldetool还是能快40~50%的,内存使用和内存分配更是少了很多。

Python

之后我还做了个同样样本的Python测试,主要是正则表达式和split方式的对比,也可以参照golang的结果对比。

代码在 https://github.com/orangle/snippets/tree/master/python/regex_vs_split

python re, re没有 compile

▶ python regex_vs_split.py
total bytes: 96130099518 cost: 5.805 qps: 92174.132

python re, re compile之后

▶ python regex_vs_split.py
total bytes: 96130099518 cost: 5.324 qps: 100500.519

python split 之后分析

▶ python regex_vs_split.py
total bytes: 96130191409 cost: 3.957 qps: 135215.152

这样看起来 split要比 regex 快一些,但是比golang又慢蛮多的。

End

下次在遇到这种需求,可以尝试下 ldetool

已标记关键词 清除标记
<div class="post-text" itemprop="text"> <p>What would be an efficient (performance and readability) of parsing lines in a log file and extracting points of interest?</p> <p>For example:</p> <pre><code>*** Time: 2/1/2019 13:51:00 17.965 Pump 10 hose FF price level 1 limit 0.0000 authorise pending (Type 00) 17.965 Pump 10 State change LOCKED_PSTATE to CALLING_PSTATE [31] 38.791 Pump 10 delivery complete, Hose 1, price 72.9500, level 1, value 100.0000, volume 1.3700, v-total 8650924.3700, m-total 21885705.8800, T13:51:38 </code></pre> <p>Things I need to extract are 10 (for pump 10), Price Level. Limit The _PSTATE changes the values from the delivery completel line etc.</p> <p>Currently I'm using a regular expression to capture each one and using capture groups. But it feels inefficient and there is quite a bit of duplication.</p> <p>For example, I have a bunch of these:</p> <pre><code>reStateChange := regexp.MustCompile(`^(?P<offset>.*) Pump (?P<pump>\d{2}) State change (?P<oldstate>\w+_PSTATE) to (?P<newstate>\w+)_PSTATE`) </code></pre> <p>Then inside a while loop</p> <pre><code>if match := reStateChange.FindStringSubmatch(text); len(match) > 0 { matched = true for i, name := range match { result[reStateChange.SubexpNames()[i]] = name } } else if match := otherReMatch.FindStringSubmatch(text); len(match) > 0 { matched = true for i, name := range match { result[reStateChange.SubexpNames()[i]] = name } } else if strings.Contains(text, "*** Time:") { } </code></pre> <p>It feels that there could be a much better way to do this. I would trade some performance for readability. The log files are only really 10MB max. Often smaller.</p> <p>I'm after some suggestions on how to make this better in golang.</p> </div>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页