bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/collectors/dsc_windows.go (about) 1 package collectors 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 11 "bosun.org/metadata" 12 "bosun.org/opentsdb" 13 "bosun.org/slog" 14 "bosun.org/util" 15 "github.com/StackExchange/mof" 16 ) 17 18 func init() { 19 collectors = append(collectors, &IntervalCollector{F: c_dsc_mof, Interval: time.Minute * 5}) 20 collectors = append(collectors, &IntervalCollector{F: c_dsc_status, Interval: time.Minute * 5}) 21 } 22 23 const ( 24 dscLCM = "dsc.lcm." 25 dscMof = "dsc.mof." 26 dscStatus = "dsc.status." 27 28 //dscDefaultConfigurationName is used when the ConfigurationName value in the struct is NULL (non-pull based configuration usually) 29 dscDefaultConfigurationName = "__no_name__" 30 ) 31 32 var ( 33 dscpath = os.ExpandEnv(`${SYSTEMROOT}\system32\Configuration\`) 34 mapMofFiles = map[string]string{ 35 "MetaConfig.mof": "Meta_Config", 36 "Current.mof": "Current_Config", 37 "backup.mof": "Backup_Config", 38 "pending.mof": "Pending_Config", 39 "DSCStatusHistory.mof": "DSC_History", 40 "DSCEngineCache.mof": "DSC_Cache", 41 } 42 ) 43 44 // c_dsc_mof monitors the size and last modified time of each mof file. 45 // These out of band metrics can be used to verify the DSC WMI Status metrics. 46 func c_dsc_mof() (opentsdb.MultiDataPoint, error) { 47 var md opentsdb.MultiDataPoint 48 c := 0 49 if _, err := os.Stat(dscpath + "MetaConfig.mof"); os.IsNotExist(err) { 50 c = 1 51 } 52 Add(&md, dscLCM+"configured", c, nil, metadata.Gauge, metadata.StatusCode, descDSCLCMConfigured) 53 if c == 1 { 54 return md, nil 55 } 56 for filename, filetype := range mapMofFiles { 57 tags := opentsdb.TagSet{"type": filetype} 58 s := int64(-1) 59 l := int64(-1) 60 if fi, fierr := os.Stat(dscpath + filename); fierr == nil { 61 s = fi.Size() 62 l = time.Now().Unix() - fi.ModTime().Unix() 63 } 64 Add(&md, dscMof+"size", s, tags, metadata.Gauge, metadata.Bytes, descDSCMofSize) 65 Add(&md, dscMof+"last_modified", l, tags, metadata.Gauge, metadata.Second, descDSCMofModified) 66 } 67 return md, nil 68 } 69 70 const ( 71 descDSCLCMConfigured = "Indicates if DSC Local Configuration Manager is configured: 0=configured, 1=not configured. If the LCM is not configured then the rest of the dsc.* metrics will be skipped on that server." 72 descDSCMofSize = "Size of the mof file in bytes or -1 if file does not exist." 73 descDSCMofModified = "Number of seconds since the mof file was last modified or -1 if file does not exist." 74 ) 75 76 func c_dsc_status() (opentsdb.MultiDataPoint, error) { 77 var md opentsdb.MultiDataPoint 78 if _, err := os.Stat(dscpath + "MetaConfig.mof"); os.IsNotExist(err) { 79 return md, nil 80 } 81 var dst MSFT_DSCConfigurationStatus 82 dscstatusmof, err := util.Command(time.Minute, nil, "wmic", 83 `/namespace:\\ROOT\Microsoft\Windows\DesiredStateConfiguration`, "class", 84 "MSFT_DSCLocalConfigurationManager", "call", "GetConfigurationStatus") 85 if err != nil { 86 //Skip if dsc is currently running a consistency check 87 if err.Error() == "exit status 2147749889" { 88 return md, nil 89 } else { 90 return nil, slog.Wrap(err) 91 } 92 } 93 dscstatusbuffer := new(bytes.Buffer) 94 _, err = dscstatusbuffer.ReadFrom(dscstatusmof) 95 if err != nil { 96 return nil, slog.Wrap(err) 97 } 98 err = mof.Unmarshal(dscstatusbuffer.Bytes(), &dst) 99 if err != nil { 100 return nil, slog.Wrap(err) 101 } 102 if dst.ReturnValue != 0 { 103 return nil, fmt.Errorf("GetConfigurationStatus ReturnValue %v", dst.ReturnValue) 104 } 105 for _, v := range dst.MSFT_DSCConfigurationStatus { 106 Add(&md, dscLCM+"auto_reboot", v.MetaConfiguration.RebootNodeIfNeeded, nil, metadata.Gauge, metadata.Bool, descWinDSCAutoReboot) 107 Add(&md, dscLCM+"frequency_config", v.MetaConfiguration.ConfigurationModeFrequencyMins, nil, metadata.Gauge, metadata.Count, descWinDSCFreqConfig) 108 Add(&md, dscLCM+"frequency_refresh", v.MetaConfiguration.RefreshFrequencyMins, nil, metadata.Gauge, metadata.Count, descWinDSCFreqRefresh) 109 Add(&md, dscLCM+"refresh_mode", dscModeToStatusCode(v.Mode), nil, metadata.Gauge, metadata.StatusCode, descWinDSCMode) 110 Add(&md, dscLCM+"state", dscStateToStatusCode(v.MetaConfiguration.LCMState), nil, metadata.Gauge, metadata.StatusCode, descWinDSCState) 111 Add(&md, dscStatus+"reboot_requested", v.RebootRequested, nil, metadata.Gauge, metadata.Bool, descWinDSCRebootRequested) 112 Add(&md, dscStatus+"resources_total", v.NumberOfResources, nil, metadata.Gauge, metadata.Count, descWinDSCNumberOfResources) 113 Add(&md, dscStatus+"run_age", dscStartDateToAge(v.StartDate), nil, metadata.Gauge, metadata.Second, descWinDSCAge) 114 Add(&md, dscStatus+"run_duration", v.DurationInSeconds, nil, metadata.Gauge, metadata.Second, descWinDSCDurationInSeconds) 115 Add(&md, dscStatus+"run_success", v.Status == "Success", nil, metadata.Gauge, metadata.Bool, descWinDSCStatus) 116 Add(&md, dscStatus+"run_type", dscTypeToStatusCode(v.Type), nil, metadata.Gauge, metadata.Count, descWinDSCType) 117 configurations := make(map[string]dscResourceCount) 118 for _, r := range v.ResourcesInDesiredState { 119 name := dscGetConfigurationName(r.ConfigurationName) 120 c := configurations[name] 121 c.Success++ 122 c.Duration += r.DurationInSeconds 123 configurations[name] = c 124 } 125 for _, r := range v.ResourcesNotInDesiredState { 126 name := dscGetConfigurationName(r.ConfigurationName) 127 c := configurations[name] 128 c.Failed++ 129 c.Duration += r.DurationInSeconds 130 configurations[name] = c 131 } 132 for key, value := range configurations { 133 Add(&md, dscStatus+"resources", value.Success, opentsdb.TagSet{"state": "Success", "configuration": key}, metadata.Gauge, metadata.Count, descWinDSCResourceState) 134 Add(&md, dscStatus+"resources", value.Failed, opentsdb.TagSet{"state": "Failed", "configuration": key}, metadata.Gauge, metadata.Count, descWinDSCResourceState) 135 Add(&md, dscStatus+"config_duration", value.Duration, opentsdb.TagSet{"configuration": key}, metadata.Gauge, metadata.Second, descWinDSCConfigSeconds) 136 } 137 } 138 return md, nil 139 } 140 141 const ( 142 descWinDSCAge = "Seconds since the last DSC run." 143 descWinDSCAutoReboot = "Bool for if the LCM can automatically reboot the system when needed." 144 descWinDSCDurationInSeconds = "Seconds taken to process entire configuration." 145 descWinDSCConfigSeconds = "Seconds taken to process each configuration." 146 descWinDSCFreqConfig = "Specifies how often (in minutes) LCM ensures that the configuration is in the desired state." 147 descWinDSCFreqRefresh = "Specifies how often (in minutes) LCM attempts to obtain the configuration from the pull server. If configuration on the pull server differs from the current one on the target node, it is copied to the pending store and applied." 148 descWinDSCMode = "Refresh mode of configuration. 0=Pull, 1=Push, 2=Disabled, -1=Unknown." 149 descWinDSCNumberOfResources = "Total number of resources in configuration." 150 descWinDSCRebootRequested = "Reboot was requested during configuration run." 151 descWinDSCResourceState = "Count of resources from each named configuration that were successful or failed." 152 descWinDSCStartDate = "Date and time when the configuration was started." 153 descWinDSCState = "Current LCM Statue. 0=Idle, 1=PendingConfiguration, 2=Ready, 3=Busy, 4=PendingReboot, -1=Unknown." 154 descWinDSCStatus = "Status of configuration." 155 descWinDSCType = "Type of configuration run. 0=Reboot, 1=Initial, 2=Consistency, 3=ReadOnly, -1=Unknown." 156 ) 157 158 type MSFT_DSCConfigurationStatus struct { 159 MSFT_DSCConfigurationStatus []struct { 160 DurationInSeconds int 161 MetaConfiguration struct { 162 ConfigurationModeFrequencyMins int 163 LCMState string 164 RebootNodeIfNeeded bool 165 RefreshFrequencyMins int 166 } 167 Mode string 168 NumberOfResources int 169 RebootRequested bool 170 ResourcesInDesiredState []struct { 171 ConfigurationName string 172 DurationInSeconds float64 173 } 174 ResourcesNotInDesiredState []struct { 175 ConfigurationName string 176 DurationInSeconds float64 177 } 178 StartDate string 179 Status string 180 Type string 181 } 182 ReturnValue int 183 } 184 185 type dscResourceCount struct { 186 Success int 187 Failed int 188 Duration float64 189 } 190 191 func dscModeToStatusCode(mode string) int64 { 192 switch strings.ToUpper(mode) { 193 case "PULL": 194 return 0 195 case "PUSH": 196 return 1 197 case "DISABLED": 198 return 2 199 default: 200 return -1 201 } 202 } 203 204 func dscTypeToStatusCode(t string) int64 { 205 switch strings.ToUpper(t) { 206 case "REBOOT": 207 return 0 208 case "INITIAL": 209 return 1 210 case "CONSISTENCY": 211 return 2 212 case "READONLY": 213 return 3 214 default: 215 return -1 216 } 217 } 218 219 func dscStateToStatusCode(t string) int64 { 220 switch strings.ToUpper(t) { 221 case "IDLE": 222 return 0 223 case "PENDINGCONFIGURATION": 224 return 1 225 case "READY": 226 return 2 227 case "BUSY": 228 return 3 229 case "PENDINGREBOOT": 230 return 4 231 default: 232 return -1 233 } 234 } 235 236 func dscStartDateToAge(startdate string) float64 { 237 var t = time.Time{} 238 var err error 239 // See https://msdn.microsoft.com/en-us/library/aa387237(v=vs.85).aspx for different WMI date time formats 240 if len(startdate) == 25 && strings.IndexAny(startdate, "+-") == 21 { 241 //Parse yyyymmddHHMMSS.mmmmmmsUUU where sUUU is timezone in +/- minutes from UTC 242 tzmin, err := strconv.Atoi(startdate[21:]) 243 if err != nil { 244 return -1 245 } 246 t, err = time.ParseInLocation("20060102150405.999999", startdate[0:21], time.FixedZone("WMI", tzmin*60)) 247 } else { 248 //Parse yyyy-mm-dd HH:MM:SS:mmm and assume UTC 249 t, err = time.Parse("2006/01/02 15:04:05", startdate) 250 } 251 if err != nil { 252 return -1 253 } 254 return time.Now().UTC().Sub(t).Seconds() 255 } 256 257 func dscGetConfigurationName(Name string) string { 258 if Name != "" { 259 return Name 260 } 261 return dscDefaultConfigurationName 262 }