You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

925 lines
27 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. /* Copyright 2016-2018 Adrian Todorov, Oxalide ato@oxalide.com
  2. Original project author: https://github.com/cblomart
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. package main
  15. import (
  16. "encoding/json"
  17. "flag"
  18. "fmt"
  19. "log"
  20. "math"
  21. "net/url"
  22. "os"
  23. "path"
  24. "regexp"
  25. "strings"
  26. "time"
  27. "github.com/davecgh/go-spew/spew"
  28. influxclient "github.com/influxdata/influxdb/client/v2"
  29. "github.com/vmware/govmomi"
  30. "github.com/vmware/govmomi/property"
  31. "github.com/vmware/govmomi/vim25/methods"
  32. "github.com/vmware/govmomi/vim25/mo"
  33. "github.com/vmware/govmomi/vim25/types"
  34. "golang.org/x/net/context"
  35. )
  36. const (
  37. // name of the service
  38. name = "vsphere-influxdb"
  39. description = "send vsphere stats to influxdb"
  40. )
  41. // Configuration is used to store config data
  42. type Configuration struct {
  43. VCenters []*VCenter
  44. Metrics []Metric
  45. Interval int
  46. Domain string
  47. RemoveHostDomainName bool
  48. InfluxDB InfluxDB
  49. }
  50. // InfluxDB is used for InfluxDB connections
  51. type InfluxDB struct {
  52. Hostname string
  53. Username string
  54. Password string
  55. Database string
  56. Prefix string
  57. }
  58. // VCenter for VMware vCenter connections
  59. type VCenter struct {
  60. Hostname string
  61. Username string
  62. Password string
  63. MetricGroups []*MetricGroup
  64. client *govmomi.Client
  65. }
  66. // MetricDef metric definition
  67. type MetricDef struct {
  68. Metric string
  69. Instances string
  70. Key int32
  71. }
  72. // Metric is used for metrics retrieval
  73. type Metric struct {
  74. ObjectType []string
  75. Definition []MetricDef
  76. }
  77. // MetricGroup is used for grouping metrics retrieval
  78. type MetricGroup struct {
  79. ObjectType string
  80. Metrics []MetricDef
  81. Mor []types.ManagedObjectReference
  82. }
  83. // EntityQuery are informations to query about an entity
  84. type EntityQuery struct {
  85. Name string
  86. Entity types.ManagedObjectReference
  87. Metrics []int32
  88. }
  89. var getversion, debug, test bool
  90. var stdlog, errlog *log.Logger
  91. var version = "master"
  92. // Connect to the actual vCenter connection used to query data
  93. func (vcenter *VCenter) Connect() error {
  94. ctx, cancel := context.WithCancel(context.Background())
  95. defer cancel()
  96. stdlog.Println("Connecting to vcenter:", vcenter.Hostname)
  97. u, err := url.Parse("https://" + vcenter.Hostname + "/sdk")
  98. u.User = url.UserPassword(vcenter.Username, vcenter.Password)
  99. if err != nil {
  100. errlog.Println("Could not parse vcenter url:", vcenter.Hostname)
  101. errlog.Println("Error:", err)
  102. return err
  103. }
  104. client, err := govmomi.NewClient(ctx, u, true)
  105. if err != nil {
  106. errlog.Println("Could not connect to vcenter:", vcenter.Hostname)
  107. errlog.Println("Error:", err)
  108. return err
  109. }
  110. vcenter.client = client
  111. return nil
  112. }
  113. // Disconnect from the vCenter
  114. func (vcenter *VCenter) Disconnect() error {
  115. ctx, cancel := context.WithCancel(context.Background())
  116. defer cancel()
  117. if vcenter.client != nil {
  118. if err := vcenter.client.Logout(ctx); err != nil {
  119. errlog.Println("Could not disconnect properly from vcenter:", vcenter.Hostname, err)
  120. return err
  121. }
  122. }
  123. return nil
  124. }
  125. // Init the VCenter connection
  126. func (vcenter *VCenter) Init(config Configuration) error {
  127. ctx, cancel := context.WithCancel(context.Background())
  128. defer cancel()
  129. client := vcenter.client
  130. // Print version
  131. if debug {
  132. aboutInfo := client.Client.ServiceContent.About
  133. stdlog.Println("Version:", aboutInfo.FullName)
  134. }
  135. var perfmanager mo.PerformanceManager
  136. err := client.RetrieveOne(ctx, *client.ServiceContent.PerfManager, nil, &perfmanager)
  137. if err != nil {
  138. errlog.Println("Could not get performance manager")
  139. errlog.Println("Error:", err)
  140. return err
  141. }
  142. // Print PerformanceManager interval collection level
  143. if debug {
  144. stdlog.Println("PerformanceManager interval collection level")
  145. spew.Dump(perfmanager.HistoricalInterval)
  146. }
  147. for _, perf := range perfmanager.PerfCounter {
  148. groupinfo := perf.GroupInfo.GetElementDescription()
  149. nameinfo := perf.NameInfo.GetElementDescription()
  150. identifier := groupinfo.Key + "." + nameinfo.Key + "." + fmt.Sprint(perf.RollupType)
  151. for _, metric := range config.Metrics {
  152. for _, metricdef := range metric.Definition {
  153. if metricdef.Metric == identifier {
  154. metricd := MetricDef{Metric: metricdef.Metric, Instances: metricdef.Instances, Key: perf.Key}
  155. for _, mtype := range metric.ObjectType {
  156. added := false
  157. for _, metricgroup := range vcenter.MetricGroups {
  158. if metricgroup.ObjectType == mtype {
  159. metricgroup.Metrics = append(metricgroup.Metrics, metricd)
  160. added = true
  161. break
  162. }
  163. }
  164. if added == false {
  165. metricgroup := MetricGroup{ObjectType: mtype, Metrics: []MetricDef{metricd}}
  166. vcenter.MetricGroups = append(vcenter.MetricGroups, &metricgroup)
  167. }
  168. }
  169. }
  170. }
  171. }
  172. }
  173. return nil
  174. }
  175. // Query a vcenter
  176. func (vcenter *VCenter) Query(config Configuration, InfluxDBClient influxclient.Client, nowTime time.Time) {
  177. stdlog.Println("Setting up query inventory of vcenter:", vcenter.Hostname)
  178. // Create the contect
  179. ctx, cancel := context.WithCancel(context.Background())
  180. defer cancel()
  181. // Get the client
  182. client := vcenter.client
  183. // Create the view manager
  184. var viewManager mo.ViewManager
  185. err := client.RetrieveOne(ctx, *client.ServiceContent.ViewManager, nil, &viewManager)
  186. if err != nil {
  187. errlog.Println("Could not get view manager from vcenter:", vcenter.Hostname)
  188. errlog.Println("Error: ", err)
  189. return
  190. }
  191. // Get the Datacenters from root folder
  192. var rootFolder mo.Folder
  193. err = client.RetrieveOne(ctx, client.ServiceContent.RootFolder, nil, &rootFolder)
  194. if err != nil {
  195. errlog.Println("Could not get root folder from vcenter:", vcenter.Hostname)
  196. errlog.Println("Error:", err)
  197. return
  198. }
  199. datacenters := []types.ManagedObjectReference{}
  200. for _, child := range rootFolder.ChildEntity {
  201. //if child.Type == "Datacenter" {
  202. datacenters = append(datacenters, child)
  203. //}
  204. }
  205. // Get intresting object types from specified queries
  206. objectTypes := []string{}
  207. for _, group := range vcenter.MetricGroups {
  208. objectTypes = append(objectTypes, group.ObjectType)
  209. }
  210. objectTypes = append(objectTypes, "ClusterComputeResource")
  211. objectTypes = append(objectTypes, "ResourcePool")
  212. objectTypes = append(objectTypes, "Datastore")
  213. // Loop trought datacenters and create the intersting object reference list
  214. mors := []types.ManagedObjectReference{}
  215. for _, datacenter := range datacenters {
  216. // Create the CreateContentView request
  217. req := types.CreateContainerView{This: viewManager.Reference(), Container: datacenter, Type: objectTypes, Recursive: true}
  218. res, err := methods.CreateContainerView(ctx, client.RoundTripper, &req)
  219. if err != nil {
  220. errlog.Println("Could not create container view from vcenter:", vcenter.Hostname)
  221. errlog.Println("Error:", err)
  222. continue
  223. }
  224. // Retrieve the created ContentView
  225. var containerView mo.ContainerView
  226. err = client.RetrieveOne(ctx, res.Returnval, nil, &containerView)
  227. if err != nil {
  228. errlog.Println("Could not get container view from vcenter:", vcenter.Hostname)
  229. errlog.Println("Error:", err)
  230. continue
  231. }
  232. // Add found object to object list
  233. mors = append(mors, containerView.View...)
  234. }
  235. // Create MORS for each object type
  236. vmRefs := []types.ManagedObjectReference{}
  237. hostRefs := []types.ManagedObjectReference{}
  238. clusterRefs := []types.ManagedObjectReference{}
  239. respoolRefs := []types.ManagedObjectReference{}
  240. datastoreRefs := []types.ManagedObjectReference{}
  241. newMors := []types.ManagedObjectReference{}
  242. if debug {
  243. spew.Dump(mors)
  244. }
  245. // Assign each MORS type to a specific array
  246. for _, mor := range mors {
  247. if mor.Type == "VirtualMachine" {
  248. vmRefs = append(vmRefs, mor)
  249. newMors = append(newMors, mor)
  250. } else if mor.Type == "HostSystem" {
  251. hostRefs = append(hostRefs, mor)
  252. newMors = append(newMors, mor)
  253. } else if mor.Type == "ClusterComputeResource" {
  254. clusterRefs = append(clusterRefs, mor)
  255. } else if mor.Type == "ResourcePool" {
  256. respoolRefs = append(respoolRefs, mor)
  257. } else if mor.Type == "Datastore" {
  258. datastoreRefs = append(datastoreRefs, mor)
  259. }
  260. }
  261. // Copy the mors without the clusters
  262. mors = newMors
  263. pc := property.DefaultCollector(client.Client)
  264. // govmomi segfaults when the list objects to retrieve is empty, so check everything
  265. // Retrieve properties for all vms
  266. var vmmo []mo.VirtualMachine
  267. if len(vmRefs) > 0 {
  268. err = pc.Retrieve(ctx, vmRefs, []string{"summary"}, &vmmo)
  269. if err != nil {
  270. fmt.Println(err)
  271. return
  272. }
  273. }
  274. // Retrieve properties for hosts
  275. var hsmo []mo.HostSystem
  276. if len(hostRefs) > 0 {
  277. err = pc.Retrieve(ctx, hostRefs, []string{"parent", "summary"}, &hsmo)
  278. if err != nil {
  279. fmt.Println(err)
  280. return
  281. }
  282. }
  283. //Retrieve properties for Cluster(s)
  284. var clmo []mo.ClusterComputeResource
  285. if len(clusterRefs) > 0 {
  286. err = pc.Retrieve(ctx, clusterRefs, []string{"name", "configuration", "host"}, &clmo)
  287. if err != nil {
  288. fmt.Println(err)
  289. return
  290. }
  291. }
  292. //Retrieve properties for ResourcePool
  293. var rpmo []mo.ResourcePool
  294. if len(respoolRefs) > 0 {
  295. err = pc.Retrieve(ctx, respoolRefs, []string{"summary"}, &rpmo)
  296. if err != nil {
  297. fmt.Println(err)
  298. return
  299. }
  300. }
  301. // Retrieve summary property for all datastores
  302. var dss []mo.Datastore
  303. if len(datastoreRefs) > 0 {
  304. err = pc.Retrieve(ctx, datastoreRefs, []string{"summary"}, &dss)
  305. if err != nil {
  306. log.Fatal(err)
  307. return
  308. }
  309. }
  310. // Initialize the map that will hold the VM MOR to ResourcePool reference
  311. vmToPool := make(map[types.ManagedObjectReference]string)
  312. var respool []mo.ResourcePool
  313. // Retrieve properties for ResourcePools
  314. if len(respoolRefs) > 0 {
  315. if debug {
  316. stdlog.Println("Going inside ResourcePools")
  317. }
  318. err = pc.Retrieve(ctx, respoolRefs, []string{"name", "config", "vm"}, &respool)
  319. if err != nil {
  320. fmt.Println(err)
  321. return
  322. }
  323. for _, pool := range respool {
  324. if debug {
  325. stdlog.Println("---resourcepool name - you should see every resourcepool here (+VMs inside)----")
  326. stdlog.Println(pool.Name)
  327. stdlog.Println(pool.Config.MemoryAllocation.GetResourceAllocationInfo().Limit)
  328. stdlog.Println(pool.Config.CpuAllocation.GetResourceAllocationInfo().Limit)
  329. }
  330. for _, vm := range pool.Vm {
  331. if debug {
  332. stdlog.Println("--VM ID - you should see every VM ID here--")
  333. stdlog.Println(vm)
  334. }
  335. vmToPool[vm] = pool.Name
  336. }
  337. }
  338. }
  339. // Initialize the map that will hold the VM MOR to cluster reference
  340. vmToCluster := make(map[types.ManagedObjectReference]string)
  341. // Initialize the map that will hold the host MOR to cluster reference
  342. hostToCluster := make(map[types.ManagedObjectReference]string)
  343. // Initialize the map that will hold the vDisk UUID per VM MOR to datastore reference
  344. // vDiskToDatastore := make(map[types.ManagedObjectReference]map[string]string)
  345. // Retrieve properties for clusters, if any
  346. if len(clusterRefs) > 0 {
  347. if debug {
  348. stdlog.Println("Going inside clusters")
  349. }
  350. // Step 1 : Get ObjectContents and Host info for VM
  351. // The host is found under the runtime structure.
  352. // Step 2 : Step 2: Get the ManagedObjectReference from the Host we just got.
  353. // Step 3 : Get a list all the clusters that vCenter knows about, and for each one, also get the host
  354. // Step 4 : Loop through all clusters that exist (which we got in step 3), and loop through each host
  355. // and see if that host matches the host we got in step 2 as the host of the vm.
  356. // If we find it, return it, otherwise we return null.
  357. for _, vm := range vmmo {
  358. // check if VM is a clone in progress and skip it
  359. if vm.Summary.Runtime.Host == nil {
  360. continue
  361. }
  362. vmhost := vm.Summary.Runtime.Host
  363. for _, cl := range clmo {
  364. for _, host := range cl.Host {
  365. hostToCluster[host] = cl.Name
  366. if *vmhost == host {
  367. vmToCluster[vm.Self] = cl.Name
  368. }
  369. }
  370. }
  371. }
  372. }
  373. // Retrieve properties for the pools
  374. respoolSummary := make(map[types.ManagedObjectReference]map[string]string)
  375. for _, pools := range rpmo {
  376. respoolSummary[pools.Self] = make(map[string]string)
  377. respoolSummary[pools.Self]["name"] = pools.Summary.GetResourcePoolSummary().Name
  378. }
  379. // Initialize the maps that will hold the extra tags and metrics for VMs
  380. hostSummary := make(map[types.ManagedObjectReference]map[string]string)
  381. hostExtraMetrics := make(map[types.ManagedObjectReference]map[string]int64)
  382. for _, host := range hsmo {
  383. // Extra tags per host
  384. hostSummary[host.Self] = make(map[string]string)
  385. hostSummary[host.Self]["name"] = host.Summary.Config.Name
  386. // Remove Domain Name from Host
  387. if config.RemoveHostDomainName {
  388. hostSummary[host.Self]["name"] = strings.Replace(host.Summary.Config.Name, config.Domain, "", -1)
  389. }
  390. hostSummary[host.Self]["cluster"] = hostToCluster[host.Self]
  391. // Extra metrics per host
  392. hostExtraMetrics[host.Self] = make(map[string]int64)
  393. hostExtraMetrics[host.Self]["uptime"] = int64(host.Summary.QuickStats.Uptime)
  394. hostExtraMetrics[host.Self]["cpu_corecount_total"] = int64(host.Summary.Hardware.NumCpuThreads)
  395. }
  396. // Initialize the maps that will hold the extra tags and metrics for VMs
  397. vmSummary := make(map[types.ManagedObjectReference]map[string]string)
  398. vmExtraMetrics := make(map[types.ManagedObjectReference]map[string]int64)
  399. // Assign extra details per VM in vmSummary
  400. for _, vm := range vmmo {
  401. // extra tags per VM
  402. vmSummary[vm.Self] = make(map[string]string)
  403. // Ugly way to extract datastore value
  404. re, err := regexp.Compile(`\[(.*?)\]`)
  405. if err != nil {
  406. fmt.Println(err)
  407. }
  408. vmSummary[vm.Self]["datastore"] = strings.Replace(strings.Replace(re.FindString(fmt.Sprintln(vm.Summary.Config)), "[", "", -1), "]", "", -1)
  409. // List all devices to get vDisks
  410. // for _, device := range vm.Config.Hardware.Device {
  411. // // Hacky way to check if it's a vDisk and if it's datastore is different than the main one for VM
  412. // if device.Backing.FileName != nil && device.Backing.Datastore.Name != vmSummary[vm.Self]["datastore"] {
  413. // if vDiskToDatastore[vm.Self] == nil {
  414. // vDiskToDatastore[vm.Self] = make(map[string]string)
  415. // }
  416. // vDiskToDatastore[vm.Self][device.diskObjectId] = device.Backing.Datastore.Name
  417. // }
  418. // }
  419. if vmToCluster[vm.Self] != "" {
  420. vmSummary[vm.Self]["cluster"] = vmToCluster[vm.Self]
  421. }
  422. if vmToPool[vm.Self] != "" {
  423. vmSummary[vm.Self]["respool"] = vmToPool[vm.Self]
  424. }
  425. if vm.Summary.Runtime.Host != nil {
  426. vmSummary[vm.Self]["esx"] = hostSummary[*vm.Summary.Runtime.Host]["name"]
  427. }
  428. // Extra metrics per VM
  429. vmExtraMetrics[vm.Self] = make(map[string]int64)
  430. vmExtraMetrics[vm.Self]["uptime"] = int64(vm.Summary.QuickStats.UptimeSeconds)
  431. }
  432. // fmt.Println("vDiskDatastore:")
  433. // spew.Dump(vDiskToDatastore)
  434. // get object names
  435. objects := []mo.ManagedEntity{}
  436. //object for propery collection
  437. propSpec := &types.PropertySpec{Type: "ManagedEntity", PathSet: []string{"name"}}
  438. var objectSet []types.ObjectSpec
  439. for _, mor := range mors {
  440. objectSet = append(objectSet, types.ObjectSpec{Obj: mor, Skip: types.NewBool(false)})
  441. }
  442. //retrieve name property
  443. propreq := types.RetrieveProperties{SpecSet: []types.PropertyFilterSpec{{ObjectSet: objectSet, PropSet: []types.PropertySpec{*propSpec}}}}
  444. propres, err := client.PropertyCollector().RetrieveProperties(ctx, propreq)
  445. if err != nil {
  446. errlog.Println("Could not retrieve object names from vcenter:", vcenter.Hostname)
  447. errlog.Println("Error:", err)
  448. return
  449. }
  450. //load retrieved properties
  451. err = mo.LoadRetrievePropertiesResponse(propres, &objects)
  452. if err != nil {
  453. errlog.Println("Could not retrieve object names from vcenter:", vcenter.Hostname)
  454. errlog.Println("Error:", err)
  455. return
  456. }
  457. //create a map to resolve object names
  458. morToName := make(map[types.ManagedObjectReference]string)
  459. for _, object := range objects {
  460. morToName[object.Self] = object.Name
  461. }
  462. //create a map to resolve metric names
  463. metricToName := make(map[int32]string)
  464. for _, metricgroup := range vcenter.MetricGroups {
  465. for _, metricdef := range metricgroup.Metrics {
  466. metricToName[metricdef.Key] = metricdef.Metric
  467. }
  468. }
  469. // Create Queries from interesting objects and requested metrics
  470. queries := []types.PerfQuerySpec{}
  471. // Common parameters
  472. intervalIDint := 20
  473. var intervalID int32
  474. intervalID = int32(intervalIDint)
  475. endTime := time.Now().Add(time.Duration(-1) * time.Second)
  476. startTime := endTime.Add(time.Duration(-config.Interval) * time.Second)
  477. // Parse objects
  478. for _, mor := range mors {
  479. metricIds := []types.PerfMetricId{}
  480. for _, metricgroup := range vcenter.MetricGroups {
  481. if metricgroup.ObjectType == mor.Type {
  482. for _, metricdef := range metricgroup.Metrics {
  483. metricIds = append(metricIds, types.PerfMetricId{CounterId: metricdef.Key, Instance: metricdef.Instances})
  484. }
  485. }
  486. }
  487. queries = append(queries, types.PerfQuerySpec{Entity: mor, StartTime: &startTime, EndTime: &endTime, MetricId: metricIds, IntervalId: intervalID})
  488. }
  489. // Query the performances
  490. perfreq := types.QueryPerf{This: *client.ServiceContent.PerfManager, QuerySpec: queries}
  491. perfres, err := methods.QueryPerf(ctx, client.RoundTripper, &perfreq)
  492. if err != nil {
  493. errlog.Println("Could not request perfs from vcenter:", vcenter.Hostname)
  494. errlog.Println("Error:", err)
  495. return
  496. }
  497. // Get the result
  498. vcName := strings.Replace(vcenter.Hostname, config.Domain, "", -1)
  499. //Influx batch points
  500. bp, err := influxclient.NewBatchPoints(influxclient.BatchPointsConfig{
  501. Database: config.InfluxDB.Database,
  502. Precision: "s",
  503. })
  504. if err != nil {
  505. errlog.Println(err)
  506. return
  507. }
  508. for _, base := range perfres.Returnval {
  509. pem := base.(*types.PerfEntityMetric)
  510. entityName := strings.ToLower(pem.Entity.Type)
  511. name := strings.ToLower(strings.Replace(morToName[pem.Entity], config.Domain, "", -1))
  512. //Create map for InfluxDB fields
  513. fields := make(map[string]interface{})
  514. // Create map for InfluxDB tags
  515. tags := map[string]string{"host": vcName, "name": name}
  516. // Add extra per VM tags
  517. if summary, ok := vmSummary[pem.Entity]; ok {
  518. for key, tag := range summary {
  519. tags[key] = tag
  520. }
  521. }
  522. if summary, ok := hostSummary[pem.Entity]; ok {
  523. for key, tag := range summary {
  524. tags[key] = tag
  525. }
  526. }
  527. if summary, ok := respoolSummary[pem.Entity]; ok {
  528. for key, tag := range summary {
  529. tags[key] = tag
  530. }
  531. }
  532. specialFields := make(map[string]map[string]map[string]map[string]interface{})
  533. specialTags := make(map[string]map[string]map[string]map[string]string)
  534. nowTime := time.Now()
  535. for _, baseserie := range pem.Value {
  536. serie := baseserie.(*types.PerfMetricIntSeries)
  537. metricName := strings.ToLower(metricToName[serie.Id.CounterId])
  538. influxMetricName := strings.Replace(metricName, ".", "_", -1)
  539. instanceName := strings.ToLower(strings.Replace(serie.Id.Instance, ".", "_", -1))
  540. measurementName := strings.Split(metricName, ".")[0]
  541. if strings.Index(influxMetricName, "datastore") != -1 {
  542. instanceName = ""
  543. }
  544. var value int64 = -1
  545. if strings.HasSuffix(metricName, ".average") {
  546. value = average(serie.Value...)
  547. } else if strings.HasSuffix(metricName, ".maximum") {
  548. value = max(serie.Value...)
  549. } else if strings.HasSuffix(metricName, ".minimum") {
  550. value = min(serie.Value...)
  551. } else if strings.HasSuffix(metricName, ".latest") {
  552. value = serie.Value[len(serie.Value)-1]
  553. } else if strings.HasSuffix(metricName, ".summation") {
  554. value = sum(serie.Value...)
  555. }
  556. if instanceName == "" {
  557. fields[influxMetricName] = value
  558. } else {
  559. // init maps
  560. if specialFields[measurementName] == nil {
  561. specialFields[measurementName] = make(map[string]map[string]map[string]interface{})
  562. specialTags[measurementName] = make(map[string]map[string]map[string]string)
  563. }
  564. if specialFields[measurementName][tags["name"]] == nil {
  565. specialFields[measurementName][tags["name"]] = make(map[string]map[string]interface{})
  566. specialTags[measurementName][tags["name"]] = make(map[string]map[string]string)
  567. }
  568. if specialFields[measurementName][tags["name"]][instanceName] == nil {
  569. specialFields[measurementName][tags["name"]][instanceName] = make(map[string]interface{})
  570. specialTags[measurementName][tags["name"]][instanceName] = make(map[string]string)
  571. }
  572. specialFields[measurementName][tags["name"]][instanceName][influxMetricName] = value
  573. for k, v := range tags {
  574. specialTags[measurementName][tags["name"]][instanceName][k] = v
  575. }
  576. specialTags[measurementName][tags["name"]][instanceName]["instance"] = instanceName
  577. }
  578. }
  579. // Create the fields for the hostExtraMetrics
  580. if metrics, ok := hostExtraMetrics[pem.Entity]; ok {
  581. for key, value := range metrics {
  582. fields[key] = value
  583. }
  584. }
  585. // Create the fields for the vmExtraMetrics
  586. if metrics, ok := vmExtraMetrics[pem.Entity]; ok {
  587. for key, value := range metrics {
  588. fields[key] = value
  589. }
  590. }
  591. //create InfluxDB points
  592. pt, err := influxclient.NewPoint(config.InfluxDB.Prefix+entityName, tags, fields, nowTime)
  593. if err != nil {
  594. errlog.Println(err)
  595. continue
  596. }
  597. bp.AddPoint(pt)
  598. for measurement, v := range specialFields {
  599. for name, metric := range v {
  600. for instance, value := range metric {
  601. pt2, err := influxclient.NewPoint(config.InfluxDB.Prefix+measurement, specialTags[measurement][name][instance], value, time.Now())
  602. if err != nil {
  603. errlog.Println(err)
  604. continue
  605. }
  606. bp.AddPoint(pt2)
  607. }
  608. }
  609. }
  610. for _, pool := range respool {
  611. respoolFields := map[string]interface{}{
  612. "cpu_limit": pool.Config.CpuAllocation.GetResourceAllocationInfo().Limit,
  613. "memory_limit": pool.Config.MemoryAllocation.GetResourceAllocationInfo().Limit,
  614. }
  615. respoolTags := map[string]string{"pool_name": pool.Name}
  616. pt3, err := influxclient.NewPoint(config.InfluxDB.Prefix+"resourcepool", respoolTags, respoolFields, time.Now())
  617. if err != nil {
  618. errlog.Println(err)
  619. continue
  620. }
  621. bp.AddPoint(pt3)
  622. }
  623. for _, datastore := range dss {
  624. datastoreFields := map[string]interface{}{
  625. "capacity": datastore.Summary.Capacity,
  626. "free_space": datastore.Summary.FreeSpace,
  627. "usage": 1.0 - (float64(datastore.Summary.FreeSpace) / float64(datastore.Summary.Capacity)),
  628. }
  629. datastoreTags := map[string]string{"ds_name": datastore.Summary.Name, "host": vcName}
  630. pt4, err := influxclient.NewPoint(config.InfluxDB.Prefix+"datastore", datastoreTags, datastoreFields, time.Now())
  631. if err != nil {
  632. errlog.Println(err)
  633. continue
  634. }
  635. bp.AddPoint(pt4)
  636. }
  637. }
  638. //InfluxDB send if not in test mode
  639. if test != true {
  640. err = InfluxDBClient.Write(bp)
  641. if err != nil {
  642. errlog.Println(err)
  643. return
  644. }
  645. stdlog.Println("Sent data to Influxdb from:", vcenter.Hostname)
  646. } else {
  647. spew.Dump(bp)
  648. }
  649. }
  650. func min(n ...int64) int64 {
  651. var min int64 = -1
  652. for _, i := range n {
  653. if i >= 0 {
  654. if min == -1 {
  655. min = i
  656. } else {
  657. if i < min {
  658. min = i
  659. }
  660. }
  661. }
  662. }
  663. return min
  664. }
  665. func max(n ...int64) int64 {
  666. var max int64 = -1
  667. for _, i := range n {
  668. if i >= 0 {
  669. if max == -1 {
  670. max = i
  671. } else {
  672. if i > max {
  673. max = i
  674. }
  675. }
  676. }
  677. }
  678. return max
  679. }
  680. func sum(n ...int64) int64 {
  681. var total int64
  682. for _, i := range n {
  683. if i > 0 {
  684. total += i
  685. }
  686. }
  687. return total
  688. }
  689. func average(n ...int64) int64 {
  690. var total int64
  691. var count int64
  692. for _, i := range n {
  693. if i >= 0 {
  694. count++
  695. total += i
  696. }
  697. }
  698. favg := float64(total) / float64(count)
  699. return int64(math.Floor(favg + .5))
  700. }
  701. func worker(id int, config Configuration, influxDBClient influxclient.Client, nowTime time.Time, vcenters <-chan *VCenter, results chan<- bool) {
  702. for vcenter := range vcenters {
  703. if debug {
  704. stdlog.Println("Worker", id, "received vcenter", vcenter.Hostname)
  705. }
  706. if err := vcenter.Connect(); err != nil {
  707. errlog.Println("Could not initialize connection to vcenter", vcenter.Hostname, err)
  708. results <- true
  709. continue
  710. }
  711. if err := vcenter.Init(config); err == nil {
  712. vcenter.Query(config, influxDBClient, nowTime)
  713. }
  714. vcenter.Disconnect()
  715. results <- true
  716. }
  717. }
  718. func main() {
  719. baseName := path.Base(os.Args[0])
  720. stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
  721. errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
  722. flag.BoolVar(&debug, "debug", false, "Debug mode")
  723. flag.BoolVar(&test, "test", false, "Test mode, data will be collected from vCenters, but nothing will be written to InfluxDB, only printed to stdout")
  724. flag.BoolVar(&getversion, "version", false, "Get version and exit")
  725. workerCount := flag.Int("workers", 4, "Number of concurrent workers to query vcenters")
  726. cfgFile := flag.String("config", "/etc/"+baseName+".json", "Config file to use")
  727. flag.Parse()
  728. if getversion {
  729. fmt.Println("Version:", version)
  730. os.Exit(0)
  731. }
  732. stdlog.Println("Starting", baseName, "with config file", *cfgFile)
  733. // read the configuration
  734. file, err := os.Open(*cfgFile)
  735. if err != nil {
  736. errlog.Println("Could not open configuration file", *cfgFile)
  737. errlog.Fatalln(err)
  738. }
  739. jsondec := json.NewDecoder(file)
  740. config := Configuration{}
  741. err = jsondec.Decode(&config)
  742. if err != nil {
  743. errlog.Println("Could not decode configuration file", *cfgFile)
  744. errlog.Fatalln(err)
  745. }
  746. // Support environemt variables / overrides for Influx Connection
  747. if ihostname := os.Getenv("INFLUX_HOSTNAME"); ihostname != "" {
  748. config.InfluxDB.Hostname = os.Getenv("INFLUX_HOSTNAME")
  749. config.InfluxDB.Username = os.Getenv("INFLUX_USERNAME")
  750. config.InfluxDB.Password = os.Getenv("INFLUX_PASSWORD")
  751. config.InfluxDB.Database = os.Getenv("INFLUX_DATABASE")
  752. }
  753. // Support environment variables for VSphere
  754. // Currently ony one server is supported and added to the list of vSphere servers
  755. if vhostname := os.Getenv("VSPHERE_HOSTNAME"); vhostname != "" {
  756. vc := VCenter{
  757. Hostname: os.Getenv("VSPHERE_HOSTNAME"),
  758. Username: os.Getenv("VSPHERE_USERNAME"),
  759. Password: os.Getenv("VSPHERE_PASSWORD"),
  760. }
  761. config.VCenters = append(config.VCenters, &vc)
  762. }
  763. // Print configuration in debug mode
  764. if debug {
  765. stdlog.Println("---Configuration - you should see the config here---")
  766. spew.Dump(config)
  767. }
  768. // Initialize InfluxDB and connect to database
  769. InfluxDBClient, err := influxclient.NewHTTPClient(influxclient.HTTPConfig{
  770. Addr: config.InfluxDB.Hostname,
  771. Username: config.InfluxDB.Username,
  772. Password: config.InfluxDB.Password,
  773. })
  774. if err != nil {
  775. errlog.Println("Could not initialize InfluxDB client")
  776. errlog.Fatalln(err)
  777. }
  778. if _, _, err := InfluxDBClient.Ping(0); err != nil {
  779. errlog.Println("Could not connect to InfluxDB")
  780. errlog.Fatalln(err)
  781. }
  782. defer InfluxDBClient.Close()
  783. stdlog.Println("Successfully connected to Influx")
  784. // make the channels, get the time, launch the goroutines
  785. vcenterCount := len(config.VCenters)
  786. vcenters := make(chan *VCenter, vcenterCount)
  787. results := make(chan bool, vcenterCount)
  788. nowTime := time.Now()
  789. for i := 0; i < *workerCount; i++ {
  790. go worker(i, config, InfluxDBClient, nowTime, vcenters, results)
  791. }
  792. for _, vcenter := range config.VCenters {
  793. vcenters <- vcenter
  794. }
  795. close(vcenters)
  796. for i := 0; i < vcenterCount; i++ {
  797. <-results
  798. }
  799. }