...

Source file src/github.com/onsi/gomega/gmeasure/measurement.go

Documentation: github.com/onsi/gomega/gmeasure

     1  package gmeasure
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"sort"
     7  	"time"
     8  
     9  	"github.com/onsi/gomega/gmeasure/table"
    10  )
    11  
    12  type MeasurementType uint
    13  
    14  const (
    15  	MeasurementTypeInvalid MeasurementType = iota
    16  	MeasurementTypeNote
    17  	MeasurementTypeDuration
    18  	MeasurementTypeValue
    19  )
    20  
    21  var letEnumSupport = newEnumSupport(map[uint]string{uint(MeasurementTypeInvalid): "INVALID LOG ENTRY TYPE", uint(MeasurementTypeNote): "Note", uint(MeasurementTypeDuration): "Duration", uint(MeasurementTypeValue): "Value"})
    22  
    23  func (s MeasurementType) String() string { return letEnumSupport.String(uint(s)) }
    24  func (s *MeasurementType) UnmarshalJSON(b []byte) error {
    25  	out, err := letEnumSupport.UnmarshJSON(b)
    26  	*s = MeasurementType(out)
    27  	return err
    28  }
    29  func (s MeasurementType) MarshalJSON() ([]byte, error) { return letEnumSupport.MarshJSON(uint(s)) }
    30  
    31  /*
    32  Measurement records all captured data for a given measurement.  You generally don't make Measurements directly - but you can fetch them from Experiments using Get().
    33  
    34  When using Ginkgo, you can register Measurements as Report Entries via AddReportEntry.  This will emit all the captured data points when Ginkgo generates the report.
    35  */
    36  type Measurement struct {
    37  	// Type is the MeasurementType - one of MeasurementTypeNote, MeasurementTypeDuration, or MeasurementTypeValue
    38  	Type MeasurementType
    39  
    40  	// ExperimentName is the name of the experiment that this Measurement is associated with
    41  	ExperimentName string
    42  
    43  	// If Type is MeasurementTypeNote, Note is populated with the note text.
    44  	Note string
    45  
    46  	// If Type is MeasurementTypeDuration or MeasurementTypeValue, Name is the name of the recorded measurement
    47  	Name string
    48  
    49  	// Style captures the styling information (if any) for this Measurement
    50  	Style string
    51  
    52  	// Units capture the units (if any) for this Measurement.  Units is set to "duration" if the Type is MeasurementTypeDuration
    53  	Units string
    54  
    55  	// PrecisionBundle captures the precision to use when rendering data for this Measurement.
    56  	// If Type is MeasurementTypeDuration then PrecisionBundle.Duration is used to round any durations before presentation.
    57  	// If Type is MeasurementTypeValue then PrecisionBundle.ValueFormat is used to format any values before presentation
    58  	PrecisionBundle PrecisionBundle
    59  
    60  	// If Type is MeasurementTypeDuration, Durations will contain all durations recorded for this measurement
    61  	Durations []time.Duration
    62  
    63  	// If Type is MeasurementTypeValue, Values will contain all float64s recorded for this measurement
    64  	Values []float64
    65  
    66  	// If Type is MeasurementTypeDuration or MeasurementTypeValue then Annotations will include string annotations for all recorded Durations or Values.
    67  	// If the user does not pass-in an Annotation() decoration for a particular value or duration, the corresponding entry in the Annotations slice will be the empty string ""
    68  	Annotations []string
    69  }
    70  
    71  type Measurements []Measurement
    72  
    73  func (m Measurements) IdxWithName(name string) int {
    74  	for idx, measurement := range m {
    75  		if measurement.Name == name {
    76  			return idx
    77  		}
    78  	}
    79  
    80  	return -1
    81  }
    82  
    83  func (m Measurement) report(enableStyling bool) string {
    84  	out := ""
    85  	style := m.Style
    86  	if !enableStyling {
    87  		style = ""
    88  	}
    89  	switch m.Type {
    90  	case MeasurementTypeNote:
    91  		out += fmt.Sprintf("%s - Note\n%s\n", m.ExperimentName, m.Note)
    92  		if style != "" {
    93  			out = style + out + "{{/}}"
    94  		}
    95  		return out
    96  	case MeasurementTypeValue, MeasurementTypeDuration:
    97  		out += fmt.Sprintf("%s - %s", m.ExperimentName, m.Name)
    98  		if m.Units != "" {
    99  			out += " [" + m.Units + "]"
   100  		}
   101  		if style != "" {
   102  			out = style + out + "{{/}}"
   103  		}
   104  		out += "\n"
   105  		out += m.Stats().String() + "\n"
   106  	}
   107  	t := table.NewTable()
   108  	t.TableStyle.EnableTextStyling = enableStyling
   109  	switch m.Type {
   110  	case MeasurementTypeValue:
   111  		t.AppendRow(table.R(table.C("Value", table.AlignTypeCenter), table.C("Annotation", table.AlignTypeCenter), table.Divider("="), style))
   112  		for idx := range m.Values {
   113  			t.AppendRow(table.R(
   114  				table.C(fmt.Sprintf(m.PrecisionBundle.ValueFormat, m.Values[idx]), table.AlignTypeRight),
   115  				table.C(m.Annotations[idx], "{{gray}}", table.AlignTypeLeft),
   116  			))
   117  		}
   118  	case MeasurementTypeDuration:
   119  		t.AppendRow(table.R(table.C("Duration", table.AlignTypeCenter), table.C("Annotation", table.AlignTypeCenter), table.Divider("="), style))
   120  		for idx := range m.Durations {
   121  			t.AppendRow(table.R(
   122  				table.C(m.Durations[idx].Round(m.PrecisionBundle.Duration).String(), style, table.AlignTypeRight),
   123  				table.C(m.Annotations[idx], "{{gray}}", table.AlignTypeLeft),
   124  			))
   125  		}
   126  	}
   127  	out += t.Render()
   128  	return out
   129  }
   130  
   131  /*
   132  ColorableString generates a styled report that includes all the data points for this Measurement.
   133  It is called automatically by Ginkgo's reporting infrastructure when the Measurement is registered as a ReportEntry via AddReportEntry.
   134  */
   135  func (m Measurement) ColorableString() string {
   136  	return m.report(true)
   137  }
   138  
   139  /*
   140  String generates an unstyled report that includes all the data points for this Measurement.
   141  */
   142  func (m Measurement) String() string {
   143  	return m.report(false)
   144  }
   145  
   146  /*
   147  Stats returns a Stats struct summarizing the statistic of this measurement
   148  */
   149  func (m Measurement) Stats() Stats {
   150  	if m.Type == MeasurementTypeInvalid || m.Type == MeasurementTypeNote {
   151  		return Stats{}
   152  	}
   153  
   154  	out := Stats{
   155  		ExperimentName:  m.ExperimentName,
   156  		MeasurementName: m.Name,
   157  		Style:           m.Style,
   158  		Units:           m.Units,
   159  		PrecisionBundle: m.PrecisionBundle,
   160  	}
   161  
   162  	switch m.Type {
   163  	case MeasurementTypeValue:
   164  		out.Type = StatsTypeValue
   165  		out.N = len(m.Values)
   166  		if out.N == 0 {
   167  			return out
   168  		}
   169  		indices, sum := make([]int, len(m.Values)), 0.0
   170  		for idx, v := range m.Values {
   171  			indices[idx] = idx
   172  			sum += v
   173  		}
   174  		sort.Slice(indices, func(i, j int) bool {
   175  			return m.Values[indices[i]] < m.Values[indices[j]]
   176  		})
   177  		out.ValueBundle = map[Stat]float64{
   178  			StatMin:    m.Values[indices[0]],
   179  			StatMax:    m.Values[indices[out.N-1]],
   180  			StatMean:   sum / float64(out.N),
   181  			StatStdDev: 0.0,
   182  		}
   183  		out.AnnotationBundle = map[Stat]string{
   184  			StatMin: m.Annotations[indices[0]],
   185  			StatMax: m.Annotations[indices[out.N-1]],
   186  		}
   187  
   188  		if out.N%2 == 0 {
   189  			out.ValueBundle[StatMedian] = (m.Values[indices[out.N/2]] + m.Values[indices[out.N/2-1]]) / 2.0
   190  		} else {
   191  			out.ValueBundle[StatMedian] = m.Values[indices[(out.N-1)/2]]
   192  		}
   193  
   194  		for _, v := range m.Values {
   195  			out.ValueBundle[StatStdDev] += (v - out.ValueBundle[StatMean]) * (v - out.ValueBundle[StatMean])
   196  		}
   197  		out.ValueBundle[StatStdDev] = math.Sqrt(out.ValueBundle[StatStdDev] / float64(out.N))
   198  	case MeasurementTypeDuration:
   199  		out.Type = StatsTypeDuration
   200  		out.N = len(m.Durations)
   201  		if out.N == 0 {
   202  			return out
   203  		}
   204  		indices, sum := make([]int, len(m.Durations)), time.Duration(0)
   205  		for idx, v := range m.Durations {
   206  			indices[idx] = idx
   207  			sum += v
   208  		}
   209  		sort.Slice(indices, func(i, j int) bool {
   210  			return m.Durations[indices[i]] < m.Durations[indices[j]]
   211  		})
   212  		out.DurationBundle = map[Stat]time.Duration{
   213  			StatMin:  m.Durations[indices[0]],
   214  			StatMax:  m.Durations[indices[out.N-1]],
   215  			StatMean: sum / time.Duration(out.N),
   216  		}
   217  		out.AnnotationBundle = map[Stat]string{
   218  			StatMin: m.Annotations[indices[0]],
   219  			StatMax: m.Annotations[indices[out.N-1]],
   220  		}
   221  
   222  		if out.N%2 == 0 {
   223  			out.DurationBundle[StatMedian] = (m.Durations[indices[out.N/2]] + m.Durations[indices[out.N/2-1]]) / 2
   224  		} else {
   225  			out.DurationBundle[StatMedian] = m.Durations[indices[(out.N-1)/2]]
   226  		}
   227  		stdDev := 0.0
   228  		for _, v := range m.Durations {
   229  			stdDev += float64(v-out.DurationBundle[StatMean]) * float64(v-out.DurationBundle[StatMean])
   230  		}
   231  		out.DurationBundle[StatStdDev] = time.Duration(math.Sqrt(stdDev / float64(out.N)))
   232  	}
   233  
   234  	return out
   235  }
   236  

View as plain text