github.com/grafana/pyroscope@v1.18.0/pkg/objstore/providers/memory/bucket_client.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package memory
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"io"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/thanos-io/objstore"
    17  )
    18  
    19  var errNotFound = errors.New("inmem: object not found")
    20  
    21  var _ objstore.Bucket = &InMemBucket{}
    22  
    23  // InMemBucket implements the objstore.Bucket interfaces against local memory.
    24  // Methods from Bucket interface are thread-safe. Objects are assumed to be immutable.
    25  type InMemBucket struct {
    26  	mtx     sync.RWMutex
    27  	objects map[string][]byte
    28  	attrs   map[string]objstore.ObjectAttributes
    29  }
    30  
    31  // NewInMemBucket returns a new in memory Bucket.
    32  // NOTE: Returned bucket is just a naive in memory bucket implementation. For test use cases only.
    33  func NewInMemBucket() *InMemBucket {
    34  	return &InMemBucket{
    35  		objects: map[string][]byte{},
    36  		attrs:   map[string]objstore.ObjectAttributes{},
    37  	}
    38  }
    39  
    40  func (b *InMemBucket) Provider() objstore.ObjProvider { return objstore.MEMORY }
    41  
    42  // Objects returns a copy of the internally stored objects.
    43  // NOTE: For assert purposes.
    44  func (b *InMemBucket) Objects() map[string][]byte {
    45  	b.mtx.RLock()
    46  	defer b.mtx.RUnlock()
    47  
    48  	objs := make(map[string][]byte)
    49  	for k, v := range b.objects {
    50  		objs[k] = v
    51  	}
    52  
    53  	return objs
    54  }
    55  
    56  // Iter calls f for each entry in the given directory. The argument to f is the full
    57  // object name including the prefix of the inspected directory.
    58  func (b *InMemBucket) Iter(_ context.Context, dir string, f func(string) error, options ...objstore.IterOption) error {
    59  	unique := map[string]struct{}{}
    60  	params := objstore.ApplyIterOptions(options...)
    61  
    62  	var dirPartsCount int
    63  	dirParts := strings.SplitAfter(dir, objstore.DirDelim)
    64  	for _, p := range dirParts {
    65  		if p == "" {
    66  			continue
    67  		}
    68  		dirPartsCount++
    69  	}
    70  
    71  	b.mtx.RLock()
    72  	for filename := range b.objects {
    73  		if !strings.HasPrefix(filename, dir) || dir == filename {
    74  			continue
    75  		}
    76  
    77  		if params.Recursive {
    78  			// Any object matching the prefix should be included.
    79  			unique[filename] = struct{}{}
    80  			continue
    81  		}
    82  
    83  		parts := strings.SplitAfter(filename, objstore.DirDelim)
    84  		unique[strings.Join(parts[:dirPartsCount+1], "")] = struct{}{}
    85  	}
    86  	b.mtx.RUnlock()
    87  
    88  	var keys []string
    89  	for n := range unique {
    90  		keys = append(keys, n)
    91  	}
    92  	sort.Slice(keys, func(i, j int) bool {
    93  		if strings.HasSuffix(keys[i], objstore.DirDelim) && strings.HasSuffix(keys[j], objstore.DirDelim) {
    94  			return strings.Compare(keys[i], keys[j]) < 0
    95  		}
    96  		if strings.HasSuffix(keys[i], objstore.DirDelim) {
    97  			return false
    98  		}
    99  		if strings.HasSuffix(keys[j], objstore.DirDelim) {
   100  			return true
   101  		}
   102  
   103  		return strings.Compare(keys[i], keys[j]) < 0
   104  	})
   105  
   106  	for _, k := range keys {
   107  		if err := f(k); err != nil {
   108  			return err
   109  		}
   110  	}
   111  	return nil
   112  }
   113  
   114  func (i *InMemBucket) SupportedIterOptions() []objstore.IterOptionType {
   115  	return []objstore.IterOptionType{objstore.Recursive}
   116  }
   117  
   118  func (b *InMemBucket) IterWithAttributes(ctx context.Context, dir string, f func(attrs objstore.IterObjectAttributes) error, options ...objstore.IterOption) error {
   119  	if err := objstore.ValidateIterOptions(b.SupportedIterOptions(), options...); err != nil {
   120  		return err
   121  	}
   122  
   123  	return b.Iter(ctx, dir, func(name string) error {
   124  		return f(objstore.IterObjectAttributes{Name: name})
   125  
   126  	}, options...)
   127  }
   128  
   129  // Get returns a reader for the given object name.
   130  func (b *InMemBucket) Get(_ context.Context, name string) (io.ReadCloser, error) {
   131  	if name == "" {
   132  		return nil, errors.New("inmem: object name is empty")
   133  	}
   134  
   135  	b.mtx.RLock()
   136  	file, ok := b.objects[name]
   137  	b.mtx.RUnlock()
   138  	if !ok {
   139  		return nil, errNotFound
   140  	}
   141  
   142  	return io.NopCloser(bytes.NewReader(file)), nil
   143  }
   144  
   145  // GetRange returns a new range reader for the given object name and range.
   146  func (b *InMemBucket) GetRange(_ context.Context, name string, off, length int64) (io.ReadCloser, error) {
   147  	if name == "" {
   148  		return nil, errors.New("inmem: object name is empty")
   149  	}
   150  
   151  	b.mtx.RLock()
   152  	file, ok := b.objects[name]
   153  	b.mtx.RUnlock()
   154  	if !ok {
   155  		return nil, errNotFound
   156  	}
   157  
   158  	if int64(len(file)) < off {
   159  		return io.NopCloser(bytes.NewReader(nil)), nil
   160  	}
   161  
   162  	if length == 0 {
   163  		return io.NopCloser(bytes.NewReader(nil)), nil
   164  	}
   165  	if length == -1 {
   166  		return io.NopCloser(bytes.NewReader(file[off:])), nil
   167  	}
   168  
   169  	if int64(len(file)) <= off+length {
   170  		// Just return maximum of what we have.
   171  		length = int64(len(file)) - off
   172  	}
   173  
   174  	return io.NopCloser(bytes.NewReader(file[off : off+length])), nil
   175  }
   176  
   177  // Exists checks if the given directory exists in memory.
   178  func (b *InMemBucket) Exists(_ context.Context, name string) (bool, error) {
   179  	b.mtx.RLock()
   180  	defer b.mtx.RUnlock()
   181  	_, ok := b.objects[name]
   182  	return ok, nil
   183  }
   184  
   185  // Attributes returns information about the specified object.
   186  func (b *InMemBucket) Attributes(_ context.Context, name string) (objstore.ObjectAttributes, error) {
   187  	b.mtx.RLock()
   188  	attrs, ok := b.attrs[name]
   189  	b.mtx.RUnlock()
   190  	if !ok {
   191  		return objstore.ObjectAttributes{}, errNotFound
   192  	}
   193  	return attrs, nil
   194  }
   195  
   196  // Upload writes the file specified in src to into the memory.
   197  func (b *InMemBucket) Upload(_ context.Context, name string, r io.Reader, opts ...objstore.ObjectUploadOption) error {
   198  	b.mtx.Lock()
   199  	defer b.mtx.Unlock()
   200  	body, err := io.ReadAll(r)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	b.objects[name] = body
   205  	b.attrs[name] = objstore.ObjectAttributes{
   206  		Size:         int64(len(body)),
   207  		LastModified: time.Now(),
   208  	}
   209  	return nil
   210  }
   211  
   212  // Delete removes all data prefixed with the dir.
   213  func (b *InMemBucket) Delete(_ context.Context, name string) error {
   214  	b.mtx.Lock()
   215  	defer b.mtx.Unlock()
   216  	if _, ok := b.objects[name]; !ok {
   217  		return errNotFound
   218  	}
   219  	delete(b.objects, name)
   220  	delete(b.attrs, name)
   221  	return nil
   222  }
   223  
   224  // IsObjNotFoundErr returns true if error means that object is not found. Relevant to Get operations.
   225  func (b *InMemBucket) IsObjNotFoundErr(err error) bool {
   226  	return errors.Is(err, errNotFound)
   227  }
   228  
   229  // IsAccessDeniedErr returns true if access to object is denied.
   230  func (b *InMemBucket) IsAccessDeniedErr(err error) bool {
   231  	return false
   232  }
   233  
   234  func (b *InMemBucket) Close() error { return nil }
   235  
   236  // Name returns the bucket name.
   237  func (b *InMemBucket) Name() string {
   238  	return "inmem"
   239  }
   240  
   241  func (b *InMemBucket) Set(name string, data []byte) {
   242  	b.mtx.Lock()
   243  	defer b.mtx.Unlock()
   244  	b.objects[name] = data
   245  	b.attrs[name] = objstore.ObjectAttributes{
   246  		Size:         int64(len(data)),
   247  		LastModified: time.Now(),
   248  	}
   249  }