fix #776
parent
5c214ccda4
commit
5bbc159df9
|
|
@ -1,4 +1,5 @@
|
||||||
.vscode
|
.vscode
|
||||||
|
*.out
|
||||||
.idea
|
.idea
|
||||||
.envrc
|
.envrc
|
||||||
cov.out
|
cov.out
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ func NewLogItem(b []byte) *LogItem {
|
||||||
|
|
||||||
// NewLogItemFromString returns a new item.
|
// NewLogItemFromString returns a new item.
|
||||||
func NewLogItemFromString(s string) *LogItem {
|
func NewLogItemFromString(s string) *LogItem {
|
||||||
l := LogItem{Bytes: []byte(s)}
|
return &LogItem{
|
||||||
l.Timestamp = time.Now().String()
|
Bytes: []byte(s),
|
||||||
|
Timestamp: time.Now().String(),
|
||||||
return &l
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns pod and or container based id.
|
// ID returns pod and or container based id.
|
||||||
|
|
@ -125,15 +125,15 @@ func (l LogItems) Lines() []string {
|
||||||
|
|
||||||
// Render returns logs as a collection of strings.
|
// Render returns logs as a collection of strings.
|
||||||
func (l LogItems) Render(showTime bool, ll [][]byte) {
|
func (l LogItems) Render(showTime bool, ll [][]byte) {
|
||||||
colors := map[string]int{}
|
colors := make(map[string]int, len(l))
|
||||||
for i, item := range l {
|
for i, item := range l {
|
||||||
info := item.ID()
|
info := item.ID()
|
||||||
c, ok := colors[item.ID()]
|
color, ok := colors[info]
|
||||||
if !ok {
|
if !ok {
|
||||||
c = colorFor(info)
|
color = colorFor(info)
|
||||||
colors[info] = c
|
colors[info] = color
|
||||||
}
|
}
|
||||||
ll[i] = item.Render(c, showTime)
|
ll[i] = item.Render(color, showTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||||
|
}
|
||||||
|
|
||||||
func TestClusterMetaDelta(t *testing.T) {
|
func TestClusterMetaDelta(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
o, n model.ClusterMeta
|
o, n model.ClusterMeta
|
||||||
|
|
|
||||||
|
|
@ -233,11 +233,11 @@ func (l *Log) Append(line *dao.LogItem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify fires of notifications to the listeners.
|
// Notify fires of notifications to the listeners.
|
||||||
func (l *Log) Notify(timedOut bool) {
|
func (l *Log) Notify() {
|
||||||
l.mx.Lock()
|
l.mx.Lock()
|
||||||
defer l.mx.Unlock()
|
defer l.mx.Unlock()
|
||||||
|
|
||||||
if timedOut && l.lastSent < len(l.lines) {
|
if l.lastSent < len(l.lines) {
|
||||||
l.fireLogBuffChanged(l.lines[l.lastSent:])
|
l.fireLogBuffChanged(l.lines[l.lastSent:])
|
||||||
l.lastSent = len(l.lines)
|
l.lastSent = len(l.lines)
|
||||||
}
|
}
|
||||||
|
|
@ -253,7 +253,7 @@ func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Debug().Msgf("Closed channel detected. Bailing out...")
|
log.Debug().Msgf("Closed channel detected. Bailing out...")
|
||||||
l.Append(item)
|
l.Append(item)
|
||||||
l.Notify(true)
|
l.Notify()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.Append(item)
|
l.Append(item)
|
||||||
|
|
@ -264,10 +264,10 @@ func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) {
|
||||||
}
|
}
|
||||||
l.mx.RUnlock()
|
l.mx.RUnlock()
|
||||||
if overflow {
|
if overflow {
|
||||||
l.Notify(true)
|
l.Notify()
|
||||||
}
|
}
|
||||||
case <-time.After(l.flushTimeout):
|
case <-time.After(l.flushTimeout):
|
||||||
l.Notify(true)
|
l.Notify()
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -322,10 +322,14 @@ func applyFilter(q string, lines dao.LogItems) (dao.LogItems, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
|
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
|
||||||
filtered, err := applyFilter(l.filter, lines)
|
filtered := lines
|
||||||
if err != nil {
|
if l.filter != "" {
|
||||||
l.fireLogError(err)
|
var err error
|
||||||
return
|
filtered, err = applyFilter(l.filter, lines)
|
||||||
|
if err != nil {
|
||||||
|
l.fireLogError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(filtered) > 0 {
|
if len(filtered) > 0 {
|
||||||
l.fireLogChanged(filtered)
|
l.fireLogChanged(filtered)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/derailed/k9s/internal/dao"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateLogs(t *testing.T) {
|
||||||
|
size := 100
|
||||||
|
m := NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
|
||||||
|
m.Init(makeFactory())
|
||||||
|
|
||||||
|
v := newMockLogView()
|
||||||
|
m.AddListener(v)
|
||||||
|
|
||||||
|
c := make(dao.LogChan)
|
||||||
|
go func() {
|
||||||
|
m.updateLogs(context.Background(), c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 2*size; i++ {
|
||||||
|
c <- dao.NewLogItemFromString("line" + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
close(c)
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
assert.Equal(t, size, v.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUpdateLogs(b *testing.B) {
|
||||||
|
size := 100
|
||||||
|
m := NewLog(client.NewGVR("fred"), makeLogOpts(size), 10*time.Millisecond)
|
||||||
|
m.Init(makeFactory())
|
||||||
|
|
||||||
|
v := newMockLogView()
|
||||||
|
m.AddListener(v)
|
||||||
|
|
||||||
|
c := make(dao.LogChan)
|
||||||
|
go func() {
|
||||||
|
m.updateLogs(context.Background(), c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
c <- dao.NewLogItemFromString("line" + strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
close(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
func makeLogOpts(count int) dao.LogOptions {
|
||||||
|
return dao.LogOptions{
|
||||||
|
Path: "fred",
|
||||||
|
Container: "blee",
|
||||||
|
Lines: int64(count),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLogView struct {
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockLogView() *mockLogView {
|
||||||
|
return &mockLogView{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockLogView) LogChanged(d dao.LogItems) {
|
||||||
|
t.count += len(d.Lines())
|
||||||
|
}
|
||||||
|
func (t *mockLogView) LogCleared() {}
|
||||||
|
func (t *mockLogView) LogFailed(err error) {}
|
||||||
|
|
@ -29,7 +29,7 @@ func TestLogFullBuffer(t *testing.T) {
|
||||||
data = append(data, dao.NewLogItemFromString("line"+strconv.Itoa(i)))
|
data = append(data, dao.NewLogItemFromString("line"+strconv.Itoa(i)))
|
||||||
m.Append(data[i])
|
m.Append(data[i])
|
||||||
}
|
}
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
|
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
|
|
@ -73,7 +73,7 @@ func TestLogFilter(t *testing.T) {
|
||||||
m.Append(data[i])
|
m.Append(data[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
|
|
@ -100,7 +100,7 @@ func TestLogStartStop(t *testing.T) {
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
m.Append(d)
|
m.Append(d)
|
||||||
}
|
}
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
m.Stop()
|
m.Stop()
|
||||||
|
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
|
|
@ -122,7 +122,7 @@ func TestLogClear(t *testing.T) {
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
m.Append(d)
|
m.Append(d)
|
||||||
}
|
}
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
m.Clear()
|
m.Clear()
|
||||||
|
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
|
|
@ -167,7 +167,7 @@ func TestLogAppend(t *testing.T) {
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, items, v.data)
|
assert.Equal(t, items, v.data)
|
||||||
|
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 2, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
|
|
@ -191,7 +191,7 @@ func TestLogTimedout(t *testing.T) {
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
m.Append(d)
|
m.Append(d)
|
||||||
}
|
}
|
||||||
m.Notify(true)
|
m.Notify()
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
|
|
|
||||||
|
|
@ -235,16 +235,16 @@ func (l *Log) Logs() *Details {
|
||||||
return l.logs
|
return l.logs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var EOL = []byte("\n")
|
||||||
|
|
||||||
// Flush write logs to viewer.
|
// Flush write logs to viewer.
|
||||||
func (l *Log) Flush(lines dao.LogItems) {
|
func (l *Log) Flush(lines dao.LogItems) {
|
||||||
defer func(t time.Time) {
|
|
||||||
log.Debug().Msgf("FLUSH %d--%v", len(lines), time.Since(t))
|
|
||||||
}(time.Now())
|
|
||||||
|
|
||||||
showTime := l.Indicator().showTime
|
showTime := l.Indicator().showTime
|
||||||
ll := make([][]byte, len(lines))
|
ll := make([][]byte, len(lines))
|
||||||
lines.Render(showTime, ll)
|
lines.Render(showTime, ll)
|
||||||
fmt.Fprintln(l.ansiWriter, string(bytes.Join(ll, []byte("\n"))))
|
if _, err := l.ansiWriter.Write(bytes.Join(ll, EOL)); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("write log failed")
|
||||||
|
}
|
||||||
l.logs.ScrollToEnd()
|
l.logs.ScrollToEnd()
|
||||||
l.indicator.Refresh()
|
l.indicator.Refresh()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ func TestLogAutoScroll(t *testing.T) {
|
||||||
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
|
v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false)
|
||||||
v.Init(makeContext())
|
v.Init(makeContext())
|
||||||
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
|
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
|
||||||
v.GetModel().Notify(true)
|
v.GetModel().Notify()
|
||||||
|
|
||||||
assert.Equal(t, 14, len(v.Hints()))
|
assert.Equal(t, 14, len(v.Hints()))
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ func TestLogTimestamp(t *testing.T) {
|
||||||
l.Logs().Clear()
|
l.Logs().Clear()
|
||||||
l.Flush(buff)
|
l.Flush(buff)
|
||||||
|
|
||||||
assert.Equal(t, fmt.Sprintf("%-30s %s", "ttt", "fred/blee:c1 Testing 1, 2, 3\n"), l.Logs().GetText(true))
|
assert.Equal(t, fmt.Sprintf("%-30s %s", "ttt", "fred/blee:c1 Testing 1, 2, 3"), l.Logs().GetText(true))
|
||||||
assert.Equal(t, 2, list.change)
|
assert.Equal(t, 2, list.change)
|
||||||
assert.Equal(t, 2, list.clear)
|
assert.Equal(t, 2, list.clear)
|
||||||
assert.Equal(t, 0, list.fail)
|
assert.Equal(t, 0, list.fail)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue