metrics.go 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // Copyright (c) 2017 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package metrics
  21. import (
  22. "fmt"
  23. "reflect"
  24. "strings"
  25. )
  26. // Init initializes the passed in metrics and initializes its fields using the passed in factory.
  27. func Init(metrics interface{}, factory Factory, globalTags map[string]string) {
  28. if err := initMetrics(metrics, factory, globalTags); err != nil {
  29. panic(err.Error())
  30. }
  31. }
  32. // initMetrics uses reflection to initialize a struct containing metrics fields
  33. // by assigning new Counter/Gauge/Timer values with the metric name retrieved
  34. // from the `metric` tag and stats tags retrieved from the `tags` tag.
  35. //
  36. // Note: all fields of the struct must be exported, have a `metric` tag, and be
  37. // of type Counter or Gauge or Timer.
  38. func initMetrics(m interface{}, factory Factory, globalTags map[string]string) error {
  39. // Allow user to opt out of reporting metrics by passing in nil.
  40. if factory == nil {
  41. factory = NullFactory
  42. }
  43. counterPtrType := reflect.TypeOf((*Counter)(nil)).Elem()
  44. gaugePtrType := reflect.TypeOf((*Gauge)(nil)).Elem()
  45. timerPtrType := reflect.TypeOf((*Timer)(nil)).Elem()
  46. v := reflect.ValueOf(m).Elem()
  47. t := v.Type()
  48. for i := 0; i < t.NumField(); i++ {
  49. tags := make(map[string]string)
  50. for k, v := range globalTags {
  51. tags[k] = v
  52. }
  53. field := t.Field(i)
  54. metric := field.Tag.Get("metric")
  55. if metric == "" {
  56. return fmt.Errorf("Field %s is missing a tag 'metric'", field.Name)
  57. }
  58. if tagString := field.Tag.Get("tags"); tagString != "" {
  59. tagPairs := strings.Split(tagString, ",")
  60. for _, tagPair := range tagPairs {
  61. tag := strings.Split(tagPair, "=")
  62. if len(tag) != 2 {
  63. return fmt.Errorf(
  64. "Field [%s]: Tag [%s] is not of the form key=value in 'tags' string [%s]",
  65. field.Name, tagPair, tagString)
  66. }
  67. tags[tag[0]] = tag[1]
  68. }
  69. }
  70. var obj interface{}
  71. if field.Type.AssignableTo(counterPtrType) {
  72. obj = factory.Counter(metric, tags)
  73. } else if field.Type.AssignableTo(gaugePtrType) {
  74. obj = factory.Gauge(metric, tags)
  75. } else if field.Type.AssignableTo(timerPtrType) {
  76. obj = factory.Timer(metric, tags)
  77. } else {
  78. return fmt.Errorf(
  79. "Field %s is not a pointer to timer, gauge, or counter",
  80. field.Name)
  81. }
  82. v.Field(i).Set(reflect.ValueOf(obj))
  83. }
  84. return nil
  85. }