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  }