142 lines
6.1 KiB
Go
142 lines
6.1 KiB
Go
//The log format encodes into a single file, multiple histograms with optional shared meta data.
|
|
package hdrhistogram
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
const HISTOGRAM_LOG_FORMAT_VERSION = "1.3"
|
|
const MsToNsRatio float64 = 1000000.0
|
|
|
|
type HistogramLogOptions struct {
|
|
startTimeStampSec float64
|
|
endTimeStampSec float64
|
|
maxValueUnitRatio float64
|
|
}
|
|
|
|
func DefaultHistogramLogOptions() *HistogramLogOptions {
|
|
return &HistogramLogOptions{0, 0, MsToNsRatio}
|
|
}
|
|
|
|
type HistogramLogWriter struct {
|
|
baseTime int64
|
|
log io.Writer
|
|
}
|
|
|
|
// Return the current base time offset
|
|
func (lw *HistogramLogWriter) BaseTime() int64 {
|
|
return lw.baseTime
|
|
}
|
|
|
|
// Set a base time to subtract from supplied histogram start/end timestamps when
|
|
// logging based on histogram timestamps.
|
|
// baseTime is expected to be in msec since the epoch, as histogram start/end times
|
|
// are typically stamped with absolute times in msec since the epoch.
|
|
func (lw *HistogramLogWriter) SetBaseTime(baseTime int64) {
|
|
lw.baseTime = baseTime
|
|
}
|
|
|
|
func NewHistogramLogWriter(log io.Writer) *HistogramLogWriter {
|
|
return &HistogramLogWriter{baseTime: 0, log: log}
|
|
}
|
|
|
|
// Output an interval histogram, using the start/end timestamp indicated in the histogram, and the [optional] tag associated with the histogram.
|
|
// The histogram start and end timestamps are assumed to be in msec units
|
|
//
|
|
// By convention, histogram start/end time are generally stamped with absolute times in msec
|
|
// since the epoch. For logging with absolute time stamps, the base time would remain zero ( default ).
|
|
// For logging with relative time stamps (time since a start point), the base time should be set with SetBaseTime(baseTime int64)
|
|
//
|
|
// The max value in the histogram will be reported scaled down by a default maxValueUnitRatio of 1000000.0 (which is the msec : nsec ratio).
|
|
// If you need to specify a different start/end timestamp or a different maxValueUnitRatio you should use OutputIntervalHistogramWithLogOptions(histogram *Histogram, logOptions *HistogramLogOptions)
|
|
func (lw *HistogramLogWriter) OutputIntervalHistogram(histogram *Histogram) (err error) {
|
|
return lw.OutputIntervalHistogramWithLogOptions(histogram, nil)
|
|
}
|
|
|
|
// Output an interval histogram, with the given timestamp information and the [optional] tag associated with the histogram
|
|
//
|
|
// If you specify non-nil logOptions, and non-zero start timestamp, the the specified timestamp information will be used, and the start timestamp information in the actual histogram will be ignored.
|
|
// If you specify non-nil logOptions, and non-zero start timestamp, the the specified timestamp information will be used, and the end timestamp information in the actual histogram will be ignored.
|
|
// If you specify non-nil logOptions, The max value reported with the interval line will be scaled by the given maxValueUnitRatio,
|
|
// otherwise a default maxValueUnitRatio of 1,000,000 (which is the msec : nsec ratio) will be used.
|
|
//
|
|
// By convention, histogram start/end time are generally stamped with absolute times in msec
|
|
// since the epoch. For logging with absolute time stamps, the base time would remain zero ( default ).
|
|
// For logging with relative time stamps (time since a start point), the base time should be set with SetBaseTime(baseTime int64)
|
|
func (lw *HistogramLogWriter) OutputIntervalHistogramWithLogOptions(histogram *Histogram, logOptions *HistogramLogOptions) (err error) {
|
|
tag := histogram.Tag()
|
|
var match bool
|
|
tagStr := ""
|
|
if tag != "" {
|
|
match, err = regexp.MatchString(".[, \\r\\n].", tag)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if match {
|
|
err = fmt.Errorf("Tag string cannot contain commas, spaces, or line breaks. Used tag: %s", tag)
|
|
return
|
|
}
|
|
tagStr = fmt.Sprintf("Tag=%s,", tag)
|
|
}
|
|
var usedStartTime float64 = float64(histogram.StartTimeMs())
|
|
var usedEndTime float64 = float64(histogram.EndTimeMs())
|
|
var maxValueUnitRatio float64 = MsToNsRatio
|
|
if logOptions != nil {
|
|
if logOptions.startTimeStampSec != 0 {
|
|
usedStartTime = logOptions.startTimeStampSec
|
|
}
|
|
if logOptions.endTimeStampSec != 0 {
|
|
usedEndTime = logOptions.endTimeStampSec
|
|
}
|
|
maxValueUnitRatio = logOptions.maxValueUnitRatio
|
|
}
|
|
startTime := usedStartTime - float64(lw.baseTime)/1000.0
|
|
endTime := usedEndTime - float64(lw.baseTime)/1000.0
|
|
maxValueAsDouble := float64(histogram.Max()) / maxValueUnitRatio
|
|
cpayload, err := histogram.Encode(V2CompressedEncodingCookieBase)
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = lw.log.Write([]byte(fmt.Sprintf("%s%f,%f,%f,%s\n", tagStr, startTime, endTime, maxValueAsDouble, string(cpayload))))
|
|
return
|
|
}
|
|
|
|
// Log a start time in the log.
|
|
// Start time is represented as seconds since epoch with up to 3 decimal places. Line starts with the leading text '#[StartTime:'
|
|
func (lw *HistogramLogWriter) OutputStartTime(start_time_msec int64) (err error) {
|
|
secs := start_time_msec / 1000
|
|
iso_str := time.Unix(secs, start_time_msec%int64(1000)*int64(1000000000)).Format(time.RFC3339)
|
|
_, err = lw.log.Write([]byte(fmt.Sprintf("#[StartTime: %d (seconds since epoch), %s]\n", secs, iso_str)))
|
|
return
|
|
}
|
|
|
|
// Log a base time in the log.
|
|
// Base time is represented as seconds since epoch with up to 3 decimal places. Line starts with the leading text '#[BaseTime:'
|
|
func (lw *HistogramLogWriter) OutputBaseTime(base_time_msec int64) (err error) {
|
|
secs := base_time_msec / 1000
|
|
_, err = lw.log.Write([]byte(fmt.Sprintf("#[Basetime: %d (seconds since epoch)]\n", secs)))
|
|
return
|
|
}
|
|
|
|
// Log a comment to the log.
|
|
// A comment is any line that leads with '#' that is not matched by the BaseTime or StartTime formats. Comments are ignored when parsed.
|
|
func (lw *HistogramLogWriter) OutputComment(comment string) (err error) {
|
|
_, err = lw.log.Write([]byte(fmt.Sprintf("#%s\n", comment)))
|
|
return
|
|
}
|
|
|
|
// Output a legend line to the log.
|
|
// Human readable column headers. Ignored when parsed.
|
|
func (lw *HistogramLogWriter) OutputLegend() (err error) {
|
|
_, err = lw.log.Write([]byte("\"StartTimestamp\",\"Interval_Length\",\"Interval_Max\",\"Interval_Compressed_Histogram\"\n"))
|
|
return
|
|
}
|
|
|
|
// Output a log format version to the log.
|
|
func (lw *HistogramLogWriter) OutputLogFormatVersion() (err error) {
|
|
return lw.OutputComment(fmt.Sprintf("[Histogram log format version %s]", HISTOGRAM_LOG_FORMAT_VERSION))
|
|
}
|