Skip to content

Commit e6d7f74

Browse files
authored
working on a buffered log viewer class (#1301)
1 parent 4a330a4 commit e6d7f74

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed

pkg/util/logview/logview.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright 2024, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package logview
5+
6+
import (
7+
"fmt"
8+
"io"
9+
"os"
10+
"regexp"
11+
)
12+
13+
const BufSize = 256 * 1024
14+
const MaxLineSize = 1024
15+
16+
type LinePtr struct {
17+
Offset int64
18+
RealLineNum int64
19+
LineNum int64
20+
}
21+
22+
type LogView struct {
23+
File *os.File
24+
MultiBuf *MultiBufferByteGetter
25+
MatchRe *regexp.Regexp
26+
}
27+
28+
func MakeLogView(file *os.File) *LogView {
29+
return &LogView{
30+
File: file,
31+
MultiBuf: MakeMultiBufferByteGetter(file, BufSize),
32+
}
33+
}
34+
35+
func (lv *LogView) Close() {
36+
lv.File.Close()
37+
}
38+
39+
func (lv *LogView) ReadLineData(linePtr *LinePtr) ([]byte, error) {
40+
return lv.readLineAt(linePtr.Offset)
41+
}
42+
43+
func (lv *LogView) readLineAt(offset int64) ([]byte, error) {
44+
var rtn []byte
45+
for {
46+
if len(rtn) > MaxLineSize {
47+
break
48+
}
49+
b, err := lv.MultiBuf.GetByte(offset)
50+
if err == io.EOF {
51+
break
52+
}
53+
if err != nil {
54+
return nil, err
55+
}
56+
if b == '\n' {
57+
break
58+
}
59+
rtn = append(rtn, b)
60+
offset++
61+
}
62+
return rtn, nil
63+
}
64+
65+
func (lv *LogView) FirstLinePtr() (*LinePtr, error) {
66+
linePtr := &LinePtr{Offset: 0, RealLineNum: 1, LineNum: 1}
67+
if lv.isLineMatch(0) {
68+
return linePtr, nil
69+
}
70+
return lv.NextLinePtr(linePtr)
71+
}
72+
73+
func (lv *LogView) isLineMatch(offset int64) bool {
74+
if lv.MatchRe == nil {
75+
return true
76+
}
77+
lineData, err := lv.readLineAt(offset)
78+
if err != nil {
79+
return false
80+
}
81+
return lv.MatchRe.Match(lineData)
82+
}
83+
84+
func (lv *LogView) NextLinePtr(linePtr *LinePtr) (*LinePtr, error) {
85+
if linePtr == nil {
86+
return nil, fmt.Errorf("linePtr is nil")
87+
}
88+
numLines := int64(0)
89+
offset := linePtr.Offset
90+
for {
91+
var err error
92+
nextOffset, err := lv.MultiBuf.NextLine(offset)
93+
if err == io.EOF {
94+
return nil, nil
95+
}
96+
if err != nil {
97+
return nil, err
98+
}
99+
numLines++
100+
if lv.isLineMatch(nextOffset) {
101+
return &LinePtr{Offset: nextOffset, RealLineNum: linePtr.RealLineNum + numLines, LineNum: linePtr.LineNum + 1}, nil
102+
}
103+
offset = nextOffset
104+
}
105+
}
106+
107+
func (lv *LogView) PrevLinePtr(linePtr *LinePtr) (*LinePtr, error) {
108+
if linePtr == nil {
109+
return nil, fmt.Errorf("linePtr is nil")
110+
}
111+
numLines := int64(0)
112+
offset := linePtr.Offset
113+
for {
114+
var err error
115+
prevOffset, err := lv.MultiBuf.PrevLine(offset)
116+
if err == ErrBOF {
117+
return nil, nil
118+
}
119+
if err != nil {
120+
return nil, err
121+
}
122+
numLines++
123+
if lv.isLineMatch(prevOffset) {
124+
return &LinePtr{Offset: prevOffset, RealLineNum: linePtr.RealLineNum - numLines, LineNum: linePtr.LineNum - 1}, nil
125+
}
126+
offset = prevOffset
127+
}
128+
}
129+
130+
func (lv *LogView) Move(linePtr *LinePtr, offset int) (int, *LinePtr, error) {
131+
var n int
132+
if offset > 0 {
133+
for {
134+
nextLinePtr, err := lv.NextLinePtr(linePtr)
135+
if err == io.EOF {
136+
break
137+
}
138+
if err != nil {
139+
return 0, nil, err
140+
}
141+
linePtr = nextLinePtr
142+
n++
143+
if n == offset {
144+
break
145+
}
146+
}
147+
return n, linePtr, nil
148+
}
149+
if offset < 0 {
150+
for {
151+
prevLinePtr, err := lv.PrevLinePtr(linePtr)
152+
if err == ErrBOF {
153+
break
154+
}
155+
if err != nil {
156+
return 0, nil, err
157+
}
158+
linePtr = prevLinePtr
159+
n--
160+
if n == offset {
161+
break
162+
}
163+
}
164+
return n, linePtr, nil
165+
}
166+
return 0, linePtr, nil
167+
}
168+
169+
func (lv *LogView) LastLinePtr(linePtr *LinePtr) (*LinePtr, error) {
170+
if linePtr == nil {
171+
var err error
172+
linePtr, err = lv.FirstLinePtr()
173+
if err != nil {
174+
return nil, err
175+
}
176+
}
177+
if linePtr == nil {
178+
return nil, nil
179+
}
180+
for {
181+
nextLinePtr, err := lv.NextLinePtr(linePtr)
182+
if err == io.EOF {
183+
break
184+
}
185+
if err != nil {
186+
return nil, err
187+
}
188+
if nextLinePtr == nil {
189+
break
190+
}
191+
linePtr = nextLinePtr
192+
}
193+
return linePtr, nil
194+
}
195+
196+
func (lv *LogView) ReadWindow(linePtr *LinePtr, winSize int) ([][]byte, error) {
197+
if linePtr == nil {
198+
return nil, nil
199+
}
200+
var rtn [][]byte
201+
for len(rtn) < winSize {
202+
lineData, err := lv.readLineAt(linePtr.Offset)
203+
if err != nil {
204+
return nil, err
205+
}
206+
rtn = append(rtn, lineData)
207+
nextLinePtr, err := lv.NextLinePtr(linePtr)
208+
if err != nil {
209+
return nil, err
210+
}
211+
if nextLinePtr == nil {
212+
break
213+
}
214+
linePtr = nextLinePtr
215+
}
216+
return rtn, nil
217+
}

pkg/util/logview/multibuf.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2024, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package logview
5+
6+
import (
7+
"errors"
8+
"io"
9+
"os"
10+
)
11+
12+
type MultiBufferByteGetter struct {
13+
File *os.File
14+
Offset int64
15+
EOF bool
16+
Buffers [][]byte
17+
BufSize int64
18+
}
19+
20+
var ErrBOF = errors.New("beginning of file")
21+
22+
func MakeMultiBufferByteGetter(file *os.File, bufSize int64) *MultiBufferByteGetter {
23+
return &MultiBufferByteGetter{
24+
File: file,
25+
Offset: 0,
26+
EOF: false,
27+
Buffers: [][]byte{},
28+
BufSize: bufSize,
29+
}
30+
}
31+
32+
func (mb *MultiBufferByteGetter) readFromBuffer(offset int64) (byte, bool) {
33+
if offset < mb.Offset || offset >= mb.Offset+int64(mb.bufSize()) {
34+
return 0, false
35+
}
36+
bufIdx := int((offset - mb.Offset) / mb.BufSize)
37+
bufOffset := (offset - mb.Offset) % mb.BufSize
38+
return mb.Buffers[bufIdx][bufOffset], true
39+
}
40+
41+
func (mb *MultiBufferByteGetter) bufSize() int {
42+
return len(mb.Buffers) * int(mb.BufSize)
43+
}
44+
45+
func (mb *MultiBufferByteGetter) rebuffer(newOffset int64) error {
46+
partNum := int(newOffset / mb.BufSize)
47+
partOffset := int64(partNum) * mb.BufSize
48+
newBuf := make([]byte, mb.BufSize)
49+
n, err := mb.File.ReadAt(newBuf, partOffset)
50+
var isEOF bool
51+
if err == io.EOF {
52+
newBuf = newBuf[:n]
53+
isEOF = true
54+
}
55+
if err != nil {
56+
return err
57+
}
58+
var newBuffers [][]byte
59+
if len(mb.Buffers) > 0 {
60+
firstBufPartNum := int(mb.Offset / mb.BufSize)
61+
lastBufPartNum := int((mb.Offset + int64(mb.bufSize())) / mb.BufSize)
62+
if firstBufPartNum == partNum+1 {
63+
newBuffers = [][]byte{newBuf, mb.Buffers[0]}
64+
} else if lastBufPartNum == partNum-1 {
65+
newBuffers = [][]byte{mb.Buffers[0], newBuf}
66+
} else {
67+
newBuffers = [][]byte{newBuf}
68+
}
69+
} else {
70+
newBuffers = [][]byte{newBuf}
71+
}
72+
mb.Buffers = newBuffers
73+
mb.Offset = partOffset
74+
mb.EOF = isEOF
75+
return nil
76+
}
77+
78+
func (mb *MultiBufferByteGetter) GetByte(offset int64) (byte, error) {
79+
b, ok := mb.readFromBuffer(offset)
80+
if ok {
81+
return b, nil
82+
}
83+
if mb.EOF && offset >= mb.Offset+int64(mb.bufSize()) {
84+
return 0, io.EOF
85+
}
86+
err := mb.rebuffer(offset)
87+
if err != nil {
88+
return 0, err
89+
}
90+
b, _ = mb.readFromBuffer(offset)
91+
return b, nil
92+
}
93+
94+
func (mb *MultiBufferByteGetter) NextLine(offset int64) (int64, error) {
95+
for {
96+
b, err := mb.GetByte(offset)
97+
if err != nil {
98+
return 0, err
99+
}
100+
if b == '\n' {
101+
break
102+
}
103+
offset++
104+
}
105+
_, lastErr := mb.GetByte(offset + 1)
106+
if lastErr == io.EOF {
107+
return 0, io.EOF
108+
}
109+
return offset + 1, nil
110+
}
111+
112+
func (mb *MultiBufferByteGetter) PrevLine(offset int64) (int64, error) {
113+
if offset == 0 {
114+
return 0, ErrBOF
115+
}
116+
offset = offset - 2
117+
for {
118+
if offset < 0 {
119+
break
120+
}
121+
b, err := mb.GetByte(offset)
122+
if err != nil {
123+
return 0, err
124+
}
125+
if b == '\n' {
126+
break
127+
}
128+
offset--
129+
}
130+
return offset + 1, nil
131+
}

0 commit comments

Comments
 (0)