github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/cmd/build_log_perftest/build_log_perftest.go (about)

     1  // Copyright 2012 Google Inc. All Rights Reserved.
     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 main
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"time"
    21  
    22  	"github.com/maruel/nin"
    23  )
    24  
    25  const testFilename = "BuildLogPerfTest-tempfile"
    26  
    27  type noDeadPaths struct {
    28  }
    29  
    30  func (n *noDeadPaths) IsPathDead(string) bool {
    31  	return false
    32  }
    33  
    34  func writeTestData() error {
    35  	log := nin.NewBuildLog()
    36  	if err := log.OpenForWrite(testFilename, &noDeadPaths{}); err != nil {
    37  		return err
    38  	}
    39  
    40  	/*
    41  	  A histogram of command lengths in chromium. For example, 407 builds,
    42  	  1.4% of all builds, had commands longer than 32 bytes but shorter than 64.
    43  	       32    407   1.4%
    44  	       64    183   0.6%
    45  	      128   1461   5.1%
    46  	      256    791   2.8%
    47  	      512   1314   4.6%
    48  	     1024   6114  21.3%
    49  	     2048  11759  41.0%
    50  	     4096   2056   7.2%
    51  	     8192   4567  15.9%
    52  	    16384     13   0.0%
    53  	    32768      4   0.0%
    54  	    65536      5   0.0%
    55  	  The average command length is 4.1 kB and there were 28674 commands in total,
    56  	  which makes for a total log size of ~120 MB (also counting output filenames).
    57  
    58  	  Based on this, write 30000 many 4 kB long command lines.
    59  	*/
    60  
    61  	// ParseManifest() is the only function allowed to create Rules.
    62  	kRuleSize := 4000
    63  	longRuleCommand := "gcc "
    64  	for i := 0; len(longRuleCommand) < kRuleSize; i++ {
    65  		longRuleCommand += fmt.Sprintf("-I../../and/arbitrary/but/fairly/long/path/suffixed/%d ", i)
    66  	}
    67  	longRuleCommand += "$in -o $out\n"
    68  
    69  	state := nin.NewState()
    70  	opts := nin.ParseManifestOpts{Quiet: true}
    71  	input := []byte("rule cxx\n  command = " + longRuleCommand + "\x00")
    72  	if err := nin.ParseManifest(&state, nil, opts, "input", input); err != nil {
    73  		return err
    74  	}
    75  
    76  	// Create build edges. Using ParseManifest() is as fast as using the State
    77  	// api for edge creation, so just use that.
    78  	kNumCommands := int32(30000)
    79  	buildRules := ""
    80  	for i := int32(0); i < kNumCommands; i++ {
    81  		buildRules += fmt.Sprintf("build input%d.o: cxx input%d.cc\n", i, i)
    82  	}
    83  
    84  	input = []byte(buildRules + "\x00")
    85  	if err := nin.ParseManifest(&state, nil, opts, "input", input); err != nil {
    86  		return err
    87  	}
    88  
    89  	for i := int32(0); i < kNumCommands; i++ {
    90  		if err := log.RecordCommand(state.Edges[i] /*startTime=*/, 100*i /*endTime=*/, 100*i+1 /*mtime=*/, 0); err != nil {
    91  			return err
    92  		}
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  func mainImpl() error {
    99  	if err := writeTestData(); err != nil {
   100  		return fmt.Errorf("failed to write test data: %w", err)
   101  	}
   102  
   103  	{
   104  		// Read once to warm up disk cache.
   105  		log := nin.NewBuildLog()
   106  		if s, err := log.Load(testFilename); s == nin.LoadError {
   107  			return fmt.Errorf("failed to read test data: %s", err)
   108  		}
   109  	}
   110  
   111  	rnd := time.Microsecond
   112  	var times []time.Duration
   113  	kNumRepetitions := 5
   114  	for i := 0; i < kNumRepetitions; i++ {
   115  		start := time.Now()
   116  		log := nin.NewBuildLog()
   117  		if s, err := log.Load(testFilename); s == nin.LoadError {
   118  			return fmt.Errorf("failed to read test data: %s", err)
   119  		}
   120  		delta := time.Since(start)
   121  		fmt.Printf("%s\n", delta.Round(rnd))
   122  		times = append(times, delta)
   123  	}
   124  
   125  	min := times[0]
   126  	max := times[0]
   127  	total := time.Duration(0)
   128  	for i := 0; i < len(times); i++ {
   129  		total += times[i]
   130  		if times[i] < min {
   131  			min = times[i]
   132  		} else if times[i] > max {
   133  			max = times[i]
   134  		}
   135  	}
   136  	avg := total / time.Duration(len(times))
   137  	fmt.Printf("min %s  max %s  avg %s\n", min.Round(rnd), max.Round(rnd), avg.Round(rnd))
   138  	return os.Remove(testFilename)
   139  }
   140  
   141  func main() {
   142  	if err := mainImpl(); err != nil {
   143  		fmt.Fprintf(os.Stderr, "build_log_perftest: %s\n", err)
   144  		os.Exit(1)
   145  	}
   146  }