pkg/genlib/generator.go (148 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package genlib
import (
"bytes"
"fmt"
"github.com/Pallinder/go-randomdata"
"github.com/lithammer/shortuuid/v3"
"math/rand"
"strings"
"time"
)
var customRand *rand.Rand
const (
textTemplateEngine = iota
customTemplateEngine
)
func fieldValueWrapByType(field Field) string {
if len(field.Value) > 0 {
return ""
}
switch field.Type {
case FieldTypeDate, FieldTypeIP:
return "\""
case FieldTypeDouble, FieldTypeFloat, FieldTypeHalfFloat, FieldTypeScaledFloat:
return ""
case FieldTypeInteger, FieldTypeLong, FieldTypeUnsignedLong:
return ""
case FieldTypeConstantKeyword:
return "\""
case FieldTypeKeyword:
return "\""
case FieldTypeBool:
return ""
case FieldTypeObject, FieldTypeNested, FieldTypeFlattened:
if len(field.ObjectType) > 0 {
field.Type = field.ObjectType
} else {
field.Type = FieldTypeKeyword
}
return fieldValueWrapByType(field)
case FieldTypeGeoPoint:
return "\""
default:
return "\""
}
}
func generateCustomTemplateFromField(cfg Config, fields Fields) ([]byte, []Field) {
return generateTemplateFromField(cfg, fields, customTemplateEngine)
}
func generateTextTemplateFromField(cfg Config, fields Fields) ([]byte, []Field) {
return generateTemplateFromField(cfg, fields, textTemplateEngine)
}
func generateTemplateFromField(cfg Config, fields Fields, templateEngine int) ([]byte, []Field) {
if len(fields) == 0 {
return nil, nil
}
dupes := make(map[string]struct{})
objectKeysField := make([]Field, 0, len(fields))
templatePrefix := "{ "
templateBuffer := bytes.NewBufferString(templatePrefix)
for i, field := range fields {
fieldWrap := fieldValueWrapByType(field)
if fieldCfg, ok := cfg.GetField(field.Name); ok {
if fieldCfg.Value != nil {
fieldWrap = ""
}
}
fieldTrailer := []byte(",")
if i == len(fields)-1 {
fieldTrailer = []byte(" }")
}
if strings.HasSuffix(field.Name, ".*") || field.Type == FieldTypeObject || field.Type == FieldTypeNested || field.Type == FieldTypeFlattened {
// This is a special case. We are randomly generating keys on the fly
// Will set the json field name as "field.Name.N"
N := 5
for ii := 0; ii < N; ii++ {
// Fire or skip
if rand.Int()%2 == 0 {
continue
}
if string(fieldTrailer) == "}" && ii < N-1 {
fieldTrailer = []byte(",")
}
var try int
const maxTries = 10
rNoun := randomdata.Noun()
_, ok := dupes[rNoun]
for ; ok && try < maxTries; try++ {
rNoun = randomdata.Noun()
_, ok = dupes[rNoun]
}
// If all else fails, use a shortuuid.
// Try to avoid this as it is alloc expensive
if try >= maxTries {
rNoun = shortuuid.New()
}
dupes[rNoun] = struct{}{}
var fieldTemplate string
fieldNameRoot := replacer.Replace(field.Name)
fieldVariableName := fieldNormalizerRegex.ReplaceAllString(fmt.Sprintf("%s%s", fieldNameRoot, rNoun), "")
fieldVariableName += "Var"
if field.Type == FieldTypeDate {
if templateEngine == textTemplateEngine {
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s.%s" }}"%s.%s": %s{{$%s.Format "2006-01-02T15:04:05.999999999Z07:00"}}%s%s`, fieldVariableName, fieldNameRoot, rNoun, fieldNameRoot, rNoun, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
} else if templateEngine == customTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s.%s": %s{{.%s.%s}}%s%s`, fieldNameRoot, rNoun, fieldWrap, fieldNameRoot, rNoun, fieldWrap, fieldTrailer)
}
} else {
if templateEngine == textTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s.%s": %s{{generate "%s.%s"}}%s%s`, fieldNameRoot, rNoun, fieldWrap, fieldNameRoot, rNoun, fieldWrap, fieldTrailer)
} else if templateEngine == customTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s.%s": %s{{.%s.%s}}%s%s`, fieldNameRoot, rNoun, fieldWrap, fieldNameRoot, rNoun, fieldWrap, fieldTrailer)
}
}
originalFieldName := field.Name
field.Name = fieldNameRoot + "." + rNoun
objectKeysField = append(objectKeysField, field)
field.Name = originalFieldName
templateBuffer.WriteString(fieldTemplate)
}
} else {
var fieldTemplate string
fieldVariableName := fieldNormalizerRegex.ReplaceAllString(field.Name, "")
fieldVariableName += "Var"
if field.Type == FieldTypeDate {
if templateEngine == textTemplateEngine {
fieldTemplate = fmt.Sprintf(`{{ $%s := generate "%s" }}"%s": %s{{$%s.Format "2006-01-02T15:04:05.999999999Z07:00"}}%s%s`, fieldVariableName, field.Name, field.Name, fieldWrap, fieldVariableName, fieldWrap, fieldTrailer)
} else if templateEngine == customTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s": %s{{.%s}}%s%s`, field.Name, fieldWrap, field.Name, fieldWrap, fieldTrailer)
}
} else {
if templateEngine == textTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s": %s{{generate "%s"}}%s%s`, field.Name, fieldWrap, field.Name, fieldWrap, fieldTrailer)
} else if templateEngine == customTemplateEngine {
fieldTemplate = fmt.Sprintf(`"%s": %s{{.%s}}%s%s`, field.Name, fieldWrap, field.Name, fieldWrap, fieldTrailer)
}
}
templateBuffer.WriteString(fieldTemplate)
}
}
return templateBuffer.Bytes(), objectKeysField
}
func NewGenerator(cfg Config, flds Fields, totEvents uint64) (Generator, error) {
template, objectKeysField := generateCustomTemplateFromField(cfg, flds)
flds = append(flds, objectKeysField...)
return NewGeneratorWithCustomTemplate(template, cfg, flds, totEvents)
}
// InitGeneratorTimeNow sets base timeNow for `date` field
func InitGeneratorTimeNow(timeNow time.Time) {
// set timeNowToBind to --now flag (already parsed or now)
timeNowToBind = timeNow
}
// InitGeneratorRandSeed sets rand seed
func InitGeneratorRandSeed(randSeed int64) {
// set rand and randomdata seed to --seed flag (custom or 1)
customRand = rand.New(rand.NewSource(randSeed))
randomdata.CustomRand(customRand)
}