github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/histogram/histogram.go (about) 1 // Copyright 2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package histogram provides a Histogram struct that represents a histogram of 16 // the number of events that occurred in each interval. It also provides a way 17 // to transform a Histogram struct into a graphical representation. In addition, 18 // it allows to create a Histogram struct from an exp-2 histogram. 19 package histogram 20 21 import ( 22 "fmt" 23 "strings" 24 ) 25 26 type Unit string 27 28 const ( 29 UnitMilliseconds Unit = "ms" 30 UnitMicroseconds Unit = "µs" 31 ) 32 33 type Interval struct { 34 Count uint64 `json:"count"` 35 Start uint64 `json:"start"` 36 End uint64 `json:"end"` 37 } 38 39 // Histogram represents a histogram of the number of events that occurred in 40 // each interval. 41 type Histogram struct { 42 Unit Unit `json:"unit,omitempty"` 43 Intervals []Interval `json:"intervals,omitempty"` 44 } 45 46 // NewIntervalsFromExp2Slots creates a new Interval array from an exp-2 47 // histogram represented in slots. 48 func NewIntervalsFromExp2Slots(slots []uint32) []Interval { 49 if len(slots) == 0 { 50 return nil 51 } 52 53 intervals := make([]Interval, 0, len(slots)) 54 indexMax := 0 55 for i, val := range slots { 56 if val > 0 { 57 indexMax = i 58 } 59 60 start := uint64(1) << i 61 end := 2*start - 1 62 if start == 1 { 63 start = 0 64 } 65 66 intervals = append(intervals, Interval{ 67 Count: uint64(val), 68 Start: start, 69 End: end, 70 }) 71 } 72 73 // The element parsedIntervals[indexMax] is the last element with a non-zero 74 // value. So, we need to use parsedIntervals[:indexMax+1] to include it in 75 // the returned array. 76 return intervals[:indexMax+1] 77 } 78 79 // String returns a string representation of the histogram. It is a golang 80 // adaption of iovisor/bcc print_log2_hist(): 81 // https://github.com/iovisor/bcc/blob/13b5563c11f7722a61a17c6ca0a1a387d2fa7788/libbpf-tools/trace_helpers.c#L895-L932 82 func (h *Histogram) String() string { 83 if len(h.Intervals) == 0 { 84 return "" 85 } 86 87 valMax := uint64(0) 88 for _, b := range h.Intervals { 89 if b.Count > valMax { 90 valMax = b.Count 91 } 92 } 93 94 spaceBefore := 8 95 spaceAfter := 16 96 width := 10 97 stars := 40 98 99 var sb strings.Builder 100 sb.WriteString(fmt.Sprintf("%*s%-*s : count distribution\n", spaceBefore, 101 "", spaceAfter, h.Unit)) 102 103 for _, b := range h.Intervals { 104 sb.WriteString(fmt.Sprintf("%*d -> %-*d : %-8d |%s|\n", width, 105 b.Start, width, b.End, b.Count, 106 starsToString(b.Count, valMax, uint64(stars)))) 107 } 108 109 return sb.String() 110 } 111 112 // starsToString returns a string with the number of stars and spaces needed to 113 // represent the value in the histogram. It is a golang adaption of iovisor/bcc 114 // print_stars(): 115 // https://github.com/iovisor/bcc/blob/13b5563c11f7722a61a17c6ca0a1a387d2fa7788/libbpf-tools/trace_helpers.c#L878-L893 116 func starsToString(val, valMax, width uint64) string { 117 if valMax == 0 { 118 return strings.Repeat(" ", int(width)) 119 } 120 121 stars := val * width / valMax 122 spaces := width - stars 123 124 var sb strings.Builder 125 sb.WriteString(strings.Repeat("*", int(stars))) 126 sb.WriteString(strings.Repeat(" ", int(spaces))) 127 128 return sb.String() 129 }