utilization.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Package utilization implements the Utilization spec, available at
  2. // https://source.datanerd.us/agents/agent-specs/blob/master/Utilization.md
  3. //
  4. package utilization
  5. import (
  6. "net/http"
  7. "runtime"
  8. "sync"
  9. "github.com/newrelic/go-agent/internal/logger"
  10. "github.com/newrelic/go-agent/internal/sysinfo"
  11. )
  12. const (
  13. metadataVersion = 3
  14. )
  15. // Config controls the behavior of utilization information capture.
  16. type Config struct {
  17. DetectAWS bool
  18. DetectAzure bool
  19. DetectGCP bool
  20. DetectPCF bool
  21. DetectDocker bool
  22. LogicalProcessors int
  23. TotalRAMMIB int
  24. BillingHostname string
  25. }
  26. type override struct {
  27. LogicalProcessors *int `json:"logical_processors,omitempty"`
  28. TotalRAMMIB *int `json:"total_ram_mib,omitempty"`
  29. BillingHostname string `json:"hostname,omitempty"`
  30. }
  31. // Data contains utilization system information.
  32. type Data struct {
  33. MetadataVersion int `json:"metadata_version"`
  34. // Although `runtime.NumCPU()` will never fail, this field is a pointer
  35. // to facilitate the cross agent tests.
  36. LogicalProcessors *int `json:"logical_processors"`
  37. RAMMiB *uint64 `json:"total_ram_mib"`
  38. Hostname string `json:"hostname"`
  39. BootID string `json:"boot_id,omitempty"`
  40. Vendors *vendors `json:"vendors,omitempty"`
  41. Config *override `json:"config,omitempty"`
  42. }
  43. var (
  44. sampleRAMMib = uint64(1024)
  45. sampleLogicProc = int(16)
  46. // SampleData contains sample utilization data useful for testing.
  47. SampleData = Data{
  48. MetadataVersion: metadataVersion,
  49. LogicalProcessors: &sampleLogicProc,
  50. RAMMiB: &sampleRAMMib,
  51. Hostname: "my-hostname",
  52. }
  53. )
  54. type docker struct {
  55. ID string `json:"id,omitempty"`
  56. }
  57. type vendors struct {
  58. AWS *aws `json:"aws,omitempty"`
  59. Azure *azure `json:"azure,omitempty"`
  60. GCP *gcp `json:"gcp,omitempty"`
  61. PCF *pcf `json:"pcf,omitempty"`
  62. Docker *docker `json:"docker,omitempty"`
  63. }
  64. func (v *vendors) isEmpty() bool {
  65. return v.AWS == nil && v.Azure == nil && v.GCP == nil && v.PCF == nil && v.Docker == nil
  66. }
  67. func overrideFromConfig(config Config) *override {
  68. ov := &override{}
  69. if 0 != config.LogicalProcessors {
  70. x := config.LogicalProcessors
  71. ov.LogicalProcessors = &x
  72. }
  73. if 0 != config.TotalRAMMIB {
  74. x := config.TotalRAMMIB
  75. ov.TotalRAMMIB = &x
  76. }
  77. ov.BillingHostname = config.BillingHostname
  78. if "" == ov.BillingHostname &&
  79. nil == ov.LogicalProcessors &&
  80. nil == ov.TotalRAMMIB {
  81. ov = nil
  82. }
  83. return ov
  84. }
  85. // Gather gathers system utilization data.
  86. func Gather(config Config, lg logger.Logger) *Data {
  87. client := &http.Client{
  88. Timeout: providerTimeout,
  89. }
  90. return gatherWithClient(config, lg, client)
  91. }
  92. func gatherWithClient(config Config, lg logger.Logger, client *http.Client) *Data {
  93. var wg sync.WaitGroup
  94. cpu := runtime.NumCPU()
  95. uDat := &Data{
  96. MetadataVersion: metadataVersion,
  97. LogicalProcessors: &cpu,
  98. Vendors: &vendors{},
  99. }
  100. warnGatherError := func(datatype string, err error) {
  101. lg.Debug("error gathering utilization data", map[string]interface{}{
  102. "error": err.Error(),
  103. "datatype": datatype,
  104. })
  105. }
  106. // This closure allows us to run each gather function in a separate goroutine
  107. // and wait for them at the end by closing over the wg WaitGroup we
  108. // instantiated at the start of the function.
  109. goGather := func(datatype string, gather func(*Data, *http.Client) error) {
  110. wg.Add(1)
  111. go func() {
  112. // Note that locking around util is not necessary since
  113. // WaitGroup provides acts as a memory barrier:
  114. // https://groups.google.com/d/msg/golang-nuts/5oHzhzXCcmM/utEwIAApCQAJ
  115. // Thus this code is fine as long as each routine is
  116. // modifying a different field of util.
  117. defer wg.Done()
  118. if err := gather(uDat, client); err != nil {
  119. warnGatherError(datatype, err)
  120. }
  121. }()
  122. }
  123. // Kick off gathering which requires network calls in goroutines.
  124. if config.DetectAWS {
  125. goGather("aws", gatherAWS)
  126. }
  127. if config.DetectAzure {
  128. goGather("azure", gatherAzure)
  129. }
  130. if config.DetectPCF {
  131. goGather("pcf", gatherPCF)
  132. }
  133. if config.DetectGCP {
  134. goGather("gcp", gatherGCP)
  135. }
  136. // Do non-network gathering sequentially since it is fast.
  137. if id, err := sysinfo.BootID(); err != nil {
  138. if err != sysinfo.ErrFeatureUnsupported {
  139. warnGatherError("bootid", err)
  140. }
  141. } else {
  142. uDat.BootID = id
  143. }
  144. if config.DetectDocker {
  145. if id, err := sysinfo.DockerID(); err != nil {
  146. if err != sysinfo.ErrFeatureUnsupported &&
  147. err != sysinfo.ErrDockerNotFound {
  148. warnGatherError("docker", err)
  149. }
  150. } else {
  151. uDat.Vendors.Docker = &docker{ID: id}
  152. }
  153. }
  154. if hostname, err := sysinfo.Hostname(); nil == err {
  155. uDat.Hostname = hostname
  156. } else {
  157. warnGatherError("hostname", err)
  158. }
  159. if bts, err := sysinfo.PhysicalMemoryBytes(); nil == err {
  160. mib := sysinfo.BytesToMebibytes(bts)
  161. uDat.RAMMiB = &mib
  162. } else {
  163. warnGatherError("memory", err)
  164. }
  165. // Now we wait for everything!
  166. wg.Wait()
  167. // Override whatever needs to be overridden.
  168. uDat.Config = overrideFromConfig(config)
  169. if uDat.Vendors.isEmpty() {
  170. // Per spec, we MUST NOT send any vendors hash if it's empty.
  171. uDat.Vendors = nil
  172. }
  173. return uDat
  174. }