2015-05-01 18:48:34 +03:00
|
|
|
package main
|
|
|
|
|
2015-05-02 22:29:02 +03:00
|
|
|
import (
|
|
|
|
"fmt"
|
2016-11-13 19:24:21 +01:00
|
|
|
"math"
|
2015-05-02 22:29:02 +03:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2016-11-10 19:58:10 +01:00
|
|
|
"github.com/antonholmquist/jason"
|
2015-05-02 22:29:02 +03:00
|
|
|
"github.com/pyk/byten"
|
|
|
|
)
|
2015-05-01 18:48:34 +03:00
|
|
|
|
|
|
|
// VarName represents variable name.
|
|
|
|
//
|
|
|
|
// It has dot-separated format, like "memstats.Alloc",
|
|
|
|
// but can be used in different forms, hence it's own type.
|
2015-05-02 10:12:38 +03:00
|
|
|
//
|
|
|
|
// It also can have optional "kind:" modifier, like "mem:" or "duration:"
|
2015-05-01 18:48:34 +03:00
|
|
|
type VarName string
|
|
|
|
|
2015-05-02 22:29:02 +03:00
|
|
|
// VarKind specifies special kinds of values, affects formatting.
|
2015-05-02 20:12:53 +03:00
|
|
|
type VarKind int
|
2015-05-02 10:12:38 +03:00
|
|
|
|
2016-11-12 22:35:51 +01:00
|
|
|
const (
|
|
|
|
KindDefault VarKind = iota
|
|
|
|
KindMemory
|
|
|
|
KindDuration
|
|
|
|
KindString
|
2016-11-12 23:59:56 +01:00
|
|
|
KindGCPauses
|
2016-11-13 15:23:30 +01:00
|
|
|
KindGCIntervals
|
2016-11-12 22:35:51 +01:00
|
|
|
)
|
|
|
|
|
2016-11-10 19:58:10 +01:00
|
|
|
// Var represents arbitrary value for variable.
|
|
|
|
type Var interface {
|
|
|
|
Kind() VarKind
|
|
|
|
String() string
|
|
|
|
Set(*jason.Value)
|
2016-11-13 21:57:32 +01:00
|
|
|
SetNA() // mark as N/A
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
|
|
|
|
2016-11-10 22:34:31 +01:00
|
|
|
// IntVar represents variable which value can be represented as integer,
|
|
|
|
// and suitable for displaying with sparklines.
|
|
|
|
type IntVar interface {
|
|
|
|
Var
|
|
|
|
Value() int
|
|
|
|
}
|
|
|
|
|
2016-11-12 23:59:56 +01:00
|
|
|
// NewVar inits new Var object with the given name.
|
|
|
|
func NewVar(name VarName) Var {
|
|
|
|
kind := name.Kind()
|
|
|
|
|
|
|
|
switch kind {
|
|
|
|
case KindDefault:
|
|
|
|
return &Number{}
|
|
|
|
case KindMemory:
|
|
|
|
return &Memory{}
|
|
|
|
case KindDuration:
|
|
|
|
return &Duration{}
|
|
|
|
case KindString:
|
|
|
|
return &String{}
|
|
|
|
case KindGCPauses:
|
|
|
|
return &GCPauses{}
|
2016-11-13 15:23:30 +01:00
|
|
|
case KindGCIntervals:
|
|
|
|
return &GCIntervals{}
|
2016-11-12 23:59:56 +01:00
|
|
|
default:
|
|
|
|
return &Number{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-10 23:59:32 +01:00
|
|
|
// Number is a type for numeric values, obtained from JSON.
|
|
|
|
// In JSON it's always float64, so there is no straightforward way
|
|
|
|
// to separate float from int, so let's keep everything as float.
|
2016-11-10 19:58:10 +01:00
|
|
|
type Number struct {
|
|
|
|
val float64
|
2016-11-13 21:57:32 +01:00
|
|
|
na bool
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Number) Kind() VarKind { return KindDefault }
|
|
|
|
func (v *Number) String() string {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
|
|
|
return "N/A"
|
|
|
|
}
|
2016-11-13 19:24:21 +01:00
|
|
|
// if fraction part is zero, assume int's integer
|
|
|
|
if _, frac := math.Modf(v.val); frac == 0 {
|
|
|
|
return fmt.Sprintf("%.0f", v.val)
|
|
|
|
}
|
|
|
|
// else, return as float
|
2016-11-10 19:58:10 +01:00
|
|
|
return fmt.Sprintf("%.02f", v.val)
|
|
|
|
}
|
|
|
|
func (v *Number) Set(j *jason.Value) {
|
2016-11-13 21:57:32 +01:00
|
|
|
v.na = false
|
2016-11-12 14:02:16 +01:00
|
|
|
if n, err := j.Float64(); err == nil {
|
2016-11-10 19:58:10 +01:00
|
|
|
v.val = n
|
2016-11-12 14:02:16 +01:00
|
|
|
} else if n, err := j.Int64(); err == nil {
|
|
|
|
v.val = float64(n)
|
2016-11-10 19:58:10 +01:00
|
|
|
} else {
|
|
|
|
v.val = 0
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *Number) SetNA() {
|
|
|
|
v.na = true
|
|
|
|
v.val = 0
|
|
|
|
}
|
2016-11-10 19:58:10 +01:00
|
|
|
|
2016-11-10 22:34:31 +01:00
|
|
|
// Value implements IntVar for Number type.
|
|
|
|
func (v *Number) Value() int {
|
|
|
|
return int(v.val)
|
|
|
|
}
|
|
|
|
|
2016-11-10 23:59:32 +01:00
|
|
|
// Memory represents memory information in bytes.
|
2016-11-10 19:58:10 +01:00
|
|
|
type Memory struct {
|
|
|
|
bytes int64
|
2016-11-13 21:57:32 +01:00
|
|
|
na bool
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Memory) Kind() VarKind { return KindMemory }
|
|
|
|
func (v *Memory) String() string {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
|
|
|
return "N/A"
|
|
|
|
}
|
2016-11-10 19:58:10 +01:00
|
|
|
return fmt.Sprintf("%s", byten.Size(v.bytes))
|
|
|
|
}
|
|
|
|
func (v *Memory) Set(j *jason.Value) {
|
2016-11-13 21:57:32 +01:00
|
|
|
v.na = false
|
2016-11-10 19:58:10 +01:00
|
|
|
if n, err := j.Int64(); err == nil {
|
|
|
|
v.bytes = n
|
|
|
|
} else {
|
|
|
|
v.bytes = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-10 22:34:31 +01:00
|
|
|
// Value implements IntVar for Memory type.
|
|
|
|
func (v *Memory) Value() int {
|
|
|
|
// TODO: check for possible overflows
|
|
|
|
return int(v.bytes)
|
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *Memory) SetNA() {
|
|
|
|
v.na = true
|
|
|
|
v.bytes = 0
|
|
|
|
}
|
2016-11-10 22:34:31 +01:00
|
|
|
|
2016-11-10 23:59:32 +01:00
|
|
|
// Duration represents duration data (in ns)
|
2016-11-10 19:58:10 +01:00
|
|
|
type Duration struct {
|
|
|
|
dur time.Duration
|
2016-11-13 21:57:32 +01:00
|
|
|
na bool
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Duration) Kind() VarKind { return KindDuration }
|
|
|
|
func (v *Duration) String() string {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
|
|
|
return "N/A"
|
|
|
|
}
|
2016-11-13 16:26:23 +01:00
|
|
|
return fmt.Sprintf("%s", round(time.Duration(v.dur)))
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v *Duration) Set(j *jason.Value) {
|
2016-11-13 21:57:32 +01:00
|
|
|
v.na = false
|
2016-11-10 19:58:10 +01:00
|
|
|
if n, err := j.Int64(); err == nil {
|
|
|
|
v.dur = time.Duration(n)
|
|
|
|
} else if n, err := j.Float64(); err == nil {
|
|
|
|
v.dur = time.Duration(int64(n))
|
|
|
|
} else {
|
|
|
|
v.dur = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-10 22:34:31 +01:00
|
|
|
// Value implements IntVar for Duration type.
|
|
|
|
func (v *Duration) Value() int {
|
|
|
|
// TODO: check for possible overflows
|
|
|
|
return int(v.dur)
|
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *Duration) SetNA() {
|
|
|
|
v.na = true
|
|
|
|
v.dur = 0
|
|
|
|
}
|
2016-11-10 22:34:31 +01:00
|
|
|
|
2016-11-10 23:59:32 +01:00
|
|
|
// Strings represents string data.
|
2016-11-10 19:58:10 +01:00
|
|
|
type String struct {
|
|
|
|
str string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *String) Kind() VarKind { return KindString }
|
|
|
|
func (v *String) String() string { return v.str }
|
|
|
|
func (v *String) Set(j *jason.Value) {
|
|
|
|
if n, err := j.String(); err == nil {
|
|
|
|
v.str = n
|
|
|
|
} else {
|
|
|
|
v.str = "N/A"
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *String) SetNA() {
|
|
|
|
v.str = "N/A"
|
|
|
|
}
|
2016-11-10 19:58:10 +01:00
|
|
|
|
2016-11-12 23:59:56 +01:00
|
|
|
// GCPauses represents GC pauses data.
|
|
|
|
//
|
|
|
|
// It uses memstat.PauseNS circular buffer, but lacks
|
|
|
|
// NumGC information, so we don't know what the start
|
|
|
|
// and the end. It's enough for most stats, though.
|
|
|
|
type GCPauses struct {
|
|
|
|
pauses [256]uint64
|
2016-11-13 19:24:21 +01:00
|
|
|
hist *Histogram
|
2016-11-13 21:57:32 +01:00
|
|
|
na bool
|
2016-11-12 23:59:56 +01:00
|
|
|
}
|
2016-11-10 19:58:10 +01:00
|
|
|
|
2016-11-13 19:24:21 +01:00
|
|
|
func (v *GCPauses) Kind() VarKind { return KindGCPauses }
|
|
|
|
func (v *GCPauses) String() string {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
|
|
|
return "N/A"
|
2016-11-13 19:24:21 +01:00
|
|
|
}
|
|
|
|
// return Mean by default
|
|
|
|
return fmt.Sprintf("%v", round(time.Duration(v.hist.Mean())))
|
|
|
|
}
|
|
|
|
func (v *GCPauses) Value() int {
|
|
|
|
if v.hist == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
// return Mean by default
|
|
|
|
return int(v.hist.Mean())
|
|
|
|
}
|
2016-11-12 23:59:56 +01:00
|
|
|
func (v *GCPauses) Set(j *jason.Value) {
|
2016-11-13 21:57:32 +01:00
|
|
|
v.na = false
|
2016-11-12 23:59:56 +01:00
|
|
|
v.pauses = [256]uint64{}
|
|
|
|
if arr, err := j.Array(); err == nil {
|
|
|
|
for i := 0; i < len(arr); i++ {
|
|
|
|
p, _ := arr[i].Int64()
|
|
|
|
v.pauses[i] = uint64(p)
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 19:24:21 +01:00
|
|
|
|
|
|
|
// we need histogram object
|
|
|
|
// to access mean() method
|
|
|
|
// number of bins doesn't matter here
|
|
|
|
v.hist = v.Histogram(1)
|
2016-11-12 23:59:56 +01:00
|
|
|
}
|
|
|
|
func (v *GCPauses) Histogram(bins int) *Histogram {
|
|
|
|
hist := NewHistogram(bins)
|
|
|
|
for i := 0; i < 256; i++ {
|
2016-11-13 12:05:26 +01:00
|
|
|
// we ignore zeros, since
|
|
|
|
// its never the case, but
|
|
|
|
// we have zeros on the very beginning
|
|
|
|
if v.pauses[i] > 0 {
|
|
|
|
hist.Add(v.pauses[i])
|
|
|
|
}
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
2016-11-13 19:24:21 +01:00
|
|
|
v.hist = hist
|
|
|
|
return v.hist
|
2016-11-10 19:58:10 +01:00
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *GCPauses) SetNA() {
|
|
|
|
v.na = true
|
|
|
|
}
|
2015-05-02 22:29:02 +03:00
|
|
|
|
2016-11-13 15:23:30 +01:00
|
|
|
// GCIntervals represents GC pauses intervals.
|
|
|
|
//
|
|
|
|
// It uses memstat.PauseEnd circular buffer w/
|
|
|
|
// timestamps.
|
|
|
|
type GCIntervals struct {
|
|
|
|
intervals [256]uint64
|
2016-11-13 19:24:21 +01:00
|
|
|
hist *Histogram
|
2016-11-13 21:57:32 +01:00
|
|
|
na bool
|
2016-11-13 15:23:30 +01:00
|
|
|
}
|
|
|
|
|
2016-11-13 19:24:21 +01:00
|
|
|
func (v *GCIntervals) Kind() VarKind { return KindGCIntervals }
|
|
|
|
func (v *GCIntervals) String() string {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
|
|
|
return "N/A"
|
2016-11-13 19:24:21 +01:00
|
|
|
}
|
|
|
|
// return Mean by default
|
|
|
|
return fmt.Sprintf("%v", round(time.Duration(v.hist.Mean())))
|
|
|
|
}
|
|
|
|
func (v *GCIntervals) Value() int {
|
2016-11-13 21:57:32 +01:00
|
|
|
if v.na {
|
2016-11-13 19:24:21 +01:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
// return Mean by default
|
|
|
|
return int(v.hist.Mean())
|
|
|
|
}
|
2016-11-13 15:23:30 +01:00
|
|
|
func (v *GCIntervals) Set(j *jason.Value) {
|
2016-11-13 21:57:32 +01:00
|
|
|
v.na = false
|
2016-11-13 15:23:30 +01:00
|
|
|
v.intervals = [256]uint64{}
|
|
|
|
// as original array contains UNIX timestamps,
|
|
|
|
// we want to calculate diffs to previous values (interval)
|
|
|
|
// and work with them
|
|
|
|
duration := func(a, b int64) uint64 {
|
|
|
|
dur := int64(a - b)
|
|
|
|
if dur < 0 {
|
|
|
|
dur = -dur
|
|
|
|
}
|
|
|
|
return uint64(dur)
|
|
|
|
}
|
|
|
|
var prev int64
|
|
|
|
if arr, err := j.Array(); err == nil {
|
2016-11-13 19:24:21 +01:00
|
|
|
// process first elem
|
|
|
|
p, _ := arr[0].Int64()
|
|
|
|
plast, _ := arr[255].Int64()
|
|
|
|
v.intervals[0] = duration(p, plast)
|
|
|
|
prev = p
|
|
|
|
|
2016-11-13 15:23:30 +01:00
|
|
|
for i := 1; i < len(arr); i++ {
|
|
|
|
p, _ := arr[i].Int64()
|
2016-11-13 17:05:00 +01:00
|
|
|
if p == 0 {
|
|
|
|
break
|
|
|
|
}
|
2016-11-13 15:23:30 +01:00
|
|
|
v.intervals[i] = duration(p, prev)
|
|
|
|
prev = p
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 19:24:21 +01:00
|
|
|
|
|
|
|
// the same as for GCPauses, we need it for mean()
|
|
|
|
// and number of bins doesn't matter here
|
|
|
|
v.hist = v.Histogram(1)
|
2016-11-13 15:23:30 +01:00
|
|
|
}
|
|
|
|
func (v *GCIntervals) Histogram(bins int) *Histogram {
|
|
|
|
hist := NewHistogram(bins)
|
|
|
|
|
|
|
|
// we need to skip maximum value here
|
|
|
|
// because it's always a diff between last and fist
|
|
|
|
// elem in cicrular buffer (we don't know NumGC)
|
|
|
|
var max uint64
|
|
|
|
for i := 0; i < 256; i++ {
|
|
|
|
if v.intervals[i] > max {
|
|
|
|
max = v.intervals[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < 256; i++ {
|
|
|
|
// we ignore zeros, since
|
|
|
|
// its never the case, but
|
|
|
|
// we have zeros on the very beginning
|
2016-11-13 19:24:21 +01:00
|
|
|
if v.intervals[i] > 0 && v.intervals[i] < max {
|
2016-11-13 15:23:30 +01:00
|
|
|
hist.Add(v.intervals[i])
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 19:24:21 +01:00
|
|
|
v.hist = hist
|
2016-11-13 15:23:30 +01:00
|
|
|
return hist
|
|
|
|
}
|
2016-11-13 21:57:32 +01:00
|
|
|
func (v *GCIntervals) SetNA() {
|
|
|
|
v.na = true
|
|
|
|
}
|
2016-11-13 15:23:30 +01:00
|
|
|
|
|
|
|
// TODO: add boolean, timestamp, types
|
2016-11-12 23:59:56 +01:00
|
|
|
|
2015-05-01 18:48:34 +03:00
|
|
|
// ToSlice converts "dot-separated" notation into the "slice of strings".
|
|
|
|
//
|
|
|
|
// "dot-separated" notation is a human-readable format, passed via args.
|
|
|
|
// "slice of strings" is used by Jason library.
|
|
|
|
//
|
|
|
|
// Example: "memstats.Alloc" => []string{"memstats", "Alloc"}
|
2015-05-02 10:12:38 +03:00
|
|
|
// Example: "mem:memstats.Alloc" => []string{"memstats", "Alloc"}
|
2015-05-01 18:48:34 +03:00
|
|
|
func (v VarName) ToSlice() []string {
|
2015-05-02 10:12:38 +03:00
|
|
|
start := strings.IndexRune(string(v), ':') + 1
|
allow escaping the dot character in paths using a backslash
Occasionally expvar output contains keys that are hostnames,
IP addresses, or filenames that contain the "." character. For
example:
{
"bleve": {
"bootDuration": 16559,
"indexes": {
"bench.bleve": {
"index": {
"analysis_time": 10889841135,
"batches": 145,
"deletes": 0,
"errors": 0,
"index_time": 21277401883,
"lookup_queue_len": 0,
"updates": 14500
},
"search_time": 0,
"searches": 0
}
}
}
...
}
I can now chart the lookup_queue_len value using the var:
bleve.indexes.bench\.bleve.index.lookup_queue_len
This partially addresses #11 (still does not escape colon character).
2015-12-01 16:53:13 -05:00
|
|
|
slice := DottedFieldsToSliceEscaped(string(v)[start:])
|
2015-05-02 10:12:38 +03:00
|
|
|
return slice
|
2015-05-01 18:48:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Short returns short name, which is typically is the last word in the long names.
|
|
|
|
func (v VarName) Short() string {
|
|
|
|
if v == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
slice := v.ToSlice()
|
|
|
|
return slice[len(slice)-1]
|
|
|
|
}
|
2015-05-02 10:12:38 +03:00
|
|
|
|
2015-05-02 10:22:49 +03:00
|
|
|
// Long returns long name, without kind: modifier.
|
|
|
|
func (v VarName) Long() string {
|
|
|
|
if v == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
start := strings.IndexRune(string(v), ':') + 1
|
|
|
|
return string(v)[start:]
|
|
|
|
}
|
|
|
|
|
2016-11-12 23:59:56 +01:00
|
|
|
// Kind returns kind of variable, based on it's name
|
|
|
|
// modifiers ("mem:") or full names for special cases.
|
2015-05-02 20:12:53 +03:00
|
|
|
func (v VarName) Kind() VarKind {
|
2016-11-12 23:59:56 +01:00
|
|
|
if v.Long() == "memstats.PauseNs" {
|
|
|
|
return KindGCPauses
|
|
|
|
}
|
2016-11-13 15:23:30 +01:00
|
|
|
if v.Long() == "memstats.PauseEnd" {
|
|
|
|
return KindGCIntervals
|
|
|
|
}
|
2016-11-12 23:59:56 +01:00
|
|
|
|
2015-05-02 10:12:38 +03:00
|
|
|
start := strings.IndexRune(string(v), ':')
|
|
|
|
if start == -1 {
|
|
|
|
return KindDefault
|
|
|
|
}
|
|
|
|
|
|
|
|
switch string(v)[:start] {
|
|
|
|
case "mem":
|
|
|
|
return KindMemory
|
|
|
|
case "duration":
|
|
|
|
return KindDuration
|
2015-05-02 21:43:32 +03:00
|
|
|
case "str":
|
|
|
|
return KindString
|
2015-05-02 10:12:38 +03:00
|
|
|
}
|
|
|
|
return KindDefault
|
|
|
|
}
|
2015-05-02 22:29:02 +03:00
|
|
|
|
2016-11-13 17:05:00 +01:00
|
|
|
// rate calculates rate per seconds for the duration.
|
|
|
|
func rate(d time.Duration, precision time.Duration) float64 {
|
|
|
|
return float64(precision) / float64(d)
|
|
|
|
}
|
|
|
|
|
2016-11-13 16:26:23 +01:00
|
|
|
// round removes unneeded precision from the String() output for time.Duration.
|
|
|
|
func round(d time.Duration) time.Duration {
|
2015-05-03 00:00:05 +03:00
|
|
|
r := time.Second
|
|
|
|
if d < time.Second {
|
|
|
|
r = time.Millisecond
|
|
|
|
}
|
|
|
|
if d < time.Millisecond {
|
|
|
|
r = time.Microsecond
|
|
|
|
}
|
2016-11-12 14:02:16 +01:00
|
|
|
if d < time.Microsecond {
|
|
|
|
r = time.Nanosecond
|
|
|
|
}
|
2015-05-03 00:00:05 +03:00
|
|
|
if r <= 0 {
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
neg := d < 0
|
|
|
|
if neg {
|
|
|
|
d = -d
|
|
|
|
}
|
|
|
|
if m := d % r; m+m < r {
|
|
|
|
d = d - m
|
|
|
|
} else {
|
|
|
|
d = d + r - m
|
|
|
|
}
|
|
|
|
if neg {
|
|
|
|
return -d
|
|
|
|
}
|
|
|
|
return d
|
|
|
|
}
|
allow escaping the dot character in paths using a backslash
Occasionally expvar output contains keys that are hostnames,
IP addresses, or filenames that contain the "." character. For
example:
{
"bleve": {
"bootDuration": 16559,
"indexes": {
"bench.bleve": {
"index": {
"analysis_time": 10889841135,
"batches": 145,
"deletes": 0,
"errors": 0,
"index_time": 21277401883,
"lookup_queue_len": 0,
"updates": 14500
},
"search_time": 0,
"searches": 0
}
}
}
...
}
I can now chart the lookup_queue_len value using the var:
bleve.indexes.bench\.bleve.index.lookup_queue_len
This partially addresses #11 (still does not escape colon character).
2015-12-01 16:53:13 -05:00
|
|
|
|
|
|
|
func DottedFieldsToSliceEscaped(s string) []string {
|
|
|
|
rv := make([]string, 0)
|
|
|
|
lastSlash := false
|
|
|
|
curr := ""
|
|
|
|
for _, r := range s {
|
|
|
|
// base case, dot not after slash
|
|
|
|
if !lastSlash && r == '.' {
|
|
|
|
if len(curr) > 0 {
|
|
|
|
rv = append(rv, curr)
|
|
|
|
curr = ""
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
} else if !lastSlash {
|
|
|
|
// any character not after slash
|
|
|
|
curr += string(r)
|
|
|
|
if r == '\\' {
|
|
|
|
lastSlash = true
|
|
|
|
} else {
|
|
|
|
lastSlash = false
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
} else if r == '\\' {
|
|
|
|
// last was slash, and so is this
|
|
|
|
lastSlash = false // 2 slashes = 0
|
|
|
|
// we already appended a single slash on first
|
|
|
|
continue
|
|
|
|
} else if r == '.' {
|
|
|
|
// we see \. but already appended \ last time
|
|
|
|
// replace it with .
|
|
|
|
curr = curr[:len(curr)-1] + "."
|
|
|
|
lastSlash = false
|
|
|
|
} else {
|
|
|
|
// \ and any other character, ignore
|
|
|
|
curr += string(r)
|
|
|
|
lastSlash = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(curr) > 0 {
|
|
|
|
rv = append(rv, curr)
|
|
|
|
}
|
|
|
|
return rv
|
|
|
|
}
|