github.com/m3db/m3@v1.5.0/src/cluster/placement/compress_test.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package placement 22 23 import ( 24 "fmt" 25 "math/rand" 26 "sync" 27 "testing" 28 29 "github.com/gogo/protobuf/proto" 30 "github.com/stretchr/testify/require" 31 "go.uber.org/goleak" 32 33 "github.com/m3db/m3/src/cluster/generated/proto/placementpb" 34 "github.com/m3db/m3/src/cluster/shard" 35 ) 36 37 func TestMarshalAndUnmarshalPlacementProto(t *testing.T) { 38 b, err := testLatestPlacementProto.Marshal() 39 require.NoError(t, err) 40 41 actual := &placementpb.Placement{} 42 err = proto.Unmarshal(b, actual) 43 require.NoError(t, err) 44 require.Equal(t, testLatestPlacementProto.String(), actual.String()) 45 } 46 47 func TestCompressAndDecompressPlacementProto(t *testing.T) { 48 t.Run("nil input", func(t *testing.T) { 49 _, err := compressPlacementProto(nil) 50 require.Equal(t, errNilPlacementSnapshotsProto, err) 51 52 _, err = decompressPlacementProto(nil) 53 require.Equal(t, errNilValue, err) 54 }) 55 56 t.Run("compress roundtrip", func(t *testing.T) { 57 defer goleak.VerifyNone(t) 58 59 var wg sync.WaitGroup 60 61 for i := 10; i < 100; i++ { 62 wg.Add(1) 63 i := i 64 go func() { 65 defer wg.Done() 66 pl, err := testRandPlacement(50, i).Proto() 67 require.NoError(t, err) 68 69 compressed, err := compressPlacementProto(pl) 70 require.NoError(t, err) 71 72 actual, err := decompressPlacementProto(compressed) 73 require.NoError(t, err) 74 require.Equal(t, pl.String(), actual.String()) 75 }() 76 } 77 78 wg.Wait() 79 }) 80 81 t.Run("compress roundtrip, invalid", func(t *testing.T) { 82 var wg sync.WaitGroup 83 84 for i := 2; i < 50; i++ { 85 wg.Add(1) 86 i := i 87 go func() { 88 defer wg.Done() 89 pl, err := testRandPlacement(50+i, i).Proto() 90 require.NoError(t, err) 91 92 compressed, err := compressPlacementProto(pl) 93 require.NoError(t, err) 94 95 // corrupt placement by trimming the last few bytes 96 l := len(compressed) - (len(compressed) / i) 97 actual, err := decompressPlacementProto(compressed[:l]) 98 require.Error(t, err) 99 require.NotEqual(t, pl.String(), actual.String()) 100 }() 101 } 102 103 wg.Wait() 104 }) 105 } 106 107 func BenchmarkCompressPlacementProto(b *testing.B) { 108 p := testBigRandPlacement() 109 ps, err := NewPlacementsFromLatest(p) 110 require.NoError(b, err) 111 112 proto, err := ps.Latest().Proto() 113 require.NoError(b, err) 114 115 totalCompressedBytes := 0 116 b.ResetTimer() 117 for n := 0; n < b.N; n++ { 118 compressed, err := compressPlacementProto(proto) 119 if err != nil { 120 b.FailNow() 121 } 122 123 totalCompressedBytes += len(compressed) 124 } 125 126 uncompressed, err := proto.Marshal() 127 require.NoError(b, err) 128 129 avgCompressedBytes := float64(totalCompressedBytes) / float64(b.N) 130 avgRate := float64(len(uncompressed)) / avgCompressedBytes 131 b.ReportMetric(avgRate, "compress_rate/op") 132 } 133 134 func BenchmarkDecompressPlacementProto(b *testing.B) { 135 ps, err := NewPlacementsFromLatest(testBigRandPlacement()) 136 require.NoError(b, err) 137 138 proto, err := ps.Latest().Proto() 139 require.NoError(b, err) 140 141 compressed, err := compressPlacementProto(proto) 142 if err != nil { 143 b.FailNow() 144 } 145 146 b.ResetTimer() 147 for n := 0; n < b.N; n++ { 148 _, err := decompressPlacementProto(compressed) 149 if err != nil { 150 b.FailNow() 151 } 152 } 153 } 154 155 func testBigRandPlacement() Placement { 156 numInstances := 2000 157 numShardsPerInstance := 20 158 return testRandPlacement(numInstances, numShardsPerInstance) 159 } 160 161 func testRandPlacement(numInstances, numShardsPerInstance int) Placement { 162 instances := make([]Instance, numInstances) 163 var shardID uint32 164 for i := 0; i < numInstances; i++ { 165 instances[i] = testRandInstance() 166 shards := make([]shard.Shard, numShardsPerInstance) 167 for j := 0; j < numShardsPerInstance; j++ { 168 shardID++ 169 shards[j] = shard.NewShard(shardID). 170 SetState(shard.Available). 171 SetCutoverNanos(0) 172 } 173 instances[i].SetShards(shard.NewShards(shards)) 174 } 175 176 return NewPlacement().SetInstances(instances) 177 } 178 179 func testRandInstance() Instance { 180 id := "prod-cluster-zone-" + randStringBytes(10) 181 host := "host" + randStringBytes(4) + "-zone" 182 port := rand.Intn(1000) // nolint:gosec 183 endpoint := fmt.Sprintf("%s:%d", host, port) 184 return NewInstance(). 185 SetID(id). 186 SetHostname(host). 187 SetIsolationGroup(host). 188 SetPort(uint32(port)). 189 SetEndpoint(endpoint). 190 SetMetadata(InstanceMetadata{DebugPort: 80}). 191 SetZone("zone"). 192 SetWeight(1) 193 } 194 195 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 196 197 func randStringBytes(n int) string { 198 b := make([]byte, n) 199 for i := range b { 200 b[i] = letterBytes[rand.Intn(len(letterBytes))] // nolint:gosec 201 } 202 return string(b) 203 }