go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/changepoints/inputbuffer/input_segment_test.go (about)

     1  // Copyright 2023 The LUCI Authors.
     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 inputbuffer
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"google.golang.org/protobuf/types/known/timestamppb"
    22  
    23  	cpb "go.chromium.org/luci/analysis/internal/changepoints/proto"
    24  
    25  	. "github.com/smartystreets/goconvey/convey"
    26  	. "go.chromium.org/luci/common/testing/assertions"
    27  )
    28  
    29  func TestSegmentizeInputBuffer(t *testing.T) {
    30  	Convey("Segmentize input buffer", t, func() {
    31  		Convey("No change point", func() {
    32  			var (
    33  				positions     = []int{1, 2, 3, 4, 5, 6}
    34  				total         = []int{1, 2, 1, 2, 1, 2}
    35  				hasUnexpected = []int{0, 1, 0, 2, 0, 0}
    36  			)
    37  			ib := genInputBuffer(10, 200, Verdicts(positions, total, hasUnexpected))
    38  			cps := []ChangePoint{}
    39  
    40  			var merged []PositionVerdict
    41  			ib.MergeBuffer(&merged)
    42  			sib := ib.Segmentize(merged, cps)
    43  			ibSegments := sib.Segments
    44  			So(len(ibSegments), ShouldEqual, 1)
    45  			So(ibSegments[0], ShouldResembleProto, &Segment{
    46  				StartIndex:          0,
    47  				EndIndex:            5,
    48  				HasStartChangepoint: false,
    49  				StartPosition:       1,
    50  				EndPosition:         6,
    51  				StartHour:           timestamppb.New(time.Unix(3600, 0)),
    52  				EndHour:             timestamppb.New(time.Unix(6*3600, 0)),
    53  				Counts: &cpb.Counts{
    54  					TotalResults:            9,
    55  					UnexpectedResults:       3,
    56  					TotalRuns:               9,
    57  					UnexpectedUnretriedRuns: 3,
    58  					TotalVerdicts:           6,
    59  					FlakyVerdicts:           1,
    60  					UnexpectedVerdicts:      1,
    61  					ExpectedPassedResults:   6,
    62  					UnexpectedFailedResults: 3,
    63  				},
    64  				MostRecentUnexpectedResultHourAllVerdicts: timestamppb.New(time.Unix(4*3600, 0)),
    65  			})
    66  		})
    67  
    68  		Convey("With change points and retries", func() {
    69  			var (
    70  				positions            = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
    71  				total                = []int{1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1}
    72  				hasUnexpected        = []int{0, 0, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1}
    73  				retries              = []int{0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0}
    74  				unexpectedAfterRetry = []int{0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0}
    75  			)
    76  			ib := genInputbufferWithRetries(10, 200, positions, total, hasUnexpected, retries, unexpectedAfterRetry)
    77  			cps := []ChangePoint{
    78  				{
    79  					NominalIndex:        3,
    80  					LowerBound99ThIndex: 2,
    81  					UpperBound99ThIndex: 4,
    82  				},
    83  				{
    84  					NominalIndex:        6,
    85  					LowerBound99ThIndex: 5,
    86  					UpperBound99ThIndex: 7,
    87  				},
    88  				{
    89  					NominalIndex:        9,
    90  					LowerBound99ThIndex: 8,
    91  					UpperBound99ThIndex: 10,
    92  				},
    93  			}
    94  			var merged []PositionVerdict
    95  			ib.MergeBuffer(&merged)
    96  			sib := ib.Segmentize(merged, cps)
    97  			ibSegments := sib.Segments
    98  			So(len(ibSegments), ShouldEqual, 4)
    99  			So(ibSegments[0], ShouldResembleProto, &Segment{
   100  				StartIndex:          0,
   101  				EndIndex:            2,
   102  				HasStartChangepoint: false,
   103  				StartPosition:       1,
   104  				EndPosition:         3,
   105  				StartHour:           timestamppb.New(time.Unix(3600, 0)),
   106  				EndHour:             timestamppb.New(time.Unix(3*3600, 0)),
   107  				Counts: &cpb.Counts{
   108  					TotalResults:          3,
   109  					TotalRuns:             3,
   110  					TotalVerdicts:         3,
   111  					ExpectedPassedResults: 3,
   112  				},
   113  			})
   114  
   115  			So(ibSegments[1], ShouldResembleProto, &Segment{
   116  				StartIndex:                  3,
   117  				EndIndex:                    5,
   118  				HasStartChangepoint:         true,
   119  				StartPosition:               4,
   120  				StartPositionLowerBound99Th: 3,
   121  				StartPositionUpperBound99Th: 5,
   122  				EndPosition:                 6,
   123  				StartHour:                   timestamppb.New(time.Unix(4*3600, 0)),
   124  				EndHour:                     timestamppb.New(time.Unix(6*3600, 0)),
   125  				Counts: &cpb.Counts{
   126  					TotalResults:             12,
   127  					UnexpectedResults:        12,
   128  					TotalRuns:                6,
   129  					UnexpectedAfterRetryRuns: 6,
   130  					TotalVerdicts:            3,
   131  					UnexpectedVerdicts:       3,
   132  					UnexpectedFailedResults:  6,
   133  					UnexpectedCrashedResults: 6,
   134  				},
   135  				MostRecentUnexpectedResultHourAllVerdicts: timestamppb.New(time.Unix(6*3600, 0)),
   136  			})
   137  
   138  			So(ibSegments[2], ShouldResembleProto, &Segment{
   139  				StartIndex:                  6,
   140  				EndIndex:                    8,
   141  				HasStartChangepoint:         true,
   142  				StartPosition:               7,
   143  				StartPositionLowerBound99Th: 6,
   144  				StartPositionUpperBound99Th: 8,
   145  				EndPosition:                 9,
   146  				StartHour:                   timestamppb.New(time.Unix(7*3600, 0)),
   147  				EndHour:                     timestamppb.New(time.Unix(9*3600, 0)),
   148  				Counts: &cpb.Counts{
   149  					TotalResults:            12,
   150  					UnexpectedResults:       6,
   151  					TotalRuns:               6,
   152  					FlakyRuns:               6,
   153  					TotalVerdicts:           3,
   154  					FlakyVerdicts:           3,
   155  					ExpectedPassedResults:   6,
   156  					UnexpectedFailedResults: 6,
   157  				},
   158  				MostRecentUnexpectedResultHourAllVerdicts: timestamppb.New(time.Unix(9*3600, 0)),
   159  			})
   160  
   161  			So(ibSegments[3], ShouldResembleProto, &Segment{
   162  				StartIndex:                  9,
   163  				EndIndex:                    11,
   164  				HasStartChangepoint:         true,
   165  				StartPosition:               10,
   166  				StartPositionLowerBound99Th: 9,
   167  				StartPositionUpperBound99Th: 11,
   168  				EndPosition:                 12,
   169  				StartHour:                   timestamppb.New(time.Unix(10*3600, 0)),
   170  				EndHour:                     timestamppb.New(time.Unix(12*3600, 0)),
   171  				Counts: &cpb.Counts{
   172  					TotalResults:            3,
   173  					UnexpectedResults:       3,
   174  					TotalRuns:               3,
   175  					UnexpectedUnretriedRuns: 3,
   176  					TotalVerdicts:           3,
   177  					UnexpectedVerdicts:      3,
   178  					UnexpectedFailedResults: 3,
   179  				},
   180  				MostRecentUnexpectedResultHourAllVerdicts: timestamppb.New(time.Unix(12*3600, 0)),
   181  			})
   182  		})
   183  	})
   184  }
   185  
   186  func TestEvictSegments(t *testing.T) {
   187  	Convey("Not evict segment", t, func() {
   188  		ib := genInputBuffer(100, 2000, simpleVerdicts(100, 1, []int{}))
   189  		segments := []*Segment{
   190  			{
   191  				StartIndex: 0,
   192  				EndIndex:   99,
   193  				Counts: &cpb.Counts{
   194  					TotalResults:  100,
   195  					TotalRuns:     100,
   196  					TotalVerdicts: 100,
   197  				},
   198  				StartPosition: 1,
   199  				EndPosition:   100,
   200  			},
   201  		}
   202  		sib := &SegmentedInputBuffer{
   203  			InputBuffer: ib,
   204  			Segments:    segments,
   205  		}
   206  		evicted := sib.EvictSegments()
   207  		remaining := sib.Segments
   208  		So(len(evicted), ShouldEqual, 0)
   209  		So(len(remaining), ShouldEqual, 1)
   210  		So(ib.IsColdBufferDirty, ShouldBeFalse)
   211  		So(remaining[0], ShouldResembleProto, segments[0])
   212  	})
   213  
   214  	Convey("Evict finalizing segment", t, func() {
   215  		ib := genInputBuffer(100, 2000, simpleVerdicts(2100, 1, []int{50, 1900}))
   216  		segments := []*Segment{
   217  			{
   218  				StartIndex: 0,
   219  				EndIndex:   2049,
   220  				Counts: &cpb.Counts{
   221  					TotalResults:          2050,
   222  					TotalRuns:             2050,
   223  					TotalVerdicts:         2050,
   224  					ExpectedPassedResults: 2050,
   225  				},
   226  				HasStartChangepoint: false,
   227  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   228  				StartPosition:       1,
   229  				EndHour:             timestamppb.New(time.Unix(2050*3600, 0)),
   230  				EndPosition:         2050,
   231  			},
   232  			{
   233  				StartIndex: 2050,
   234  				EndIndex:   2099,
   235  				Counts: &cpb.Counts{
   236  					TotalResults:          50,
   237  					TotalRuns:             50,
   238  					TotalVerdicts:         50,
   239  					ExpectedPassedResults: 50,
   240  				},
   241  				HasStartChangepoint: true,
   242  				StartHour:           timestamppb.New(time.Unix(2051*3600, 0)),
   243  				StartPosition:       2051,
   244  				EndHour:             timestamppb.New(time.Unix(2100*3600, 0)),
   245  				EndPosition:         2100,
   246  			},
   247  		}
   248  		sib := &SegmentedInputBuffer{
   249  			InputBuffer: ib,
   250  			Segments:    segments,
   251  		}
   252  
   253  		evicted := sib.EvictSegments()
   254  		remaining := sib.Segments
   255  		So(len(evicted), ShouldEqual, 1)
   256  		So(len(remaining), ShouldEqual, 2)
   257  		So(ib.IsColdBufferDirty, ShouldBeTrue)
   258  
   259  		So(evicted[0], ShouldResembleProto, EvictedSegment{
   260  			Segment: &cpb.Segment{
   261  				State:               cpb.SegmentState_FINALIZING,
   262  				HasStartChangepoint: false,
   263  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   264  				StartPosition:       1,
   265  				FinalizedCounts: &cpb.Counts{
   266  					TotalResults:            100,
   267  					UnexpectedResults:       1,
   268  					TotalRuns:               100,
   269  					UnexpectedUnretriedRuns: 1,
   270  					TotalVerdicts:           100,
   271  					UnexpectedVerdicts:      1,
   272  					ExpectedPassedResults:   99,
   273  					UnexpectedFailedResults: 1,
   274  				},
   275  				MostRecentUnexpectedResultHour: timestamppb.New(time.Unix(51*3600, 0)),
   276  			},
   277  			Verdicts: simpleVerdicts(100, 1, []int{50}),
   278  		})
   279  
   280  		So(remaining[0], ShouldResembleProto, &Segment{
   281  			StartIndex:  0,
   282  			EndIndex:    1949,
   283  			EndPosition: 2050,
   284  			EndHour:     timestamppb.New(time.Unix(2050*3600, 0)),
   285  			Counts: &cpb.Counts{
   286  				TotalResults:            1950,
   287  				UnexpectedResults:       1,
   288  				TotalRuns:               1950,
   289  				UnexpectedUnretriedRuns: 1,
   290  				TotalVerdicts:           1950,
   291  				UnexpectedVerdicts:      1,
   292  				ExpectedPassedResults:   1949,
   293  				UnexpectedFailedResults: 1,
   294  			},
   295  			MostRecentUnexpectedResultHourAllVerdicts: timestamppb.New(time.Unix(1901*3600, 0)),
   296  		})
   297  
   298  		So(remaining[1], ShouldResembleProto, &Segment{
   299  			StartIndex:          1950,
   300  			EndIndex:            1999,
   301  			HasStartChangepoint: true,
   302  			StartPosition:       2051,
   303  			StartHour:           timestamppb.New(time.Unix(2051*3600, 0)),
   304  			EndPosition:         2100,
   305  			EndHour:             timestamppb.New(time.Unix(2100*3600, 0)),
   306  			Counts: &cpb.Counts{
   307  				TotalResults:          50,
   308  				TotalRuns:             50,
   309  				TotalVerdicts:         50,
   310  				ExpectedPassedResults: 50,
   311  			},
   312  		})
   313  	})
   314  
   315  	Convey("Evict finalized segment", t, func() {
   316  		ib := genInputBuffer(100, 2000, simpleVerdicts(2100, 1, []int{}))
   317  		segments := []*Segment{
   318  			{
   319  				StartIndex: 0, // Finalized segment.
   320  				EndIndex:   39,
   321  				Counts: &cpb.Counts{
   322  					TotalResults:          40,
   323  					TotalRuns:             40,
   324  					TotalVerdicts:         40,
   325  					ExpectedPassedResults: 40,
   326  				},
   327  				HasStartChangepoint: false,
   328  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   329  				StartPosition:       1,
   330  				EndHour:             timestamppb.New(time.Unix(40*3600, 0)),
   331  				EndPosition:         40,
   332  			},
   333  			{
   334  				StartIndex: 40, // Finalized segment.
   335  				EndIndex:   79,
   336  				Counts: &cpb.Counts{
   337  					TotalResults:          40,
   338  					TotalRuns:             40,
   339  					TotalVerdicts:         40,
   340  					ExpectedPassedResults: 40,
   341  				},
   342  				HasStartChangepoint:         true,
   343  				StartHour:                   timestamppb.New(time.Unix(41*3600, 0)),
   344  				StartPositionLowerBound99Th: 30,
   345  				StartPositionUpperBound99Th: 50,
   346  				StartPosition:               41,
   347  				EndHour:                     timestamppb.New(time.Unix(80*3600, 0)),
   348  				EndPosition:                 80,
   349  			},
   350  			{
   351  				StartIndex: 80, // A finalizing segment.
   352  				EndIndex:   2049,
   353  				Counts: &cpb.Counts{
   354  					TotalResults:          1970,
   355  					TotalRuns:             1970,
   356  					TotalVerdicts:         1970,
   357  					ExpectedPassedResults: 1970,
   358  				},
   359  				HasStartChangepoint:         true,
   360  				StartHour:                   timestamppb.New(time.Unix(81*3600, 0)),
   361  				StartPosition:               81,
   362  				StartPositionLowerBound99Th: 70,
   363  				StartPositionUpperBound99Th: 90,
   364  				EndHour:                     timestamppb.New(time.Unix(2050*3600, 0)),
   365  				EndPosition:                 2050,
   366  			},
   367  			{
   368  				StartIndex: 2050, // An active segment.
   369  				EndIndex:   2099,
   370  				Counts: &cpb.Counts{
   371  					TotalResults:          50,
   372  					TotalRuns:             50,
   373  					TotalVerdicts:         50,
   374  					ExpectedPassedResults: 50,
   375  				},
   376  				HasStartChangepoint: true,
   377  				StartHour:           timestamppb.New(time.Unix(2051*3600, 0)),
   378  				StartPosition:       2051,
   379  				EndHour:             timestamppb.New(time.Unix(2100*3600, 0)),
   380  				EndPosition:         2100,
   381  			},
   382  		}
   383  
   384  		sib := &SegmentedInputBuffer{
   385  			InputBuffer: ib,
   386  			Segments:    segments,
   387  		}
   388  		evicted := sib.EvictSegments()
   389  		remaining := sib.Segments
   390  		So(len(evicted), ShouldEqual, 3)
   391  		So(len(remaining), ShouldEqual, 2)
   392  		So(ib.IsColdBufferDirty, ShouldBeTrue)
   393  
   394  		So(evicted[0], ShouldResembleProto, EvictedSegment{
   395  			Segment: &cpb.Segment{
   396  				State:               cpb.SegmentState_FINALIZED,
   397  				HasStartChangepoint: false,
   398  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   399  				StartPosition:       1,
   400  				EndHour:             timestamppb.New(time.Unix(40*3600, 0)),
   401  				EndPosition:         40,
   402  				FinalizedCounts: &cpb.Counts{
   403  					TotalResults:          40,
   404  					TotalRuns:             40,
   405  					TotalVerdicts:         40,
   406  					ExpectedPassedResults: 40,
   407  				},
   408  			},
   409  			Verdicts: simpleVerdicts(40, 1, []int{}),
   410  		})
   411  
   412  		So(evicted[1], ShouldResembleProto, EvictedSegment{
   413  			Segment: &cpb.Segment{
   414  				State:                        cpb.SegmentState_FINALIZED,
   415  				HasStartChangepoint:          true,
   416  				StartHour:                    timestamppb.New(time.Unix(41*3600, 0)),
   417  				StartPosition:                41,
   418  				StartPositionLowerBound_99Th: 30,
   419  				StartPositionUpperBound_99Th: 50,
   420  				EndHour:                      timestamppb.New(time.Unix(80*3600, 0)),
   421  				EndPosition:                  80,
   422  				FinalizedCounts: &cpb.Counts{
   423  					TotalResults:          40,
   424  					TotalRuns:             40,
   425  					TotalVerdicts:         40,
   426  					ExpectedPassedResults: 40,
   427  				},
   428  			},
   429  			Verdicts: simpleVerdicts(40, 41, []int{}),
   430  		})
   431  
   432  		So(evicted[2], ShouldResembleProto, EvictedSegment{
   433  			Segment: &cpb.Segment{
   434  				State:                        cpb.SegmentState_FINALIZING,
   435  				HasStartChangepoint:          true,
   436  				StartHour:                    timestamppb.New(time.Unix(81*3600, 0)),
   437  				StartPosition:                81,
   438  				StartPositionLowerBound_99Th: 70,
   439  				StartPositionUpperBound_99Th: 90,
   440  				FinalizedCounts: &cpb.Counts{
   441  					TotalResults:          20,
   442  					TotalRuns:             20,
   443  					TotalVerdicts:         20,
   444  					ExpectedPassedResults: 20,
   445  				},
   446  			},
   447  			Verdicts: simpleVerdicts(20, 81, []int{}),
   448  		})
   449  
   450  		So(remaining[0], ShouldResembleProto, &Segment{
   451  			StartIndex:  0,
   452  			EndIndex:    1949,
   453  			EndPosition: 2050,
   454  			EndHour:     timestamppb.New(time.Unix(2050*3600, 0)),
   455  			Counts: &cpb.Counts{
   456  				TotalResults:          1950,
   457  				TotalRuns:             1950,
   458  				TotalVerdicts:         1950,
   459  				ExpectedPassedResults: 1950,
   460  			},
   461  		})
   462  
   463  		So(remaining[1], ShouldResembleProto, &Segment{
   464  			StartIndex:          1950,
   465  			EndIndex:            1999,
   466  			HasStartChangepoint: true,
   467  			StartPosition:       2051,
   468  			StartHour:           timestamppb.New(time.Unix(2051*3600, 0)),
   469  			EndPosition:         2100,
   470  			EndHour:             timestamppb.New(time.Unix(2100*3600, 0)),
   471  			Counts: &cpb.Counts{
   472  				TotalResults:          50,
   473  				TotalRuns:             50,
   474  				TotalVerdicts:         50,
   475  				ExpectedPassedResults: 50,
   476  			},
   477  		})
   478  	})
   479  
   480  	Convey("Evict all hot buffer", t, func() {
   481  		ib := genInputBuffer(100, 2000, simpleVerdicts(2000, 1, []int{}))
   482  		ib.HotBuffer = History{
   483  			Verdicts: []PositionVerdict{
   484  				{
   485  					CommitPosition: 10,
   486  				},
   487  			},
   488  		}
   489  		segments := []*Segment{
   490  			{
   491  				StartIndex: 0, // Finalized segment.
   492  				EndIndex:   39,
   493  				Counts: &cpb.Counts{
   494  					TotalResults:          40,
   495  					TotalRuns:             40,
   496  					TotalVerdicts:         40,
   497  					ExpectedPassedResults: 40,
   498  				},
   499  				HasStartChangepoint: false,
   500  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   501  				StartPosition:       1,
   502  				EndHour:             timestamppb.New(time.Unix(39*3600, 0)),
   503  				EndPosition:         39,
   504  			},
   505  			{
   506  				StartIndex: 40, // A finalizing segment.
   507  				EndIndex:   2000,
   508  				Counts: &cpb.Counts{
   509  					TotalResults:          1961,
   510  					TotalRuns:             1961,
   511  					TotalVerdicts:         1961,
   512  					ExpectedPassedResults: 1961,
   513  				},
   514  				HasStartChangepoint:         true,
   515  				StartHour:                   timestamppb.New(time.Unix(40*3600, 0)),
   516  				StartPosition:               40,
   517  				StartPositionLowerBound99Th: 30,
   518  				StartPositionUpperBound99Th: 50,
   519  				EndHour:                     timestamppb.New(time.Unix(2000*3600, 0)),
   520  				EndPosition:                 2000,
   521  			},
   522  		}
   523  
   524  		sib := &SegmentedInputBuffer{
   525  			InputBuffer: ib,
   526  			Segments:    segments,
   527  		}
   528  		evicted := sib.EvictSegments()
   529  		remaining := sib.Segments
   530  		So(len(evicted), ShouldEqual, 2)
   531  		So(len(remaining), ShouldEqual, 1)
   532  		So(sib.InputBuffer.IsColdBufferDirty, ShouldBeTrue)
   533  
   534  		// Hot bufffer should be empty.
   535  		So(len(sib.InputBuffer.HotBuffer.Verdicts), ShouldEqual, 0)
   536  		So(len(sib.InputBuffer.ColdBuffer.Verdicts), ShouldEqual, 1961)
   537  
   538  		expectedVerdicts := []PositionVerdict{
   539  			// The verdict in the hot buffer.
   540  			{CommitPosition: 10},
   541  		}
   542  		// Plus the evicted verdicts in the cold buffer.
   543  		expectedVerdicts = append(expectedVerdicts, simpleVerdicts(39, 1, []int{})...)
   544  
   545  		So(evicted[0], ShouldResembleProto, EvictedSegment{
   546  			Segment: &cpb.Segment{
   547  				State:               cpb.SegmentState_FINALIZED,
   548  				HasStartChangepoint: false,
   549  				StartHour:           timestamppb.New(time.Unix(1*3600, 0)),
   550  				StartPosition:       1,
   551  				EndHour:             timestamppb.New(time.Unix(39*3600, 0)),
   552  				EndPosition:         39,
   553  				FinalizedCounts: &cpb.Counts{
   554  					TotalResults:          40,
   555  					TotalRuns:             40,
   556  					TotalVerdicts:         40,
   557  					ExpectedPassedResults: 40,
   558  				},
   559  			},
   560  			Verdicts: expectedVerdicts,
   561  		})
   562  
   563  		So(evicted[1], ShouldResembleProto, EvictedSegment{
   564  			Segment: &cpb.Segment{
   565  				State:                        cpb.SegmentState_FINALIZING,
   566  				HasStartChangepoint:          true,
   567  				StartHour:                    timestamppb.New(time.Unix(40*3600, 0)),
   568  				StartPosition:                40,
   569  				StartPositionLowerBound_99Th: 30,
   570  				StartPositionUpperBound_99Th: 50,
   571  				FinalizedCounts:              &cpb.Counts{},
   572  			},
   573  			Verdicts: []PositionVerdict{},
   574  		})
   575  
   576  		So(remaining[0], ShouldResembleProto, segments[1])
   577  	})
   578  }
   579  
   580  func simpleVerdicts(verdictCount int, startPos int, unexpectedIndices []int) []PositionVerdict {
   581  	positions := make([]int, verdictCount)
   582  	total := make([]int, verdictCount)
   583  	hasUnexpected := make([]int, verdictCount)
   584  	for i := 0; i < verdictCount; i++ {
   585  		positions[i] = i + startPos
   586  		total[i] = 1
   587  	}
   588  	for _, ui := range unexpectedIndices {
   589  		hasUnexpected[ui] = 1
   590  	}
   591  	return Verdicts(positions, total, hasUnexpected)
   592  }
   593  
   594  func genInputBuffer(hotCap int, coldCap int, history []PositionVerdict) *Buffer {
   595  	return &Buffer{
   596  		HotBufferCapacity:  hotCap,
   597  		ColdBufferCapacity: coldCap,
   598  		HotBuffer:          History{},
   599  		ColdBuffer: History{
   600  			Verdicts: history,
   601  		},
   602  	}
   603  }
   604  
   605  func genInputbufferWithRetries(hotCap int, coldCap int, positions, total, hasUnexpected, retried, unexpectedAfterRetry []int) *Buffer {
   606  	history := VerdictsWithRetries(positions, total, hasUnexpected, retried, unexpectedAfterRetry)
   607  
   608  	return &Buffer{
   609  		HotBufferCapacity:  hotCap,
   610  		ColdBufferCapacity: coldCap,
   611  		HotBuffer:          History{},
   612  		ColdBuffer: History{
   613  			Verdicts: history,
   614  		},
   615  	}
   616  }
   617  
   618  func BenchmarkEncode(b *testing.B) {
   619  	// Last known result (23-Jun-2023):
   620  	// cpu: Intel(R) Xeon(R) CPU @ 2.00GHz
   621  	// BenchmarkEncode-96    	   41640	     31182 ns/op	    2160 B/op	       2 allocs/op
   622  
   623  	b.StopTimer()
   624  	hs := &HistorySerializer{}
   625  	hs.ensureAndClearBuf()
   626  	ib := &Buffer{
   627  		HotBufferCapacity:  100,
   628  		HotBuffer:          History{Verdicts: simpleVerdicts(100, 1, []int{5})},
   629  		ColdBufferCapacity: 2000,
   630  		ColdBuffer:         History{Verdicts: simpleVerdicts(2000, 1, []int{102, 174, 872, 971})},
   631  	}
   632  	b.StartTimer()
   633  
   634  	for i := 0; i < b.N; i++ {
   635  		hs.Encode(ib.ColdBuffer)
   636  		hs.Encode(ib.HotBuffer)
   637  	}
   638  }
   639  
   640  func BenchmarkDecode(b *testing.B) {
   641  	// Last known result (23-Jun-2023):
   642  	// cpu: Intel(R) Xeon(R) CPU @ 2.00GHz
   643  	// BenchmarkDecode-96    	   20103	     59558 ns/op	     216 B/op	       7 allocs/op
   644  
   645  	b.StopTimer()
   646  	var hs HistorySerializer
   647  	hs.ensureAndClearBuf()
   648  	inputBuffer := NewWithCapacity(100, 2000)
   649  
   650  	ib := &Buffer{
   651  		HotBufferCapacity:  100,
   652  		HotBuffer:          History{Verdicts: simpleVerdicts(100, 1, []int{5})},
   653  		ColdBufferCapacity: 2000,
   654  		ColdBuffer:         History{Verdicts: simpleVerdicts(2000, 1, []int{102, 174, 872, 971})},
   655  	}
   656  	encodedColdBuffer := hs.Encode(ib.ColdBuffer) // 62 bytes compressed
   657  	encodedHotBuffer := hs.Encode(ib.HotBuffer)   // 38 bytes compressed
   658  	b.StartTimer()
   659  
   660  	for i := 0; i < b.N; i++ {
   661  		err := hs.DecodeInto(&inputBuffer.ColdBuffer, encodedColdBuffer)
   662  		if err != nil {
   663  			panic(err)
   664  		}
   665  		err = hs.DecodeInto(&inputBuffer.HotBuffer, encodedHotBuffer)
   666  		if err != nil {
   667  			panic(err)
   668  		}
   669  	}
   670  }