Skip to content

Commit b416e6b

Browse files
authored
refactor: Optimize log file reading logic (#11064)
* refactor: Optimize log file reading with buffered reader pool and improve pagination logic * refactor: Enhance file reading logic with improved buffer management and constant usage * refactor: Adjust maximum read file size and enhance line reading functionality
1 parent ac1f49f commit b416e6b

File tree

2 files changed

+146
-69
lines changed

2 files changed

+146
-69
lines changed

agent/app/service/file.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8-
"github.com/1Panel-dev/1Panel/agent/app/task"
9-
"github.com/1Panel-dev/1Panel/agent/i18n"
10-
"github.com/1Panel-dev/1Panel/agent/utils/convert"
11-
"github.com/1Panel-dev/1Panel/agent/utils/ini_conf"
128
"io"
139
"io/fs"
1410
"os"
@@ -21,6 +17,11 @@ import (
2117
"time"
2218
"unicode/utf8"
2319

20+
"github.com/1Panel-dev/1Panel/agent/app/task"
21+
"github.com/1Panel-dev/1Panel/agent/i18n"
22+
"github.com/1Panel-dev/1Panel/agent/utils/convert"
23+
"github.com/1Panel-dev/1Panel/agent/utils/ini_conf"
24+
2425
"github.com/1Panel-dev/1Panel/agent/app/dto"
2526
"github.com/jinzhu/copier"
2627
"golang.org/x/text/encoding"
@@ -591,7 +592,7 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
591592
scope string
592593
logFileRes *dto.LogFileRes
593594
)
594-
if stat.Size() > 500*1024*1024 {
595+
if stat.Size() > files.MaxReadFileSize {
595596
lines, err = files.TailFromEnd(logFilePath, req.PageSize)
596597
isEndOfFile = true
597598
scope = "tail"

agent/utils/files/utils.go

Lines changed: 140 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ package files
33
import (
44
"bufio"
55
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"os/user"
10+
"path/filepath"
11+
"strconv"
12+
"strings"
13+
"sync"
14+
615
"github.com/1Panel-dev/1Panel/agent/app/dto"
716
"github.com/1Panel-dev/1Panel/agent/buserr"
817
"github.com/1Panel-dev/1Panel/agent/constant"
@@ -14,13 +23,11 @@ import (
1423
"golang.org/x/text/encoding/simplifiedchinese"
1524
"golang.org/x/text/encoding/traditionalchinese"
1625
"golang.org/x/text/encoding/unicode"
17-
"io"
18-
"net/http"
19-
"os"
20-
"os/user"
21-
"path/filepath"
22-
"strconv"
23-
"strings"
26+
)
27+
28+
const (
29+
MaxReadFileSize = 512 * 1024 * 1024
30+
tailBufSize = int64(32768)
2431
)
2532

2633
func IsSymlink(mode os.FileMode) bool {
@@ -78,27 +85,33 @@ func IsHidden(path string) bool {
7885
return len(base) > 1 && base[0] == dotCharacter
7986
}
8087

81-
func countLines(path string) (int, error) {
82-
file, err := os.Open(path)
83-
if err != nil {
84-
return 0, err
85-
}
86-
defer file.Close()
87-
reader := bufio.NewReader(file)
88-
count := 0
89-
for {
90-
_, err := reader.ReadString('\n')
91-
if err != nil {
92-
if err == io.EOF {
93-
if count > 0 {
94-
count++
95-
}
96-
return count, nil
97-
}
98-
return count, err
88+
var readerPool = sync.Pool{
89+
New: func() interface{} {
90+
return bufio.NewReaderSize(nil, 8192)
91+
},
92+
}
93+
94+
var tailBufPool = sync.Pool{
95+
New: func() interface{} {
96+
buf := make([]byte, tailBufSize)
97+
return &buf
98+
},
99+
}
100+
101+
func readLineTrimmed(reader *bufio.Reader) (string, error) {
102+
line, err := reader.ReadString('\n')
103+
if err == io.EOF {
104+
if len(line) == 0 {
105+
return "", io.EOF
99106
}
100-
count++
107+
err = nil
101108
}
109+
if err != nil {
110+
return "", err
111+
}
112+
line = strings.TrimSuffix(line, "\n")
113+
line = strings.TrimSuffix(line, "\r")
114+
return line, nil
102115
}
103116

104117
func TailFromEnd(filename string, lines int) ([]string, error) {
@@ -114,24 +127,26 @@ func TailFromEnd(filename string, lines int) ([]string, error) {
114127
}
115128
fileSize := stat.Size()
116129

117-
bufSize := int64(4096)
130+
bufPtr := tailBufPool.Get().(*[]byte)
131+
buf := *bufPtr
132+
defer tailBufPool.Put(bufPtr)
133+
118134
var result []string
119135
var leftover string
120136

121137
for offset := fileSize; offset > 0 && len(result) < lines; {
122-
readSize := bufSize
123-
if offset < bufSize {
138+
readSize := tailBufSize
139+
if offset < tailBufSize {
124140
readSize = offset
125141
}
126142
offset -= readSize
127143

128-
buf := make([]byte, readSize)
129-
_, err := file.ReadAt(buf, offset)
144+
_, err := file.ReadAt(buf[:readSize], offset)
130145
if err != nil && err != io.EOF {
131146
return nil, err
132147
}
133148

134-
data := string(buf) + leftover
149+
data := string(buf[:readSize]) + leftover
135150
linesInChunk := strings.Split(data, "\n")
136151

137152
if offset > 0 {
@@ -142,20 +157,28 @@ func TailFromEnd(filename string, lines int) ([]string, error) {
142157
}
143158

144159
for i := len(linesInChunk) - 1; i >= 0; i-- {
145-
if len(result) < lines {
146-
if !(i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0) {
147-
result = append([]string{linesInChunk[i]}, result...)
148-
}
160+
if len(result) >= lines {
161+
break
162+
}
163+
if i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0 {
164+
continue
149165
}
166+
// 反插数据
167+
result = append(result, linesInChunk[i])
150168
}
151169
}
152170

153171
if leftover != "" && len(result) < lines {
154-
result = append([]string{leftover}, result...)
172+
result = append(result, leftover)
155173
}
156174

157175
if len(result) > lines {
158-
result = result[len(result)-lines:]
176+
result = result[:lines]
177+
}
178+
179+
// 反转数据
180+
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
181+
result[i], result[j] = result[j], result[i]
159182
}
160183

161184
return result, nil
@@ -165,6 +188,10 @@ func ReadFileByLine(filename string, page, pageSize int, latest bool) (res *dto.
165188
if !NewFileOp().Stat(filename) {
166189
return
167190
}
191+
if pageSize <= 0 {
192+
err = fmt.Errorf("pageSize must be positive")
193+
return
194+
}
168195
file, err := os.Open(filename)
169196
if err != nil {
170197
return
@@ -176,43 +203,92 @@ func ReadFileByLine(filename string, page, pageSize int, latest bool) (res *dto.
176203
return
177204
}
178205

179-
if fi.Size() > 500*1024*1024 {
206+
if fi.Size() > MaxReadFileSize {
180207
err = buserr.New("ErrLogFileToLarge")
181208
return
182209
}
183210

184-
totalLines, err := countLines(filename)
185-
if err != nil {
186-
return
187-
}
188211
res = &dto.LogFileRes{}
189-
total := (totalLines + pageSize - 1) / pageSize
190-
res.TotalPages = total
191-
res.TotalLines = totalLines
192-
reader := bufio.NewReaderSize(file, 8192)
212+
reader := readerPool.Get().(*bufio.Reader)
213+
reader.Reset(file)
214+
defer readerPool.Put(reader)
193215

194216
if latest {
195-
page = total
196-
}
197-
currentLine := 0
198-
startLine := (page - 1) * pageSize
199-
endLine := startLine + pageSize
200-
lines := make([]string, 0, pageSize)
201-
for {
202-
line, _, err := reader.ReadLine()
203-
if err == io.EOF {
204-
break
217+
ringBuf := make([]string, pageSize)
218+
writeIdx := 0
219+
totalLines := 0
220+
221+
for {
222+
line, readErr := readLineTrimmed(reader)
223+
if readErr == io.EOF {
224+
break
225+
}
226+
if readErr != nil {
227+
err = readErr
228+
return
229+
}
230+
ringBuf[writeIdx%pageSize] = line
231+
writeIdx++
232+
totalLines++
233+
}
234+
235+
if totalLines == 0 {
236+
res.Lines = []string{}
237+
res.TotalLines = 0
238+
res.TotalPages = 0
239+
res.IsEndOfFile = true
240+
return
241+
}
242+
243+
total := (totalLines + pageSize - 1) / pageSize
244+
res.TotalPages = total
245+
res.TotalLines = totalLines
246+
247+
lastPageSize := totalLines % pageSize
248+
if lastPageSize == 0 {
249+
lastPageSize = pageSize
250+
}
251+
if lastPageSize > totalLines {
252+
lastPageSize = totalLines
205253
}
206-
if currentLine >= startLine && currentLine < endLine {
207-
lines = append(lines, string(line))
254+
255+
result := make([]string, 0, lastPageSize)
256+
startIdx := writeIdx - lastPageSize
257+
for i := 0; i < lastPageSize; i++ {
258+
idx := (startIdx + i) % pageSize
259+
result = append(result, ringBuf[idx])
208260
}
209-
currentLine++
210-
if currentLine >= endLine {
211-
break
261+
res.Lines = result
262+
res.IsEndOfFile = true
263+
} else {
264+
startLine := (page - 1) * pageSize
265+
endLine := startLine + pageSize
266+
currentLine := 0
267+
lines := make([]string, 0, pageSize)
268+
269+
for {
270+
line, readErr := readLineTrimmed(reader)
271+
if readErr == io.EOF {
272+
break
273+
}
274+
if readErr != nil {
275+
err = readErr
276+
return
277+
}
278+
279+
if currentLine >= startLine && currentLine < endLine {
280+
lines = append(lines, line)
281+
}
282+
currentLine++
212283
}
284+
285+
res.Lines = lines
286+
res.TotalLines = currentLine
287+
total := (currentLine + pageSize - 1) / pageSize
288+
res.TotalPages = total
289+
res.IsEndOfFile = page >= total
213290
}
214-
res.Lines = lines
215-
res.IsEndOfFile = currentLine < endLine
291+
216292
return
217293
}
218294

0 commit comments

Comments
 (0)