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 }