github.com/onflow/atree@v0.6.0/cmd/stress/array.go (about)

     1  /*
     2   * Atree - Scalable Arrays and Ordered Maps
     3   *
     4   * Copyright 2021 Dapper Labs, Inc.
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package main
    20  
    21  import (
    22  	"fmt"
    23  	"os"
    24  	"runtime"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/onflow/atree"
    29  )
    30  
    31  const (
    32  	arrayAppendOp = iota
    33  	arrayInsertOp
    34  	arraySetOp
    35  	arrayRemoveOp
    36  	maxArrayOp
    37  )
    38  
    39  type arrayStatus struct {
    40  	lock sync.RWMutex
    41  
    42  	startTime time.Time
    43  
    44  	count uint64 // number of elements in array
    45  
    46  	appendOps uint64
    47  	insertOps uint64
    48  	setOps    uint64
    49  	removeOps uint64
    50  }
    51  
    52  var _ Status = &arrayStatus{}
    53  
    54  func newArrayStatus() *arrayStatus {
    55  	return &arrayStatus{startTime: time.Now()}
    56  }
    57  
    58  func (status *arrayStatus) String() string {
    59  	status.lock.RLock()
    60  	defer status.lock.RUnlock()
    61  
    62  	duration := time.Since(status.startTime)
    63  
    64  	var m runtime.MemStats
    65  	runtime.ReadMemStats(&m)
    66  
    67  	return fmt.Sprintf("duration %s, heapAlloc %d MiB, %d elements, %d appends, %d sets, %d inserts, %d removes",
    68  		duration.Truncate(time.Second).String(),
    69  		m.Alloc/1024/1024,
    70  		status.count,
    71  		status.appendOps,
    72  		status.setOps,
    73  		status.insertOps,
    74  		status.removeOps,
    75  	)
    76  }
    77  
    78  func (status *arrayStatus) incAppend() {
    79  	status.lock.Lock()
    80  	defer status.lock.Unlock()
    81  
    82  	status.appendOps++
    83  	status.count++
    84  }
    85  
    86  func (status *arrayStatus) incSet() {
    87  	status.lock.Lock()
    88  	defer status.lock.Unlock()
    89  
    90  	status.setOps++
    91  }
    92  
    93  func (status *arrayStatus) incInsert() {
    94  	status.lock.Lock()
    95  	defer status.lock.Unlock()
    96  
    97  	status.insertOps++
    98  	status.count++
    99  }
   100  
   101  func (status *arrayStatus) incRemove() {
   102  	status.lock.Lock()
   103  	defer status.lock.Unlock()
   104  
   105  	status.removeOps++
   106  	status.count--
   107  }
   108  
   109  func (status *arrayStatus) Write() {
   110  	writeStatus(status.String())
   111  }
   112  
   113  func testArray(
   114  	storage *atree.PersistentSlabStorage,
   115  	address atree.Address,
   116  	typeInfo atree.TypeInfo,
   117  	maxLength uint64,
   118  	status *arrayStatus,
   119  	minHeapAllocMiB uint64,
   120  	maxHeapAllocMiB uint64,
   121  ) {
   122  
   123  	// Create new array
   124  	array, err := atree.NewArray(storage, address, typeInfo)
   125  	if err != nil {
   126  		fmt.Fprintf(os.Stderr, "Failed to create new array: %s", err)
   127  		return
   128  	}
   129  
   130  	// values contains array elements in the same order.  It is used to check data loss.
   131  	values := make([]atree.Value, 0, maxLength)
   132  
   133  	reduceHeapAllocs := false
   134  
   135  	opCount := uint64(0)
   136  
   137  	var m runtime.MemStats
   138  
   139  	for {
   140  		runtime.ReadMemStats(&m)
   141  		allocMiB := m.Alloc / 1024 / 1024
   142  
   143  		if !reduceHeapAllocs && allocMiB > maxHeapAllocMiB {
   144  			fmt.Printf("\nHeapAlloc is %d MiB, removing elements to reduce allocs...\n", allocMiB)
   145  			reduceHeapAllocs = true
   146  		} else if reduceHeapAllocs && allocMiB < minHeapAllocMiB {
   147  			fmt.Printf("\nHeapAlloc is %d MiB, resuming random operation...\n", allocMiB)
   148  			reduceHeapAllocs = false
   149  		}
   150  
   151  		if reduceHeapAllocs && array.Count() == 0 {
   152  			fmt.Printf("\nHeapAlloc is %d MiB while array is empty, drop read/write cache to free mem\n", allocMiB)
   153  			reduceHeapAllocs = false
   154  
   155  			// Commit slabs to storage and drop read and write to reduce mem
   156  			err = storage.FastCommit(runtime.NumCPU())
   157  			if err != nil {
   158  				fmt.Fprintf(os.Stderr, "Failed to commit to storage: %s", err)
   159  				return
   160  			}
   161  
   162  			storage.DropDeltas()
   163  			storage.DropCache()
   164  
   165  			// Load root slab from storage and cache it in read cache
   166  			rootID := array.StorageID()
   167  			array, err = atree.NewArrayWithRootID(storage, rootID)
   168  			if err != nil {
   169  				fmt.Fprintf(os.Stderr, "Failed to create array from root id %s: %s", rootID, err)
   170  				return
   171  			}
   172  
   173  			runtime.GC()
   174  
   175  			// Check if map is using > MaxHeapAlloc while empty.
   176  			runtime.ReadMemStats(&m)
   177  			allocMiB = m.Alloc / 1024 / 1024
   178  			fmt.Printf("\nHeapAlloc is %d MiB after cleanup and forced gc\n", allocMiB)
   179  
   180  			// Prevent infinite loop that doesn't do useful work.
   181  			if allocMiB > maxHeapAllocMiB {
   182  				// This shouldn't happen unless there's a memory leak.
   183  				fmt.Fprintf(
   184  					os.Stderr,
   185  					"Exiting because allocMiB %d > maxMapHeapAlloMiB %d with empty map\n",
   186  					allocMiB,
   187  					maxHeapAllocMiB)
   188  				return
   189  			}
   190  		}
   191  
   192  		nextOp := r.Intn(maxArrayOp)
   193  
   194  		if array.Count() == maxLength || reduceHeapAllocs {
   195  			nextOp = arrayRemoveOp
   196  		}
   197  
   198  		switch nextOp {
   199  
   200  		case arrayAppendOp:
   201  			opCount++
   202  
   203  			nestedLevels := r.Intn(maxNestedLevels)
   204  			v, err := randomValue(storage, address, nestedLevels)
   205  			if err != nil {
   206  				fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err)
   207  				return
   208  			}
   209  
   210  			copiedValue, err := copyValue(storage, atree.Address{}, v)
   211  			if err != nil {
   212  				fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err)
   213  				return
   214  			}
   215  
   216  			// Append to values
   217  			values = append(values, copiedValue)
   218  
   219  			// Append to array
   220  			err = array.Append(v)
   221  			if err != nil {
   222  				fmt.Fprintf(os.Stderr, "Failed to append %s: %s", v, err)
   223  				return
   224  			}
   225  
   226  			// Update status
   227  			status.incAppend()
   228  
   229  		case arraySetOp:
   230  			opCount++
   231  
   232  			if array.Count() == 0 {
   233  				continue
   234  			}
   235  
   236  			k := r.Intn(int(array.Count()))
   237  
   238  			nestedLevels := r.Intn(maxNestedLevels)
   239  			v, err := randomValue(storage, address, nestedLevels)
   240  			if err != nil {
   241  				fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err)
   242  				return
   243  			}
   244  
   245  			copiedValue, err := copyValue(storage, atree.Address{}, v)
   246  			if err != nil {
   247  				fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err)
   248  				return
   249  			}
   250  
   251  			oldV := values[k]
   252  
   253  			// Update values
   254  			values[k] = copiedValue
   255  
   256  			// Update array
   257  			existingStorable, err := array.Set(uint64(k), v)
   258  			if err != nil {
   259  				fmt.Fprintf(os.Stderr, "Failed to set %s at index %d: %s", v, k, err)
   260  				return
   261  			}
   262  
   263  			// Compare overwritten value from array with overwritten value from values
   264  			existingValue, err := existingStorable.StoredValue(storage)
   265  			if err != nil {
   266  				fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingStorable, err)
   267  				return
   268  			}
   269  
   270  			err = valueEqual(oldV, existingValue)
   271  			if err != nil {
   272  				fmt.Fprintf(os.Stderr, "Failed to compare %s and %s: %s", existingValue, oldV, err)
   273  				return
   274  			}
   275  
   276  			// Delete overwritten element from storage
   277  			err = removeStorable(storage, existingStorable)
   278  			if err != nil {
   279  				fmt.Fprintf(os.Stderr, "Failed to remove storable %s: %s", existingStorable, err)
   280  				return
   281  			}
   282  
   283  			err = removeValue(storage, oldV)
   284  			if err != nil {
   285  				fmt.Fprintf(os.Stderr, "Failed to remove copied overwritten value %s: %s", oldV, err)
   286  				return
   287  			}
   288  
   289  			// Update status
   290  			status.incSet()
   291  
   292  		case arrayInsertOp:
   293  			opCount++
   294  
   295  			k := r.Intn(int(array.Count() + 1))
   296  
   297  			nestedLevels := r.Intn(maxNestedLevels)
   298  			v, err := randomValue(storage, address, nestedLevels)
   299  			if err != nil {
   300  				fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err)
   301  				return
   302  			}
   303  
   304  			copiedValue, err := copyValue(storage, atree.Address{}, v)
   305  			if err != nil {
   306  				fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err)
   307  				return
   308  			}
   309  
   310  			// Update values
   311  			if k == int(array.Count()) {
   312  				values = append(values, copiedValue)
   313  			} else {
   314  				values = append(values, nil)
   315  				copy(values[k+1:], values[k:])
   316  				values[k] = copiedValue
   317  			}
   318  
   319  			// Update array
   320  			err = array.Insert(uint64(k), v)
   321  			if err != nil {
   322  				fmt.Fprintf(os.Stderr, "Failed to insert %s into index %d: %s", v, k, err)
   323  				return
   324  			}
   325  
   326  			// Update status
   327  			status.incInsert()
   328  
   329  		case arrayRemoveOp:
   330  			if array.Count() == 0 {
   331  				continue
   332  			}
   333  
   334  			opCount++
   335  
   336  			k := r.Intn(int(array.Count()))
   337  
   338  			oldV := values[k]
   339  
   340  			// Update values
   341  			copy(values[k:], values[k+1:])
   342  			values[len(values)-1] = nil
   343  			values = values[:len(values)-1]
   344  
   345  			// Update array
   346  			existingStorable, err := array.Remove(uint64(k))
   347  			if err != nil {
   348  				fmt.Fprintf(os.Stderr, "Failed to remove element at index %d: %s", k, err)
   349  				return
   350  			}
   351  
   352  			// Compare removed value from array with removed value from values
   353  			existingValue, err := existingStorable.StoredValue(storage)
   354  			if err != nil {
   355  				fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingStorable, err)
   356  				return
   357  			}
   358  
   359  			err = valueEqual(oldV, existingValue)
   360  			if err != nil {
   361  				fmt.Fprintf(os.Stderr, "Failed to compare %s and %s: %s", existingValue, oldV, err)
   362  				return
   363  			}
   364  
   365  			// Delete removed element from storage
   366  			err = removeStorable(storage, existingStorable)
   367  			if err != nil {
   368  				fmt.Fprintf(os.Stderr, "Failed to remove element %s: %s", existingStorable, err)
   369  				return
   370  			}
   371  
   372  			err = removeValue(storage, oldV)
   373  			if err != nil {
   374  				fmt.Fprintf(os.Stderr, "Failed to remove copied removed value %s: %s", oldV, err)
   375  				return
   376  			}
   377  
   378  			// Update status
   379  			status.incRemove()
   380  		}
   381  
   382  		// Check array elements against values after every op
   383  		err = checkArrayDataLoss(array, values)
   384  		if err != nil {
   385  			fmt.Fprintln(os.Stderr, err)
   386  			return
   387  		}
   388  
   389  		if opCount >= 100 {
   390  			opCount = 0
   391  			rootIDs, err := atree.CheckStorageHealth(storage, -1)
   392  			if err != nil {
   393  				fmt.Fprintln(os.Stderr, err)
   394  				return
   395  			}
   396  			ids := make([]atree.StorageID, 0, len(rootIDs))
   397  			for id := range rootIDs {
   398  				// filter out root ids with empty address
   399  				if id.Address != atree.AddressUndefined {
   400  					ids = append(ids, id)
   401  				}
   402  			}
   403  			if len(ids) != 1 || ids[0] != array.StorageID() {
   404  				fmt.Fprintf(os.Stderr, "root storage ids %v in storage, want %s\n", ids, array.StorageID())
   405  				return
   406  			}
   407  		}
   408  	}
   409  }
   410  
   411  func checkArrayDataLoss(array *atree.Array, values []atree.Value) error {
   412  
   413  	// Check array has the same number of elements as values
   414  	if array.Count() != uint64(len(values)) {
   415  		return fmt.Errorf("Count() %d != len(values) %d", array.Count(), len(values))
   416  	}
   417  
   418  	// Check every element
   419  	for i, v := range values {
   420  		storable, err := array.Get(uint64(i))
   421  		if err != nil {
   422  			return fmt.Errorf("failed to get element at %d: %w", i, err)
   423  		}
   424  		convertedValue, err := storable.StoredValue(array.Storage)
   425  		if err != nil {
   426  			return fmt.Errorf("failed to convert storable to value at %d: %w", i, err)
   427  		}
   428  		err = valueEqual(v, convertedValue)
   429  		if err != nil {
   430  			return fmt.Errorf("failed to compare %s and %s: %w", v, convertedValue, err)
   431  		}
   432  	}
   433  
   434  	return nil
   435  }