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  }