[gold] Tools (and small fixes) from poking around time travel.
Change-Id: Ibfff2c69b409fa09c8be034bc5a516ed2fe10985
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/303340
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
diff --git a/golden/cmd/trace_tool/trace_tool.go b/golden/cmd/trace_tool/trace_tool.go
new file mode 100644
index 0000000..345e60a
--- /dev/null
+++ b/golden/cmd/trace_tool/trace_tool.go
@@ -0,0 +1,208 @@
+// The trace_tool executable is meant for directly interacting with the traces stored in
+// BigTable. It was last used to clean up bad data in a series of tiles.
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "sync/atomic"
+
+ "cloud.google.com/go/bigtable"
+ "go.skia.org/infra/go/bt"
+ "go.skia.org/infra/go/gitiles"
+ "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/go/vcsinfo/bt_vcs"
+ "go.skia.org/infra/golden/go/tracestore/bt_tracestore"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ instanceID = "production" // The user is expected to edit these by hand or turn them into
+ projectID = "skia-public" // flags
+
+ gitTableID = "git-repos2"
+ traceBTTable = "gold-skia"
+ gitRepoURL = "https://skia.googlesource.com/skia.git"
+)
+
+func main() {
+ flag.Parse()
+ ctx := context.Background()
+
+ btConf := &bt_gitstore.BTConfig{
+ InstanceID: instanceID,
+ ProjectID: projectID,
+ TableID: gitTableID,
+ AppProfile: bt.TestingAppProfile,
+ }
+
+ gitStore, err := bt_gitstore.New(ctx, btConf, gitRepoURL)
+ if err != nil {
+ sklog.Fatalf("Error instantiating gitstore: %s", err)
+ }
+
+ gitilesRepo := gitiles.NewRepo("", nil)
+ vcs, err := bt_vcs.New(ctx, gitStore, "master", gitilesRepo)
+ if err != nil {
+ sklog.Fatalf("Error creating BT-backed VCS instance: %s", err)
+ }
+
+ btc := bt_tracestore.BTConfig{
+ InstanceID: instanceID,
+ ProjectID: projectID,
+ TableID: traceBTTable,
+ VCS: vcs,
+ }
+
+ offending, err := vcs.IndexOf(ctx, "a0f9a3e62afad0568380f982fe1a68f787682fa6")
+ if err != nil {
+ sklog.Fatalf("err", err)
+ }
+ sklog.Infof("offending commit index %d", offending)
+
+ n := vcs.LastNIndex(1)
+ sklog.Infof("Last commit was %s at %s index %d", n[0].Hash, n[0].Timestamp, n[0].Index)
+ tk, offset := bt_tracestore.GetTileKey(n[0].Index)
+ sklog.Infof("This is tile key %d offset %d", tk, offset)
+
+ tk++
+ sklog.Infof("Cleaning up the past to %d", tk)
+
+ client, err := bigtable.NewClient(ctx, btc.ProjectID, btc.InstanceID)
+ if err != nil {
+ sklog.Fatalf("Error creating client for project %s and instance %s: %s", btc.ProjectID, btc.InstanceID, err)
+ }
+
+ adminClient, err := bigtable.NewAdminClient(ctx, btc.ProjectID, btc.InstanceID)
+ if err != nil {
+ sklog.Fatalf("Error creating admin client for project %s and instance %s: %s", btc.ProjectID, btc.InstanceID, err)
+ }
+
+ must := func(err error) {
+ if err != nil {
+ sklog.Fatalf(err.Error())
+ }
+ }
+
+ must(countTraceRowsForTile(ctx, client, int(tk)))
+ must(deleteTraceDataForTile(ctx, adminClient, int(tk)))
+ must(countTraceRowsForTile(ctx, client, int(tk)))
+
+ must(countTraceOptionsForTile(ctx, client, int(tk)))
+ must(deleteTraceOptionsForTile(ctx, adminClient, int(tk)))
+ must(countTraceOptionsForTile(ctx, client, int(tk)))
+
+ must(deleteOPSForTile(ctx, adminClient, int(tk)))
+}
+
+func countTraceRowsForTile(ctx context.Context, bc *bigtable.Client, tk int) error {
+ count := int32(0)
+ table := bc.Open(traceBTTable)
+
+ eg, ctx := errgroup.WithContext(ctx)
+ for shard := 0; shard < bt_tracestore.DefaultShards; shard++ {
+ shardedPrefix := fmt.Sprintf("%02d:ts:t:%d", shard, tk)
+ eg.Go(func() error {
+ prefixRange := bigtable.PrefixRange(shardedPrefix)
+ return table.ReadRows(ctx, prefixRange, func(row bigtable.Row) bool {
+ atomic.AddInt32(&count, 1)
+ return true
+ }, bigtable.RowFilter(
+ bigtable.ChainFilters(
+ bigtable.StripValueFilter(), // https://cloud.google.com/bigtable/docs/using-filters#strip-value
+ bigtable.CellsPerRowLimitFilter(1),
+ bigtable.LatestNFilter(1),
+ ),
+ ))
+ })
+ }
+
+ err := eg.Wait()
+ sklog.Infof("Counted %d trace rows for tile %d", count, tk)
+ return err
+}
+
+func countTraceOptionsForTile(ctx context.Context, bc *bigtable.Client, tk int) error {
+ count := int32(0)
+ table := bc.Open(traceBTTable)
+
+ eg, ctx := errgroup.WithContext(ctx)
+ for shard := 0; shard < bt_tracestore.DefaultShards; shard++ {
+ shardedPrefix := fmt.Sprintf("%02d:ts:p:%d", shard, tk)
+ eg.Go(func() error {
+ prefixRange := bigtable.PrefixRange(shardedPrefix)
+ return table.ReadRows(ctx, prefixRange, func(row bigtable.Row) bool {
+ atomic.AddInt32(&count, 1)
+ return true
+ }, bigtable.RowFilter(
+ bigtable.ChainFilters(
+ bigtable.StripValueFilter(), // https://cloud.google.com/bigtable/docs/using-filters#strip-value
+ bigtable.CellsPerRowLimitFilter(1),
+ bigtable.LatestNFilter(1),
+ ),
+ ))
+ })
+ }
+
+ err := eg.Wait()
+ sklog.Infof("Counted %d trace options for tile %d", count, tk)
+ return err
+}
+
+func deleteTraceDataForTile(ctx context.Context, ac *bigtable.AdminClient, tk int) error {
+ for shard := 0; shard < bt_tracestore.DefaultShards; shard++ {
+ shardedPrefix := fmt.Sprintf("%02d:ts:t:%d", shard, tk)
+ sklog.Infof("Drop range %s", shardedPrefix)
+ err := ac.DropRowRange(ctx, traceBTTable, shardedPrefix)
+ if err != nil {
+ return err
+ }
+ }
+ sklog.Infof("finished dropping traces in tile %d", tk)
+ return nil
+}
+
+func deleteTraceOptionsForTile(ctx context.Context, ac *bigtable.AdminClient, tk int) error {
+ for shard := 0; shard < bt_tracestore.DefaultShards; shard++ {
+ shardedPrefix := fmt.Sprintf("%02d:ts:p:%d", shard, tk)
+ sklog.Infof("Drop range %s", shardedPrefix)
+ err := ac.DropRowRange(ctx, traceBTTable, shardedPrefix)
+ if err != nil {
+ return err
+ }
+ }
+ sklog.Infof("finished dropping options in tile %d", tk)
+ return nil
+}
+
+func deleteOPSForTile(ctx context.Context, ac *bigtable.AdminClient, tk int) error {
+ prefix := fmt.Sprintf(":ts:o:%d", tk)
+ sklog.Infof("Drop range %s", prefix)
+ err := ac.DropRowRange(ctx, traceBTTable, prefix)
+ if err != nil {
+ return err
+ }
+ sklog.Infof("finished dropping OPS in tile %d", tk)
+ return nil
+}
+
+func readTracesForTile(ctx context.Context, btc bt_tracestore.BTConfig, tk bt_tracestore.TileKey) {
+ traceStore, err := bt_tracestore.New(ctx, btc, false)
+ if err != nil {
+ sklog.Fatalf("Could not instantiate BT tracestore: %s", err)
+ }
+
+ sklog.Infof("Going to fetch tile %d", tk)
+
+ traceMap, _, err := traceStore.DEBUG_getTracesInRange(ctx, tk, tk, 0, 255)
+ if err != nil {
+ sklog.Fatalf("Could not get traces: %s", err)
+ }
+ for id, trace := range traceMap {
+ if trace.Keys["extra_config"] == "Direct3D" && trace.Keys["cpu_or_gpu_value"] == "RadeonHD7770" {
+ sklog.Infof("trace %s has digests %q", id, trace.Digests)
+ }
+ }
+}
diff --git a/golden/go/tracestore/bt_tracestore/bt_tracestore.go b/golden/go/tracestore/bt_tracestore/bt_tracestore.go
index 36f9117..f607afd 100644
--- a/golden/go/tracestore/bt_tracestore/bt_tracestore.go
+++ b/golden/go/tracestore/bt_tracestore/bt_tracestore.go
@@ -136,7 +136,7 @@
// Find out what tile we need to fetch and what index into that tile we need.
// Reminder that tileKeys start at 2^32-1 and decrease in value.
- tileKey, commitIndex := b.getTileKey(repoIndex)
+ tileKey, commitIndex := GetTileKey(repoIndex)
// If these entries have any params we haven't seen before, we need to store those in BigTable.
ops, err := b.updateOrderedParamSet(ctx, tileKey, paramSet)
@@ -162,7 +162,7 @@
// the rows that need updating and the mutations to apply to those rows.
// Specifically, the mutations will add the given entries to BT, clearing out
// anything that was there previously.
-func (b *BTTraceStore) createPutMutations(entries []*tracestore.Entry, tk tileKey, commitIndex int, ops *paramtools.OrderedParamSet, digestTS, optionsTS time.Time) ([]string, []*bigtable.Mutation, error) {
+func (b *BTTraceStore) createPutMutations(entries []*tracestore.Entry, tk TileKey, commitIndex int, ops *paramtools.OrderedParamSet, digestTS, optionsTS time.Time) ([]string, []*bigtable.Mutation, error) {
// These mutations...
mutations := make([]*bigtable.Mutation, 0, len(entries))
// .. should be applied to these rows.
@@ -222,10 +222,10 @@
// These commits could span across multiple tiles, so derive the tiles we need to query.
c := idxCommits[0]
- startTileKey, startCommitIndex := b.getTileKey(c.Index)
+ startTileKey, startCommitIndex := GetTileKey(c.Index)
c = idxCommits[len(idxCommits)-1]
- endTileKey, endCommitIndex := b.getTileKey(c.Index)
+ endTileKey, endCommitIndex := GetTileKey(c.Index)
var egroup errgroup.Group
@@ -268,9 +268,15 @@
return ret, commits, nil
}
+// DEBUG_getTracesInRange exposes getTracesInRange to make it easy to call directly from a helper
+// executable such as trace_tool.
+func (b *BTTraceStore) DEBUG_getTracesInRange(ctx context.Context, startTileKey, endTileKey TileKey, startCommitIndex, endCommitIndex int) (traceMap, paramtools.ParamSet, error) {
+ return b.getTracesInRange(ctx, startTileKey, endTileKey, startCommitIndex, endCommitIndex)
+}
+
// getTracesInRange returns a traceMap with data from the given start and stop points (tile and index).
// It also includes the ParamSet for that range.
-func (b *BTTraceStore) getTracesInRange(ctx context.Context, startTileKey, endTileKey tileKey, startCommitIndex, endCommitIndex int) (traceMap, paramtools.ParamSet, error) {
+func (b *BTTraceStore) getTracesInRange(ctx context.Context, startTileKey, endTileKey TileKey, startCommitIndex, endCommitIndex int) (traceMap, paramtools.ParamSet, error) {
sklog.Debugf("getTracesInRange(%d, %d, %d, %d)", startTileKey, endTileKey, startCommitIndex, endCommitIndex)
// Query those tiles.
nTiles := int(startTileKey - endTileKey + 1)
@@ -280,7 +286,7 @@
var egroup errgroup.Group
tk := startTileKey
for idx := 0; idx < nTiles; idx++ {
- func(idx int, tk tileKey) {
+ func(idx int, tk TileKey) {
egroup.Go(func() error {
var err error
encTiles[idx], err = b.loadTile(ctx, tk)
@@ -386,7 +392,7 @@
}
c := idxCommits[0]
- endKey, endIdx := b.getTileKey(c.Index)
+ endKey, endIdx := GetTileKey(c.Index)
tileStartCommitIdx := c.Index - endIdx
// Given nCommits and the current index, we can figure out how many tiles to
@@ -512,17 +518,17 @@
return allCommits, denseCommits, nil
}
-// getTileKey retrieves the tile key and the index of the commit in the given tile (commitIndex)
+// GetTileKey retrieves the tile key and the index of the commit in the given tile (commitIndex)
// given the index of a commit in the repo (repoIndex).
// commitIndex starts at 0 for the oldest commit in the tile.
-func (b *BTTraceStore) getTileKey(repoIndex int) (tileKey, int) {
+func GetTileKey(repoIndex int) (TileKey, int) {
tileIndex := int32(repoIndex) / DefaultTileSize
- commitIndex := repoIndex % int(DefaultTileSize)
+ commitIndex := repoIndex % DefaultTileSize
return tileKeyFromIndex(tileIndex), commitIndex
}
-// loadTile returns an *encTile corresponding to the tileKey.
-func (b *BTTraceStore) loadTile(ctx context.Context, tileKey tileKey) (*encTile, error) {
+// loadTile returns an *encTile corresponding to the TileKey.
+func (b *BTTraceStore) loadTile(ctx context.Context, tileKey TileKey) (*encTile, error) {
defer metrics2.FuncTimer().Stop()
var egroup errgroup.Group
@@ -558,7 +564,7 @@
}
// loadOptions returns the options map corresponding to the given tile.
-func (b *BTTraceStore) loadOptions(ctx context.Context, tileKey tileKey) (map[encodedTraceID]paramtools.Params, error) {
+func (b *BTTraceStore) loadOptions(ctx context.Context, tileKey TileKey) (map[encodedTraceID]paramtools.Params, error) {
defer metrics2.FuncTimer().Stop()
var egroup errgroup.Group
@@ -628,10 +634,10 @@
return ret, nil
}
-// loadEncodedTraces returns all traces belonging to the given tileKey.
+// loadEncodedTraces returns all traces belonging to the given TileKey.
// As outlined in BIGTABLE.md, the trace ids and the digest ids they
// map to are in an encoded form and will need to be expanded prior to use.
-func (b *BTTraceStore) loadEncodedTraces(ctx context.Context, tileKey tileKey) ([]*encodedTracePair, error) {
+func (b *BTTraceStore) loadEncodedTraces(ctx context.Context, tileKey TileKey) ([]*encodedTracePair, error) {
defer metrics2.FuncTimer().Stop()
var egroup errgroup.Group
shardResults := [DefaultShards][]*encodedTracePair{}
@@ -696,7 +702,7 @@
bigtable.ChainFilters(
bigtable.FamilyFilter(traceFamily),
// can be used for local testing to keep RAM usage lower
- //bigtable.RowSampleFilter(0.1),
+ // bigtable.RowSampleFilter(0.01),
bigtable.LatestNFilter(1),
bigtable.CellsPerRowLimitFilter(DefaultTileSize),
),
@@ -751,9 +757,9 @@
}
// calcShardedRowName deterministically assigns a shard for the given subkey (e.g. traceID)
-// Once this is done, the shard, rowtype, tileKey and the subkey are combined into a
+// Once this is done, the shard, rowtype, TileKey and the subkey are combined into a
// single string to be used as a row name in BT.
-func (b *BTTraceStore) calcShardedRowName(tileKey tileKey, rowType, subkey string) string {
+func (b *BTTraceStore) calcShardedRowName(tileKey TileKey, rowType, subkey string) string {
shard := int32(crc32.ChecksumIEEE([]byte(subkey)) % uint32(DefaultShards))
return shardedRowName(shard, rowType, tileKey, subkey)
}
@@ -761,8 +767,8 @@
// Copied from btts.go in infra/perf
// UpdateOrderedParamSet will add all params from 'p' to the OrderedParamSet
-// for 'tileKey' and write it back to BigTable.
-func (b *BTTraceStore) updateOrderedParamSet(ctx context.Context, tileKey tileKey, p paramtools.ParamSet) (*paramtools.OrderedParamSet, error) {
+// for 'TileKey' and write it back to BigTable.
+func (b *BTTraceStore) updateOrderedParamSet(ctx context.Context, tileKey TileKey, p paramtools.ParamSet) (*paramtools.OrderedParamSet, error) {
defer metrics2.FuncTimer().Stop()
tctx, cancel := context.WithTimeout(ctx, writeTimeout)
@@ -866,7 +872,7 @@
// Note that it will create a new OpsCacheEntry if none exists.
//
// getOps returns false if the OPS in BT was empty, true otherwise (even if cached).
-func (b *BTTraceStore) getOPS(ctx context.Context, tk tileKey) (*opsCacheEntry, bool, error) {
+func (b *BTTraceStore) getOPS(ctx context.Context, tk TileKey) (*opsCacheEntry, bool, error) {
defer metrics2.FuncTimer().Stop()
if b.cacheOps {
entry, ok := b.opsCache.Load(tk.OpsRowName())
diff --git a/golden/go/tracestore/bt_tracestore/bt_tracestore_test.go b/golden/go/tracestore/bt_tracestore/bt_tracestore_test.go
index 3c0c313..7d1a083 100644
--- a/golden/go/tracestore/bt_tracestore/bt_tracestore_test.go
+++ b/golden/go/tracestore/bt_tracestore/bt_tracestore_test.go
@@ -637,51 +637,42 @@
}
// TestGetTileKey tests the internal workings of deriving a
-// tileKey from the commit index. See BIGTABLE.md for more.
+// TileKey from the commit index. See BIGTABLE.md for more.
func TestGetTileKey(t *testing.T) {
- unittest.LargeTest(t)
- unittest.RequiresBigTableEmulator(t)
-
- btConf := BTConfig{
- // Leaving other things blank because we won't actually hit BT or use VCS.
- }
-
- ctx := context.Background()
- traceStore, err := New(ctx, btConf, true)
- require.NoError(t, err)
+ unittest.SmallTest(t)
type testStruct struct {
InputRepoIndex int
- ExpectedKey tileKey
+ ExpectedKey TileKey
ExpectedIndex int
}
// test data is valid, but arbitrary.
tests := []testStruct{
{
InputRepoIndex: 0,
- ExpectedKey: tileKey(2147483647),
+ ExpectedKey: TileKey(2147483647),
ExpectedIndex: 0,
},
{
InputRepoIndex: 10,
- ExpectedKey: tileKey(2147483647),
+ ExpectedKey: TileKey(2147483647),
ExpectedIndex: 10,
},
{
InputRepoIndex: 300,
- ExpectedKey: tileKey(2147483646),
+ ExpectedKey: TileKey(2147483646),
ExpectedIndex: 44,
},
{
InputRepoIndex: 123456,
- ExpectedKey: tileKey(2147483165),
+ ExpectedKey: TileKey(2147483165),
ExpectedIndex: 64,
},
}
for _, test := range tests {
- key, index := traceStore.getTileKey(test.InputRepoIndex)
+ key, index := GetTileKey(test.InputRepoIndex)
require.Equal(t, test.ExpectedKey, key)
require.Equal(t, test.ExpectedIndex, index)
}
@@ -703,7 +694,7 @@
require.NoError(t, err)
type testStruct struct {
- InputKey tileKey
+ InputKey TileKey
InputRowType string
InputSubKey string
@@ -712,14 +703,14 @@
// test data is valid, but arbitrary.
tests := []testStruct{
{
- InputKey: tileKey(2147483647),
+ InputKey: TileKey(2147483647),
InputRowType: typeTrace,
InputSubKey: ",0=1,1=3,3=0,",
ExpectedRowName: "09:ts:t:2147483647:,0=1,1=3,3=0,",
},
{
- InputKey: tileKey(2147483647),
+ InputKey: TileKey(2147483647),
InputRowType: typeTrace,
InputSubKey: ",0=1,1=3,9=0,",
diff --git a/golden/go/tracestore/bt_tracestore/types.go b/golden/go/tracestore/bt_tracestore/types.go
index 846095e..6bea097 100644
--- a/golden/go/tracestore/bt_tracestore/types.go
+++ b/golden/go/tracestore/bt_tracestore/types.go
@@ -73,7 +73,7 @@
maxTilesForDenseTile = 50
// BadTileKey is returned in error conditions.
- badTileKey = tileKey(-1)
+ badTileKey = TileKey(-1)
)
var (
@@ -81,12 +81,12 @@
missingDigestBytes = []byte("")
)
-// tileKey is the identifier for each tile held in BigTable.
+// TileKey is the identifier for each tile held in BigTable.
//
// Note that tile keys are in the opposite order of tile offset, that is, the first commit
// in a repo goes in the first tile, which has key 2^32-1. We do this so more recent
// tiles come first in sort order.
-type tileKey int32
+type TileKey int32
// encodedTraceID is a shortened form of a tiling.TraceID, e.g. 0=1,1=3,3=0,
// Those indices are references to the OrderedParamSet stored in encTile.
diff --git a/golden/go/tracestore/bt_tracestore/util.go b/golden/go/tracestore/bt_tracestore/util.go
index 3daa8ca..94f72e4 100644
--- a/golden/go/tracestore/bt_tracestore/util.go
+++ b/golden/go/tracestore/bt_tracestore/util.go
@@ -15,24 +15,24 @@
"go.skia.org/infra/golden/go/types"
)
-// tileKeyFromIndex converts the tile index to the tileKey.
+// tileKeyFromIndex converts the tile index to the TileKey.
// See BIGTABLE.md for more on this conversion.
-func tileKeyFromIndex(tileIndex int32) tileKey {
+func tileKeyFromIndex(tileIndex int32) TileKey {
if tileIndex < 0 {
return badTileKey
}
- return tileKey(math.MaxInt32 - tileIndex)
+ return TileKey(math.MaxInt32 - tileIndex)
}
// OpsRowName returns the name of the BigTable row which stores the OrderedParamSet
// for this tile.
-func (t tileKey) OpsRowName() string {
+func (t TileKey) OpsRowName() string {
return unshardedRowName(typeOPS, t)
}
// unshardedRowName calculates the row for the given data which all has the same format:
// :[namespace]:[type]:[tile]:
-func unshardedRowName(rowType string, tileKey tileKey) string {
+func unshardedRowName(rowType string, tileKey TileKey) string {
return fmt.Sprintf(":%s:%s:%010d:", traceStoreNameSpace, rowType, tileKey)
}
@@ -40,7 +40,7 @@
// [shard]:[namespace]:[type]:[tile]:[subkey]
// For some data types, where there is only one row, or when doing a prefix-match,
// subkey may be "".
-func shardedRowName(shard int32, rowType string, tileKey tileKey, subkey string) string {
+func shardedRowName(shard int32, rowType string, tileKey TileKey, subkey string) string {
return fmt.Sprintf("%02d:%s:%s:%010d:%s", shard, traceStoreNameSpace, rowType, tileKey, subkey)
}
diff --git a/golden/go/tracestore/bt_tracestore/util_test.go b/golden/go/tracestore/bt_tracestore/util_test.go
index f332300..53426cc 100644
--- a/golden/go/tracestore/bt_tracestore/util_test.go
+++ b/golden/go/tracestore/bt_tracestore/util_test.go
@@ -17,9 +17,9 @@
unittest.SmallTest(t)
// spot-check some arbitrary values
- require.Equal(t, tileKey(2147483647), tileKeyFromIndex(0))
- require.Equal(t, tileKey(2147483451), tileKeyFromIndex(196))
- require.Equal(t, tileKey(908536335), tileKeyFromIndex(1238947312))
+ require.Equal(t, TileKey(2147483647), tileKeyFromIndex(0))
+ require.Equal(t, TileKey(2147483451), tileKeyFromIndex(196))
+ require.Equal(t, TileKey(908536335), tileKeyFromIndex(1238947312))
}
func TestOpsRowName(t *testing.T) {
@@ -35,8 +35,8 @@
unittest.SmallTest(t)
shard := int32(3) // arbitrarily picked
- tileZeroKey := tileKey(math.MaxInt32 - 1)
- veryNewTileKey := tileKey(57)
+ tileZeroKey := TileKey(math.MaxInt32 - 1)
+ veryNewTileKey := TileKey(57)
// Example RowName for a trace
encodedTrace := ",0=1,1=3,3=0,"