github.com/google/cloudprober@v0.11.3/sysvars/sysvars.go (about)

     1  // Copyright 2017-2020 The Cloudprober 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 sysvars implements a system variables exporter. It exports variables defined
    16  // through an environment variable, as well other system variables like process uptime.
    17  package sysvars
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"flag"
    30  
    31  	"github.com/google/cloudprober/config/runconfig"
    32  	"github.com/google/cloudprober/logger"
    33  	"github.com/google/cloudprober/metrics"
    34  )
    35  
    36  var (
    37  	sysVarsMu sync.RWMutex
    38  	sysVars   map[string]string
    39  	l         *logger.Logger
    40  	startTime time.Time
    41  )
    42  
    43  var cloudMetadataFlag = flag.String("cloud_metadata", "auto", "Collect cloud metadata for [auto|gce|ec2|none]")
    44  
    45  var cloudProviders = struct {
    46  	auto, gce, ec2 string
    47  }{
    48  	auto: "auto",
    49  	gce:  "gce",
    50  	ec2:  "ec2",
    51  }
    52  
    53  // Vars returns a copy of the system variables map, if already initialized.
    54  // Otherwise an empty map is returned.
    55  func Vars() map[string]string {
    56  	vars := make(map[string]string)
    57  	// We should never have to wait for these locks as sysVars are
    58  	// updated inside Init and Init should be called only once, in
    59  	// the beginning.
    60  	sysVarsMu.RLock()
    61  	defer sysVarsMu.RUnlock()
    62  	if sysVars == nil {
    63  		// Log an error and return an empty map if sysVars is not initialized yet.
    64  		l.Error("Sysvars map is un-initialized. sysvars.Vars() was called before sysvars.Init().")
    65  		return vars
    66  	}
    67  	for k, v := range sysVars {
    68  		vars[k] = v
    69  	}
    70  	return vars
    71  }
    72  
    73  func parseEnvVars(envVarsName string) map[string]string {
    74  	envVars := make(map[string]string)
    75  	if os.Getenv(envVarsName) == "" {
    76  		return envVars
    77  	}
    78  	l.Infof("%s: %s", envVarsName, os.Getenv(envVarsName))
    79  	for _, v := range strings.Split(os.Getenv(envVarsName), ",") {
    80  		kv := strings.Split(v, "=")
    81  		if len(kv) != 2 {
    82  			l.Warningf("Bad env var: %s, skipping", v)
    83  			continue
    84  		}
    85  		envVars[kv[0]] = kv[1]
    86  	}
    87  	return envVars
    88  }
    89  
    90  // StartTime returns cloudprober's start time.
    91  func StartTime() time.Time {
    92  	return startTime
    93  }
    94  
    95  func providersToCheck(fv string) []string {
    96  	if fv == "" || fv == "none" {
    97  		return nil
    98  	}
    99  	// Update this list when we add new providers
   100  	if fv == cloudProviders.auto {
   101  		return []string{cloudProviders.gce, cloudProviders.ec2}
   102  	}
   103  	return []string{fv}
   104  }
   105  
   106  func initCloudMetadata(fv string) error {
   107  	for _, provider := range providersToCheck(fv) {
   108  		switch provider {
   109  		case cloudProviders.gce:
   110  			onGCE, err := gceVars(sysVars, l)
   111  			// Once we know it's GCE, don't continue checking.
   112  			if onGCE {
   113  				return err
   114  			}
   115  		case cloudProviders.ec2:
   116  			onEC2, err := ec2Vars(sysVars, l)
   117  			// Once we know it's EC2, don't continue checking.
   118  			if onEC2 {
   119  				return err
   120  			}
   121  		default:
   122  			return fmt.Errorf("unknown cloud provider: %v", provider)
   123  		}
   124  	}
   125  	return nil
   126  }
   127  
   128  // Init initializes the sysvars module's global data structure. Init makes sure
   129  // to initialize only once, further calls are a no-op. If needed, userVars
   130  // can be passed to Init to add custom variables to sysVars. This can be useful
   131  // for tests which require sysvars that might not exist, or might have the wrong
   132  // value.
   133  func Init(ll *logger.Logger, userVars map[string]string) error {
   134  	sysVarsMu.Lock()
   135  	defer sysVarsMu.Unlock()
   136  	if sysVars != nil {
   137  		return nil
   138  	}
   139  
   140  	l = ll
   141  	startTime = time.Now()
   142  	sysVars = map[string]string{
   143  		"version": runconfig.Version(),
   144  	}
   145  
   146  	hostname, err := os.Hostname()
   147  	if err != nil {
   148  		return fmt.Errorf("sysvars.Init(): error getting local hostname: %v", err)
   149  	}
   150  	sysVars["hostname"] = hostname
   151  
   152  	if err := initCloudMetadata(*cloudMetadataFlag); err != nil {
   153  		return err
   154  	}
   155  
   156  	for k, v := range userVars {
   157  		sysVars[k] = v
   158  	}
   159  	return nil
   160  }
   161  
   162  // Start exports system variables at the given interval. It overlays variables with
   163  // variables passed through the envVarsName env variable.
   164  func Start(ctx context.Context, dataChan chan *metrics.EventMetrics, interval time.Duration, envVarsName string) {
   165  	vars := Vars()
   166  	for k, v := range parseEnvVars(envVarsName) {
   167  		vars[k] = v
   168  	}
   169  	// Add reset timestamp (Unix epoch corresponding to when Cloudprober was started)
   170  	vars["start_timestamp"] = strconv.FormatInt(startTime.Unix(), 10)
   171  
   172  	var varsKeys []string
   173  	for k := range vars {
   174  		varsKeys = append(varsKeys, k)
   175  	}
   176  	sort.Strings(varsKeys)
   177  
   178  	em := metrics.NewEventMetrics(time.Now()).
   179  		AddLabel("ptype", "sysvars").
   180  		AddLabel("probe", "sysvars")
   181  	em.Kind = metrics.GAUGE
   182  	for _, k := range varsKeys {
   183  		em.AddMetric(k, metrics.NewString(vars[k]))
   184  	}
   185  	l.Info(em.String())
   186  
   187  	for ts := range time.Tick(interval) {
   188  		// Don't run another cycles if context is canceled already.
   189  		select {
   190  		case <-ctx.Done():
   191  			return
   192  		default:
   193  		}
   194  
   195  		// Update timestamp and publish static variables.
   196  		em.Timestamp = ts
   197  		dataChan <- em.Clone()
   198  		l.Debug(em.String())
   199  
   200  		runtimeVars(dataChan, l)
   201  	}
   202  }