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  }