map_helpers.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package report
  2. import (
  3. "bytes"
  4. "fmt"
  5. "sort"
  6. "time"
  7. "github.com/ugorji/go/codec"
  8. "github.com/weaveworks/ps"
  9. )
  10. // Helper functions for ps.Map, without considering what is inside
  11. // Return a new map containing all the elements of the two input maps
  12. // and where the same key is in both, pick 'b' where prefer(a,b) is true
  13. func mergeMaps(m, n ps.Map, prefer func(a, b interface{}) bool) ps.Map {
  14. switch {
  15. case m == nil:
  16. return n
  17. case n == nil:
  18. return m
  19. case m.Size() < n.Size():
  20. m, n = n, m
  21. }
  22. n.ForEach(func(key string, val interface{}) {
  23. if existingVal, found := m.Lookup(key); found {
  24. if prefer(existingVal, val) {
  25. m = m.Set(key, val)
  26. }
  27. } else {
  28. m = m.Set(key, val)
  29. }
  30. })
  31. return m
  32. }
  33. func mapEqual(m, n ps.Map, equalf func(a, b interface{}) bool) bool {
  34. var mSize, nSize int
  35. if m != nil {
  36. mSize = m.Size()
  37. }
  38. if n != nil {
  39. nSize = n.Size()
  40. }
  41. if mSize != nSize {
  42. return false
  43. }
  44. if mSize == 0 {
  45. return true
  46. }
  47. equal := true
  48. m.ForEach(func(k string, val interface{}) {
  49. if otherValue, ok := n.Lookup(k); !ok {
  50. equal = false
  51. } else {
  52. equal = equal && equalf(val, otherValue)
  53. }
  54. })
  55. return equal
  56. }
  57. // very similar to ps.Map.String() but with keys sorted
  58. func mapToString(m ps.Map) string {
  59. buf := bytes.NewBufferString("{")
  60. for _, key := range mapKeys(m) {
  61. val, _ := m.Lookup(key)
  62. fmt.Fprintf(buf, "%s: %s,\n", key, val)
  63. }
  64. fmt.Fprintf(buf, "}")
  65. return buf.String()
  66. }
  67. func mapKeys(m ps.Map) []string {
  68. if m == nil {
  69. return nil
  70. }
  71. keys := m.Keys()
  72. sort.Strings(keys)
  73. return keys
  74. }
  75. // constants from https://github.com/ugorji/go/blob/master/codec/helper.go#L207
  76. const (
  77. containerMapKey = 2
  78. containerMapValue = 3
  79. containerMapEnd = 4
  80. // from https://github.com/ugorji/go/blob/master/codec/helper.go#L152
  81. cUTF8 = 2
  82. )
  83. // This implementation does not use the intermediate form as that was a
  84. // performance issue; skipping it saved almost 10% CPU. Note this means
  85. // we are using undocumented, internal APIs, which could break in the future.
  86. // See https://github.com/weaveworks/scope/pull/1709 for more information.
  87. func mapRead(decoder *codec.Decoder, decodeValue func(isNil bool) interface{}) ps.Map {
  88. z, r := codec.GenHelperDecoder(decoder)
  89. if r.TryDecodeAsNil() {
  90. return ps.NewMap()
  91. }
  92. length := r.ReadMapStart()
  93. out := ps.NewMap()
  94. for i := 0; length < 0 || i < length; i++ {
  95. if length < 0 && r.CheckBreak() {
  96. break
  97. }
  98. var key string
  99. z.DecSendContainerState(containerMapKey)
  100. if !r.TryDecodeAsNil() {
  101. key = lookupCommonKey(r.DecodeStringAsBytes())
  102. }
  103. z.DecSendContainerState(containerMapValue)
  104. value := decodeValue(r.TryDecodeAsNil())
  105. out = out.UnsafeMutableSet(key, value)
  106. }
  107. z.DecSendContainerState(containerMapEnd)
  108. return out
  109. }
  110. // Inverse of mapRead, done for performance. Same comments about
  111. // undocumented internal APIs apply.
  112. func mapWrite(m ps.Map, encoder *codec.Encoder, encodeValue func(*codec.Encoder, interface{})) {
  113. z, r := codec.GenHelperEncoder(encoder)
  114. if m == nil || m.IsNil() {
  115. r.EncodeNil()
  116. return
  117. }
  118. r.EncodeMapStart(m.Size())
  119. m.ForEach(func(key string, val interface{}) {
  120. z.EncSendContainerState(containerMapKey)
  121. r.EncodeString(cUTF8, key)
  122. z.EncSendContainerState(containerMapValue)
  123. encodeValue(encoder, val)
  124. })
  125. z.EncSendContainerState(containerMapEnd)
  126. }
  127. // Now follow helpers for StringLatestMap
  128. // These let us sort a StringLatestMap strings by key
  129. func (m StringLatestMap) Len() int { return len(m) }
  130. func (m StringLatestMap) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
  131. func (m StringLatestMap) Less(i, j int) bool { return m[i].key < m[j].key }
  132. // sort entries and shuffle down any duplicates. NOTE: may modify contents of m.
  133. func (m StringLatestMap) sortedAndDeduplicated() StringLatestMap {
  134. sort.Sort(m)
  135. for i := 1; i < len(m); {
  136. if m[i-1].key == m[i].key {
  137. if m[i-1].Timestamp.Before(m[i].Timestamp) {
  138. m = append(m[:i-1], m[i:]...)
  139. } else {
  140. m = append(m[:i], m[i+1:]...)
  141. }
  142. } else {
  143. i++
  144. }
  145. }
  146. return m
  147. }
  148. // add several entries at the same timestamp
  149. func (m StringLatestMap) addMapEntries(ts time.Time, n map[string]string) StringLatestMap {
  150. out := make(StringLatestMap, len(m), len(m)+len(n))
  151. copy(out, m)
  152. for k, v := range n {
  153. out = append(out, stringLatestEntry{key: k, Value: v, Timestamp: ts})
  154. }
  155. return out.sortedAndDeduplicated()
  156. }
  157. // Propagate a set of latest values from one set to another.
  158. func (m StringLatestMap) Propagate(from StringLatestMap, keys ...string) StringLatestMap {
  159. out := make(StringLatestMap, len(m), len(m)+len(keys))
  160. copy(out, m)
  161. for _, k := range keys {
  162. if v, ts, ok := from.LookupEntry(k); ok {
  163. out = append(out, stringLatestEntry{key: k, Value: v, Timestamp: ts})
  164. }
  165. }
  166. return out.sortedAndDeduplicated()
  167. }