diff --git a/cmd/scollector/collectors/activedirectory_windows.go b/cmd/scollector/collectors/activedirectory_windows.go new file mode 100644 index 0000000000..443aa82eea --- /dev/null +++ b/cmd/scollector/collectors/activedirectory_windows.go @@ -0,0 +1,71 @@ +package collectors + +import ( + "strings" + "time" + + "bosun.org/metadata" + "bosun.org/opentsdb" +) + +func init() { + c_ad := &IntervalCollector{ + F: c_activedirectory_windows, + Interval: time.Minute * 5, + } + c_ad.init = wmiInitNamespace(c_ad, func() interface{} { return &[]MSAD_ReplNeighbor{} }, "", &adQuery, rootMSAD) + collectors = append(collectors, c_ad) +} + +var ( + adQuery string + rootMSAD = "root\\MicrosoftActiveDirectory" +) + +func c_activedirectory_windows() (opentsdb.MultiDataPoint, error) { + var dst []MSAD_ReplNeighbor + err := queryWmiNamespace(adQuery, &dst, rootMSAD) + if err != nil { + return nil, err + } + var md opentsdb.MultiDataPoint + for _, v := range dst { + lastSuccess, err := wmiParseCIMDatetime(v.TimeOfLastSyncSuccess) + if err != nil { + return nil, err + } + tags := opentsdb.TagSet{"source": strings.ToLower(v.SourceDsaCN), "context": activedirectory_context(v.NamingContextDN)} + sinceLastSuccess := time.Now().Sub(lastSuccess).Seconds() + Add(&md, "activedirectory.replication.sync_age", sinceLastSuccess, tags, metadata.Gauge, metadata.Second, descADReplicationSuccess) + Add(&md, "activedirectory.replication.consecutive_failures", v.NumConsecutiveSyncFailures, tags, metadata.Gauge, metadata.Count, descADReplicationFailures) + } + return md, nil +} + +const ( + descADReplicationSuccess = "The number of seconds since the last successful replication attempt for this context." + descADReplicationFailures = "The number of consecutive failed replication attempts for this context." +) + +type MSAD_ReplNeighbor struct { + SourceDsaCN string + NamingContextDN string + TimeOfLastSyncSuccess string + NumConsecutiveSyncFailures uint32 +} + +func activedirectory_context(NamingContextDN string) string { + if strings.HasPrefix(NamingContextDN, "DC=DomainDnsZones,") { + return "DomainDNSZones" + } + if strings.HasPrefix(NamingContextDN, "DC=ForestDnsZones,") { + return "ForestDnsZones" + } + if strings.HasPrefix(NamingContextDN, "CN=Schema,CN=Configuration,") { + return "Schema" + } + if strings.HasPrefix(NamingContextDN, "CN=Configuration,") { + return "Configuration" + } + return "Domain" +} diff --git a/cmd/scollector/collectors/wmi_windows.go b/cmd/scollector/collectors/wmi_windows.go index 4a3359a451..e44042aa1c 100644 --- a/cmd/scollector/collectors/wmi_windows.go +++ b/cmd/scollector/collectors/wmi_windows.go @@ -1,6 +1,16 @@ package collectors -import "bosun.org/_third_party/github.com/StackExchange/wmi" +import ( + "fmt" + "strings" + "time" + + "bosun.org/_third_party/github.com/StackExchange/wmi" +) + +var ( + epochUTCTime = time.Unix(0, 0).UTC() +) func queryWmi(query string, dst interface{}) error { return queryWmiNamespace(query, dst, "") @@ -27,3 +37,21 @@ func wmiInitNamespace(c *IntervalCollector, dst func() interface{}, where string } } } + +// wmiParseDatetime converts a string from the CIM_DATETIME format into UTC time. +// Example: "20150616101948.494497-360" = 2015-06-16 04:19:48.494497 +0000 UTC. +func wmiParseCIMDatetime(cimdatetime string) (time.Time, error) { + i := strings.IndexAny(cimdatetime, "+-") + if i < 0 { + return epochUTCTime, fmt.Errorf("Invalid CIM_DATETIME format, cannot find UTC offset.") + } + t, err := time.Parse("20060102150405", cimdatetime[0:i]) + if err != nil { + return epochUTCTime, err + } + offset, err := time.ParseDuration(fmt.Sprintf("%vm", cimdatetime[i:])) + if err != nil { + return epochUTCTime, err + } + return t.Add(offset), nil +}