Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,21 @@ package main
import (
"fmt"
"log"
"os"

"github.com/ua-parser/uap-go/uaparser"
)

func main() {
uagent := "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.1.0-80) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true"

parser, err := uaparser.New("./regexes.yaml")

regexes, err := os.ReadFile("./regexes.yaml")
if err != nil {
log.Fatal(err)
}

parser, err := uaparser.New(regexes)
if err != nil {
log.Fatal(err)
}
Expand Down
29 changes: 25 additions & 4 deletions test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,26 @@ func main() {
fmt.Printf("Usage: %s [old|new|both] [concurrency level]\n", os.Args[0])
return
}

regexes, err := os.ReadFile("./uap-core/regexes.yaml")
if err != nil {
fmt.Printf("Failed to read regexes file. Error: %s\n", err.Error())
return
}

var wg sync.WaitGroup
cLevel, _ := strconv.Atoi(os.Args[2])
switch os.Args[1] {
case "new":
fmt.Println("Running new version of uap...")
uaParser, _ := uaparser.NewWithOptions("./uap-core/regexes.yaml", (uaparser.EOsLookUpMode | uaparser.EUserAgentLookUpMode), 100, 20, true, true, 1024)
uaParser, _ := uaparser.New(regexes,
uaparser.WithMode(uaparser.EOsLookUpMode|uaparser.EUserAgentLookUpMode),
uaparser.WithMissesThreshold(100),
uaparser.WithMatchIdxNotOk(20),
uaparser.WithSort(true),
uaparser.WithDebug(true),
uaparser.WithCacheSize(1024),
)
for i := 0; i < cLevel; i++ {
wg.Add(1)
go runTest(uaParser, i, &wg)
Expand All @@ -30,7 +44,7 @@ func main() {
return
case "old":
fmt.Println("Running old version of uap...")
uaParser, _ := uaparser.New("./uap-core/regexes.yaml")
uaParser, _ := uaparser.New(regexes)
for i := 0; i < cLevel; i++ {
wg.Add(1)
go runTest(uaParser, i, &wg)
Expand All @@ -39,13 +53,20 @@ func main() {
return
case "both":
fmt.Println("Running new version of uap...")
uaParser, _ := uaparser.NewWithOptions("./uap-core/regexes.yaml", (uaparser.EOsLookUpMode | uaparser.EUserAgentLookUpMode), 100, 20, true, true, 1024)
uaParser, _ := uaparser.New(regexes,
uaparser.WithMode(uaparser.EOsLookUpMode|uaparser.EUserAgentLookUpMode),
uaparser.WithMissesThreshold(100),
uaparser.WithMatchIdxNotOk(20),
uaparser.WithSort(true),
uaparser.WithDebug(true),
uaparser.WithCacheSize(1024),
)
for i := 0; i < cLevel; i++ {
wg.Add(1)
runTest(uaParser, i, &wg)
}
fmt.Println("Running old version of uap...")
uaParser, _ = uaparser.New("./uap-core/regexes.yaml")
uaParser, _ = uaparser.New(regexes)
for i := 0; i < cLevel; i++ {
wg.Add(1)
runTest(uaParser, i, &wg)
Expand Down
41 changes: 30 additions & 11 deletions uaparser/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@ var largeUasSample []string

func init() {
var err error
benchedParser, err = New("../uap-core/regexes.yaml")

regexes, err := os.ReadFile("../uap-core/regexes.yaml")
if err != nil {
log.Fatal(err)
}

benchedParser, err = New(regexes)
if err != nil {
log.Fatal(err)
}
benchedParserWithOptions, err = NewWithOptions("../uap-core/regexes.yaml", (EOsLookUpMode | EUserAgentLookUpMode), 100, 20, true, true, cDefaultCacheSize)
benchedParserWithOptions, err = New(regexes,
WithMode(EOsLookUpMode|EUserAgentLookUpMode),
WithMissesThreshold(100),
WithMatchIdxNotOk(20),
WithSort(true),
WithDebug(true),
WithCacheSize(cDefaultCacheSize),
)

if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -48,17 +62,22 @@ func BenchmarkParserWithOptions(b *testing.B) {
}

func BenchmarkParserWithDifferentCacheSize(b *testing.B) {
sizes := []int{cDefaultCacheSize, cDefaultCacheSize*2, cDefaultCacheSize*3, cDefaultCacheSize*4}
sizes := []int{cDefaultCacheSize, cDefaultCacheSize * 2, cDefaultCacheSize * 3, cDefaultCacheSize * 4}

regexes, err := os.ReadFile("../uap-core/regexes.yaml")
if err != nil {
log.Fatal(err)
}

for _, size := range sizes {
parser, err := NewWithOptions(
"../uap-core/regexes.yaml",
EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode,
cDefaultMissesTreshold,
cDefaultMatchIdxNotOk,
false,
false,
size)
parser, err := New(regexes,
WithMode(EOsLookUpMode|EUserAgentLookUpMode|EDeviceLookUpMode),
WithMissesThreshold(cDefaultMissesTreshold),
WithMatchIdxNotOk(cDefaultMatchIdxNotOk),
WithSort(false),
WithDebug(false),
WithCacheSize(size),
)
if err != nil {
log.Fatal(err)
}
Expand Down
39 changes: 39 additions & 0 deletions uaparser/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uaparser

type Option func(*Parser)

func WithMode(mode LookupMode) Option {
return func(s *Parser) {
s.config.Mode = mode
}
}

func WithSort(useSort bool) Option {
return func(s *Parser) {
s.config.UseSort = useSort
}
}

func WithDebug(debug bool) Option {
return func(s *Parser) {
s.config.DebugMode = debug
}
}

func WithCacheSize(size int) Option {
return func(s *Parser) {
s.config.CacheSize = size
}
}

func WithMissesThreshold(threshold uint64) Option {
return func(s *Parser) {
s.config.MissesThreshold = threshold
}
}
Comment thread
jkroepke marked this conversation as resolved.
Outdated

func WithMatchIdxNotOk(idx int) Option {
return func(s *Parser) {
s.config.MatchIdxNotOk = idx
}
}
139 changes: 51 additions & 88 deletions uaparser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package uaparser

import (
"fmt"
"os"
"regexp"
"sort"
"sync"
Expand Down Expand Up @@ -129,7 +128,7 @@ type Client struct {
}

type parserConfig struct {
Mode int
Mode LookupMode
UseSort bool
DebugMode bool
CacheSize int
Expand All @@ -146,121 +145,85 @@ type Parser struct {
DeviceMisses uint64

config *parserConfig
cache *cache
cache *cache

RegexesDefinitions
}

type LookupMode int

const (
EOsLookUpMode = 1 /* 00000001 */
EUserAgentLookUpMode = 2 /* 00000010 */
EDeviceLookUpMode = 4 /* 00000100 */
cMinMissesTreshold = 100000
cDefaultMissesTreshold = 500000
cDefaultMatchIdxNotOk = 20
cDefaultSortOption = false
cDefaultDebugMode = false
cDefaultCacheSize = 1024
EOsLookUpMode LookupMode = 1 /* 00000001 */
EUserAgentLookUpMode LookupMode = 2 /* 00000010 */
EDeviceLookUpMode LookupMode = 4 /* 00000100 */
cMinMissesTreshold = 100000
cDefaultMissesTreshold = 500000
cDefaultMatchIdxNotOk = 20
cDefaultSortOption = false
cDefaultDebugMode = false
cDefaultCacheSize = 1024
)

func (parser *Parser) mustCompile() { // until we can use yaml.UnmarshalYAML with embedded pointer struct
for _, p := range parser.UA {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
}
for _, p := range parser.OS {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
}
for _, p := range parser.Device {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
func New(data []byte, options ...Option) (*Parser, error) {
Comment thread
dgoldstein0 marked this conversation as resolved.
Outdated
parser := &Parser{
config: &parserConfig{
Mode: EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode,
UseSort: cDefaultSortOption,
DebugMode: cDefaultDebugMode,
CacheSize: cDefaultCacheSize,
MissesThreshold: cMinMissesTreshold,
Comment thread
jkroepke marked this conversation as resolved.
Outdated
MatchIdxNotOk: cDefaultMatchIdxNotOk,
},
}
}

func defaultParserConfig() *parserConfig {
return &parserConfig{
Mode: EOsLookUpMode | EUserAgentLookUpMode | EDeviceLookUpMode,
UseSort: cDefaultSortOption,
DebugMode: cDefaultDebugMode,
CacheSize: cDefaultCacheSize,
MissesThreshold: cMinMissesTreshold,
MatchIdxNotOk: cDefaultMatchIdxNotOk,
for _, o := range options {
o(parser)
}
}

func NewWithOptions(regexFile string, mode, treshold, topCnt int, useSort, debugMode bool, cacheSize int) (*Parser, error) {
data, err := os.ReadFile(regexFile)
if nil != err {
return nil, err
if parser.config.MatchIdxNotOk < 0 {
parser.config.MatchIdxNotOk = 0
}

cfg := &parserConfig{
Mode: mode,
UseSort: useSort,
DebugMode: debugMode,
MatchIdxNotOk: cDefaultMatchIdxNotOk,
MissesThreshold: cDefaultMissesTreshold,
CacheSize: cDefaultCacheSize,
if parser.config.MissesThreshold <= cMinMissesTreshold {
parser.config.MissesThreshold = cMinMissesTreshold
}

if topCnt >= 0 {
cfg.MatchIdxNotOk = topCnt
}
if treshold > cMinMissesTreshold {
cfg.MissesThreshold = uint64(treshold)
}
if cacheSize > 0 {
cfg.CacheSize = cacheSize
if parser.config.CacheSize < 0 {
parser.config.CacheSize = cDefaultCacheSize
}

parser, err := newFromBytes(data, cfg)
if err != nil {
return nil, err
if parser.cache == nil {
Comment thread
dgoldstein0 marked this conversation as resolved.
parser.cache = newCache(parser.config.CacheSize)
}
return parser, nil
}

func New(regexFile string) (*Parser, error) {
data, err := os.ReadFile(regexFile)
if nil != err {
return nil, err
}
parser, err := newFromBytes(data, defaultParserConfig())
if err != nil {
return nil, err
if data == nil {
data = DefinitionYaml
}
return parser, nil
}

func NewFromSaved() *Parser {
parser, err := newFromBytes(DefinitionYaml, defaultParserConfig())
if err != nil {
// if the YAML is malformed, it's a programmatic error inside what
// we've statically-compiled in our binary. Panic!
panic(err.Error())
}
return parser
}

func NewFromBytes(data []byte) (*Parser, error) {
return newFromBytes(data, defaultParserConfig())
}

func newFromBytes(data []byte, config *parserConfig) (*Parser, error) {
parser := &Parser{
config: config,
cache: newCache(config.CacheSize),
}
if err := yaml.Unmarshal(data, &parser.RegexesDefinitions); err != nil {
return nil, err
return nil, fmt.Errorf("error parsing regexes definitions: %w", err)
}

parser.mustCompile()

return parser, nil
}

func (parser *Parser) mustCompile() { // until we can use yaml.UnmarshalYAML with embedded pointer struct
for _, p := range parser.UA {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
}
for _, p := range parser.OS {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
}
for _, p := range parser.Device {
p.Reg = compileRegex(p.Flags, p.Expr)
p.setDefaults()
}
}

func (parser *Parser) Parse(line string) *Client {
cli := new(Client)
var wg sync.WaitGroup
Expand Down
Loading