// Ensure the engine can write field metadata and reload it. func TestEngine_LoadMetadataIndex_Fields(t *testing.T) { e := OpenDefaultEngine() defer e.Close() // Setup mock that writes the index fields := map[string]*tsdb.MeasurementFields{ "cpu": &tsdb.MeasurementFields{ Fields: map[string]*tsdb.Field{ "value": &tsdb.Field{ID: 0, Name: "value"}, }, }, } e.PointsWriter.WritePointsFn = func(a []models.Point) error { return e.WriteIndex(nil, fields, nil) } // Write series metadata. if err := e.WritePoints(nil, fields, nil); err != nil { t.Fatal(err) } // Load metadata index. mfs := make(map[string]*tsdb.MeasurementFields) if err := e.LoadMetadataIndex(tsdb.NewDatabaseIndex(), mfs); err != nil { t.Fatal(err) } // Verify measurement field is correct. if mf := mfs["cpu"]; mf == nil { t.Fatal("measurement fields not found") } else if !reflect.DeepEqual(mf.Fields, map[string]*tsdb.Field{"value": &tsdb.Field{ID: 0, Name: "value"}}) { t.Fatalf("unexpected fields: %#v", mf.Fields) } }
// benchmarkWritePoints benchmarks writing new series to a shard. // mCnt - measurement count // tkCnt - tag key count // tvCnt - tag value count (values per tag) // pntCnt - points per series. # of series = mCnt * (tvCnt ^ tkCnt) func benchmarkWritePoints(b *testing.B, mCnt, tkCnt, tvCnt, pntCnt int) { // Generate test series (measurements + unique tag sets). series := genTestSeries(mCnt, tkCnt, tvCnt) // Create index for the shard to use. index := tsdb.NewDatabaseIndex() // Generate point data to write to the shard. points := []tsdb.Point{} for _, s := range series { for val := 0.0; val < float64(pntCnt); val++ { p := tsdb.NewPoint(s.Measurement, s.Series.Tags, map[string]interface{}{"value": val}, time.Now()) points = append(points, p) } } // Stop & reset timers and mem-stats before the main benchmark loop. b.StopTimer() b.ResetTimer() // Run the benchmark loop. for n := 0; n < b.N; n++ { tmpDir, _ := ioutil.TempDir("", "shard_test") tmpShard := path.Join(tmpDir, "shard") shard := tsdb.NewShard(1, index, tmpShard, tsdb.NewEngineOptions()) shard.Open() b.StartTimer() // Call the function being benchmarked. chunkedWrite(shard, points) b.StopTimer() shard.Close() os.RemoveAll(tmpDir) } }
func TestShardWriteAddNewField(t *testing.T) { tmpDir, _ := ioutil.TempDir("", "shard_test") defer os.RemoveAll(tmpDir) tmpShard := path.Join(tmpDir, "shard") tmpWal := path.Join(tmpDir, "wal") index := tsdb.NewDatabaseIndex() opts := tsdb.NewEngineOptions() opts.Config.WALDir = filepath.Join(tmpDir, "wal") sh := tsdb.NewShard(1, index, tmpShard, tmpWal, opts) if err := sh.Open(); err != nil { t.Fatalf("error openeing shard: %s", err.Error()) } defer sh.Close() pt := tsdb.NewPoint( "cpu", map[string]string{"host": "server"}, map[string]interface{}{"value": 1.0}, time.Unix(1, 2), ) err := sh.WritePoints([]tsdb.Point{pt}) if err != nil { t.Fatalf(err.Error()) } pt = tsdb.NewPoint( "cpu", map[string]string{"host": "server"}, map[string]interface{}{"value": 1.0, "value2": 2.0}, time.Unix(1, 2), ) err = sh.WritePoints([]tsdb.Point{pt}) if err != nil { t.Fatalf(err.Error()) } if !reflect.DeepEqual(index.Names(), []string{"cpu"}) { t.Fatalf("measurement names in shard didn't match") } if index.SeriesN() != 1 { t.Fatalf("series wasn't in index") } seriesTags := index.Series(string(pt.Key())).Tags if len(seriesTags) != len(pt.Tags()) || pt.Tags()["host"] != seriesTags["host"] { t.Fatalf("tags weren't properly saved to series index: %v, %v", pt.Tags(), seriesTags) } if !reflect.DeepEqual(index.Measurement("cpu").TagKeys(), []string{"host"}) { t.Fatalf("tag key wasn't saved to measurement index") } if len(index.Measurement("cpu").FieldNames()) != 2 { t.Fatalf("field names wasn't saved to measurement index") } }
func mustCreateShard(dir string) *tsdb.Shard { tmpShard := path.Join(dir, "shard") index := tsdb.NewDatabaseIndex() sh := tsdb.NewShard(index, tmpShard, tsdb.NewEngineOptions()) if err := sh.Open(); err != nil { panic(fmt.Sprintf("error opening shard: %s", err.Error())) } return sh }
func mustCreateShard(dir string) *tsdb.Shard { tmpShard := path.Join(dir, "shard") tmpWal := path.Join(dir, "wal") index := tsdb.NewDatabaseIndex() opts := tsdb.NewEngineOptions() opts.Config.WALDir = filepath.Join(dir, "wal") sh := tsdb.NewShard(1, index, tmpShard, tmpWal, opts) if err := sh.Open(); err != nil { panic(fmt.Sprintf("error opening shard: %s", err.Error())) } return sh }
func benchmarkCreateSeriesIndex(b *testing.B, series []*TestSeries) { idxs := make([]*tsdb.DatabaseIndex, 0, b.N) for i := 0; i < b.N; i++ { idxs = append(idxs, tsdb.NewDatabaseIndex()) } b.ResetTimer() for n := 0; n < b.N; n++ { idx := idxs[n] for _, s := range series { idx.CreateSeriesIndexIfNotExists(s.Measurement, s.Series) } } }
// MustOpenShard returns a temporary, opened shard. func MustOpenShard(id uint64) *Shard { path, err := ioutil.TempDir("", "copier-") if err != nil { panic(err) } sh := &Shard{ Shard: tsdb.NewShard(id, tsdb.NewDatabaseIndex(), filepath.Join(path, "data"), filepath.Join(path, "wal"), tsdb.NewEngineOptions(), ), path: path, } if err := sh.Open(); err != nil { sh.Close() panic(err) } return sh }
// Ensure the engine can write series metadata and reload it. func TestEngine_LoadMetadataIndex_Series(t *testing.T) { e := OpenDefaultEngine() defer e.Close() // Setup mock that writes the index seriesToCreate := []*tsdb.SeriesCreate{ {Series: tsdb.NewSeries(string(models.MakeKey([]byte("cpu"), map[string]string{"host": "server0"})), map[string]string{"host": "server0"})}, {Series: tsdb.NewSeries(string(models.MakeKey([]byte("cpu"), map[string]string{"host": "server1"})), map[string]string{"host": "server1"})}, {Series: tsdb.NewSeries("series with spaces", nil)}, } e.PointsWriter.WritePointsFn = func(a []models.Point) error { return e.WriteIndex(nil, nil, seriesToCreate) } // Write series metadata. if err := e.WritePoints(nil, nil, seriesToCreate); err != nil { t.Fatal(err) } // Load metadata index. index := tsdb.NewDatabaseIndex() if err := e.LoadMetadataIndex(index, make(map[string]*tsdb.MeasurementFields)); err != nil { t.Fatal(err) } // Verify index is correct. if m := index.Measurement("cpu"); m == nil { t.Fatal("measurement not found") } else if s := m.SeriesByID(1); s.Key != "cpu,host=server0" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "server0"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } else if s = m.SeriesByID(2); s.Key != "cpu,host=server1" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "server1"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } if m := index.Measurement("series with spaces"); m == nil { t.Fatal("measurement not found") } else if s := m.SeriesByID(3); s.Key != "series with spaces" { t.Fatalf("unexpected series: %q", s.Key) } }
// Ensure the shard will automatically flush the WAL after a threshold has been reached. func TestShard_Autoflush_FlushInterval(t *testing.T) { path, _ := ioutil.TempDir("", "shard_test") defer os.RemoveAll(path) // Open shard with a high size threshold, small time threshold. sh := tsdb.NewShard(1, tsdb.NewDatabaseIndex(), filepath.Join(path, "shard"), tsdb.EngineOptions{ EngineVersion: b1.Format, MaxWALSize: 10 * 1024 * 1024, // 10MB WALFlushInterval: 100 * time.Millisecond, WALPartitionFlushDelay: 1 * time.Millisecond, }) if err := sh.Open(); err != nil { t.Fatal(err) } defer sh.Close() // Write some points. for i := 0; i < 100; i++ { if err := sh.WritePoints([]tsdb.Point{tsdb.NewPoint( fmt.Sprintf("cpu%d", i), map[string]string{"host": "server"}, map[string]interface{}{"value": 1.0}, time.Unix(1, 2), )}); err != nil { t.Fatal(err) } } // Wait for time-based flush. time.Sleep(100 * time.Millisecond) // Make sure we have series buckets created outside the WAL. if n, err := sh.SeriesCount(); err != nil { t.Fatal(err) } else if n < 10 { t.Fatalf("not enough series, expected at least 10, got %d", n) } }
func TestWAL_SeriesAndFieldsGetPersisted(t *testing.T) { log := openTestWAL() defer log.Close() defer os.RemoveAll(log.path) if err := log.Open(); err != nil { t.Fatalf("couldn't open wal: %s", err.Error()) } codec := tsdb.NewFieldCodec(map[string]*tsdb.Field{ "value": { ID: uint8(1), Name: "value", Type: influxql.Float, }, }) var measurementsToIndex map[string]*tsdb.MeasurementFields var seriesToIndex []*tsdb.SeriesCreate log.Index = &testIndexWriter{fn: func(pointsByKey map[string][][]byte, measurementFieldsToSave map[string]*tsdb.MeasurementFields, seriesToCreate []*tsdb.SeriesCreate) error { measurementsToIndex = measurementFieldsToSave seriesToIndex = append(seriesToIndex, seriesToCreate...) return nil }} // test that we can write to two different series p1 := parsePoint("cpu,host=A value=23.2 1", codec) p2 := parsePoint("cpu,host=A value=25.3 4", codec) p3 := parsePoint("cpu,host=B value=1.0 1", codec) seriesToCreate := []*tsdb.SeriesCreate{ {Series: tsdb.NewSeries(string(tsdb.MakeKey([]byte("cpu"), map[string]string{"host": "A"})), map[string]string{"host": "A"})}, {Series: tsdb.NewSeries(string(tsdb.MakeKey([]byte("cpu"), map[string]string{"host": "B"})), map[string]string{"host": "B"})}, } measaurementsToCreate := map[string]*tsdb.MeasurementFields{ "cpu": { Fields: map[string]*tsdb.Field{ "value": {ID: 1, Name: "value"}, }, }, } if err := log.WritePoints([]tsdb.Point{p1, p2, p3}, measaurementsToCreate, seriesToCreate); err != nil { t.Fatalf("failed to write points: %s", err.Error()) } // now close it and see if loading the metadata index will populate the measurement and series info log.Close() idx := tsdb.NewDatabaseIndex() mf := make(map[string]*tsdb.MeasurementFields) if err := log.LoadMetadataIndex(idx, mf); err != nil { t.Fatalf("error loading metadata index: %s", err.Error()) } s := idx.Series("cpu,host=A") if s == nil { t.Fatal("expected to find series cpu,host=A in index") } s = idx.Series("cpu,host=B") if s == nil { t.Fatal("expected to find series cpu,host=B in index") } m := mf["cpu"] if m == nil { t.Fatal("expected to find measurement fields for cpu", mf) } if m.Fields["value"] == nil { t.Fatal("expected to find field definition for 'value'") } // ensure that they were actually flushed to the index. do it this way because the annoying deepequal doessn't really work for these for i, s := range seriesToCreate { if seriesToIndex[i].Measurement != s.Measurement { t.Fatal("expected measurement to be the same") } if seriesToIndex[i].Series.Key != s.Series.Key { t.Fatal("expected series key to be the same") } if !reflect.DeepEqual(seriesToIndex[i].Series.Tags, s.Series.Tags) { t.Fatal("expected series tags to be the same") } } // ensure that the measurement fields were flushed to the index for k, v := range measaurementsToCreate { m := measurementsToIndex[k] if m == nil { t.Fatalf("measurement %s wasn't indexed", k) } if !reflect.DeepEqual(m.Fields, v.Fields) { t.Fatal("measurement fields not equal") } } // now open and close the log and try to reload the metadata index, which should now be empty if err := log.Open(); err != nil { t.Fatalf("error opening log: %s", err.Error()) } if err := log.Close(); err != nil { t.Fatalf("error closing log: %s", err.Error()) } idx = tsdb.NewDatabaseIndex() mf = make(map[string]*tsdb.MeasurementFields) if err := log.LoadMetadataIndex(idx, mf); err != nil { t.Fatalf("error loading metadata index: %s", err.Error()) } if len(idx.Measurements()) != 0 || len(mf) != 0 { t.Fatal("expected index and measurement fields to be empty") } }
func TestShardWriteAndIndex(t *testing.T) { tmpDir, _ := ioutil.TempDir("", "shard_test") defer os.RemoveAll(tmpDir) tmpShard := path.Join(tmpDir, "shard") index := tsdb.NewDatabaseIndex() opts := tsdb.NewEngineOptions() opts.Config.WALDir = filepath.Join(tmpDir, "wal") sh := tsdb.NewShard(1, index, tmpShard, opts) if err := sh.Open(); err != nil { t.Fatalf("error openeing shard: %s", err.Error()) } pt := tsdb.NewPoint( "cpu", map[string]string{"host": "server"}, map[string]interface{}{"value": 1.0}, time.Unix(1, 2), ) err := sh.WritePoints([]tsdb.Point{pt}) if err != nil { t.Fatalf(err.Error()) } pt.SetTime(time.Unix(2, 3)) err = sh.WritePoints([]tsdb.Point{pt}) if err != nil { t.Fatalf(err.Error()) } validateIndex := func() { if !reflect.DeepEqual(index.Names(), []string{"cpu"}) { t.Fatalf("measurement names in shard didn't match") } if index.SeriesN() != 1 { t.Fatalf("series wasn't in index") } seriesTags := index.Series(string(pt.Key())).Tags if len(seriesTags) != len(pt.Tags()) || pt.Tags()["host"] != seriesTags["host"] { t.Fatalf("tags weren't properly saved to series index: %v, %v", pt.Tags(), seriesTags) } if !reflect.DeepEqual(index.Measurement("cpu").TagKeys(), []string{"host"}) { t.Fatalf("tag key wasn't saved to measurement index") } } validateIndex() // ensure the index gets loaded after closing and opening the shard sh.Close() index = tsdb.NewDatabaseIndex() sh = tsdb.NewShard(1, index, tmpShard, opts) if err := sh.Open(); err != nil { t.Fatalf("error openeing shard: %s", err.Error()) } validateIndex() // and ensure that we can still write data pt.SetTime(time.Unix(2, 6)) err = sh.WritePoints([]tsdb.Point{pt}) if err != nil { t.Fatalf(err.Error()) } }
func TestDevEngine_LoadMetadataIndex(t *testing.T) { // Generate temporary file. f, _ := ioutil.TempFile("", "tsm") f.Close() os.Remove(f.Name()) walPath := filepath.Join(f.Name(), "wal") os.MkdirAll(walPath, 0777) defer os.RemoveAll(f.Name()) // Create a few points. p1 := parsePoint("cpu,host=A value=1.1 1000000000") p2 := parsePoint("cpu,host=B value=1.2 2000000000") // Write those points to the engine. e := NewDevEngine(f.Name(), walPath, tsdb.NewEngineOptions()).(*DevEngine) if err := e.Open(); err != nil { t.Fatalf("failed to open tsm1 engine: %s", err.Error()) } if err := e.WritePoints([]models.Point{p1}, nil, nil); err != nil { t.Fatalf("failed to write points: %s", err.Error()) } // ensure we can close and load index from the WAL if err := e.Close(); err != nil { t.Fatalf("error closing: %s", err.Error()) } if err := e.Open(); err != nil { t.Fatalf("error opening: %s", err.Error()) } // Load metadata index. index := tsdb.NewDatabaseIndex() if err := e.LoadMetadataIndex(nil, index, make(map[string]*tsdb.MeasurementFields)); err != nil { t.Fatal(err) } // Verify index is correct. if m := index.Measurement("cpu"); m == nil { t.Fatal("measurement not found") } else if s := m.SeriesByID(1); s.Key != "cpu,host=A" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "A"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } // write the snapshot, ensure we can close and load index from TSM if err := e.WriteSnapshot(); err != nil { t.Fatalf("error writing snapshot: %s", err.Error()) } // ensure we can close and load index from the WAL if err := e.Close(); err != nil { t.Fatalf("error closing: %s", err.Error()) } if err := e.Open(); err != nil { t.Fatalf("error opening: %s", err.Error()) } // Load metadata index. index = tsdb.NewDatabaseIndex() if err := e.LoadMetadataIndex(nil, index, make(map[string]*tsdb.MeasurementFields)); err != nil { t.Fatal(err) } // Verify index is correct. if m := index.Measurement("cpu"); m == nil { t.Fatal("measurement not found") } else if s := m.SeriesByID(1); s.Key != "cpu,host=A" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "A"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } // write a new point and ensure we can close and load index from TSM and WAL if err := e.WritePoints([]models.Point{p2}, nil, nil); err != nil { t.Fatalf("failed to write points: %s", err.Error()) } // ensure we can close and load index from the TSM and WAL if err := e.Close(); err != nil { t.Fatalf("error closing: %s", err.Error()) } if err := e.Open(); err != nil { t.Fatalf("error opening: %s", err.Error()) } // Load metadata index. index = tsdb.NewDatabaseIndex() if err := e.LoadMetadataIndex(nil, index, make(map[string]*tsdb.MeasurementFields)); err != nil { t.Fatal(err) } // Verify index is correct. if m := index.Measurement("cpu"); m == nil { t.Fatal("measurement not found") } else if s := m.SeriesByID(1); s.Key != "cpu,host=A" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "A"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } else if s := m.SeriesByID(2); s.Key != "cpu,host=B" || !reflect.DeepEqual(s.Tags, map[string]string{"host": "B"}) { t.Fatalf("unexpected series: %q / %#v", s.Key, s.Tags) } }