diff --git a/backend/backend.go b/backend/backend.go index e410e06..cd17f2b 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -1,177 +1,177 @@ package backend import ( + "errors" "log" - "strings" - "errors" - "time" "strconv" + "strings" + "time" - "github.com/marpaia/graphite-golang" influxclient "github.com/influxdata/influxdb/client/v2" + "github.com/marpaia/graphite-golang" ) - type Point struct { - VCenter string - ObjectType string - ObjectName string - Group string - Counter string - Instance string - Rollup string - Value int64 - Datastore []string - ESXi string - Cluster string - Network []string - Timestamp int64 + VCenter string + ObjectType string + ObjectName string + Group string + Counter string + Instance string + Rollup string + Value int64 + Datastore []string + ESXi string + Cluster string + Network []string + ResourcePool string + Timestamp int64 } - //Storage backend type Backend struct { - Hostname string - Port int - Database string - Username string - Password string - Type string - NoArray bool - carbon *graphite.Graphite - influx influxclient.Client - ValueField string + Hostname string + Port int + Database string + Username string + Password string + Type string + NoArray bool + carbon *graphite.Graphite + influx influxclient.Client + ValueField string } -var stdlog, errlog *log.Logger -var carbon graphite.Graphite +var stdlog, errlog *log.Logger +var carbon graphite.Graphite -func (backend *Backend) Init(standardLogs *log.Logger, errorLogs *log.Logger) error { - stdlog := standardLogs - errlog := errorLogs +func (backend *Backend) Init(standardLogs *log.Logger, errorLogs *log.Logger) error { + stdlog := standardLogs + errlog := errorLogs if len(backend.ValueField) == 0 { // for compatibility reason with previous version // can now be changed in the config file. // the default can later be changed to another value. - // most probably "value" (lower case) - backend.ValueField = "Value" + // most probably "value" (lower case) + backend.ValueField = "Value" + } + switch backendType := strings.ToLower(backend.Type); backendType { + case "graphite": + // Initialize Graphite + stdlog.Println("Intializing " + backendType + " backend") + carbon, err := graphite.NewGraphite(backend.Hostname, backend.Port) + if err != nil { + errlog.Println("Error connecting to graphite") + return err + } + backend.carbon = carbon + return nil + case "influxdb": + //Initialize Influx DB + stdlog.Println("Intializing " + backendType + " backend") + influxclt, err := influxclient.NewHTTPClient(influxclient.HTTPConfig{ + Addr: "http://" + backend.Hostname + ":" + strconv.Itoa(backend.Port), + Username: backend.Username, + Password: backend.Password, + }) + if err != nil { + errlog.Println("Error connecting to InfluxDB") + return err + } + backend.influx = influxclt + return nil + default: + errlog.Println("Backend " + backendType + " unknown.") + return errors.New("Backend " + backendType + " unknown.") } - switch backendType := strings.ToLower(backend.Type); backendType { - case "graphite": - // Initialize Graphite - stdlog.Println("Intializing " + backendType + " backend") - carbon, err := graphite.NewGraphite(backend.Hostname, backend.Port) - if err != nil { - errlog.Println("Error connecting to graphite") - return err - } - backend.carbon = carbon - return nil - case "influxdb": - //Initialize Influx DB - stdlog.Println("Intializing " + backendType + " backend") - influxclt, err := influxclient.NewHTTPClient(influxclient.HTTPConfig{ - Addr: "http://" + backend.Hostname + ":" + strconv.Itoa(backend.Port), - Username: backend.Username, - Password: backend.Password, - }) - if err != nil { - errlog.Println("Error connecting to InfluxDB") - return err - } - backend.influx = influxclt - return nil - default: - errlog.Println("Backend " + backendType + " unknown.") - return errors.New("Backend " + backendType + " unknown.") - } } func (backend *Backend) Disconnect() { - switch backendType := strings.ToLower(backend.Type); backendType { - case "graphite": - // Disconnect from graphite - stdlog.Println("Disconnecting from " + backendType) - backend.carbon.Disconnect() - case "influxdb": - // Disconnect from influxdb - stdlog.Println("Disconnecting from " + backendType) - backend.influx.Close() - default: - errlog.Println("Backend " + backendType + " unknown.") + switch backendType := strings.ToLower(backend.Type); backendType { + case "graphite": + // Disconnect from graphite + stdlog.Println("Disconnecting from " + backendType) + backend.carbon.Disconnect() + case "influxdb": + // Disconnect from influxdb + stdlog.Println("Disconnecting from " + backendType) + backend.influx.Close() + default: + errlog.Println("Backend " + backendType + " unknown.") } } func (backend *Backend) SendMetrics(metrics []Point) { - switch backendType := strings.ToLower(backend.Type); backendType { - case "graphite": - var graphiteMetrics []graphite.Metric - for _, point := range metrics { - //key := "vsphere." + vcName + "." + entityName + "." + name + "." + metricName - key := "vsphere." + point.VCenter + "." + point.ObjectType + "." + point.ObjectName + "." + point.Group + "." + point.Counter + "." + point.Rollup - if len(point.Instance) > 0 { - key += "." + strings.ToLower(strings.Replace(point.Instance, ".", "_", -1)) - } - graphiteMetrics = append(graphiteMetrics, graphite.Metric{Name: key , Value: strconv.FormatInt(point.Value,10), Timestamp: point.Timestamp}) - } - err := backend.carbon.SendMetrics(graphiteMetrics) - if err != nil { - errlog.Println("Error sending metrics (trying to reconnect): ", err) - backend.carbon.Connect() - } - case "influxdb": - //Influx batch points - bp, err := influxclient.NewBatchPoints(influxclient.BatchPointsConfig{ - Database: backend.Database, - Precision: "s", - }) - if err != nil { - errlog.Println("Error creating influx batchpoint") - errlog.Println(err) - return + switch backendType := strings.ToLower(backend.Type); backendType { + case "graphite": + var graphiteMetrics []graphite.Metric + for _, point := range metrics { + //key := "vsphere." + vcName + "." + entityName + "." + name + "." + metricName + key := "vsphere." + point.VCenter + "." + point.ObjectType + "." + point.ObjectName + "." + point.Group + "." + point.Counter + "." + point.Rollup + if len(point.Instance) > 0 { + key += "." + strings.ToLower(strings.Replace(point.Instance, ".", "_", -1)) } - for _, point := range metrics { - key := point.Group + "_" + point.Counter + "_" + point.Rollup - tags := map[string]string{} - tags["vcenter"] = point.VCenter - tags["type"] = point.ObjectType - tags["name"] = point.ObjectName - if backend.NoArray { - if len(point.Datastore) > 0 { - tags["datastore"] = point.Datastore[0] - } else { - tags["datastore"] = "" - } + graphiteMetrics = append(graphiteMetrics, graphite.Metric{Name: key, Value: strconv.FormatInt(point.Value, 10), Timestamp: point.Timestamp}) + } + err := backend.carbon.SendMetrics(graphiteMetrics) + if err != nil { + errlog.Println("Error sending metrics (trying to reconnect): ", err) + backend.carbon.Connect() + } + case "influxdb": + //Influx batch points + bp, err := influxclient.NewBatchPoints(influxclient.BatchPointsConfig{ + Database: backend.Database, + Precision: "s", + }) + if err != nil { + errlog.Println("Error creating influx batchpoint") + errlog.Println(err) + return + } + for _, point := range metrics { + key := point.Group + "_" + point.Counter + "_" + point.Rollup + tags := map[string]string{} + tags["vcenter"] = point.VCenter + tags["type"] = point.ObjectType + tags["name"] = point.ObjectName + if backend.NoArray { + if len(point.Datastore) > 0 { + tags["datastore"] = point.Datastore[0] } else { - tags["datastore"] = strings.Join(point.Datastore, "\\,") + tags["datastore"] = "" } - if backend.NoArray { - if len(point.Network) > 0 { - tags["network"] = point.Network[0] - } else { - tags["network"] = "" - } + } else { + tags["datastore"] = strings.Join(point.Datastore, "\\,") + } + if backend.NoArray { + if len(point.Network) > 0 { + tags["network"] = point.Network[0] } else { - tags["network"] = strings.Join(point.Network, "\\,") + tags["network"] = "" } - tags["host"] = point.ESXi - tags["cluster"] = point.Cluster - tags["instance"] = point.Instance - fields := make(map[string]interface{}) - fields[backend.ValueField] = point.Value - pt, err := influxclient.NewPoint(key, tags, fields, time.Unix(point.Timestamp, 0)) - if err != nil { - errlog.Println("Could not create influxdb point") - errlog.Println(err) - continue - } - bp.AddPoint(pt) + } else { + tags["network"] = strings.Join(point.Network, "\\,") } - err = backend.influx.Write(bp) + tags["host"] = point.ESXi + tags["cluster"] = point.Cluster + tags["instance"] = point.Instance + tags["resourcepool"] = point.ResourcePool + fields := make(map[string]interface{}) + fields[backend.ValueField] = point.Value + pt, err := influxclient.NewPoint(key, tags, fields, time.Unix(point.Timestamp, 0)) if err != nil { - errlog.Println("Error sending metrics: ", err) + errlog.Println("Could not create influxdb point") + errlog.Println(err) + continue } - default: - errlog.Println("Backend " + backendType + " unknown.") - } + bp.AddPoint(pt) + } + err = backend.influx.Write(bp) + if err != nil { + errlog.Println("Error sending metrics: ", err) + } + default: + errlog.Println("Backend " + backendType + " unknown.") + } } diff --git a/vsphere/vsphere.go b/vsphere/vsphere.go index 565076b..669c69b 100644 --- a/vsphere/vsphere.go +++ b/vsphere/vsphere.go @@ -79,12 +79,12 @@ func (vcenter *VCenter) Init(metrics []Metric, standardLogs *log.Logger, errorLo ctx, cancel := context.WithCancel(context.Background()) defer cancel() client, err := vcenter.Connect() - defer client.Logout(ctx) if err != nil { errlog.Println("Could not connect to vcenter: ", vcenter.Hostname) errlog.Println("Error: ", err) return } + defer client.Logout(ctx) var perfmanager mo.PerformanceManager err = client.RetrieveOne(ctx, *client.ServiceContent.PerfManager, nil, &perfmanager) if err != nil { @@ -167,7 +167,7 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe } // Get intresting object types from specified queries - objectTypes := []string{"ClusterComputeResource", "Datastore", "HostSystem", "DistributedVirtualPortgroup", "Network"} + objectTypes := []string{"ClusterComputeResource", "Datastore", "HostSystem", "DistributedVirtualPortgroup", "Network", "ResourcePool"} for _, group := range vcenter.MetricGroups { found := false for _, tmp := range objectTypes { @@ -212,9 +212,9 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe //properties specifications propSet := []types.PropertySpec{} - propSet = append(propSet, types.PropertySpec{Type: "ManagedEntity", PathSet: []string{"name"}}) + propSet = append(propSet, types.PropertySpec{Type: "ManagedEntity", PathSet: []string{"name", "parent"}}) propSet = append(propSet, types.PropertySpec{Type: "VirtualMachine", PathSet: []string{"datastore", "network", "runtime.host"}}) - propSet = append(propSet, types.PropertySpec{Type: "HostSystem", PathSet: []string{"parent"}}) + propSet = append(propSet, types.PropertySpec{Type: "ResourcePool", PathSet: []string{"vm"}}) //retrieve properties propreq := types.RetrieveProperties{SpecSet: []types.PropertyFilterSpec{{ObjectSet: objectSet, PropSet: propSet}}} @@ -238,7 +238,10 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe vmToHost := make(map[types.ManagedObjectReference]types.ManagedObjectReference) //create a map to resolve host to parent - in a cluster the parent should be a cluster - hostToParent := make(map[types.ManagedObjectReference]types.ManagedObjectReference) + morToParent := make(map[types.ManagedObjectReference]types.ManagedObjectReference) + + //create a map to resolve mor to vms - object that contains multiple vms as child objects + morToVms := make(map[types.ManagedObjectReference][]types.ManagedObjectReference) for _, objectContent := range propres.Returnval { for _, Property := range objectContent.PropSet { @@ -257,7 +260,7 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe vmToDatastore[objectContent.Obj] = mors.ManagedObjectReference } } else { - errlog.Println("Datastore property of " + objectContent.Obj.String() + " was not a ManagedObjectReference, it was " + fmt.Sprintf("%T", Property.Val)) + errlog.Println("Datastore property of " + objectContent.Obj.String() + " was not a ManagedObjectReferences, it was " + fmt.Sprintf("%T", Property.Val)) } case "network": mors, ok := Property.Val.(types.ArrayOfManagedObjectReference) @@ -266,7 +269,7 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe vmToNetwork[objectContent.Obj] = mors.ManagedObjectReference } } else { - errlog.Println("Network property of " + objectContent.Obj.String() + " was not an array of ManagedObjectReference, it was " + fmt.Sprintf("%T", Property.Val)) + errlog.Println("Network property of " + objectContent.Obj.String() + " was not an array of ManagedObjectReferences, it was " + fmt.Sprintf("%T", Property.Val)) } case "runtime.host": mor, ok := Property.Val.(types.ManagedObjectReference) @@ -278,10 +281,19 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe case "parent": mor, ok := Property.Val.(types.ManagedObjectReference) if ok { - hostToParent[objectContent.Obj] = mor + morToParent[objectContent.Obj] = mor } else { errlog.Println("Parent property of " + objectContent.Obj.String() + " was not a ManagedObjectReference, it was " + fmt.Sprintf("%T", Property.Val)) } + case "vm": + mors, ok := Property.Val.(types.ArrayOfManagedObjectReference) + if ok { + if len(mors.ManagedObjectReference) > 0 { + morToVms[objectContent.Obj] = mors.ManagedObjectReference + } + } else { + errlog.Println("VM property of " + objectContent.Obj.String() + " was not an array of ManagedObjectReferences, it was " + fmt.Sprintf("%T", Property.Val)) + } default: errlog.Println("Unhandled property '" + propertyName + "' for " + objectContent.Obj.String() + " whose type is " + fmt.Sprintf("%T", Property.Val)) } @@ -296,6 +308,42 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe } } + //create a map to resolve vm to their ressourcepool + vmToResourcePoolPath := make(map[types.ManagedObjectReference]string) + for mor, vmmors := range morToVms { + // not doing case sensitive as this could be extensive + if mor.Type == "ResourcePool" { + // find the full path of the resource pool + poolpath := "" + poolmor := mor + ok := true + for ok { + poolname, ok := morToName[poolmor] + if !ok { + // could not find name + errlog.Println("Could not find name for resourcepool " + mor.String()) + break + } + // add the name to the path + poolpath += poolname + "/" + poolpath + poolmor, ok := morToParent[poolmor] + if !ok { + // no parent pool found + errlog.Println("Could not find parent for resourcepool " + poolname) + break + } + if poolmor.Type != "ResourcePool" { + ok = false + } + } + for _, vmmor := range vmmors { + if vmmor.Type == "VirtualMachine" { + vmToResourcePoolPath[vmmor] = poolpath + } + } + } + } + // Create Queries from interesting objects and requested metrics queries := []types.PerfQuerySpec{} @@ -334,7 +382,7 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe vcName := strings.Replace(vcenter.Hostname, domain, "", -1) for _, base := range perfres.Returnval { pem := base.(*types.PerfEntityMetric) - entityName := strings.ToLower(pem.Entity.Type) + //entityName := strings.ToLower(pem.Entity.Type) name := strings.ToLower(strings.Replace(morToName[pem.Entity], domain, "", -1)) //find datastore datastore := []string{} @@ -346,10 +394,10 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe //find host and cluster vmhost := "" cluster := "" - if entityName == "virtualmachine" { + if pem.Entity.Type == "VirtualMachine" { if esximor, ok := vmToHost[pem.Entity]; ok { vmhost = strings.ToLower(strings.Replace(morToName[esximor], domain, "", -1)) - if parmor, ok := hostToParent[esximor]; ok { + if parmor, ok := morToParent[esximor]; ok { if parmor.Type == "ClusterComputeResource" { cluster = morToName[parmor] } else { @@ -357,9 +405,9 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe } } } - } else if entityName == "hostsystem" { + } else if pem.Entity.Type == "HostSystem" { //find cluster if entity is a host - if parmor, ok := hostToParent[pem.Entity]; ok { + if parmor, ok := morToParent[pem.Entity]; ok { if parmor.Type == "ClusterComputeResource" { cluster = morToName[parmor] } else { @@ -374,11 +422,16 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe network = append(network, morToName[mor]) } } + //find resource pool path + resourcepool := "" + if rppath, ok := vmToResourcePoolPath[pem.Entity]; ok { + resourcepool = rppath + } for _, baseserie := range pem.Value { serie := baseserie.(*types.PerfMetricIntSeries) metricName := strings.ToLower(metricToName[serie.Id.CounterId]) instanceName := serie.Id.Instance - key := "vsphere." + vcName + "." + entityName + "." + name + "." + metricName + key := "vsphere." + vcName + "." + strings.ToLower(pem.Entity.Type) + "." + name + "." + metricName if len(instanceName) > 0 { key += "." + strings.ToLower(strings.Replace(instanceName, ".", "_", -1)) } @@ -396,19 +449,20 @@ func (vcenter *VCenter) Query(interval int, domain string, channel *chan []backe } metricparts := strings.Split(metricName, ".") point := backend.Point{ - VCenter: vcName, - ObjectType: entityName, - ObjectName: name, - Group: metricparts[0], - Counter: metricparts[1], - Instance: instanceName, - Rollup: metricparts[2], - Value: value, - Datastore: datastore, - ESXi: vmhost, - Cluster: cluster, - Network: network, - Timestamp: endTime.Unix(), + VCenter: vcName, + ObjectType: strings.ToLower(pem.Entity.Type), + ObjectName: name, + Group: metricparts[0], + Counter: metricparts[1], + Instance: instanceName, + Rollup: metricparts[2], + Value: value, + Datastore: datastore, + ESXi: vmhost, + Cluster: cluster, + Network: network, + ResourcePool: resourcepool, + Timestamp: endTime.Unix(), } values = append(values, point) }