github.com/grafana/pyroscope@v1.18.0/pkg/settings/recording/recording_test.go (about)

     1  package recording
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"connectrpc.com/connect"
    11  	"github.com/go-kit/log"
    12  	"github.com/grafana/dskit/user"
    13  	"github.com/stretchr/testify/require"
    14  	"golang.org/x/exp/rand"
    15  
    16  	settingsv1 "github.com/grafana/pyroscope/api/gen/proto/go/settings/v1"
    17  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    18  	"github.com/grafana/pyroscope/pkg/objstore/providers/filesystem"
    19  	"github.com/grafana/pyroscope/pkg/validation"
    20  )
    21  
    22  func Test_validateGet(t *testing.T) {
    23  	tests := []struct {
    24  		Name    string
    25  		Req     *settingsv1.GetRecordingRuleRequest
    26  		WantErr string
    27  	}{
    28  		{
    29  			Name: "valid",
    30  			Req: &settingsv1.GetRecordingRuleRequest{
    31  				Id: "random",
    32  			},
    33  			WantErr: "",
    34  		},
    35  		{
    36  			Name: "valid_with_formatted_fields",
    37  			Req: &settingsv1.GetRecordingRuleRequest{
    38  				Id: "  random	",
    39  			},
    40  			WantErr: "",
    41  		},
    42  		{
    43  			Name: "empty_id",
    44  			Req: &settingsv1.GetRecordingRuleRequest{
    45  				Id: "",
    46  			},
    47  			WantErr: "id is required",
    48  		},
    49  	}
    50  
    51  	for _, tt := range tests {
    52  		t.Run(tt.Name, func(t *testing.T) {
    53  			err := validateGet(tt.Req)
    54  			if tt.WantErr != "" {
    55  				require.Error(t, err)
    56  				require.EqualError(t, err, tt.WantErr)
    57  			} else {
    58  				require.NoError(t, err)
    59  			}
    60  		})
    61  	}
    62  }
    63  
    64  func Test_validateUpsert(t *testing.T) {
    65  	tests := []struct {
    66  		Name    string
    67  		Req     *settingsv1.UpsertRecordingRuleRequest
    68  		WantErr string
    69  	}{
    70  		{
    71  			Name: "valid",
    72  			Req: &settingsv1.UpsertRecordingRuleRequest{
    73  				Id:         "abcdef",
    74  				MetricName: "profiles_recorded_my_metric",
    75  				Matchers: []string{
    76  					`{ label_a = "A" }`,
    77  					`{ label_b =~ "B" }`,
    78  				},
    79  				GroupBy: []string{
    80  					"label_c",
    81  				},
    82  				ExternalLabels: []*typesv1.LabelPair{
    83  					{Name: "label_a", Value: "A"},
    84  					{Name: "label_b", Value: "B"},
    85  				},
    86  				Generation: 1,
    87  			},
    88  			WantErr: "",
    89  		},
    90  		{
    91  			Name: "minimal_valid",
    92  			Req: &settingsv1.UpsertRecordingRuleRequest{
    93  				Id:             "",
    94  				MetricName:     "profiles_recorded_my_metric",
    95  				Matchers:       []string{},
    96  				GroupBy:        []string{},
    97  				ExternalLabels: []*typesv1.LabelPair{},
    98  			},
    99  			WantErr: "",
   100  		},
   101  		{
   102  			Name: "valid_with_formatted_fields",
   103  			Req: &settingsv1.UpsertRecordingRuleRequest{
   104  				Id:             "abcdef",
   105  				MetricName:     "  profiles_recorded_my_metric	",
   106  				Matchers:       []string{},
   107  				GroupBy:        []string{},
   108  				ExternalLabels: []*typesv1.LabelPair{},
   109  			},
   110  			WantErr: "",
   111  		},
   112  		{
   113  			Name: "empty_id",
   114  			Req: &settingsv1.UpsertRecordingRuleRequest{
   115  				Id:             "",
   116  				MetricName:     "profiles_recorded_my_metric",
   117  				Matchers:       []string{},
   118  				GroupBy:        []string{},
   119  				ExternalLabels: []*typesv1.LabelPair{},
   120  			},
   121  			WantErr: "",
   122  		},
   123  		{
   124  			Name: "whitespace_only_id",
   125  			Req: &settingsv1.UpsertRecordingRuleRequest{
   126  				Id:             "  ",
   127  				MetricName:     "profiles_recorded_my_metric",
   128  				Matchers:       []string{},
   129  				GroupBy:        []string{},
   130  				ExternalLabels: []*typesv1.LabelPair{},
   131  			},
   132  			WantErr: `id "  " must match ^[a-zA-Z]+$`,
   133  		},
   134  		{
   135  			Name: "invalid_id",
   136  			Req: &settingsv1.UpsertRecordingRuleRequest{
   137  				Id:             "?",
   138  				MetricName:     "profiles_recorded_my_metric",
   139  				Matchers:       []string{},
   140  				GroupBy:        []string{},
   141  				ExternalLabels: []*typesv1.LabelPair{},
   142  			},
   143  			WantErr: `id "?" must match ^[a-zA-Z]+$`,
   144  		},
   145  		{
   146  			Name: "empty_metric_name",
   147  			Req: &settingsv1.UpsertRecordingRuleRequest{
   148  				MetricName:     "",
   149  				Matchers:       []string{},
   150  				GroupBy:        []string{},
   151  				ExternalLabels: []*typesv1.LabelPair{},
   152  			},
   153  			WantErr: "metric_name is required",
   154  		},
   155  		{
   156  			Name: "invalid_metric_name",
   157  			Req: &settingsv1.UpsertRecordingRuleRequest{
   158  				MetricName:     string([]byte{0xC0, 0xAF}), // invalid utf-8
   159  				Matchers:       []string{},
   160  				GroupBy:        []string{},
   161  				ExternalLabels: []*typesv1.LabelPair{},
   162  			},
   163  			WantErr: "metric_name \"\\xc0\\xaf\" is invalid: invalid metric name: \xc0\xaf",
   164  		},
   165  		{
   166  			Name: "invalid_matchers",
   167  			Req: &settingsv1.UpsertRecordingRuleRequest{
   168  				MetricName: "profiles_recorded_my_metric",
   169  				Matchers: []string{
   170  					"",
   171  				},
   172  				GroupBy:        []string{},
   173  				ExternalLabels: []*typesv1.LabelPair{},
   174  			},
   175  			WantErr: `matcher "" is invalid: unknown position: parse error: unexpected end of input`,
   176  		},
   177  		{
   178  			Name: "invalid_group_by_empty",
   179  			Req: &settingsv1.UpsertRecordingRuleRequest{
   180  				MetricName: "profiles_recorded_my_metric",
   181  				Matchers:   []string{},
   182  				GroupBy: []string{
   183  					"",
   184  				},
   185  				ExternalLabels: []*typesv1.LabelPair{},
   186  			},
   187  			WantErr: `group_by label "" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   188  		},
   189  		{
   190  			Name: "invalid_group_by_with_dot",
   191  			Req: &settingsv1.UpsertRecordingRuleRequest{
   192  				MetricName: "profiles_recorded_my_metric",
   193  				Matchers:   []string{},
   194  				GroupBy: []string{
   195  					"service.name",
   196  				},
   197  				ExternalLabels: []*typesv1.LabelPair{},
   198  			},
   199  			WantErr: `group_by label "service.name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   200  		},
   201  		{
   202  			Name: "invalid_group_by_with_utf8",
   203  			Req: &settingsv1.UpsertRecordingRuleRequest{
   204  				MetricName: "profiles_recorded_my_metric",
   205  				Matchers:   []string{},
   206  				GroupBy: []string{
   207  					"世界",
   208  				},
   209  				ExternalLabels: []*typesv1.LabelPair{},
   210  			},
   211  			WantErr: `group_by label "世界" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   212  		},
   213  		{
   214  			Name: "invalid_group_by_starts_with_number",
   215  			Req: &settingsv1.UpsertRecordingRuleRequest{
   216  				MetricName: "profiles_recorded_my_metric",
   217  				Matchers:   []string{},
   218  				GroupBy: []string{
   219  					"123invalid",
   220  				},
   221  				ExternalLabels: []*typesv1.LabelPair{},
   222  			},
   223  			WantErr: `group_by label "123invalid" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   224  		},
   225  		{
   226  			Name: "invalid_external_label_utf8",
   227  			Req: &settingsv1.UpsertRecordingRuleRequest{
   228  				MetricName: "profiles_recorded_my_metric",
   229  				Matchers:   []string{},
   230  				GroupBy:    []string{},
   231  				ExternalLabels: []*typesv1.LabelPair{
   232  					{
   233  						Name:  string([]byte{0xC0, 0xAF}), // invalid utf-8
   234  						Value: string([]byte{0xC0, 0xAF}), // invalid utf-8
   235  					},
   236  				},
   237  			},
   238  			WantErr: `external_labels name "\xc0\xaf" must match ^[a-zA-Z_][a-zA-Z0-9_]*$
   239  external_labels value "\xc0\xaf" must be a valid utf-8 string`,
   240  		},
   241  		{
   242  			Name: "invalid_external_label_with_dot",
   243  			Req: &settingsv1.UpsertRecordingRuleRequest{
   244  				MetricName: "profiles_recorded_my_metric",
   245  				Matchers:   []string{},
   246  				GroupBy:    []string{},
   247  				ExternalLabels: []*typesv1.LabelPair{
   248  					{Name: "service.name", Value: "foo"},
   249  				},
   250  			},
   251  			WantErr: `external_labels name "service.name" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   252  		},
   253  		{
   254  			Name: "invalid_external_label_with_utf8_name",
   255  			Req: &settingsv1.UpsertRecordingRuleRequest{
   256  				MetricName: "profiles_recorded_my_metric",
   257  				Matchers:   []string{},
   258  				GroupBy:    []string{},
   259  				ExternalLabels: []*typesv1.LabelPair{
   260  					{Name: "世界", Value: "value"},
   261  				},
   262  			},
   263  			WantErr: `external_labels name "世界" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   264  		},
   265  		{
   266  			Name: "invalid_external_label_starts_with_number",
   267  			Req: &settingsv1.UpsertRecordingRuleRequest{
   268  				MetricName: "profiles_recorded_my_metric",
   269  				Matchers:   []string{},
   270  				GroupBy:    []string{},
   271  				ExternalLabels: []*typesv1.LabelPair{
   272  					{Name: "123invalid", Value: "value"},
   273  				},
   274  			},
   275  			WantErr: `external_labels name "123invalid" must match ^[a-zA-Z_][a-zA-Z0-9_]*$`,
   276  		},
   277  		{
   278  			Name: "invalid_generation",
   279  			Req: &settingsv1.UpsertRecordingRuleRequest{
   280  				Id:             "abcdef",
   281  				MetricName:     "profiles_recorded_my_metric",
   282  				Matchers:       []string{},
   283  				GroupBy:        []string{},
   284  				ExternalLabels: []*typesv1.LabelPair{},
   285  				Generation:     -1,
   286  			},
   287  			WantErr: "generation must be positive",
   288  		},
   289  		{
   290  			Name: "multiple_errors",
   291  			Req: &settingsv1.UpsertRecordingRuleRequest{
   292  				MetricName: "",
   293  				Matchers: []string{
   294  					"",
   295  				},
   296  				GroupBy:        []string{},
   297  				ExternalLabels: []*typesv1.LabelPair{},
   298  			},
   299  			WantErr: "metric_name is required\nmatcher \"\" is invalid: unknown position: parse error: unexpected end of input",
   300  		},
   301  	}
   302  
   303  	for _, tt := range tests {
   304  		t.Run(tt.Name, func(t *testing.T) {
   305  			err := validateUpsert(tt.Req)
   306  			if tt.WantErr != "" {
   307  				require.Error(t, err)
   308  				require.EqualError(t, err, tt.WantErr)
   309  			} else {
   310  				require.NoError(t, err)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  func Test_validateDelete(t *testing.T) {
   317  	tests := []struct {
   318  		Name    string
   319  		Req     *settingsv1.DeleteRecordingRuleRequest
   320  		WantErr string
   321  	}{
   322  		{
   323  			Name: "valid",
   324  			Req: &settingsv1.DeleteRecordingRuleRequest{
   325  				Id: "random",
   326  			},
   327  			WantErr: "",
   328  		},
   329  		{
   330  			Name: "valid_with_formatted_fields",
   331  			Req: &settingsv1.DeleteRecordingRuleRequest{
   332  				Id: "  random	",
   333  			},
   334  			WantErr: "",
   335  		},
   336  		{
   337  			Name: "empty_id",
   338  			Req: &settingsv1.DeleteRecordingRuleRequest{
   339  				Id: "",
   340  			},
   341  			WantErr: "id is required",
   342  		},
   343  	}
   344  
   345  	for _, tt := range tests {
   346  		t.Run(tt.Name, func(t *testing.T) {
   347  			err := validateDelete(tt.Req)
   348  			if tt.WantErr != "" {
   349  				require.Error(t, err)
   350  				require.EqualError(t, err, tt.WantErr)
   351  			} else {
   352  				require.NoError(t, err)
   353  			}
   354  		})
   355  	}
   356  }
   357  
   358  func Test_idForRule(t *testing.T) {
   359  	tests := []struct {
   360  		name       string
   361  		rule       *settingsv1.RecordingRule
   362  		expectedId string
   363  	}{
   364  		{
   365  			name:       "some-rule",
   366  			expectedId: "veouCnOZTo",
   367  			rule: &settingsv1.RecordingRule{
   368  				MetricName:  "metric1",
   369  				ProfileType: "cpu",
   370  				Matchers: []string{
   371  					`{ label_a = "A" }`,
   372  					`{ label_b =~ "B" }`,
   373  				},
   374  				GroupBy: []string{"label_c", "label_d"},
   375  				ExternalLabels: []*typesv1.LabelPair{
   376  					{Name: "label_e", Value: "E"},
   377  					{Name: "label_f", Value: "F"},
   378  				},
   379  				StacktraceFilter: &settingsv1.StacktraceFilter{
   380  					FunctionName: &settingsv1.StacktraceFilterFunctionName{
   381  						FunctionName: "function_name",
   382  					},
   383  				},
   384  			},
   385  		},
   386  		{
   387  			name:       "some-other-rule",
   388  			expectedId: "XMMpSpeTom",
   389  			rule: &settingsv1.RecordingRule{
   390  				MetricName:  "metric1",
   391  				ProfileType: "cpu",
   392  				Matchers: []string{
   393  					`{ label_a = "A" }`,
   394  					`{ label_b =~ "B" }`,
   395  				},
   396  				GroupBy: []string{"label_c", "label_d"},
   397  				ExternalLabels: []*typesv1.LabelPair{
   398  					{Name: "label_e", Value: "E"},
   399  					{Name: "label_f", Value: "F"},
   400  				},
   401  				StacktraceFilter: &settingsv1.StacktraceFilter{
   402  					FunctionName: &settingsv1.StacktraceFilterFunctionName{
   403  						FunctionName: "another_function_name",
   404  					},
   405  				},
   406  			},
   407  		},
   408  	}
   409  	for _, tt := range tests {
   410  		t.Run(tt.name, func(t *testing.T) {
   411  			result := idForRule(tt.rule)
   412  			require.Equal(t, tt.expectedId, result)
   413  		})
   414  	}
   415  }
   416  
   417  type testRecordingRules struct {
   418  	*RecordingRules
   419  	bucketPath string
   420  }
   421  
   422  func newTestRecordingRules(t *testing.T, overrides *validation.Overrides) *testRecordingRules {
   423  	logger := log.NewNopLogger()
   424  	if testing.Verbose() {
   425  		logger = log.NewLogfmtLogger(os.Stderr)
   426  	}
   427  	bucketPath := t.TempDir()
   428  	bucket, err := filesystem.NewBucket(bucketPath)
   429  	require.NoError(t, err)
   430  
   431  	return &testRecordingRules{
   432  		RecordingRules: New(bucket, logger, overrides),
   433  		bucketPath:     bucketPath,
   434  	}
   435  }
   436  
   437  func TestRecordingRules_Get(t *testing.T) {
   438  	testUser := "user1"
   439  	storeRule1 := RandomRule()
   440  	storeRule2 := RandomRule()
   441  	configRule1 := RandomRule()
   442  	configRule1.Id = storeRule2.Id // configRule1 overrides storeRule2
   443  	configRule2 := RandomRule()
   444  	configRule2.Id = "" // this rule doesn't override any rule
   445  
   446  	r := newTestRecordingRules(t, validation.MockOverrides(func(defaults *validation.Limits, tenantLimits map[string]*validation.Limits) {
   447  		user1 := validation.MockDefaultLimits()
   448  		user1.RecordingRules = validation.RecordingRules{
   449  			configRule1,
   450  			configRule2,
   451  		}
   452  		tenantLimits[testUser] = user1
   453  	}))
   454  
   455  	ctx := user.InjectOrgID(context.Background(), testUser)
   456  
   457  	t.Run("Get not found", func(t *testing.T) {
   458  		_, err := r.GetRecordingRule(ctx, connect.NewRequest(&settingsv1.GetRecordingRuleRequest{Id: storeRule1.Id}))
   459  		require.EqualError(t, err, fmt.Sprintf("not_found: no rule with id='%s' found", storeRule1.Id))
   460  	})
   461  
   462  	t.Run("List rules empty for other user", func(t *testing.T) {
   463  		ctx2 := user.InjectOrgID(context.Background(), "user2")
   464  		resp, err := r.ListRecordingRules(ctx2, connect.NewRequest(&settingsv1.ListRecordingRulesRequest{}))
   465  		require.NoError(t, err)
   466  		require.Empty(t, resp.Msg.Rules)
   467  	})
   468  
   469  	t.Run("Get config rule from autogenerated ID", func(t *testing.T) {
   470  		idForConfigRule2 := idForRule(configRule2)
   471  		resp, err := r.GetRecordingRule(ctx, connect.NewRequest(&settingsv1.GetRecordingRuleRequest{Id: idForConfigRule2}))
   472  		require.NoError(t, err)
   473  		require.Equal(t, configRule2, resp.Msg.Rule)
   474  	})
   475  
   476  	t.Run("Insert store rules", func(t *testing.T) {
   477  		rule, err := r.UpsertRecordingRule(ctx, connect.NewRequest(&settingsv1.UpsertRecordingRuleRequest{
   478  			Id:               storeRule1.Id,
   479  			MetricName:       storeRule1.MetricName,
   480  			Matchers:         storeRule1.Matchers,
   481  			GroupBy:          storeRule1.GroupBy,
   482  			Generation:       storeRule1.Generation,
   483  			ExternalLabels:   storeRule1.ExternalLabels,
   484  			StacktraceFilter: storeRule1.StacktraceFilter,
   485  		}))
   486  		require.NoError(t, err)
   487  		require.Equal(t, storeRule1, rule.Msg.Rule)
   488  		rule, err = r.UpsertRecordingRule(ctx, connect.NewRequest(&settingsv1.UpsertRecordingRuleRequest{
   489  			Id:               storeRule2.Id,
   490  			MetricName:       storeRule2.MetricName,
   491  			Matchers:         storeRule2.Matchers,
   492  			GroupBy:          storeRule2.GroupBy,
   493  			Generation:       storeRule2.Generation,
   494  			ExternalLabels:   storeRule2.ExternalLabels,
   495  			StacktraceFilter: storeRule2.StacktraceFilter,
   496  		}))
   497  		require.NoError(t, err)
   498  		require.Equal(t, storeRule2, rule.Msg.Rule)
   499  	})
   500  
   501  	t.Run("Get overridden rule from config", func(t *testing.T) {
   502  		resp, err := r.GetRecordingRule(ctx, connect.NewRequest(&settingsv1.GetRecordingRuleRequest{Id: storeRule2.Id}))
   503  		require.NoError(t, err)
   504  		require.NotEqual(t, storeRule2, configRule1)
   505  		require.Equal(t, configRule1, resp.Msg.Rule)
   506  	})
   507  
   508  	t.Run("List rules with overrides", func(t *testing.T) {
   509  		resp, err := r.ListRecordingRules(ctx, connect.NewRequest(&settingsv1.ListRecordingRulesRequest{}))
   510  		require.NoError(t, err)
   511  		require.EqualValues(t, resp.Msg.Rules, []*settingsv1.RecordingRule{
   512  			configRule1,
   513  			configRule2,
   514  			storeRule1,
   515  			// No storeRule2 as it's overridden by configRule1
   516  		})
   517  	})
   518  
   519  	t.Run("Upsert overridden rule just changes the original", func(t *testing.T) {
   520  		upsertedRule, err := r.UpsertRecordingRule(ctx, connect.NewRequest(&settingsv1.UpsertRecordingRuleRequest{
   521  			Id:               storeRule2.Id,
   522  			MetricName:       storeRule2.MetricName,
   523  			Matchers:         storeRule2.Matchers,
   524  			GroupBy:          storeRule2.GroupBy,
   525  			Generation:       storeRule2.Generation,
   526  			ExternalLabels:   storeRule2.ExternalLabels,
   527  			StacktraceFilter: storeRule2.StacktraceFilter,
   528  		}))
   529  		storeRule2.Generation++
   530  		require.NoError(t, err)
   531  		require.Equal(t, storeRule2, upsertedRule.Msg.Rule)
   532  
   533  		rule, err := r.GetRecordingRule(ctx, connect.NewRequest(&settingsv1.GetRecordingRuleRequest{Id: storeRule2.Id}))
   534  		require.NoError(t, err)
   535  		require.Equal(t, configRule1, rule.Msg.Rule)
   536  	})
   537  
   538  	t.Run("Delete store rules", func(t *testing.T) {
   539  		_, err := r.DeleteRecordingRule(ctx, connect.NewRequest(&settingsv1.DeleteRecordingRuleRequest{Id: storeRule1.Id}))
   540  		require.NoError(t, err)
   541  		_, err = r.DeleteRecordingRule(ctx, connect.NewRequest(&settingsv1.DeleteRecordingRuleRequest{Id: storeRule2.Id}))
   542  		require.NoError(t, err)
   543  		resp, err := r.ListRecordingRules(ctx, connect.NewRequest(&settingsv1.ListRecordingRulesRequest{}))
   544  		require.NoError(t, err)
   545  		require.EqualValues(t, resp.Msg.Rules, []*settingsv1.RecordingRule{
   546  			configRule1,
   547  			configRule2,
   548  		})
   549  	})
   550  
   551  	t.Run("Can't delete config rules", func(t *testing.T) {
   552  		_, err := r.DeleteRecordingRule(ctx, connect.NewRequest(&settingsv1.DeleteRecordingRuleRequest{Id: configRule1.Id}))
   553  
   554  		require.EqualError(t, err, fmt.Sprintf("not_found: no rule with ID='%s' found", configRule1.Id))
   555  	})
   556  
   557  }
   558  
   559  func init() {
   560  	rand.Seed(uint64(time.Now().UnixNano()))
   561  }
   562  
   563  const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
   564  
   565  func RandomString(n int) string {
   566  	b := make([]byte, n)
   567  	for i := range b {
   568  		b[i] = letters[rand.Intn(len(letters))]
   569  	}
   570  	return string(b)
   571  }
   572  
   573  func RandomRule() *settingsv1.RecordingRule {
   574  	profileType := RandomString(5)
   575  	matchers := make([]string, rand.Intn(3))
   576  	for i := range matchers {
   577  		matchers[i] = fmt.Sprintf(`{ %s = "%s" }`, RandomString(5), RandomString(5))
   578  	}
   579  	matchers = append(matchers, fmt.Sprintf(`{ __profile_type__ = "%s" }`, profileType))
   580  	groupBy := make([]string, rand.Intn(2)+1)
   581  	for i := range groupBy {
   582  		groupBy[i] = RandomString(5)
   583  	}
   584  	externalLabels := make([]*typesv1.LabelPair, rand.Intn(2)+1)
   585  	for i := range externalLabels {
   586  		externalLabels[i] = &typesv1.LabelPair{
   587  			Name:  RandomString(5),
   588  			Value: RandomString(5),
   589  		}
   590  	}
   591  	var functionFilter *settingsv1.StacktraceFilter
   592  	if rand.Intn(2) == 1 {
   593  		functionFilter = &settingsv1.StacktraceFilter{
   594  			FunctionName: &settingsv1.StacktraceFilterFunctionName{
   595  				FunctionName: RandomString(5),
   596  			},
   597  		}
   598  	}
   599  	return &settingsv1.RecordingRule{
   600  		Id:               RandomString(10),
   601  		MetricName:       "profiles_recorded_" + RandomString(5),
   602  		ProfileType:      profileType,
   603  		Matchers:         matchers,
   604  		GroupBy:          groupBy,
   605  		Generation:       1,
   606  		ExternalLabels:   externalLabels,
   607  		StacktraceFilter: functionFilter,
   608  	}
   609  }