func TestWALWriter_WritePoints_Single(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := tsm1.NewValue(time.Unix(1, 0), 1.1) p2 := tsm1.NewValue(time.Unix(1, 0), int64(1)) p3 := tsm1.NewValue(time.Unix(1, 0), true) p4 := tsm1.NewValue(time.Unix(1, 0), "string") values := map[string][]tsm1.Value{ "cpu,host=A#!~#float": []tsm1.Value{p1}, "cpu,host=A#!~#int": []tsm1.Value{p2}, "cpu,host=A#!~#bool": []tsm1.Value{p3}, "cpu,host=A#!~#string": []tsm1.Value{p4}, } entry := &tsm1.WriteWALEntry{ Values: values, } if err := w.Write(mustMarshalEntry(entry)); err != nil { fatal(t, "write points", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } for k, v := range e.Values { for i, vv := range v { if got, exp := vv.String(), values[k][i].String(); got != exp { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } } if n := r.Count(); n != MustReadFileSize(f) { t.Fatalf("wrong count of bytes read, got %d, exp %d", n, MustReadFileSize(f)) } }
func TestWALWriter_WritePoints_Multiple(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := parsePoint("cpu,host=A value=1.1 1000000000") p2 := parsePoint("cpu,host=B value=1.1 1000000000") exp := [][]models.Point{ []models.Point{ p1, }, []models.Point{ p2, }, } for _, e := range exp { entry := &tsm1.WriteWALEntry{ Points: e, } if err := w.Write(entry); err != nil { fatal(t, "write points", err) } } // Seek back to the beinning of the file for reading if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) for _, ep := range exp { if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } points := e.Points for i, p := range ep { if exp, got := points[i].String(), p.String(); exp != got { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } } }
func TestWALWriter_WritePoints_LargeBatch(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) var points []tsm1.Value for i := 0; i < 100000; i++ { points = append(points, tsm1.NewValue(time.Unix(int64(i), 0), int64(1))) } values := map[string][]tsm1.Value{ "cpu,host=A,server=01,foo=bar,tag=really-long#!~#float": points, "mem,host=A,server=01,foo=bar,tag=really-long#!~#float": points, } entry := &tsm1.WriteWALEntry{ Values: values, } if err := w.Write(mustMarshalEntry(entry)); err != nil { fatal(t, "write points", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } for k, v := range e.Values { for i, vv := range v { if got, exp := vv.String(), values[k][i].String(); got != exp { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } } if n := r.Count(); n != MustReadFileSize(f) { t.Fatalf("wrong count of bytes read, got %d, exp %d", n, MustReadFileSize(f)) } }
func TestWALWriter_Corrupt(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) corruption := []byte{1, 4, 0, 0, 0} p1 := tsm1.NewValue(time.Unix(1, 0), 1.1) values := map[string][]tsm1.Value{ "cpu,host=A#!~#float": []tsm1.Value{p1}, } entry := &tsm1.WriteWALEntry{ Values: values, } if err := w.Write(mustMarshalEntry(entry)); err != nil { fatal(t, "write points", err) } // Write some random bytes to the file to simulate corruption. if _, err := f.Write(corruption); err != nil { fatal(t, "corrupt WAL segment", err) } // Create the WAL segment reader. if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) // Try to decode two entries. if !r.Next() { t.Fatalf("expected next, got false") } if _, err := r.Read(); err != nil { fatal(t, "read entry", err) } if !r.Next() { t.Fatalf("expected next, got false") } if _, err := r.Read(); err == nil { fatal(t, "read entry did not return err", nil) } // Count should only return size of valid data. expCount := MustReadFileSize(f) - int64(len(corruption)) if n := r.Count(); n != expCount { t.Fatalf("wrong count of bytes read, got %d, exp %d", n, expCount) } }
func MustWALSegment(dir string, entries []tsm1.WALEntry) *tsm1.WALSegmentReader { f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) for _, e := range entries { if err := w.Write(mustMarshalEntry(e)); err != nil { panic(fmt.Sprintf("write WAL entry: %v", err)) } } if _, err := f.Seek(0, os.SEEK_SET); err != nil { panic(fmt.Sprintf("seek WAL: %v", err)) } return tsm1.NewWALSegmentReader(f) }
func TestWALWriter_WritePoints_Single(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := parsePoint("cpu,host=A value=1.1 1000000000") points := []models.Point{ p1, } entry := &tsm1.WriteWALEntry{ Points: points, } if err := w.Write(entry); err != nil { fatal(t, "write points", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } for i, p := range e.Points { if exp, got := points[i].String(), p.String(); exp != got { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } }
func TestWALWriter_WriteDelete_Single(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) entry := &tsm1.DeleteWALEntry{ Keys: []string{"cpu"}, } if err := w.Write(entry); err != nil { fatal(t, "write points", err) } if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.DeleteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } if got, exp := len(e.Keys), len(entry.Keys); got != exp { t.Fatalf("key length mismatch: got %v, exp %v", got, exp) } if got, exp := e.Keys[0], entry.Keys[0]; got != exp { t.Fatalf("key mismatch: got %v, exp %v", got, exp) } }
func BenchmarkWALSegmentReader(b *testing.B) { points := map[string][]tsm1.Value{} for i := 0; i < 5000; i++ { k := "cpu,host=A#!~#value" points[k] = append(points[k], tsm1.NewValue(time.Unix(int64(i), 0), 1.1)) } dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) write := &tsm1.WriteWALEntry{ Values: points, } for i := 0; i < 100; i++ { if err := w.Write(write); err != nil { b.Fatalf("unexpected error writing entry: %v", err) } } r := tsm1.NewWALSegmentReader(f) b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() f.Seek(0, os.SEEK_SET) b.StartTimer() for r.Next() { _, err := r.Read() if err != nil { b.Fatalf("unexpected error reading entry: %v", err) } } } }
func BenchmarkWALSegmentReader(b *testing.B) { points := make([]models.Point, 5000) for i := range points { points[i] = parsePoint(fmt.Sprintf("cpu,host=host-%d value=1.1 1000000000", i)) } dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) write := &tsm1.WriteWALEntry{ Points: points, } for i := 0; i < 100; i++ { if err := w.Write(write); err != nil { b.Fatalf("unexpected error writing entry: %v", err) } } r := tsm1.NewWALSegmentReader(f) b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() f.Seek(0, os.SEEK_SET) b.StartTimer() for r.Next() { _, err := r.Read() if err != nil { b.Fatalf("unexpected error reading entry: %v", err) } } } }
func BenchmarkWALSegmentWriter(b *testing.B) { points := map[string][]tsm1.Value{} for i := 0; i < 5000; i++ { k := "cpu,host=A#!~#value" points[k] = append(points[k], tsm1.NewValue(time.Unix(int64(i), 0), 1.1)) } dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) write := &tsm1.WriteWALEntry{ Values: points, } b.ResetTimer() for i := 0; i < b.N; i++ { if err := w.Write(mustMarshalEntry(write)); err != nil { b.Fatalf("unexpected error writing entry: %v", err) } } }
func TestWALWriter_WritePoints_Multiple(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := tsm1.NewValue(time.Unix(1, 0), int64(1)) p2 := tsm1.NewValue(time.Unix(1, 0), int64(2)) exp := []struct { key string values []tsm1.Value }{ {"cpu,host=A#!~#value", []tsm1.Value{p1}}, {"cpu,host=B#!~#value", []tsm1.Value{p2}}, } for _, v := range exp { entry := &tsm1.WriteWALEntry{ Values: map[string][]tsm1.Value{v.key: v.values}, } if err := w.Write(entry); err != nil { fatal(t, "write points", err) } } // Seek back to the beinning of the file for reading if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) for _, ep := range exp { if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } for k, v := range e.Values { if got, exp := k, ep.key; got != exp { t.Fatalf("key mismatch. got %v, exp %v", got, exp) } if got, exp := len(v), len(ep.values); got != exp { t.Fatalf("values length mismatch: got %v, exp %v", got, exp) } for i, vv := range v { if got, exp := vv.String(), ep.values[i].String(); got != exp { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } } } }
func TestWALWriter_WritePointsDelete_Multiple(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := tsm1.NewValue(time.Unix(1, 0), true) values := map[string][]tsm1.Value{ "cpu,host=A#!~#value": []tsm1.Value{p1}, } writeEntry := &tsm1.WriteWALEntry{ Values: values, } if err := w.Write(writeEntry); err != nil { fatal(t, "write points", err) } // Write the delete entry deleteEntry := &tsm1.DeleteWALEntry{ Keys: []string{"cpu,host=A#!~value"}, } if err := w.Write(deleteEntry); err != nil { fatal(t, "write points", err) } // Seek back to the beinning of the file for reading if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) // Read the write points first if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } for k, v := range e.Values { if got, exp := len(v), len(values[k]); got != exp { t.Fatalf("values length mismatch: got %v, exp %v", got, exp) } for i, vv := range v { if got, exp := vv.String(), values[k][i].String(); got != exp { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } } // Read the delete second if !r.Next() { t.Fatalf("expected next, got false") } we, err = r.Read() if err != nil { fatal(t, "read entry", err) } de, ok := we.(*tsm1.DeleteWALEntry) if !ok { t.Fatalf("expected DeleteWALEntry: got %#v", e) } if got, exp := len(de.Keys), len(deleteEntry.Keys); got != exp { t.Fatalf("key length mismatch: got %v, exp %v", got, exp) } if got, exp := de.Keys[0], deleteEntry.Keys[0]; got != exp { t.Fatalf("key mismatch: got %v, exp %v", got, exp) } }
func TestWALWriter_WritePointsDelete_Multiple(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) f := MustTempFile(dir) w := tsm1.NewWALSegmentWriter(f) p1 := parsePoint("cpu,host=A value=1.1 1000000000") write := &tsm1.WriteWALEntry{ Points: []models.Point{p1}, } if err := w.Write(write); err != nil { fatal(t, "write points", err) } // Write the delete entry deleteEntry := &tsm1.DeleteWALEntry{ Keys: []string{"cpu"}, } if err := w.Write(deleteEntry); err != nil { fatal(t, "write points", err) } // Seek back to the beinning of the file for reading if _, err := f.Seek(0, os.SEEK_SET); err != nil { fatal(t, "seek", err) } r := tsm1.NewWALSegmentReader(f) // Read the write points first if !r.Next() { t.Fatalf("expected next, got false") } we, err := r.Read() if err != nil { fatal(t, "read entry", err) } e, ok := we.(*tsm1.WriteWALEntry) if !ok { t.Fatalf("expected WriteWALEntry: got %#v", e) } points := e.Points for i, p := range write.Points { if exp, got := points[i].String(), p.String(); exp != got { t.Fatalf("points mismatch: got %v, exp %v", got, exp) } } // Read the delete second if !r.Next() { t.Fatalf("expected next, got false") } we, err = r.Read() if err != nil { fatal(t, "read entry", err) } de, ok := we.(*tsm1.DeleteWALEntry) if !ok { t.Fatalf("expected DeleteWALEntry: got %#v", e) } if got, exp := len(de.Keys), len(deleteEntry.Keys); got != exp { t.Fatalf("key length mismatch: got %v, exp %v", got, exp) } if got, exp := de.Keys[0], deleteEntry.Keys[0]; got != exp { t.Fatalf("key mismatch: got %v, exp %v", got, exp) } }
// // Tests compacting a multiple wal segment into one tsm file func TestCompactor_MultipleWALSegment(t *testing.T) { dir := MustTempDir() defer os.RemoveAll(dir) // First WAL segment v1 := tsm1.NewValue(time.Unix(1, 0), float64(1)) v2 := tsm1.NewValue(time.Unix(1, 0), float64(1)) v3 := tsm1.NewValue(time.Unix(2, 0), float64(2)) points1 := map[string][]tsm1.Value{ "cpu,host=A#!~#value": []tsm1.Value{v1, v3}, "cpu,host=B#!~#value": []tsm1.Value{v2}, } entries := []tsm1.WALEntry{ &tsm1.WriteWALEntry{ Values: points1, }, } f1 := MustTempFile(dir) defer f1.Close() w := tsm1.NewWALSegmentWriter(f1) for _, e := range entries { if err := w.Write(e); err != nil { t.Fatalf("unexpected error writing entry: %v", err) } } if err := w.Close(); err != nil { t.Fatalf("unexpected error closing writer: %v", err) } // Second WAL segment v4 := tsm1.NewValue(time.Unix(2, 0), float64(2)) v5 := tsm1.NewValue(time.Unix(3, 0), float64(1)) v6 := tsm1.NewValue(time.Unix(4, 0), float64(1)) points2 := map[string][]tsm1.Value{ "cpu,host=A#!~#value": []tsm1.Value{v5, v6}, "cpu,host=B#!~#value": []tsm1.Value{v4}, } entries = []tsm1.WALEntry{ &tsm1.WriteWALEntry{ Values: points2, }, } f2 := MustTempFile(dir) defer f2.Close() w = tsm1.NewWALSegmentWriter(f2) for _, e := range entries { if err := w.Write(e); err != nil { t.Fatalf("unexpected error writing entry: %v", err) } } if err := w.Close(); err != nil { t.Fatalf("unexpected error closing writer: %v", err) } compactor := &tsm1.Compactor{ Dir: dir, } files, err := compactor.Compact([]string{f1.Name(), f2.Name()}) if err != nil { t.Fatalf("unexpected error compacting: %v", err) } if got, exp := len(files), 1; got != exp { t.Fatalf("files length mismatch: got %v, exp %v", got, exp) } f, err := os.Open(files[0]) if err != nil { t.Fatalf("unexpected error openting tsm: %v", err) } defer f.Close() r, err := tsm1.NewTSMReader(f) if err != nil { t.Fatalf("unexpected error creating tsm reader: %v", err) } defer r.Close() keys := r.Keys() if got, exp := len(keys), 2; got != exp { t.Fatalf("keys length mismatch: got %v, exp %v", got, exp) } var data = []struct { key string points []tsm1.Value }{ {"cpu,host=A#!~#value", []tsm1.Value{v1, v3, v5, v6}}, {"cpu,host=B#!~#value", []tsm1.Value{v2, v4}}, } for _, p := range data { values, err := r.ReadAll(p.key) if err != nil { t.Fatalf("unexpected error reading: %v", err) } if got, exp := len(values), len(p.points); got != exp { t.Fatalf("values length mismatch: got %v, exp %v", got, exp) } for i, point := range p.points { assertValueEqual(t, values[i], point) } } }