github.com/m3db/m3@v1.5.0/src/x/instrument/build.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package instrument
    22  
    23  import (
    24  	"errors"
    25  	"log"
    26  	"os"
    27  	"runtime"
    28  	"strconv"
    29  	"sync"
    30  	"time"
    31  )
    32  
    33  var (
    34  	// Revision is the VCS revision associated with this build. Overridden using ldflags
    35  	// at compile time. Example:
    36  	// $ go build -ldflags "-X github.com/m3db/m3/src/x/instrument.Revision=abcdef" ...
    37  	// Adapted from: https://www.atatus.com/blog/golang-auto-build-versioning/
    38  	Revision = "unknown"
    39  
    40  	// Branch is the VCS branch associated with this build.
    41  	Branch = "unknown"
    42  
    43  	// Version is the version associated with this build.
    44  	Version = "unknown"
    45  
    46  	// BuildDate is the date this build was created.
    47  	BuildDate = "unknown"
    48  
    49  	// BuildTimeUnix is the seconds since epoch representing the date this build was created.
    50  	BuildTimeUnix = "0"
    51  
    52  	// LogBuildInfoAtStartup controls whether we log build information at startup. If its
    53  	// set to a non-empty string, we log the build information at process startup.
    54  	LogBuildInfoAtStartup string
    55  
    56  	// LogBuildInfoToStdout controls whether we log build information to stdout or stderr.
    57  	// If it is set to a non-empty string then the build info will be logged to stdout,
    58  	// otherwise it will be logged to stderr (assuming LogBuildInfoAtStartup is also
    59  	// non-empty).
    60  	LogBuildInfoToStdout string
    61  
    62  	// goVersion is the current runtime version.
    63  	goVersion = runtime.Version()
    64  
    65  	// buildInfoMetricName is the emitted build information metric's name.
    66  	buildInfoMetricName = "build-information"
    67  
    68  	// buildAgeMetricName is the emitted build age metric's name.
    69  	buildAgeMetricName = "build-age"
    70  )
    71  
    72  var (
    73  	errAlreadyStarted    = errors.New("reporter already started")
    74  	errNotStarted        = errors.New("reporter not started")
    75  	errBuildTimeNegative = errors.New("reporter build time must be non-negative")
    76  )
    77  
    78  // LogBuildInfo logs the build information using the default logger.
    79  func LogBuildInfo() {
    80  	LogBuildInfoWithLogger(log.Default())
    81  }
    82  
    83  // LogBuildInfoWithLogger logs the build information using the provided logger.
    84  func LogBuildInfoWithLogger(logger *log.Logger) {
    85  	logger.Printf("Go Runtime version: %s\n", goVersion)
    86  	logger.Printf("Build Version:      %s\n", Version)
    87  	logger.Printf("Build Revision:     %s\n", Revision)
    88  	logger.Printf("Build Branch:       %s\n", Branch)
    89  	logger.Printf("Build Date:         %s\n", BuildDate)
    90  	logger.Printf("Build TimeUnix:     %s\n", BuildTimeUnix)
    91  }
    92  
    93  func init() {
    94  	if LogBuildInfoAtStartup != "" {
    95  		logger := log.Default()
    96  		if LogBuildInfoToStdout != "" {
    97  			logger = log.New(os.Stdout, "", log.LstdFlags)
    98  		}
    99  		LogBuildInfoWithLogger(logger)
   100  	}
   101  }
   102  
   103  type buildReporter struct {
   104  	sync.Mutex
   105  
   106  	opts      Options
   107  	buildTime time.Time
   108  	active    bool
   109  	closeCh   chan struct{}
   110  	doneCh    chan struct{}
   111  }
   112  
   113  // NewBuildReporter returns a new build version reporter.
   114  func NewBuildReporter(
   115  	opts Options,
   116  ) Reporter {
   117  	return &buildReporter{
   118  		opts: opts,
   119  	}
   120  }
   121  
   122  func (b *buildReporter) Start() error {
   123  	const (
   124  		base    = 10
   125  		bitSize = 64
   126  	)
   127  	sec, err := strconv.ParseInt(BuildTimeUnix, base, bitSize)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	if sec < 0 {
   132  		return errBuildTimeNegative
   133  	}
   134  	buildTime := time.Unix(sec, 0)
   135  
   136  	b.Lock()
   137  	defer b.Unlock()
   138  	if b.active {
   139  		return errAlreadyStarted
   140  	}
   141  	b.buildTime = buildTime
   142  	b.active = true
   143  	b.closeCh = make(chan struct{})
   144  	b.doneCh = make(chan struct{})
   145  	go b.report()
   146  	return nil
   147  }
   148  
   149  func (b *buildReporter) report() {
   150  	tags := map[string]string{
   151  		"revision":      Revision,
   152  		"branch":        Branch,
   153  		"build-date":    BuildDate,
   154  		"build-version": Version,
   155  		"go-version":    goVersion,
   156  	}
   157  
   158  	for k, v := range b.opts.CustomBuildTags() {
   159  		tags[k] = v
   160  	}
   161  
   162  	scope := b.opts.MetricsScope().Tagged(tags)
   163  	buildInfoGauge := scope.Gauge(buildInfoMetricName)
   164  	buildAgeGauge := scope.Gauge(buildAgeMetricName)
   165  	buildInfoGauge.Update(1.0)
   166  	buildAgeGauge.Update(float64(time.Since(b.buildTime)))
   167  
   168  	ticker := time.NewTicker(b.opts.ReportInterval())
   169  	defer func() {
   170  		close(b.doneCh)
   171  		ticker.Stop()
   172  	}()
   173  
   174  	for {
   175  		select {
   176  		case <-ticker.C:
   177  			buildInfoGauge.Update(1.0)
   178  			buildAgeGauge.Update(float64(time.Since(b.buildTime)))
   179  
   180  		case <-b.closeCh:
   181  			return
   182  		}
   183  	}
   184  }
   185  
   186  func (b *buildReporter) Stop() error {
   187  	b.Lock()
   188  	defer b.Unlock()
   189  	if !b.active {
   190  		return errNotStarted
   191  	}
   192  	close(b.closeCh)
   193  	<-b.doneCh
   194  	b.active = false
   195  	return nil
   196  }