<commit_before>package sampler

import (
	"sync"
	"time"
)

const (
	DefaultIntent = iota
	TimeIntent
)

const (
	StatsType = iota
	GaugeType
	CounterType
)

type Fields map[string]interface{}

type Entry struct {
	Metric *Metric                `json:"metric"`
	Time   time.Time              `json:"time"`
	Fields map[string]interface{} `json:"fields"`
	Value  int64                  `json:"value"`
}

func (e *Entry) WithField(key string, value interface{}) *Entry {
	e.Fields[key] = value
	return e
}

func (e *Entry) WithFields(fields Fields) *Entry {
	for key, value := range fields {
		e.Fields[key] = value
	}
	return e
}

func (e *Entry) Int(v int) {
	e.Value = int64(v)
	e.Metric.Write(e)
}

func (e *Entry) Int64(v int64) {
	e.Value = v
	e.Metric.Write(e)
}

func (e *Entry) Duration(d time.Duration) {
	e.Value = d.Nanoseconds()
	e.Metric.Intent = TimeIntent
	e.Metric.Write(e)
}

type Metric struct {
	Name    string   `json:"name"`
	Sampler *Sampler `json:"-"`

	Type   int `json:"type"`
	Intent int `json:"intent"`

	values     []int64    `json:"-"`
	valueMutex sync.Mutex `json:"-"`
}

func (m *Metric) Entry() *Entry {
	return &Entry{
		Metric: m,
		Time:   time.Now(),
		Fields: make(map[string]interface{}),
	}
}

func (m *Metric) WithField(key string, value interface{}) *Entry {
	return m.Entry().WithField(key, value)
}

func (m *Metric) WithFields(fields Fields) *Entry {
	return m.Entry().WithFields(fields)
}

func (m *Metric) Int(v int) {
	m.Entry().Int(v)
}

func (m *Metric) Int64(v int64) {
	m.Entry().Int64(v)
}

func (m *Metric) Duration(d time.Duration) {
	m.Entry().Duration(d)
}

func (m *Metric) Write(e *Entry) {
	m.valueMutex.Lock()
	defer m.valueMutex.Unlock()

	if m.Sampler.Accumulate {
		m.values = append(m.values, e.Value)
	}
	m.Sampler.Write(m, e)
}

func (m *Metric) Min() int64 {
	var min int64
	for _, v := range m.values {
		if min == 0 || v < min {
			min = v
		}
	}
	return min
}

func (m *Metric) Max() int64 {
	var max int64
	for _, v := range m.values {
		if v > max {
			max = v
		}
	}
	return max
}

func (m *Metric) Avg() int64 {
	if len(m.values) == 0 {
		return 0
	}

	var sum int64
	for _, v := range m.values {
		sum += v
	}
	return sum / int64(len(m.values))
}

func (m *Metric) Med() int64 {
	idx := len(m.values) / 2
	if idx >= len(m.values) {
		idx = len(m.values) - 1
	}
	return m.values[idx]
}

func (m *Metric) Sum() int64 {
	sum := int64(0)
	for _, v := range m.values {
		sum += v
	}
	return sum
}

func (m *Metric) Last() int64 {
	return m.values[len(m.values)-1]
}

type Sampler struct {
	Metrics map[string]*Metric
	Outputs []Output
	OnError func(error)

	// Accumulate entry values; allows summary functions on Metrics for some rudimentary summary
	// output. Note that entry metadata is not preserved, only values.
	Accumulate bool

	MetricMutex sync.Mutex
}<commit_msg>[fix] Possible crash in median calculation<commit_after>package sampler

import (
	"sync"
	"time"
)

const (
	DefaultIntent = iota
	TimeIntent
)

const (
	StatsType = iota
	GaugeType
	CounterType
)

type Fields map[string]interface{}

type Entry struct {
	Metric *Metric                `json:"metric"`
	Time   time.Time              `json:"time"`
	Fields map[string]interface{} `json:"fields"`
	Value  int64                  `json:"value"`
}

func (e *Entry) WithField(key string, value interface{}) *Entry {
	e.Fields[key] = value
	return e
}

func (e *Entry) WithFields(fields Fields) *Entry {
	for key, value := range fields {
		e.Fields[key] = value
	}
	return e
}

func (e *Entry) Int(v int) {
	e.Value = int64(v)
	e.Metric.Write(e)
}

func (e *Entry) Int64(v int64) {
	e.Value = v
	e.Metric.Write(e)
}

func (e *Entry) Duration(d time.Duration) {
	e.Value = d.Nanoseconds()
	e.Metric.Intent = TimeIntent
	e.Metric.Write(e)
}

type Metric struct {
	Name    string   `json:"name"`
	Sampler *Sampler `json:"-"`

	Type   int `json:"type"`
	Intent int `json:"intent"`

	values     []int64    `json:"-"`
	valueMutex sync.Mutex `json:"-"`
}

func (m *Metric) Entry() *Entry {
	return &Entry{
		Metric: m,
		Time:   time.Now(),
		Fields: make(map[string]interface{}),
	}
}

func (m *Metric) WithField(key string, value interface{}) *Entry {
	return m.Entry().WithField(key, value)
}

func (m *Metric) WithFields(fields Fields) *Entry {
	return m.Entry().WithFields(fields)
}

func (m *Metric) Int(v int) {
	m.Entry().Int(v)
}

func (m *Metric) Int64(v int64) {
	m.Entry().Int64(v)
}

func (m *Metric) Duration(d time.Duration) {
	m.Entry().Duration(d)
}

func (m *Metric) Write(e *Entry) {
	m.valueMutex.Lock()
	defer m.valueMutex.Unlock()

	if m.Sampler.Accumulate {
		m.values = append(m.values, e.Value)
	}
	m.Sampler.Write(m, e)
}

func (m *Metric) Min() int64 {
	var min int64
	for _, v := range m.values {
		if min == 0 || v < min {
			min = v
		}
	}
	return min
}

func (m *Metric) Max() int64 {
	var max int64
	for _, v := range m.values {
		if v > max {
			max = v
		}
	}
	return max
}

func (m *Metric) Avg() int64 {
	if len(m.values) == 0 {
		return 0
	}

	var sum int64
	for _, v := range m.values {
		sum += v
	}
	return sum / int64(len(m.values))
}

func (m *Metric) Med() int64 {
	switch len(m.values) {
	case 0:
		return 0
	case 1:
		return m.values[0]
	default:
		return m.values[len(m.values)/2]
	}
}

func (m *Metric) Sum() int64 {
	sum := int64(0)
	for _, v := range m.values {
		sum += v
	}
	return sum
}

func (m *Metric) Last() int64 {
	return m.values[len(m.values)-1]
}

type Sampler struct {
	Metrics map[string]*Metric
	Outputs []Output
	OnError func(error)

	// Accumulate entry values; allows summary functions on Metrics for some rudimentary summary
	// output. Note that entry metadata is not preserved, only values.
	Accumulate bool

	MetricMutex sync.Mutex
}
{'fraction_non_alphanumeric': 0.1537253533414762, 'fraction_numerical': 0.016227534461699528, 'mean_word_length': 3.230258302583026, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 6, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6393391', 'n_tokens_mistral': 2339, 'n_tokens_neox': 2230, 'n_words': 619}