【发布时间】:2021-11-27 21:26:18
【问题描述】:
我目前必须解析大约 35000 到 50000 个日志文件来提取感兴趣的行。 由于限制和政策,我必须在没有任何外部库的情况下在 Powershell 中完成。
日志大小介于 100 kB 和 1000 kB 之间。
我将结果写入一个文件,该文件末尾大约有 500 万到 700 万行。
性能令人毛骨悚然……解析 50000 条日志并将结果写入输出文件大约需要 1 小时 15 分钟。
我只知道这对性能不利:
if (($result[1..8] -join "").Trim() -ne "") {
还有一个复杂度为 O(V*E) 的嵌套循环如果我错了,请纠正我
foreach ($file in $fileList) {
...
while (($line = $reader.ReadLine()) -ne $null) {
...
$search 变量保存字符串“自定义日志条目:”
根据您的要求,这里是日志文件内容的示例:
2021 年 10 月 2 日星期六 00:20:12 信息:带有一些信息的字符串: mail.address@domain.com
2021 年 10 月 2 日星期六 00:20:12 信息:第二 带有一些信息的字符串
2021 年 10 月 2 日星期六 00:20:12 信息:XZY 000000000 关于当前线路的一些信息
Sat Oct 2 00:20:12 2021 信息:XZY 000000000 发生了一些动作:动作
10 月 2 日星期六 2021 年 00:20:12 信息:XZY 000000000 使用了某些东西:使用过的对象
2021 年 10 月 2 日星期六 00:20:12 信息:XZY 000000000 有关的一些信息 当前线路
Sat Oct 2 00:20:12 2021 信息:XZY 000000000 一些 当前线路信息
Sat Oct 2 00:20:12 2021 信息: XZY 000000000 部分数据:判决否定
2021 年 10 月 2 日星期六 00:20:12 信息:XZY 000000000 自定义日志条目:重要线路
10 月 2 日星期六 2021 年 00:20:12 信息:XZY 000000000 一些信息
10 月 2 日星期六 2021 年 00:20:12 自定义日志条目:重要行
我查看了foreach -parallel (...),但工作流的限制实在是太可怕了......
也许只是打开每个文件,将其写入 MemoryStream,然后处理所有文件(RAM 不是问题)?
你们能给我一些关于如何加快速度的建议吗?
下面是对代码的更全面的了解:
try {
# Output stream in which we write.
$outStream = New-Object System.IO.FileStream( `
"C:\Users\anon\outfile.csv", `
[System.IO.FileMode]::Create, `
[System.IO.FileAccess]::Write, `
[System.IO.FileAccess]::Read)
# Writer object which is used to write to stream.
$outWrite = New-Object System.IO.StreamWriter($outStream)
# Iterate through files.
foreach ($file in $fileList) {
try {
# Create reader stream for log.
$reader = New-Object System.IO.StreamReader($file)
# Length of time stamp
$fileNameDateLen = fileNameDateFormat.Length
$fileNameDate = $file.Substring($file.Length - 2 - $fileNameDateLen, $fileNameDateLen)
# Convert to usable DateTime object.
$fileNameDateConverted = ([System.DateTime]::ParseExact(`
$fileNameDate, `
$fileNameDateFormat, `
[System.Globalization.CultureInfo]::InvariantCulture))
# Change format of extracted file name date.
$fileNameDateConverted = $fileNameDateConverted.Date.ToString("yyyy-MM-dd")
# StringBuilder for storing row values.
$rowBuffer = New-Object System.Text.StringBuilder
# Iterate through files.
while (($line = $reader.ReadLine()) -ne $null) {
# Validate line.
if ($line -Match $search) {
# Calc position of relevant data.
$pos = $line.IndexOf($search) + $searchLength
# Actual length of relevant data.
$relLength = $line.Length - $pos
# Extract relevant data.
$result = $line.Substring($pos, $relLength).Trim().Split(';')
# Check if line is empty.
if (($result[1..8] -join "").Trim() -ne "") {
# Get timestamp from line.
#$timeValue = $timeRegex.Match($line).Value
$timeValue = $line.Substring(12, 8)
# Combine date from file name with time.
$dateString = "$fileNameDateConverted $timeValue"
# Format timestamp.
$timeStamp = Get-Date $dateString -Format "yyyy-MM-dd HH:mm:ss"
# Format last result.
$result[8] = $result[8] -Replace "^""|""$"
# Create CSV row.
[void] $rowBuffer.AppendLine("$timeStamp;$($result[1]);$($result[2]);" `
+ "$($result[3]);$($result[4]);$($result[5]);" `
+ "$($result[6]);$($result[7]);$($result[8])")
}
}
}
# Write results to file.
$outWrite.Write($rowBuffer.ToString())
# Clear buffer.
[void] $rowBuffer.Clear()
# Close input.
[void] $reader.Close()
# Free input memory.
[void] $reader.Dispose()
}
catch {
if ($rowBuffer -ne $null) {
[void] $rowBuffer.Clear()
}
if ($reader -ne $null) {
[void] $reader.Close()
[void] $reader.Dispose()
}
}
}
$sp.Stop()
Write-Host "Finished after $($sp.Elapsed)"
}
catch {
if ($outWrite -ne $null) {
[void] $outWrite.Dispose()
}
if ($outStream -ne $null) {
[void] $outStream.Dispose()
}
}
finally {
# Close and free output.
[void] $outWrite.Close()
[void] $outStream.Close()
[void] $outWrite.Dispose()
[void] $outStream.Dispose()
}
【问题讨论】:
-
虽然您很可能会在 StackOverflow 上获得一些极好的建议,但您可能需要考虑将此类问题发布到 codereview.stackexchange.com。也就是说,我会去看看你有什么,看看我是否发现了什么。这里有一些用户对这种事情很了不起,但我可能会遇到一些可以提供帮助的东西。
-
你可以尝试预编译你的正则表达式——比如
$myRegex = [Regex]::new($search, "Compiled, IgnoreCase, CultureInvariant")然后$match = $myRegex.Match($line); if( $match.Success ) { ... }。$match对象为您提供匹配的开始、长度和字符串,因此您可以更快地到达$result($result = $match.Value.Trim().Split(";")。您需要测量性能以查看它是否真的有帮助...... -
@Max 请添加一些示例条目to your question(必要时清除任何敏感内容)
-
它确实在幕后使用了正则表达式,但我不知道它是优化使用相同模式的重复调用,还是每次都盲目地重新解析你的模式。如果您创建自己的正则表达式对象,您知道它是预编译的,并且您还可以获得
System.Text.RegularExpressions.Match对象的所有位置属性。 -
@mclayton:PowerShell performs its own regex caching,每个不同的正则表达式选项集最多 1000 个正则表达式。 .NET itself performs caching too,但仅适用于传递给 static 方法调用的正则表达式。这种缓存使用“高级代码”作为编译目标,而只有 显式 编译产生可以 JITted 的 MSIL 代码。
标签: powershell performance parsing logging