github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/stats/stats_test.go (about)

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package stats
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"cloud.google.com/go/bigquery"
    28  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/command"
    29  	"github.com/google/go-cmp/cmp"
    30  	"github.com/google/go-cmp/cmp/cmpopts"
    31  	"google.golang.org/protobuf/testing/protocmp"
    32  	"google.golang.org/protobuf/types/known/timestamppb"
    33  
    34  	lpb "github.com/bazelbuild/reclient/api/log"
    35  	stpb "github.com/bazelbuild/reclient/api/stat"
    36  	spb "github.com/bazelbuild/reclient/api/stats"
    37  
    38  	cpb "github.com/bazelbuild/remote-apis-sdks/go/api/command"
    39  )
    40  
    41  var (
    42  	minfo                      = machineInfo()
    43  	cmpStatsOpts               = []cmp.Option{protocmp.Transform(), protocmp.SortRepeatedFields(&stpb.Stat{}, "counts_by_value"), protocmp.SortRepeatedFields(&spb.Stats{}, "stats")}
    44  	cmpStatsIgnoreBuildLatency = protocmp.IgnoreFields(&spb.Stats{}, "build_latency") // This is invalid if no proxy execution times provided.
    45  	recordsToWrite             = []*lpb.LogRecord{
    46  		&lpb.LogRecord{
    47  			RemoteMetadata: &lpb.RemoteMetadata{
    48  				CacheHit: true,
    49  			},
    50  		},
    51  	}
    52  	proxyInfosToWrite = []*lpb.ProxyInfo{&lpb.ProxyInfo{
    53  		EventTimes: map[string]*cpb.TimeInterval{
    54  			"Event": &cpb.TimeInterval{},
    55  		},
    56  	}}
    57  	st = &Stats{
    58  		NumRecords: 1,
    59  		Stats:      map[string]*Stat{},
    60  	}
    61  	statsToWrite = &Stats{
    62  		NumRecords: 5,
    63  		Stats: map[string]*Stat{
    64  			"empty": &Stat{},
    65  			"b": &Stat{
    66  				Count: 3,
    67  				CountByValue: map[string]int64{
    68  					"v1": 4,
    69  					"v2": 5,
    70  				},
    71  			},
    72  			"a": &Stat{
    73  				Count:        6,
    74  				Median:       10,
    75  				Percentile75: 11,
    76  				Percentile85: 12,
    77  				Percentile95: 13,
    78  				Average:      9.8,
    79  				Outlier1:     &stpb.Outlier{CommandId: "foo", Value: 15},
    80  				Outlier2:     &stpb.Outlier{CommandId: "foo", Value: 14},
    81  			},
    82  		},
    83  		ProxyInfos: []*lpb.ProxyInfo{&lpb.ProxyInfo{
    84  			EventTimes: map[string]*cpb.TimeInterval{
    85  				"Event": &cpb.TimeInterval{},
    86  			},
    87  		}},
    88  	}
    89  )
    90  
    91  // TODO (b/269614799): Test refactor/clean up
    92  
    93  func TestStatsToProto(t *testing.T) {
    94  	st := &Stats{
    95  		NumRecords: 5,
    96  		Stats: map[string]*Stat{
    97  			"empty": &Stat{},
    98  			"b": &Stat{
    99  				Count: 3,
   100  				CountByValue: map[string]int64{
   101  					"v1": 4,
   102  					"v2": 5,
   103  				},
   104  			},
   105  			"a": &Stat{
   106  				Count:        6,
   107  				Median:       10,
   108  				Percentile75: 11,
   109  				Percentile85: 12,
   110  				Percentile95: 13,
   111  				Average:      9.8,
   112  				Outlier1:     &stpb.Outlier{CommandId: "foo", Value: 15},
   113  				Outlier2:     &stpb.Outlier{CommandId: "foo", Value: 14},
   114  			},
   115  		},
   116  		ProxyInfos: []*lpb.ProxyInfo{&lpb.ProxyInfo{
   117  			EventTimes: map[string]*cpb.TimeInterval{
   118  				"Event": &cpb.TimeInterval{},
   119  			},
   120  		}},
   121  		cacheHits:         10,
   122  		minProxyExecStart: 23.5,
   123  		maxProxyExecEnd:   50.75,
   124  	}
   125  	expected := &spb.Stats{
   126  		NumRecords: 5,
   127  		Stats: []*stpb.Stat{
   128  			&stpb.Stat{
   129  				Name:         "a",
   130  				Count:        6,
   131  				Median:       10,
   132  				Percentile75: 11,
   133  				Percentile85: 12,
   134  				Percentile95: 13,
   135  				Average:      9.8,
   136  				Outliers: []*stpb.Outlier{
   137  					&stpb.Outlier{CommandId: "foo", Value: 15},
   138  					&stpb.Outlier{CommandId: "foo", Value: 14},
   139  				},
   140  			},
   141  			&stpb.Stat{
   142  				Name:  "b",
   143  				Count: 3,
   144  				CountsByValue: []*stpb.Stat_Value{
   145  					&stpb.Stat_Value{Name: "v1", Count: 4},
   146  					&stpb.Stat_Value{Name: "v2", Count: 5},
   147  				},
   148  			},
   149  		},
   150  		MachineInfo: minfo,
   151  		ProxyInfo: []*lpb.ProxyInfo{&lpb.ProxyInfo{
   152  			EventTimes: map[string]*cpb.TimeInterval{
   153  				"Event": &cpb.TimeInterval{},
   154  			},
   155  		}},
   156  		BuildCacheHitRatio: 2,
   157  		BuildLatency:       27.25,
   158  	}
   159  	if diff := cmp.Diff(expected, st.ToProto(), protocmp.Transform()); diff != "" {
   160  		t.Errorf("ToProto returned diff in result: (-want +got)\n%s", diff)
   161  	}
   162  }
   163  
   164  func TestWriteStats(t *testing.T) {
   165  	st := &Stats{
   166  		NumRecords: 5,
   167  		Stats: map[string]*Stat{
   168  			"empty": &Stat{},
   169  			"b": &Stat{
   170  				Count: 3,
   171  				CountByValue: map[string]int64{
   172  					"v1": 4,
   173  					"v2": 5,
   174  				},
   175  			},
   176  			"a": &Stat{
   177  				Count:        6,
   178  				Median:       10,
   179  				Percentile75: 11,
   180  				Percentile85: 12,
   181  				Percentile95: 13,
   182  				Average:      9.8,
   183  				Outlier1:     &stpb.Outlier{CommandId: "foo", Value: 15},
   184  				Outlier2:     &stpb.Outlier{CommandId: "foo", Value: 14},
   185  			},
   186  		},
   187  		ProxyInfos: []*lpb.ProxyInfo{&lpb.ProxyInfo{
   188  			EventTimes: map[string]*cpb.TimeInterval{
   189  				"Event": &cpb.TimeInterval{},
   190  			},
   191  		}},
   192  	}
   193  	outDir := t.TempDir()
   194  	WriteStats(st.ToProto(), outDir)
   195  
   196  	got := fileContent(t, filepath.Join(outDir, "rbe_metrics.txt"))
   197  
   198  	if len(strings.Fields(got)) <= 2 {
   199  		t.Errorf("WriteStats did not generate multiline output, got: \n%s", got)
   200  	}
   201  }
   202  
   203  func fileContent(t *testing.T, path string) string {
   204  	t.Helper()
   205  	b, err := os.ReadFile(path)
   206  	if err != nil {
   207  		t.Fatalf("Failed to open %v", path)
   208  	}
   209  	return string(b)
   210  }
   211  
   212  func TestBoolStats(t *testing.T) {
   213  	recs := []*lpb.LogRecord{
   214  		&lpb.LogRecord{
   215  			RemoteMetadata: &lpb.RemoteMetadata{
   216  				CacheHit: true,
   217  			},
   218  			LocalMetadata: &lpb.LocalMetadata{
   219  				ValidCacheHit:   true,
   220  				ExecutedLocally: false,
   221  				UpdatedCache:    false,
   222  			},
   223  		},
   224  		&lpb.LogRecord{
   225  			RemoteMetadata: &lpb.RemoteMetadata{
   226  				CacheHit: true,
   227  			},
   228  			LocalMetadata: &lpb.LocalMetadata{
   229  				ValidCacheHit:   true,
   230  				ExecutedLocally: false,
   231  				UpdatedCache:    false,
   232  			},
   233  		},
   234  		&lpb.LogRecord{
   235  			RemoteMetadata: &lpb.RemoteMetadata{
   236  				CacheHit: false,
   237  			},
   238  			LocalMetadata: &lpb.LocalMetadata{
   239  				ValidCacheHit:   false,
   240  				ExecutedLocally: true,
   241  				UpdatedCache:    false,
   242  			},
   243  		},
   244  		&lpb.LogRecord{
   245  			RemoteMetadata: &lpb.RemoteMetadata{
   246  				CacheHit: false,
   247  			},
   248  			LocalMetadata: &lpb.LocalMetadata{
   249  				ValidCacheHit:   false,
   250  				ExecutedLocally: true,
   251  				UpdatedCache:    true,
   252  			},
   253  		},
   254  		&lpb.LogRecord{
   255  			RemoteMetadata: &lpb.RemoteMetadata{
   256  				CacheHit: true,
   257  			},
   258  			LocalMetadata: &lpb.LocalMetadata{
   259  				ValidCacheHit:   false,
   260  				ExecutedLocally: true,
   261  				UpdatedCache:    false,
   262  			},
   263  		},
   264  	}
   265  	s := NewFromRecords(recs, nil)
   266  	wantStats := &spb.Stats{
   267  		NumRecords: 5,
   268  		Stats: []*stpb.Stat{
   269  			&stpb.Stat{
   270  				Name: "CompletionStatus",
   271  				CountsByValue: []*stpb.Stat_Value{
   272  					{
   273  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   274  						Count: 5,
   275  					},
   276  				},
   277  			},
   278  			&stpb.Stat{
   279  				Name:  "LocalMetadata.ExecutedLocally",
   280  				Count: 3,
   281  			},
   282  			&stpb.Stat{
   283  				Name:  "LocalMetadata.UpdatedCache",
   284  				Count: 1,
   285  			},
   286  			&stpb.Stat{
   287  				Name:  "LocalMetadata.ValidCacheHit",
   288  				Count: 2,
   289  			},
   290  			&stpb.Stat{
   291  				Name:  "RemoteMetadata.CacheHit",
   292  				Count: 3,
   293  			},
   294  		},
   295  		MachineInfo: minfo,
   296  		ProxyInfo:   []*lpb.ProxyInfo{},
   297  	}
   298  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   299  		t.Errorf("Boolean stat returned diff in result: (-want +got)\n%s", diff)
   300  	}
   301  }
   302  
   303  func TestEnumStats(t *testing.T) {
   304  	recs := []*lpb.LogRecord{
   305  		&lpb.LogRecord{
   306  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_REMOTE_ERROR},
   307  			CompletionStatus: lpb.CompletionStatus_STATUS_REMOTE_FAILURE,
   308  		},
   309  		&lpb.LogRecord{
   310  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_SUCCESS},
   311  			CompletionStatus: lpb.CompletionStatus_STATUS_REMOTE_EXECUTION,
   312  		},
   313  		&lpb.LogRecord{
   314  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_CACHE_HIT},
   315  			CompletionStatus: lpb.CompletionStatus_STATUS_CACHE_HIT,
   316  		},
   317  		&lpb.LogRecord{
   318  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_REMOTE_ERROR},
   319  			CompletionStatus: lpb.CompletionStatus_STATUS_REMOTE_FAILURE,
   320  		},
   321  		&lpb.LogRecord{
   322  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_SUCCESS},
   323  			CompletionStatus: lpb.CompletionStatus_STATUS_LOCAL_EXECUTION,
   324  		},
   325  		&lpb.LogRecord{
   326  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_SUCCESS},
   327  			CompletionStatus: lpb.CompletionStatus_STATUS_RACING_LOCAL,
   328  		},
   329  		&lpb.LogRecord{
   330  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_TIMEOUT},
   331  			CompletionStatus: lpb.CompletionStatus_STATUS_TIMEOUT,
   332  		},
   333  		&lpb.LogRecord{
   334  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_SUCCESS},
   335  			CompletionStatus: lpb.CompletionStatus_STATUS_RACING_REMOTE,
   336  		},
   337  		&lpb.LogRecord{
   338  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_CACHE_HIT},
   339  			CompletionStatus: lpb.CompletionStatus_STATUS_CACHE_HIT,
   340  		},
   341  		&lpb.LogRecord{
   342  			Result:           &cpb.CommandResult{Status: cpb.CommandResultStatus_NON_ZERO_EXIT},
   343  			CompletionStatus: lpb.CompletionStatus_STATUS_NON_ZERO_EXIT,
   344  		},
   345  	}
   346  	s := NewFromRecords(recs, nil)
   347  	wantStats := &spb.Stats{
   348  		NumRecords: 10,
   349  		Stats: []*stpb.Stat{
   350  			&stpb.Stat{
   351  				Name: "CompletionStatus",
   352  				CountsByValue: []*stpb.Stat_Value{
   353  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_CACHE_HIT.String(), Count: 2},
   354  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_NON_ZERO_EXIT.String(), Count: 1},
   355  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_REMOTE_FAILURE.String(), Count: 2},
   356  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_REMOTE_EXECUTION.String(), Count: 1},
   357  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_LOCAL_EXECUTION.String(), Count: 1},
   358  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_RACING_LOCAL.String(), Count: 1},
   359  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_RACING_REMOTE.String(), Count: 1},
   360  					&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_TIMEOUT.String(), Count: 1},
   361  				},
   362  			},
   363  			&stpb.Stat{
   364  				Name: "Result.Status",
   365  				CountsByValue: []*stpb.Stat_Value{
   366  					&stpb.Stat_Value{Name: "CACHE_HIT", Count: 2},
   367  					&stpb.Stat_Value{Name: "NON_ZERO_EXIT", Count: 1},
   368  					&stpb.Stat_Value{Name: "REMOTE_ERROR", Count: 2},
   369  					&stpb.Stat_Value{Name: "SUCCESS", Count: 4},
   370  					&stpb.Stat_Value{Name: "TIMEOUT", Count: 1},
   371  				},
   372  			},
   373  		},
   374  		MachineInfo:        minfo,
   375  		ProxyInfo:          []*lpb.ProxyInfo{},
   376  		BuildCacheHitRatio: 0.2,
   377  	}
   378  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   379  		t.Errorf("Enum stat returned diff in result: (-want +got)\n%s", diff)
   380  	}
   381  }
   382  
   383  func TestInvocationIDs(t *testing.T) {
   384  	// Maps in Go have random iteration order, which is a good test for our stats.
   385  	invIDs := []string{"inv1", "inv2"}
   386  	var recs []*lpb.LogRecord
   387  	for i := 0; i < 5; i++ {
   388  		recs = append(recs, &lpb.LogRecord{
   389  			Command: &cpb.Command{Identifiers: &cpb.Identifiers{
   390  				InvocationId: invIDs[i%2],
   391  			}},
   392  		})
   393  	}
   394  	s := NewFromRecords(recs, nil)
   395  	wantStats := &spb.Stats{
   396  		NumRecords:    5,
   397  		InvocationIds: invIDs,
   398  		MachineInfo:   minfo,
   399  		ProxyInfo:     []*lpb.ProxyInfo{},
   400  		Stats: []*stpb.Stat{
   401  			{
   402  				Name: "CompletionStatus",
   403  				CountsByValue: []*stpb.Stat_Value{
   404  					{
   405  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   406  						Count: 5,
   407  					},
   408  				},
   409  			},
   410  		},
   411  	}
   412  	strSliceCmp := cmpopts.SortSlices(func(a, b string) bool { return a < b })
   413  	if diff := cmp.Diff(wantStats, s.ToProto(), protocmp.Transform(), strSliceCmp, cmpStatsIgnoreBuildLatency); diff != "" {
   414  		t.Errorf("Int stat returned diff in result: (-want +got)\n%s", diff)
   415  	}
   416  }
   417  
   418  func TestIntStats(t *testing.T) {
   419  	// Maps in Go have random iteration order, which is a good test for our stats.
   420  	m := make(map[string]int)
   421  	for i := 1; i <= 100; i++ {
   422  		m[fmt.Sprint(i)] = i
   423  	}
   424  	var recs []*lpb.LogRecord
   425  	for n, v := range m {
   426  		recs = append(recs, &lpb.LogRecord{
   427  			Command:        &cpb.Command{Identifiers: &cpb.Identifiers{CommandId: n}},
   428  			RemoteMetadata: &lpb.RemoteMetadata{NumInputFiles: int32(v)},
   429  		})
   430  	}
   431  	s := NewFromRecords(recs, nil)
   432  	wantStats := &spb.Stats{
   433  		NumRecords: 100,
   434  		Stats: []*stpb.Stat{
   435  			{
   436  				Name: "CompletionStatus",
   437  				CountsByValue: []*stpb.Stat_Value{
   438  					{
   439  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   440  						Count: 100,
   441  					},
   442  				},
   443  			},
   444  			&stpb.Stat{
   445  				Name:         "RemoteMetadata.NumInputFiles",
   446  				Average:      50.5,
   447  				Count:        5050,
   448  				Median:       51,
   449  				Percentile75: 76,
   450  				Percentile85: 86,
   451  				Percentile95: 96,
   452  				Outliers: []*stpb.Outlier{
   453  					&stpb.Outlier{CommandId: "100", Value: 100},
   454  					&stpb.Outlier{CommandId: "99", Value: 99},
   455  				},
   456  			},
   457  		},
   458  		MachineInfo: minfo,
   459  		ProxyInfo:   []*lpb.ProxyInfo{},
   460  	}
   461  	if diff := cmp.Diff(wantStats, s.ToProto(), protocmp.Transform(), cmpStatsIgnoreBuildLatency); diff != "" {
   462  		t.Errorf("Int stat returned diff in result: (-want +got)\n%s", diff)
   463  	}
   464  }
   465  
   466  func TestLabelsAggregation(t *testing.T) {
   467  	var recs []*lpb.LogRecord
   468  	for i := 1; i <= 10; i++ {
   469  		recs = append(recs, &lpb.LogRecord{
   470  			Command: &cpb.Command{Identifiers: &cpb.Identifiers{CommandId: "c_" + fmt.Sprint(i)}},
   471  			LocalMetadata: &lpb.LocalMetadata{
   472  				Labels: map[string]string{
   473  					"lang":   "c++",
   474  					"action": "compile",
   475  				},
   476  			},
   477  			RemoteMetadata: &lpb.RemoteMetadata{NumInputFiles: 2},
   478  		})
   479  	}
   480  	for i := 1; i <= 20; i++ {
   481  		recs = append(recs, &lpb.LogRecord{
   482  			Command: &cpb.Command{Identifiers: &cpb.Identifiers{CommandId: "l_" + fmt.Sprint(i)}},
   483  			LocalMetadata: &lpb.LocalMetadata{
   484  				Labels: map[string]string{
   485  					"lang":   "c++",
   486  					"action": "link",
   487  				},
   488  			},
   489  			RemoteMetadata: &lpb.RemoteMetadata{NumInputFiles: 5},
   490  		})
   491  	}
   492  	s := NewFromRecords(recs, nil)
   493  	wantStats := &spb.Stats{
   494  		NumRecords: 30,
   495  		Stats: []*stpb.Stat{
   496  			&stpb.Stat{
   497  				Name:         "RemoteMetadata.NumInputFiles",
   498  				Average:      4,
   499  				Count:        120,
   500  				Median:       5,
   501  				Percentile75: 5,
   502  				Percentile85: 5,
   503  				Percentile95: 5,
   504  				Outliers: []*stpb.Outlier{
   505  					&stpb.Outlier{CommandId: "l_1", Value: 5},
   506  					&stpb.Outlier{CommandId: "l_2", Value: 5},
   507  				},
   508  			},
   509  			&stpb.Stat{
   510  				Name:         "[action=compile,lang=c++].RemoteMetadata.NumInputFiles",
   511  				Average:      2,
   512  				Count:        20,
   513  				Median:       2,
   514  				Percentile75: 2,
   515  				Percentile85: 2,
   516  				Percentile95: 2,
   517  				Outliers: []*stpb.Outlier{
   518  					&stpb.Outlier{CommandId: "c_1", Value: 2},
   519  					&stpb.Outlier{CommandId: "c_2", Value: 2},
   520  				},
   521  			},
   522  			&stpb.Stat{
   523  				Name:         "[action=link,lang=c++].RemoteMetadata.NumInputFiles",
   524  				Average:      5,
   525  				Count:        100,
   526  				Median:       5,
   527  				Percentile75: 5,
   528  				Percentile85: 5,
   529  				Percentile95: 5,
   530  				Outliers: []*stpb.Outlier{
   531  					&stpb.Outlier{CommandId: "l_1", Value: 5},
   532  					&stpb.Outlier{CommandId: "l_2", Value: 5},
   533  				},
   534  			},
   535  			{
   536  				Name: "CompletionStatus",
   537  				CountsByValue: []*stpb.Stat_Value{
   538  					{
   539  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   540  						Count: 30,
   541  					},
   542  				},
   543  			},
   544  			{
   545  				Name: "[action=compile,lang=c++].CompletionStatus",
   546  				CountsByValue: []*stpb.Stat_Value{
   547  					{
   548  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   549  						Count: 10,
   550  					},
   551  				},
   552  			},
   553  			{
   554  				Name: "[action=link,lang=c++].CompletionStatus",
   555  				CountsByValue: []*stpb.Stat_Value{
   556  					{
   557  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   558  						Count: 20,
   559  					},
   560  				},
   561  			},
   562  		},
   563  		MachineInfo: minfo,
   564  		ProxyInfo:   []*lpb.ProxyInfo{},
   565  	}
   566  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   567  		t.Errorf("Stat returned diff in result: (-want +got)\n%s", diff)
   568  	}
   569  }
   570  
   571  func TestVerification(t *testing.T) {
   572  	recs := []*lpb.LogRecord{
   573  		&lpb.LogRecord{
   574  			LocalMetadata: &lpb.LocalMetadata{
   575  				Verification: &lpb.Verification{
   576  					Mismatches: []*lpb.Verification_Mismatch{
   577  						&lpb.Verification_Mismatch{
   578  							Path:          "foo.o",
   579  							RemoteDigests: []string{"aaa/1"},
   580  							LocalDigest:   "bbb/1",
   581  						},
   582  						&lpb.Verification_Mismatch{
   583  							Path:          "foo.d",
   584  							RemoteDigests: []string{"aaa/2"},
   585  							LocalDigest:   "bbb/2",
   586  						},
   587  					},
   588  					TotalMismatches: 2,
   589  					TotalVerified:   2,
   590  				},
   591  			},
   592  		},
   593  		&lpb.LogRecord{
   594  			LocalMetadata: &lpb.LocalMetadata{
   595  				Verification: &lpb.Verification{
   596  					Mismatches: []*lpb.Verification_Mismatch{
   597  						&lpb.Verification_Mismatch{
   598  							Path:          "bar.o",
   599  							RemoteDigests: []string{"aaa/3"},
   600  							LocalDigest:   "bbb/3",
   601  						},
   602  						&lpb.Verification_Mismatch{
   603  							Path:          "bar.d",
   604  							RemoteDigests: []string{"aaa/4"},
   605  							LocalDigest:   "bbb/4",
   606  						},
   607  					},
   608  					TotalMismatches: 2,
   609  					TotalVerified:   2,
   610  				},
   611  			},
   612  		},
   613  		&lpb.LogRecord{
   614  			LocalMetadata: &lpb.LocalMetadata{
   615  				Verification: &lpb.Verification{
   616  					Mismatches: []*lpb.Verification_Mismatch{
   617  						&lpb.Verification_Mismatch{
   618  							Path:          "bla.txt",
   619  							RemoteDigests: []string{"aaa/5"},
   620  							LocalDigest:   "bbb/5",
   621  						},
   622  					},
   623  					TotalMismatches: 1,
   624  					TotalVerified:   2,
   625  				},
   626  			},
   627  		},
   628  	}
   629  	s := NewFromRecords(recs, nil)
   630  	wantStats := &spb.Stats{
   631  		NumRecords: 3,
   632  		Stats: []*stpb.Stat{
   633  			{
   634  				Name: "CompletionStatus",
   635  				CountsByValue: []*stpb.Stat_Value{
   636  					{
   637  						Name:  "STATUS_UNKNOWN",
   638  						Count: 3,
   639  					},
   640  				},
   641  			},
   642  			&stpb.Stat{
   643  				Name:         "LocalMetadata.Verification.TotalMismatches",
   644  				Count:        5,
   645  				Median:       2,
   646  				Percentile75: 2,
   647  				Percentile85: 2,
   648  				Percentile95: 2,
   649  				Average:      5.0 / 3.0,
   650  				Outliers: []*stpb.Outlier{
   651  					&stpb.Outlier{Value: 2},
   652  					&stpb.Outlier{Value: 2},
   653  				},
   654  			},
   655  			&stpb.Stat{
   656  				Name:         "LocalMetadata.Verification.TotalVerified",
   657  				Count:        6,
   658  				Median:       2,
   659  				Percentile75: 2,
   660  				Percentile85: 2,
   661  				Percentile95: 2,
   662  				Average:      6.0 / 3.0,
   663  				Outliers: []*stpb.Outlier{
   664  					&stpb.Outlier{Value: 2},
   665  					&stpb.Outlier{Value: 2},
   666  				},
   667  			},
   668  		},
   669  		Verification: &lpb.Verification{
   670  			Mismatches: []*lpb.Verification_Mismatch{
   671  				&lpb.Verification_Mismatch{
   672  					Path:          "bar.d",
   673  					RemoteDigests: []string{"aaa/4"},
   674  					LocalDigest:   "bbb/4",
   675  				},
   676  				&lpb.Verification_Mismatch{
   677  					Path:          "bar.o",
   678  					RemoteDigests: []string{"aaa/3"},
   679  					LocalDigest:   "bbb/3",
   680  				},
   681  				&lpb.Verification_Mismatch{
   682  					Path:          "bla.txt",
   683  					RemoteDigests: []string{"aaa/5"},
   684  					LocalDigest:   "bbb/5",
   685  				},
   686  				&lpb.Verification_Mismatch{
   687  					Path:          "foo.d",
   688  					RemoteDigests: []string{"aaa/2"},
   689  					LocalDigest:   "bbb/2",
   690  				},
   691  				&lpb.Verification_Mismatch{
   692  					Path:          "foo.o",
   693  					RemoteDigests: []string{"aaa/1"},
   694  					LocalDigest:   "bbb/1",
   695  				},
   696  			},
   697  			TotalMismatches: 5,
   698  			TotalVerified:   6,
   699  		},
   700  		MachineInfo: minfo,
   701  		ProxyInfo:   []*lpb.ProxyInfo{},
   702  	}
   703  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   704  		t.Errorf("Int stat returned diff in result: (-want +got)\n%s", diff)
   705  	}
   706  }
   707  
   708  func TestTimeStats(t *testing.T) {
   709  	// Maps in Go have random iteration order, which is a good test for our stats.
   710  	m := make(map[string]int)
   711  	for i := 0; i < 100; i++ {
   712  		m[fmt.Sprint(i)] = i
   713  	}
   714  	from, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
   715  	fromPb := command.TimeToProto(from)
   716  	var recs []*lpb.LogRecord
   717  	for n, v := range m {
   718  		recs = append(recs, &lpb.LogRecord{
   719  			Command: &cpb.Command{Identifiers: &cpb.Identifiers{CommandId: n}},
   720  			RemoteMetadata: &lpb.RemoteMetadata{
   721  				EventTimes: map[string]*cpb.TimeInterval{
   722  					"Foo": &cpb.TimeInterval{
   723  						From: fromPb,
   724  						To:   command.TimeToProto(from.Add(time.Duration(v) * time.Millisecond)),
   725  					},
   726  				},
   727  			},
   728  		})
   729  	}
   730  	s := NewFromRecords(recs, nil)
   731  	wantStats := &spb.Stats{
   732  		NumRecords: 100,
   733  		Stats: []*stpb.Stat{
   734  			{
   735  				Name: "CompletionStatus",
   736  				CountsByValue: []*stpb.Stat_Value{
   737  					{
   738  						Name:  lpb.CompletionStatus_STATUS_UNKNOWN.String(),
   739  						Count: 100,
   740  					},
   741  				},
   742  			},
   743  			&stpb.Stat{
   744  				Name:         "RemoteMetadata.EventTimes.FooMillis",
   745  				Count:        100,
   746  				Average:      49.5,
   747  				Median:       50,
   748  				Percentile75: 75,
   749  				Percentile85: 85,
   750  				Percentile95: 95,
   751  				Outliers: []*stpb.Outlier{
   752  					&stpb.Outlier{CommandId: "99", Value: 99},
   753  					&stpb.Outlier{CommandId: "98", Value: 98},
   754  				},
   755  			},
   756  		},
   757  		MachineInfo: minfo,
   758  		ProxyInfo:   []*lpb.ProxyInfo{},
   759  	}
   760  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   761  		t.Errorf("Time stat returned diff in result: (-want +got)\n%s", diff)
   762  	}
   763  }
   764  
   765  func TestTimeStatsDoNotAggregatePartial(t *testing.T) {
   766  	from, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
   767  	fromPb := command.TimeToProto(from)
   768  	recs := []*lpb.LogRecord{
   769  		&lpb.LogRecord{
   770  			RemoteMetadata: &lpb.RemoteMetadata{
   771  				EventTimes: map[string]*cpb.TimeInterval{"Foo": &cpb.TimeInterval{From: fromPb}},
   772  			},
   773  		},
   774  		&lpb.LogRecord{
   775  			RemoteMetadata: &lpb.RemoteMetadata{
   776  				EventTimes: map[string]*cpb.TimeInterval{"Foo": &cpb.TimeInterval{From: fromPb}},
   777  			},
   778  		},
   779  		&lpb.LogRecord{
   780  			RemoteMetadata: &lpb.RemoteMetadata{
   781  				EventTimes: map[string]*cpb.TimeInterval{
   782  					"Foo": &cpb.TimeInterval{From: fromPb, To: fromPb},
   783  				},
   784  			},
   785  		},
   786  		&lpb.LogRecord{
   787  			RemoteMetadata: &lpb.RemoteMetadata{
   788  				EventTimes: map[string]*cpb.TimeInterval{
   789  					"Foo": &cpb.TimeInterval{
   790  						From: fromPb,
   791  						To:   command.TimeToProto(from.Add(2 * time.Microsecond)),
   792  					},
   793  				},
   794  			},
   795  		},
   796  		&lpb.LogRecord{
   797  			RemoteMetadata: &lpb.RemoteMetadata{
   798  				EventTimes: map[string]*cpb.TimeInterval{
   799  					"Foo": &cpb.TimeInterval{
   800  						From: fromPb,
   801  						To:   command.TimeToProto(from.Add(2 * time.Millisecond)),
   802  					},
   803  				},
   804  			},
   805  		},
   806  	}
   807  	s := NewFromRecords(recs, nil)
   808  
   809  	wantStats := &spb.Stats{
   810  		NumRecords: 5,
   811  		Stats: []*stpb.Stat{
   812  			{
   813  				Name: "CompletionStatus",
   814  				CountsByValue: []*stpb.Stat_Value{
   815  					{
   816  						Name:  "STATUS_UNKNOWN",
   817  						Count: 5,
   818  					},
   819  				},
   820  			},
   821  			&stpb.Stat{
   822  				Name:         "RemoteMetadata.EventTimes.FooMillis",
   823  				Count:        3,
   824  				Average:      2.0 / 3.0,
   825  				Median:       0,
   826  				Percentile75: 2,
   827  				Percentile85: 2,
   828  				Percentile95: 2,
   829  				Outliers: []*stpb.Outlier{
   830  					&stpb.Outlier{Value: 2},
   831  				},
   832  			},
   833  		},
   834  		MachineInfo: minfo,
   835  		ProxyInfo:   []*lpb.ProxyInfo{},
   836  	}
   837  	if diff := cmp.Diff(wantStats, s.ToProto(), append(cmpStatsOpts, cmpStatsIgnoreBuildLatency)...); diff != "" {
   838  		t.Errorf("Time stat returned diff in result: (-want +got)\n%s", diff)
   839  	}
   840  }
   841  
   842  func TestBandwidthStats(t *testing.T) {
   843  	testCases := []struct {
   844  		name     string
   845  		stat     *Stats
   846  		wantDown string
   847  		wantUp   string
   848  	}{
   849  		{
   850  			name: "Test bytes",
   851  			stat: &Stats{
   852  				Stats: map[string]*Stat{
   853  					"RemoteMetadata.RealBytesDownloaded": &Stat{Count: 999},
   854  					"RemoteMetadata.RealBytesUploaded":   &Stat{Count: 999},
   855  				},
   856  			},
   857  			wantDown: "999 B",
   858  			wantUp:   "999 B",
   859  		},
   860  		{
   861  			name: "Test kilo-bytes",
   862  			stat: &Stats{
   863  				Stats: map[string]*Stat{
   864  					"RemoteMetadata.RealBytesDownloaded": &Stat{Count: 2520},
   865  					"RemoteMetadata.RealBytesUploaded":   &Stat{Count: 2520},
   866  				},
   867  			},
   868  			wantDown: "2.52 KB",
   869  			wantUp:   "2.52 KB",
   870  		},
   871  		{
   872  			name: "Test mega-bytes",
   873  			stat: &Stats{
   874  				Stats: map[string]*Stat{
   875  					"RemoteMetadata.RealBytesDownloaded": &Stat{Count: 1000 * 1100},
   876  					"RemoteMetadata.RealBytesUploaded":   &Stat{Count: 1000 * 1100},
   877  				},
   878  			},
   879  			wantDown: "1.10 MB",
   880  			wantUp:   "1.10 MB",
   881  		},
   882  		{
   883  			name: "Test giga-bytes",
   884  			stat: &Stats{
   885  				Stats: map[string]*Stat{
   886  					"RemoteMetadata.RealBytesDownloaded": &Stat{Count: 1000 * 1100 * 2000},
   887  					"RemoteMetadata.RealBytesUploaded":   &Stat{Count: 1000 * 1100 * 2000},
   888  				},
   889  			},
   890  			wantDown: "2.20 GB",
   891  			wantUp:   "2.20 GB",
   892  		},
   893  	}
   894  
   895  	for _, tc := range testCases {
   896  		t.Run(tc.name, func(t *testing.T) {
   897  			gotDown, gotUp := BandwidthStats(tc.stat.ToProto())
   898  			if gotDown != tc.wantDown || gotUp != tc.wantUp {
   899  				t.Fatalf("BandwidthStats() returned incorrect response, gotDown=%v, wantDown=%v, gotUp=%v, wantUp=%v", gotDown, tc.wantDown, gotUp, tc.wantUp)
   900  			}
   901  		})
   902  	}
   903  }
   904  
   905  func TestStatsProtoSaver(t *testing.T) {
   906  	original := &spb.Stats{
   907  		NumRecords: 5,
   908  		Stats: []*stpb.Stat{
   909  			&stpb.Stat{
   910  				Name:         "a",
   911  				Count:        6,
   912  				Median:       10,
   913  				Percentile75: 11,
   914  				Percentile85: 12,
   915  				Percentile95: 13,
   916  				Average:      9.8,
   917  				Outliers: []*stpb.Outlier{
   918  					&stpb.Outlier{CommandId: "foo", Value: 15},
   919  					&stpb.Outlier{CommandId: "foo", Value: 14},
   920  				},
   921  			},
   922  			&stpb.Stat{
   923  				Name:  "b",
   924  				Count: 3,
   925  				CountsByValue: []*stpb.Stat_Value{
   926  					&stpb.Stat_Value{Name: "v1", Count: 4},
   927  					&stpb.Stat_Value{Name: "v2", Count: 5},
   928  				},
   929  			},
   930  		},
   931  		MachineInfo: minfo,
   932  		ProxyInfo: []*lpb.ProxyInfo{&lpb.ProxyInfo{
   933  			EventTimes: map[string]*cpb.TimeInterval{
   934  				"Event": &cpb.TimeInterval{
   935  					From: timestamppb.New(time.Unix(1676388339, 0)),
   936  				},
   937  			},
   938  		}},
   939  	}
   940  	vs := &ProtoSaver{original}
   941  	got, id, err := vs.Save()
   942  	if err != nil {
   943  		t.Errorf("Save() returned error: %v", err)
   944  	}
   945  	want := map[string]bigquery.Value{
   946  		"machine_info": map[string]interface{}{
   947  			"arch":      minfo.Arch,
   948  			"num_cpu":   strconv.FormatInt(minfo.NumCpu, 10),
   949  			"os_family": minfo.OsFamily,
   950  			"ram_mbs":   strconv.FormatInt(minfo.RamMbs, 10),
   951  		},
   952  		"num_records": string("5"),
   953  		"proxy_info": []interface{}{
   954  			map[string]interface{}{
   955  				"event_times": []interface{}{
   956  					map[string]interface{}{
   957  						"key":   string("Event"),
   958  						"value": map[string]interface{}{"from": string("2023-02-14T15:25:39Z")},
   959  					},
   960  				},
   961  			},
   962  		},
   963  		"stats": []interface{}{
   964  			map[string]interface{}{
   965  				"average": float64(9.8),
   966  				"count":   string("6"),
   967  				"median":  string("10"),
   968  				"name":    string("a"),
   969  				"outliers": []interface{}{
   970  					map[string]interface{}{"command_id": string("foo"), "value": string("15")},
   971  					map[string]interface{}{"command_id": string("foo"), "value": string("14")},
   972  				},
   973  				"percentile75": string("11"),
   974  				"percentile85": string("12"),
   975  				"percentile95": string("13"),
   976  			},
   977  			map[string]interface{}{"count": string("3"), "counts_by_value": []interface{}{
   978  				map[string]interface{}{"count": string("4"), "name": string("v1")},
   979  				map[string]interface{}{"count": string("5"), "name": string("v2")},
   980  			}, "name": string("b")},
   981  		},
   982  	}
   983  	if len(id) == 0 {
   984  		t.Errorf("Save() returned zero length id")
   985  	}
   986  
   987  	if diff := cmp.Diff(want, got); diff != "" {
   988  		t.Errorf("Save() returned diff in result: (-want +got)\n%s", diff)
   989  	}
   990  }
   991  
   992  // BenchmarkProtoSaver measures the runtime of saving a large stats proto as a bq compatible map
   993  func BenchmarkProtoSaver(b *testing.B) {
   994  	testNow := time.Unix(1676388339, 0)
   995  	proxyEvents := map[string]*cpb.TimeInterval{}
   996  	flags := map[string]string{}
   997  	metrics := map[string]*lpb.Metric{}
   998  	statArr := []*stpb.Stat{}
   999  	for i := 0; i < 1000; i++ {
  1000  		proxyEvents[fmt.Sprintf("Event%d", i)] = &cpb.TimeInterval{
  1001  			From: timestamppb.New(testNow.Add(time.Duration(i) * time.Minute)),
  1002  			To:   timestamppb.New(testNow.Add(time.Duration(i+1) * time.Minute)),
  1003  		}
  1004  		flags[fmt.Sprintf("flag_%d", i)] = fmt.Sprintf("value_%d", i)
  1005  		metrics[fmt.Sprintf("flag_%d_bool", i)] = &lpb.Metric{Value: &lpb.Metric_BoolValue{i%2 == 0}}
  1006  		metrics[fmt.Sprintf("flag_%d_double", i)] = &lpb.Metric{Value: &lpb.Metric_DoubleValue{float64(i) + 0.5}}
  1007  		metrics[fmt.Sprintf("flag_%d_int64", i)] = &lpb.Metric{Value: &lpb.Metric_Int64Value{int64(i)}}
  1008  		statArr = append(statArr, &stpb.Stat{
  1009  			Name:         "a",
  1010  			Count:        int64(6 * i),
  1011  			Median:       int64(10 + i),
  1012  			Percentile75: int64(11 + i),
  1013  			Percentile85: int64(12 + i),
  1014  			Percentile95: int64(13 + i),
  1015  			Average:      9.8 + float64(i),
  1016  			Outliers: []*stpb.Outlier{
  1017  				&stpb.Outlier{CommandId: "foo", Value: int64(15 + i)},
  1018  				&stpb.Outlier{CommandId: "foo", Value: int64(14 + i)},
  1019  			},
  1020  		})
  1021  	}
  1022  	original := &spb.Stats{
  1023  		NumRecords:  5,
  1024  		Stats:       statArr,
  1025  		MachineInfo: minfo,
  1026  		ProxyInfo: []*lpb.ProxyInfo{&lpb.ProxyInfo{
  1027  			EventTimes: proxyEvents,
  1028  			Metrics:    metrics,
  1029  			Flags:      flags,
  1030  		}},
  1031  	}
  1032  	for n := 0; n < b.N; n++ {
  1033  		(&ProtoSaver{original}).Save()
  1034  	}
  1035  }
  1036  
  1037  func TestCompletionStats(t *testing.T) {
  1038  	tests := []struct {
  1039  		name  string
  1040  		stats *spb.Stats
  1041  		want  string
  1042  	}{
  1043  		{
  1044  			name: "OneStatus",
  1045  			stats: &spb.Stats{
  1046  				Stats: []*stpb.Stat{
  1047  					&stpb.Stat{
  1048  						Name: "CompletionStatus",
  1049  						CountsByValue: []*stpb.Stat_Value{
  1050  							&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_REMOTE_EXECUTION.String(), Count: 4},
  1051  						},
  1052  					},
  1053  				},
  1054  			},
  1055  			want: "4 remote executions",
  1056  		},
  1057  		{
  1058  			name: "MultipleStatuses",
  1059  			stats: &spb.Stats{
  1060  				Stats: []*stpb.Stat{
  1061  					&stpb.Stat{
  1062  						Name: "CompletionStatus",
  1063  						CountsByValue: []*stpb.Stat_Value{
  1064  							&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_REMOTE_EXECUTION.String(), Count: 4},
  1065  							&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_LOCAL_EXECUTION.String(), Count: 3},
  1066  							&stpb.Stat_Value{Name: lpb.CompletionStatus_STATUS_LOCAL_FALLBACK.String(), Count: 1},
  1067  						},
  1068  					},
  1069  				},
  1070  			},
  1071  			want: "4 remote executions, 1 local fallback, 3 local executions",
  1072  		},
  1073  		{
  1074  			name:  "NoStatuses",
  1075  			stats: &spb.Stats{},
  1076  			want:  "",
  1077  		},
  1078  	}
  1079  	for _, tc := range tests {
  1080  		tc := tc
  1081  		t.Run(tc.name, func(t *testing.T) {
  1082  			t.Parallel()
  1083  			got := CompletionStats(tc.stats)
  1084  
  1085  			if diff := cmp.Diff(tc.want, got); diff != "" {
  1086  				t.Errorf("CompletionStats(%v) returned diff in result: (-want +got)\n%s", tc.stats, diff)
  1087  			}
  1088  		})
  1089  	}
  1090  }
  1091  
  1092  func TestFromSeriesToProto(t *testing.T) {
  1093  	tests := []struct {
  1094  		testName  string
  1095  		name      string
  1096  		rawValues []int64
  1097  		want      *stpb.Stat
  1098  	}{
  1099  		{testName: "no rawValues", name: "foo", rawValues: []int64{}, want: &stpb.Stat{Name: "foo", Median: 0, Percentile75: 0, Percentile85: 0, Percentile95: 0, Average: 0.0}},
  1100  		{testName: "same rawValues", name: "foo", rawValues: []int64{1, 1, 1, 1, 1}, want: &stpb.Stat{Name: "foo", Median: 1, Percentile75: 1, Percentile85: 1, Percentile95: 1, Average: 1.0}},
  1101  		{testName: "in-order rawValues", name: "foo", rawValues: []int64{1, 2, 3, 4, 5}, want: &stpb.Stat{Name: "foo", Median: 3, Percentile75: 4, Percentile85: 5, Percentile95: 5, Average: 3.0}},
  1102  		{testName: "off-order rawValues", name: "foo", rawValues: []int64{5, 2, 3, 1, 4}, want: &stpb.Stat{Name: "foo", Median: 3, Percentile75: 4, Percentile85: 5, Percentile95: 5, Average: 3.0}},
  1103  	}
  1104  	for _, tt := range tests {
  1105  		t.Run(tt.name, func(t *testing.T) {
  1106  			got := FromSeriesToProto(tt.name, tt.rawValues)
  1107  			if !reflect.DeepEqual(got, tt.want) {
  1108  				t.Errorf("%v FromSeriesToProto() = %v, want %v", tt.testName, got, tt.want)
  1109  			}
  1110  		})
  1111  	}
  1112  }
  1113  
  1114  func TestWriteFromRecords(t *testing.T) {
  1115  	type args struct {
  1116  		recs      []*lpb.LogRecord
  1117  		pInfo     []*lpb.ProxyInfo
  1118  		wantStats *spb.Stats
  1119  	}
  1120  	tests := []struct {
  1121  		name string
  1122  		args args
  1123  	}{
  1124  		{name: "recs is nil", args: args{nil, nil, &spb.Stats{}}},
  1125  		{name: "recs is not nil", args: args{recordsToWrite, proxyInfosToWrite, NewFromRecords(recordsToWrite, proxyInfosToWrite).ToProto()}},
  1126  	}
  1127  	for _, tt := range tests {
  1128  		t.Run(tt.name, func(t *testing.T) {
  1129  			wantDir := t.TempDir()
  1130  			WriteStats(tt.args.wantStats, wantDir)
  1131  			want := fileContent(t, filepath.Join(wantDir, "rbe_metrics.txt"))
  1132  
  1133  			gotDir := t.TempDir()
  1134  			WriteFromRecords(tt.args.recs, tt.args.pInfo, gotDir)
  1135  			got := fileContent(t, filepath.Join(gotDir, "rbe_metrics.txt"))
  1136  			if diff := cmp.Diff(want, got); diff != "" {
  1137  				t.Errorf("TestAggregateRecordsToFiles generate diff in file content: (-want +got)\n", diff)
  1138  			}
  1139  		})
  1140  	}
  1141  }