derailed 2020-06-19 16:15:48 -06:00
parent 5c214ccda4
commit 5bbc159df9
8 changed files with 120 additions and 31 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vscode .vscode
*.out
.idea .idea
.envrc .envrc
cov.out cov.out

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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)

View File

@ -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) {}

View File

@ -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)

View File

@ -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()
} }

View File

@ -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)