@@ -3,6 +3,15 @@ package files
33import (
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
2633func 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
104117func 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