github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/storage_delete_test.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package storage
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	. "github.com/onsi/ginkgo/v2"
    11  	. "github.com/onsi/gomega"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/sirupsen/logrus"
    14  
    15  	"github.com/pyroscope-io/pyroscope/pkg/config"
    16  	"github.com/pyroscope-io/pyroscope/pkg/health"
    17  	"github.com/pyroscope-io/pyroscope/pkg/storage/dict"
    18  	"github.com/pyroscope-io/pyroscope/pkg/storage/dimension"
    19  	"github.com/pyroscope-io/pyroscope/pkg/storage/segment"
    20  	"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
    21  	"github.com/pyroscope-io/pyroscope/pkg/testing"
    22  )
    23  
    24  var _ = Describe("storage package", func() {
    25  	var s *Storage
    26  
    27  	testing.WithConfig(func(cfg **config.Config) {
    28  		JustBeforeEach(func() {
    29  			var err error
    30  			s, err = New(NewConfig(&(*cfg).Server), logrus.StandardLogger(), prometheus.NewRegistry(), new(health.Controller), NoopApplicationMetadataService{})
    31  			Expect(err).ToNot(HaveOccurred())
    32  		})
    33  	})
    34  
    35  	Context("delete app", func() {
    36  		/*************************************/
    37  		/*  h e l p e r   f u n c t i o n s  */
    38  		/*************************************/
    39  		checkSegmentsPresence := func(appname string, presence bool) {
    40  			segmentKey, err := segment.ParseKey(string(appname))
    41  			Expect(err).ToNot(HaveOccurred())
    42  			segmentKeyStr := segmentKey.SegmentKey()
    43  			Expect(segmentKeyStr).To(Equal(appname + "{}"))
    44  			_, ok := s.segments.Lookup(segmentKeyStr)
    45  
    46  			if presence {
    47  				Expect(ok).To(BeTrue())
    48  			} else {
    49  				Expect(ok).To(BeFalse())
    50  			}
    51  		}
    52  
    53  		checkDimensionsPresence := func(appname string, presence bool) interface{} {
    54  			d, ok := s.lookupAppDimension(appname)
    55  			if presence {
    56  				Expect(ok).To(BeTrue())
    57  			} else {
    58  				Expect(ok).To(BeFalse())
    59  			}
    60  
    61  			return d
    62  		}
    63  
    64  		checkTreesPresence := func(appname string, st time.Time, depth int, presence bool) interface{} {
    65  			key, err := segment.ParseKey(appname)
    66  			Expect(err).ToNot(HaveOccurred())
    67  			treeKeyName := key.TreeKey(depth, st)
    68  			t, ok := s.trees.Lookup(treeKeyName)
    69  			if presence {
    70  				Expect(ok).To(BeTrue())
    71  			} else {
    72  				Expect(ok).To(BeFalse())
    73  			}
    74  
    75  			return t
    76  		}
    77  
    78  		checkDictsPresence := func(appname string, presence bool) interface{} {
    79  			d, ok := s.dicts.Lookup(appname)
    80  			if presence {
    81  				Expect(ok).To(BeTrue())
    82  			} else {
    83  				Expect(ok).To(BeFalse())
    84  			}
    85  			return d
    86  		}
    87  
    88  		checkLabelsPresence := func(appname string, presence bool) {
    89  			// this indirectly calls s.labels
    90  			appnames := s.GetAppNames(context.TODO())
    91  
    92  			// linear scan should be fast enough here
    93  			found := false
    94  			for _, v := range appnames {
    95  				if v == appname {
    96  					found = true
    97  				}
    98  			}
    99  
   100  			if presence {
   101  				Expect(found).To(BeTrue())
   102  			} else {
   103  				Expect(found).To(BeFalse())
   104  			}
   105  		}
   106  
   107  		Context("simple app", func() {
   108  			It("works correctly", func() {
   109  				appname := "my.app.cpu"
   110  
   111  				// We insert info for an app
   112  				tree1 := tree.New()
   113  				tree1.Insert([]byte("a;b"), uint64(1))
   114  
   115  				st := testing.SimpleTime(10)
   116  				et := testing.SimpleTime(19)
   117  				key, _ := segment.ParseKey(appname)
   118  				err := s.Put(context.TODO(), &PutInput{
   119  					StartTime:  st,
   120  					EndTime:    et,
   121  					Key:        key,
   122  					Val:        tree1,
   123  					SpyName:    "testspy",
   124  					SampleRate: 100,
   125  				})
   126  				Expect(err).ToNot(HaveOccurred())
   127  
   128  				// Since the DeleteApp also removes dictionaries
   129  				// therefore we need to create them manually here
   130  				// (they are normally created when TODO)
   131  				d := dict.New()
   132  				s.dicts.Put(appname, d)
   133  
   134  				/*******************************/
   135  				/*  S a n i t y   C h e c k s  */
   136  				/*******************************/
   137  				// Dimensions
   138  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(1)))
   139  				checkDimensionsPresence(appname, true)
   140  
   141  				// Trees
   142  				Expect(s.trees.CacheSize()).To(Equal(uint64(1)))
   143  				checkTreesPresence(appname, st, 0, true)
   144  
   145  				// Segments
   146  				Expect(s.segments.CacheSize()).To(Equal(uint64(1)))
   147  				checkSegmentsPresence(appname, true)
   148  
   149  				// Dicts
   150  				// I manually inserted a dictionary so it should be fine?
   151  				Expect(s.dicts.CacheSize()).To(Equal(uint64(1)))
   152  				checkDictsPresence(appname, true)
   153  
   154  				// Labels
   155  				checkLabelsPresence(appname, true)
   156  
   157  				/*************************/
   158  				/*  D e l e t e   a p p  */
   159  				/*************************/
   160  				err = s.DeleteApp(context.TODO(), appname)
   161  				Expect(err).ToNot(HaveOccurred())
   162  
   163  				// Trees
   164  				// should've been deleted from CACHE
   165  				Expect(s.trees.CacheSize()).To(Equal(uint64(0)))
   166  				checkTreesPresence(appname, st, 0, false)
   167  
   168  				// Dimensions
   169  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(0)))
   170  				checkDimensionsPresence(appname, false)
   171  
   172  				// Dicts
   173  				Expect(s.dicts.CacheSize()).To(Equal(uint64(0)))
   174  				checkDictsPresence(appname, false)
   175  
   176  				// Segments
   177  				Expect(s.segments.CacheSize()).To(Equal(uint64(0)))
   178  				checkSegmentsPresence(appname, false)
   179  
   180  				// Labels
   181  				checkLabelsPresence(appname, false)
   182  			})
   183  		})
   184  
   185  		Context("app with labels", func() {
   186  			It("works correctly", func() {
   187  				appname := "my.app.cpu"
   188  
   189  				// We insert info for an app
   190  				tree1 := tree.New()
   191  				tree1.Insert([]byte("a;b"), uint64(1))
   192  
   193  				// We are mirroring this on the simple.golang.cpu example
   194  				labels := []string{
   195  					"",
   196  					"{foo=bar,function=fast}",
   197  					"{foo=bar,function=slow}",
   198  				}
   199  
   200  				st := testing.SimpleTime(10)
   201  				et := testing.SimpleTime(19)
   202  				for _, l := range labels {
   203  					key, _ := segment.ParseKey(appname + l)
   204  					err := s.Put(context.TODO(), &PutInput{
   205  						StartTime:  st,
   206  						EndTime:    et,
   207  						Key:        key,
   208  						Val:        tree1,
   209  						SpyName:    "testspy",
   210  						SampleRate: 100,
   211  					})
   212  					Expect(err).ToNot(HaveOccurred())
   213  				}
   214  
   215  				// Since the DeleteApp also removes dictionaries
   216  				// therefore we need to create them manually here
   217  				// (they are normally created when TODO)
   218  				d := dict.New()
   219  				s.dicts.Put(appname, d)
   220  
   221  				/*******************************/
   222  				/*  S a n i t y   C h e c k s  */
   223  				/*******************************/
   224  
   225  				By("checking dimensions were created")
   226  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(4)))
   227  				checkDimensionsPresence(appname, true)
   228  
   229  				By("checking trees were created")
   230  				Expect(s.trees.CacheSize()).To(Equal(uint64(3)))
   231  				checkTreesPresence(appname, st, 0, true)
   232  
   233  				By("checking segments were created")
   234  				Expect(s.segments.CacheSize()).To(Equal(uint64(3)))
   235  				checkSegmentsPresence(appname, true)
   236  
   237  				By("checking dicts were created")
   238  				// Dicts
   239  				// I manually inserted a dictionary so it should be fine?
   240  				Expect(s.dicts.CacheSize()).To(Equal(uint64(1)))
   241  				checkDictsPresence(appname, true)
   242  
   243  				// Labels
   244  				checkLabelsPresence(appname, true)
   245  
   246  				/*************************/
   247  				/*  D e l e t e   a p p  */
   248  				/*************************/
   249  				By("deleting the app")
   250  				err := s.DeleteApp(context.TODO(), appname)
   251  				Expect(err).ToNot(HaveOccurred())
   252  
   253  				By("checking trees were deleted")
   254  				// Trees
   255  				// should've been deleted from CACHE
   256  				Expect(s.trees.CacheSize()).To(Equal(uint64(0)))
   257  				checkTreesPresence(appname, st, 0, false)
   258  
   259  				// Dimensions
   260  				By("checking dimensions were deleted")
   261  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(0)))
   262  				checkDimensionsPresence(appname, false)
   263  
   264  				// Dicts
   265  				By("checking dicts were deleted")
   266  				Expect(s.dicts.CacheSize()).To(Equal(uint64(0)))
   267  				checkDictsPresence(appname, false)
   268  
   269  				// Segments
   270  				By("checking segments were deleted")
   271  				Expect(s.segments.CacheSize()).To(Equal(uint64(0)))
   272  				checkSegmentsPresence(appname, false)
   273  
   274  				// Labels
   275  				By("checking labels were deleted")
   276  				checkLabelsPresence(appname, false)
   277  			})
   278  		})
   279  
   280  		// In this test we have 2 apps with the same label
   281  		// And deleting one app should not interfer with the labels of the other app
   282  		Context("multiple apps with labels", func() {
   283  			It("deletes the correct data", func() {
   284  				st := testing.SimpleTime(10)
   285  				insert := func(appname string) {
   286  					// We insert info for an app
   287  					tree1 := tree.New()
   288  					tree1.Insert([]byte("a;b"), uint64(1))
   289  
   290  					// We are mirroring this on the simple.golang.cpu example
   291  					labels := []string{
   292  						"",
   293  						"{foo=bar,function=fast}",
   294  						"{foo=bar,function=slow}",
   295  					}
   296  
   297  					et := testing.SimpleTime(19)
   298  					for _, l := range labels {
   299  						key, _ := segment.ParseKey(appname + l)
   300  						err := s.Put(context.TODO(), &PutInput{
   301  							StartTime:  st,
   302  							EndTime:    et,
   303  							Key:        key,
   304  							Val:        tree1,
   305  							SpyName:    "testspy",
   306  							SampleRate: 100,
   307  						})
   308  						Expect(err).ToNot(HaveOccurred())
   309  					}
   310  
   311  					// Since the DeleteApp also removes dictionaries
   312  					// therefore we need to create them manually here
   313  					// (they are normally created when TODO)
   314  					d := dict.New()
   315  					s.dicts.Put(appname, d)
   316  				}
   317  
   318  				app1name := "myapp1.cpu"
   319  				app2name := "myapp2.cpu"
   320  
   321  				insert(app1name)
   322  				insert(app2name)
   323  
   324  				/*******************************/
   325  				/*  S a n i t y   C h e c k s  */
   326  				/*******************************/
   327  				By("checking dimensions were created")
   328  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(5)))
   329  				checkDimensionsPresence(app1name, true)
   330  				checkDimensionsPresence(app2name, true)
   331  
   332  				By("checking trees were created")
   333  				Expect(s.trees.CacheSize()).To(Equal(uint64(6)))
   334  				checkTreesPresence(app1name, st, 0, true)
   335  				checkTreesPresence(app2name, st, 0, true)
   336  
   337  				By("checking segments were created")
   338  				Expect(s.segments.CacheSize()).To(Equal(uint64(6)))
   339  				checkSegmentsPresence(app1name, true)
   340  				checkSegmentsPresence(app2name, true)
   341  
   342  				By("checking dicts were created")
   343  				Expect(s.dicts.CacheSize()).To(Equal(uint64(2)))
   344  				checkDictsPresence(app1name, true)
   345  				checkDictsPresence(app2name, true)
   346  
   347  				By("checking labels were created")
   348  				checkLabelsPresence(app1name, true)
   349  				checkLabelsPresence(app2name, true)
   350  
   351  				/*************************/
   352  				/*  D e l e t e   a p p  */
   353  				/*************************/
   354  				By("deleting the app")
   355  				err := s.DeleteApp(context.TODO(), app1name)
   356  				Expect(err).ToNot(HaveOccurred())
   357  
   358  				By("checking trees were deleted")
   359  				Expect(s.trees.CacheSize()).To(Equal(uint64(3)))
   360  				checkTreesPresence(app1name, st, 0, false)
   361  				checkTreesPresence(app2name, st, 0, true)
   362  
   363  				// Dimensions
   364  				By("checking dimensions were deleted")
   365  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(4)))
   366  
   367  				// Dimensions that refer to app2 are still intact
   368  				v, ok := s.dimensions.Lookup("__name__:myapp2.cpu")
   369  				Expect(ok).To(Equal(true))
   370  				Expect(v.(*dimension.Dimension).Keys).To(Equal([]dimension.Key{
   371  					dimension.Key("myapp2.cpu{foo=bar,function=fast}"),
   372  					dimension.Key("myapp2.cpu{foo=bar,function=slow}"),
   373  					dimension.Key("myapp2.cpu{}"),
   374  				}))
   375  
   376  				v, ok = s.dimensions.Lookup("foo:bar")
   377  				Expect(ok).To(Equal(true))
   378  				Expect(v.(*dimension.Dimension).Keys).To(Equal([]dimension.Key{
   379  					dimension.Key("myapp2.cpu{foo=bar,function=fast}"),
   380  					dimension.Key("myapp2.cpu{foo=bar,function=slow}"),
   381  				}))
   382  
   383  				v, ok = s.dimensions.Lookup("function:fast")
   384  				Expect(ok).To(Equal(true))
   385  				Expect(v.(*dimension.Dimension).Keys).To(Equal([]dimension.Key{
   386  					dimension.Key("myapp2.cpu{foo=bar,function=fast}"),
   387  				}))
   388  
   389  				v, ok = s.dimensions.Lookup("function:slow")
   390  				Expect(ok).To(Equal(true))
   391  				Expect(v.(*dimension.Dimension).Keys).To(Equal([]dimension.Key{
   392  					dimension.Key("myapp2.cpu{foo=bar,function=slow}"),
   393  				}))
   394  
   395  				By("checking dicts were deleted")
   396  				Expect(s.dicts.CacheSize()).To(Equal(uint64(1)))
   397  				checkDictsPresence(app1name, false)
   398  				checkDictsPresence(app2name, true)
   399  
   400  				By("checking segments were deleted")
   401  				Expect(s.segments.CacheSize()).To(Equal(uint64(3)))
   402  				checkSegmentsPresence(app1name, false)
   403  				checkSegmentsPresence(app2name, true)
   404  
   405  				By("checking labels were deleted")
   406  				checkLabelsPresence(app1name, false)
   407  				checkSegmentsPresence(app2name, true)
   408  			})
   409  		})
   410  
   411  		// Delete an unrelated app
   412  		// It should not fail
   413  		It("is idempotent", func() {
   414  			appname := "my.app.cpu"
   415  
   416  			// We insert info for an app
   417  			tree1 := tree.New()
   418  			tree1.Insert([]byte("a;b"), uint64(1))
   419  
   420  			// We are mirroring this on the simple.golang.cpu example
   421  			labels := []string{
   422  				"",
   423  				"{foo=bar,function=fast}",
   424  				"{foo=bar,function=slow}",
   425  			}
   426  
   427  			st := testing.SimpleTime(10)
   428  			et := testing.SimpleTime(19)
   429  			for _, l := range labels {
   430  				key, _ := segment.ParseKey(appname + l)
   431  				err := s.Put(context.TODO(), &PutInput{
   432  					StartTime:  st,
   433  					EndTime:    et,
   434  					Key:        key,
   435  					Val:        tree1,
   436  					SpyName:    "testspy",
   437  					SampleRate: 100,
   438  				})
   439  				Expect(err).ToNot(HaveOccurred())
   440  			}
   441  
   442  			// Since the DeleteApp also removes dictionaries
   443  			// therefore we need to create them manually here
   444  			// (they are normally created when TODO)
   445  			d := dict.New()
   446  			s.dicts.Put(appname, d)
   447  
   448  			/*******************************/
   449  			/*  S a n i t y   C h e c k s  */
   450  			/*******************************/
   451  			sanityChecks := func() {
   452  				By("checking dimensions were created")
   453  				Expect(s.dimensions.CacheSize()).To(Equal(uint64(4)))
   454  				checkDimensionsPresence(appname, true)
   455  
   456  				By("checking trees were created")
   457  				Expect(s.trees.CacheSize()).To(Equal(uint64(3)))
   458  				checkTreesPresence(appname, st, 0, true)
   459  
   460  				By("checking segments were created")
   461  				Expect(s.segments.CacheSize()).To(Equal(uint64(3)))
   462  				checkSegmentsPresence(appname, true)
   463  
   464  				By("checking dicts were created")
   465  				Expect(s.dicts.CacheSize()).To(Equal(uint64(1)))
   466  				checkDictsPresence(appname, true)
   467  
   468  				checkLabelsPresence(appname, true)
   469  			}
   470  
   471  			sanityChecks()
   472  
   473  			/*************************/
   474  			/*  D e l e t e   a p p  */
   475  			/*************************/
   476  			By("deleting the app")
   477  			err := s.DeleteApp(context.TODO(), "random.app")
   478  			Expect(err).ToNot(HaveOccurred())
   479  
   480  			// nothing should have happened
   481  			// since the deleted app does not exist
   482  			sanityChecks()
   483  		})
   484  	})
   485  })