restructure - move strategies to pkg, split evaluate and move parts to pkg

pull/15/head
Simon Let 6 years ago
parent 96cf2ae032
commit 79d7f1c45e
  1. 425
      cmd/evaluate/main.go
  2. 24
      cmd/evaluate/strategy-dummy.go
  3. 246
      pkg/histanal/histeval.go
  4. 179
      pkg/histanal/histload.go
  5. 15
      pkg/strat/directory-sensitive.go
  6. 24
      pkg/strat/dummy.go
  7. 38
      pkg/strat/dynamic-record-distance.go
  8. 14
      pkg/strat/frequent.go
  9. 26
      pkg/strat/markov-chain-cmd.go
  10. 26
      pkg/strat/markov-chain.go
  11. 20
      pkg/strat/random.go
  12. 16
      pkg/strat/recent-bash.go
  13. 12
      pkg/strat/recent.go
  14. 28
      pkg/strat/record-distance.go
  15. 44
      pkg/strat/strat.go

@ -1,23 +1,16 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"os/exec"
"os/user"
"path/filepath"
"github.com/curusarn/resh/pkg/histanal"
"github.com/curusarn/resh/pkg/records"
"github.com/jpillora/longestcommon"
"github.com/schollz/progressbar"
"github.com/curusarn/resh/pkg/strat"
)
// Version from git set during build
@ -81,27 +74,20 @@ func main() {
}
}
evaluator := evaluator{sanitizedInput: *sanitizedInput, maxCandidates: maxCandidates,
BatchMode: batchMode, skipFailedCmds: *skipFailedCmds, debugRecords: *debugRecords}
var evaluator histanal.HistEval
if batchMode {
err := evaluator.initBatchMode(*input, *inputDataRoot)
if err != nil {
log.Fatal("Evaluator initBatchMode() error:", err)
}
evaluator = histanal.NewHistEvalBatchMode(*input, *inputDataRoot, maxCandidates, *skipFailedCmds, *debugRecords, *sanitizedInput)
} else {
err := evaluator.init(*input)
if err != nil {
log.Fatal("Evaluator init() error:", err)
}
evaluator = histanal.NewHistEval(*input, maxCandidates, *skipFailedCmds, *debugRecords, *sanitizedInput)
}
var simpleStrategies []ISimpleStrategy
var strategies []IStrategy
var simpleStrategies []strat.ISimpleStrategy
var strategies []strat.IStrategy
// dummy := strategyDummy{}
// simpleStrategies = append(simpleStrategies, &dummy)
simpleStrategies = append(simpleStrategies, &strategyRecent{})
simpleStrategies = append(simpleStrategies, &strat.Recent{})
// frequent := strategyFrequent{}
// frequent.init()
@ -111,401 +97,56 @@ func main() {
// random.init()
// simpleStrategies = append(simpleStrategies, &random)
directory := strategyDirectorySensitive{}
directory.init()
directory := strat.DirectorySensitive{}
directory.Init()
simpleStrategies = append(simpleStrategies, &directory)
dynamicDistG := strategyDynamicRecordDistance{
maxDepth: 3000,
distParams: records.DistParams{Pwd: 10, RealPwd: 10, SessionID: 1, Time: 1, Git: 10},
label: "10*pwd,10*realpwd,session,time,10*git",
dynamicDistG := strat.DynamicRecordDistance{
MaxDepth: 3000,
DistParams: records.DistParams{Pwd: 10, RealPwd: 10, SessionID: 1, Time: 1, Git: 10},
Label: "10*pwd,10*realpwd,session,time,10*git",
}
dynamicDistG.init()
dynamicDistG.Init()
strategies = append(strategies, &dynamicDistG)
distanceStaticBest := strategyRecordDistance{
maxDepth: 3000,
distParams: records.DistParams{Pwd: 10, RealPwd: 10, SessionID: 1, Time: 1},
label: "10*pwd,10*realpwd,session,time",
distanceStaticBest := strat.RecordDistance{
MaxDepth: 3000,
DistParams: records.DistParams{Pwd: 10, RealPwd: 10, SessionID: 1, Time: 1},
Label: "10*pwd,10*realpwd,session,time",
}
strategies = append(strategies, &distanceStaticBest)
recentBash := strategyRecentBash{}
recentBash.init()
recentBash := strat.RecentBash{}
recentBash.Init()
strategies = append(strategies, &recentBash)
if *slow {
markovCmd := strategyMarkovChainCmd{order: 1}
markovCmd.init()
markovCmd := strat.MarkovChainCmd{Order: 1}
markovCmd.Init()
markovCmd2 := strategyMarkovChainCmd{order: 2}
markovCmd2.init()
markovCmd2 := strat.MarkovChainCmd{Order: 2}
markovCmd2.Init()
markov := strategyMarkovChain{order: 1}
markov.init()
markov := strat.MarkovChain{Order: 1}
markov.Init()
markov2 := strategyMarkovChain{order: 2}
markov2.init()
markov2 := strat.MarkovChain{Order: 2}
markov2.Init()
simpleStrategies = append(simpleStrategies, &markovCmd2, &markovCmd, &markov2, &markov)
}
for _, strat := range simpleStrategies {
strategies = append(strategies, NewSimpleStrategyWrapper(strat))
for _, strategy := range simpleStrategies {
strategies = append(strategies, strat.NewSimpleStrategyWrapper(strategy))
}
for _, strat := range strategies {
err := evaluator.evaluate(strat)
err := evaluator.Evaluate(strat)
if err != nil {
log.Println("Evaluator evaluate() error:", err)
}
}
evaluator.calculateStatsAndPlot(*plottingScript)
}
type ISimpleStrategy interface {
GetTitleAndDescription() (string, string)
GetCandidates() []string
AddHistoryRecord(record *records.EnrichedRecord) error
ResetHistory() error
}
type IStrategy interface {
GetTitleAndDescription() (string, string)
GetCandidates(r records.EnrichedRecord) []string
AddHistoryRecord(record *records.EnrichedRecord) error
ResetHistory() error
}
type simpleStrategyWrapper struct {
strategy ISimpleStrategy
}
// NewSimpleStrategyWrapper returns IStrategy created by wrapping given ISimpleStrategy
func NewSimpleStrategyWrapper(strategy ISimpleStrategy) *simpleStrategyWrapper {
return &simpleStrategyWrapper{strategy: strategy}
}
func (s *simpleStrategyWrapper) GetTitleAndDescription() (string, string) {
return s.strategy.GetTitleAndDescription()
}
func (s *simpleStrategyWrapper) GetCandidates(r records.EnrichedRecord) []string {
return s.strategy.GetCandidates()
}
func (s *simpleStrategyWrapper) AddHistoryRecord(r *records.EnrichedRecord) error {
return s.strategy.AddHistoryRecord(r)
}
func (s *simpleStrategyWrapper) ResetHistory() error {
return s.strategy.ResetHistory()
}
type matchJSON struct {
Match bool
Distance int
CharsRecalled int
}
type multiMatchItemJSON struct {
Distance int
CharsRecalled int
}
type multiMatchJSON struct {
Match bool
Entries []multiMatchItemJSON
}
type strategyJSON struct {
Title string
Description string
Matches []matchJSON
PrefixMatches []multiMatchJSON
}
type deviceRecords struct {
Name string
Records []records.EnrichedRecord
}
type userRecords struct {
Name string
Devices []deviceRecords
}
type evaluator struct {
sanitizedInput bool
BatchMode bool
maxCandidates int
skipFailedCmds bool
debugRecords float64
UsersRecords []userRecords
Strategies []strategyJSON
}
func (e *evaluator) initBatchMode(input string, inputDataRoot string) error {
e.UsersRecords = e.loadHistoryRecordsBatchMode(input, inputDataRoot)
e.preprocessRecords()
return nil
}
func (e *evaluator) init(inputPath string) error {
records := e.loadHistoryRecords(inputPath)
device := deviceRecords{Records: records}
user := userRecords{}
user.Devices = append(user.Devices, device)
e.UsersRecords = append(e.UsersRecords, user)
e.preprocessRecords()
return nil
}
func (e *evaluator) calculateStatsAndPlot(scriptName string) {
evalJSON, err := json.Marshal(e)
if err != nil {
log.Fatal("json marshal error", err)
}
buffer := bytes.Buffer{}
buffer.Write(evalJSON)
// run python script to stat and plot/
cmd := exec.Command(scriptName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = &buffer
err = cmd.Run()
if err != nil {
log.Printf("Command finished with error: %v", err)
}
}
func (e *evaluator) preprocessDeviceRecords(device deviceRecords) deviceRecords {
sessionIDs := map[string]uint64{}
var nextID uint64
nextID = 1 // start with 1 because 0 won't get saved to json
for k, record := range device.Records {
id, found := sessionIDs[record.SessionID]
if found == false {
id = nextID
sessionIDs[record.SessionID] = id
nextID++
}
device.Records[k].SeqSessionID = id
// assert
if record.Sanitized != e.sanitizedInput {
if e.sanitizedInput {
log.Fatal("ASSERT failed: '--sanitized-input' is present but data is not sanitized")
}
log.Fatal("ASSERT failed: data is sanitized but '--sanitized-input' is not present")
}
device.Records[k].SeqSessionID = id
if e.debugRecords > 0 && rand.Float64() < e.debugRecords {
device.Records[k].DebugThisRecord = true
}
}
// sort.SliceStable(device.Records, func(x, y int) bool {
// if device.Records[x].SeqSessionID == device.Records[y].SeqSessionID {
// return device.Records[x].RealtimeAfterLocal < device.Records[y].RealtimeAfterLocal
// }
// return device.Records[x].SeqSessionID < device.Records[y].SeqSessionID
// })
// iterate from back and mark last record of each session
sessionIDSet := map[string]bool{}
for i := len(device.Records) - 1; i >= 0; i-- {
var record *records.EnrichedRecord
record = &device.Records[i]
if sessionIDSet[record.SessionID] {
continue
}
sessionIDSet[record.SessionID] = true
record.LastRecordOfSession = true
}
return device
}
// enrich records and add sequential session ID
func (e *evaluator) preprocessRecords() {
for i := range e.UsersRecords {
for j := range e.UsersRecords[i].Devices {
e.UsersRecords[i].Devices[j] = e.preprocessDeviceRecords(e.UsersRecords[i].Devices[j])
}
}
}
func (e *evaluator) evaluate(strategy IStrategy) error {
title, description := strategy.GetTitleAndDescription()
log.Println("Evaluating strategy:", title, "-", description)
strategyData := strategyJSON{Title: title, Description: description}
for i := range e.UsersRecords {
for j := range e.UsersRecords[i].Devices {
bar := progressbar.New(len(e.UsersRecords[i].Devices[j].Records))
var prevRecord records.EnrichedRecord
for _, record := range e.UsersRecords[i].Devices[j].Records {
if e.skipFailedCmds && record.ExitCode != 0 {
continue
}
candidates := strategy.GetCandidates(records.Stripped(record))
if record.DebugThisRecord {
log.Println()
log.Println("===================================================")
log.Println("STRATEGY:", title, "-", description)
log.Println("===================================================")
log.Println("Previous record:")
if prevRecord.RealtimeBefore == 0 {
log.Println("== NIL")
} else {
rec, _ := prevRecord.ToString()
log.Println(rec)
}
log.Println("---------------------------------------------------")
log.Println("Recommendations for:")
rec, _ := record.ToString()
log.Println(rec)
log.Println("---------------------------------------------------")
for i, candidate := range candidates {
if i > 10 {
break
}
log.Println(string(candidate))
}
log.Println("===================================================")
}
matchFound := false
longestPrefixMatchLength := 0
multiMatch := multiMatchJSON{}
for i, candidate := range candidates {
// make an option (--calculate-total) to turn this on/off ?
// if i >= e.maxCandidates {
// break
// }
commonPrefixLength := len(longestcommon.Prefix([]string{candidate, record.CmdLine}))
if commonPrefixLength > longestPrefixMatchLength {
longestPrefixMatchLength = commonPrefixLength
prefixMatch := multiMatchItemJSON{Distance: i + 1, CharsRecalled: commonPrefixLength}
multiMatch.Match = true
multiMatch.Entries = append(multiMatch.Entries, prefixMatch)
}
if candidate == record.CmdLine {
match := matchJSON{Match: true, Distance: i + 1, CharsRecalled: record.CmdLength}
matchFound = true
strategyData.Matches = append(strategyData.Matches, match)
strategyData.PrefixMatches = append(strategyData.PrefixMatches, multiMatch)
break
}
}
if matchFound == false {
strategyData.Matches = append(strategyData.Matches, matchJSON{})
strategyData.PrefixMatches = append(strategyData.PrefixMatches, multiMatch)
}
err := strategy.AddHistoryRecord(&record)
if err != nil {
log.Println("Error while evauating", err)
return err
}
bar.Add(1)
prevRecord = record
}
strategy.ResetHistory()
fmt.Println()
}
}
e.Strategies = append(e.Strategies, strategyData)
return nil
}
func (e *evaluator) loadHistoryRecordsBatchMode(fname string, dataRootPath string) []userRecords {
var records []userRecords
info, err := os.Stat(dataRootPath)
if err != nil {
log.Fatal("Error: Directory", dataRootPath, "does not exist - exiting! (", err, ")")
}
if info.IsDir() == false {
log.Fatal("Error:", dataRootPath, "is not a directory - exiting!")
}
users, err := ioutil.ReadDir(dataRootPath)
if err != nil {
log.Fatal("Could not read directory:", dataRootPath)
}
fmt.Println("Listing users in <", dataRootPath, ">...")
for _, user := range users {
userRecords := userRecords{Name: user.Name()}
userFullPath := filepath.Join(dataRootPath, user.Name())
if user.IsDir() == false {
log.Println("Warn: Unexpected file (not a directory) <", userFullPath, "> - skipping.")
continue
}
fmt.Println()
fmt.Printf("*- %s\n", user.Name())
devices, err := ioutil.ReadDir(userFullPath)
if err != nil {
log.Fatal("Could not read directory:", userFullPath)
}
for _, device := range devices {
deviceRecords := deviceRecords{Name: device.Name()}
deviceFullPath := filepath.Join(userFullPath, device.Name())
if device.IsDir() == false {
log.Println("Warn: Unexpected file (not a directory) <", deviceFullPath, "> - skipping.")
continue
}
fmt.Printf(" \\- %s\n", device.Name())
files, err := ioutil.ReadDir(deviceFullPath)
if err != nil {
log.Fatal("Could not read directory:", deviceFullPath)
}
for _, file := range files {
fileFullPath := filepath.Join(deviceFullPath, file.Name())
if file.Name() == fname {
fmt.Printf(" \\- %s - loading ...", file.Name())
// load the data
deviceRecords.Records = e.loadHistoryRecords(fileFullPath)
fmt.Println(" OK ✓")
} else {
fmt.Printf(" \\- %s - skipped\n", file.Name())
}
}
userRecords.Devices = append(userRecords.Devices, deviceRecords)
}
records = append(records, userRecords)
}
return records
}
func (e *evaluator) loadHistoryRecords(fname string) []records.EnrichedRecord {
file, err := os.Open(fname)
if err != nil {
log.Fatal("Open() resh history file error:", err)
}
defer file.Close()
var recs []records.EnrichedRecord
scanner := bufio.NewScanner(file)
for scanner.Scan() {
record := records.Record{}
fallbackRecord := records.FallbackRecord{}
line := scanner.Text()
err = json.Unmarshal([]byte(line), &record)
if err != nil {
err = json.Unmarshal([]byte(line), &fallbackRecord)
if err != nil {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = records.ConvertRecord(&fallbackRecord)
}
if e.sanitizedInput == false {
if record.CmdLength != 0 {
log.Fatal("Assert failed - 'cmdLength' is set in raw data. Maybe you want to use '--sanitized-input' option?")
}
record.CmdLength = len(record.CmdLine)
}
if record.CmdLength == 0 {
log.Fatal("Assert failed - 'cmdLength' is unset in the data. This should not happen.")
}
recs = append(recs, record.Enrich())
}
return recs
evaluator.CalculateStatsAndPlot(*plottingScript)
}

@ -1,24 +0,0 @@
package main
import "github.com/curusarn/resh/pkg/records"
type strategyDummy struct {
history []string
}
func (s *strategyDummy) GetTitleAndDescription() (string, string) {
return "dummy", "Return empty candidate list"
}
func (s *strategyDummy) GetCandidates() []string {
return nil
}
func (s *strategyDummy) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history = append(s.history, record.CmdLine)
return nil
}
func (s *strategyDummy) ResetHistory() error {
return nil
}

@ -0,0 +1,246 @@
package histanal
import (
"bytes"
"encoding/json"
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"github.com/curusarn/resh/pkg/records"
"github.com/curusarn/resh/pkg/strat"
"github.com/jpillora/longestcommon"
"github.com/schollz/progressbar"
)
type matchJSON struct {
Match bool
Distance int
CharsRecalled int
}
type multiMatchItemJSON struct {
Distance int
CharsRecalled int
}
type multiMatchJSON struct {
Match bool
Entries []multiMatchItemJSON
}
type strategyJSON struct {
Title string
Description string
Matches []matchJSON
PrefixMatches []multiMatchJSON
}
// HistEval evaluates history
type HistEval struct {
HistLoad
BatchMode bool
maxCandidates int
Strategies []strategyJSON
}
// NewHistEval constructs new HistEval
func NewHistEval(inputPath string,
maxCandidates int, skipFailedCmds bool,
debugRecords float64, sanitizedInput bool) HistEval {
e := HistEval{
HistLoad: HistLoad{
skipFailedCmds: skipFailedCmds,
debugRecords: debugRecords,
sanitizedInput: sanitizedInput,
},
maxCandidates: maxCandidates,
BatchMode: false,
}
records := e.loadHistoryRecords(inputPath)
device := deviceRecords{Records: records}
user := userRecords{}
user.Devices = append(user.Devices, device)
e.UsersRecords = append(e.UsersRecords, user)
e.preprocessRecords()
return e
}
// NewHistEvalBatchMode constructs new HistEval in batch mode
func NewHistEvalBatchMode(input string, inputDataRoot string,
maxCandidates int, skipFailedCmds bool,
debugRecords float64, sanitizedInput bool) HistEval {
e := HistEval{
HistLoad: HistLoad{
skipFailedCmds: skipFailedCmds,
debugRecords: debugRecords,
sanitizedInput: sanitizedInput,
},
maxCandidates: maxCandidates,
BatchMode: false,
}
e.UsersRecords = e.loadHistoryRecordsBatchMode(input, inputDataRoot)
e.preprocessRecords()
return e
}
func (e *HistEval) preprocessDeviceRecords(device deviceRecords) deviceRecords {
sessionIDs := map[string]uint64{}
var nextID uint64
nextID = 1 // start with 1 because 0 won't get saved to json
for k, record := range device.Records {
id, found := sessionIDs[record.SessionID]
if found == false {
id = nextID
sessionIDs[record.SessionID] = id
nextID++
}
device.Records[k].SeqSessionID = id
// assert
if record.Sanitized != e.sanitizedInput {
if e.sanitizedInput {
log.Fatal("ASSERT failed: '--sanitized-input' is present but data is not sanitized")
}
log.Fatal("ASSERT failed: data is sanitized but '--sanitized-input' is not present")
}
device.Records[k].SeqSessionID = id
if e.debugRecords > 0 && rand.Float64() < e.debugRecords {
device.Records[k].DebugThisRecord = true
}
}
// sort.SliceStable(device.Records, func(x, y int) bool {
// if device.Records[x].SeqSessionID == device.Records[y].SeqSessionID {
// return device.Records[x].RealtimeAfterLocal < device.Records[y].RealtimeAfterLocal
// }
// return device.Records[x].SeqSessionID < device.Records[y].SeqSessionID
// })
// iterate from back and mark last record of each session
sessionIDSet := map[string]bool{}
for i := len(device.Records) - 1; i >= 0; i-- {
var record *records.EnrichedRecord
record = &device.Records[i]
if sessionIDSet[record.SessionID] {
continue
}
sessionIDSet[record.SessionID] = true
record.LastRecordOfSession = true
}
return device
}
// enrich records and add sequential session ID
func (e *HistEval) preprocessRecords() {
for i := range e.UsersRecords {
for j := range e.UsersRecords[i].Devices {
e.UsersRecords[i].Devices[j] = e.preprocessDeviceRecords(e.UsersRecords[i].Devices[j])
}
}
}
// Evaluate a given strategy
func (e *HistEval) Evaluate(strategy strat.IStrategy) error {
title, description := strategy.GetTitleAndDescription()
log.Println("Evaluating strategy:", title, "-", description)
strategyData := strategyJSON{Title: title, Description: description}
for i := range e.UsersRecords {
for j := range e.UsersRecords[i].Devices {
bar := progressbar.New(len(e.UsersRecords[i].Devices[j].Records))
var prevRecord records.EnrichedRecord
for _, record := range e.UsersRecords[i].Devices[j].Records {
if e.skipFailedCmds && record.ExitCode != 0 {
continue
}
candidates := strategy.GetCandidates(records.Stripped(record))
if record.DebugThisRecord {
log.Println()
log.Println("===================================================")
log.Println("STRATEGY:", title, "-", description)
log.Println("===================================================")
log.Println("Previous record:")
if prevRecord.RealtimeBefore == 0 {
log.Println("== NIL")
} else {
rec, _ := prevRecord.ToString()
log.Println(rec)
}
log.Println("---------------------------------------------------")
log.Println("Recommendations for:")
rec, _ := record.ToString()
log.Println(rec)
log.Println("---------------------------------------------------")
for i, candidate := range candidates {
if i > 10 {
break
}
log.Println(string(candidate))
}
log.Println("===================================================")
}
matchFound := false
longestPrefixMatchLength := 0
multiMatch := multiMatchJSON{}
for i, candidate := range candidates {
// make an option (--calculate-total) to turn this on/off ?
// if i >= e.maxCandidates {
// break
// }
commonPrefixLength := len(longestcommon.Prefix([]string{candidate, record.CmdLine}))
if commonPrefixLength > longestPrefixMatchLength {
longestPrefixMatchLength = commonPrefixLength
prefixMatch := multiMatchItemJSON{Distance: i + 1, CharsRecalled: commonPrefixLength}
multiMatch.Match = true
multiMatch.Entries = append(multiMatch.Entries, prefixMatch)
}
if candidate == record.CmdLine {
match := matchJSON{Match: true, Distance: i + 1, CharsRecalled: record.CmdLength}
matchFound = true
strategyData.Matches = append(strategyData.Matches, match)
strategyData.PrefixMatches = append(strategyData.PrefixMatches, multiMatch)
break
}
}
if matchFound == false {
strategyData.Matches = append(strategyData.Matches, matchJSON{})
strategyData.PrefixMatches = append(strategyData.PrefixMatches, multiMatch)
}
err := strategy.AddHistoryRecord(&record)
if err != nil {
log.Println("Error while evauating", err)
return err
}
bar.Add(1)
prevRecord = record
}
strategy.ResetHistory()
fmt.Println()
}
}
e.Strategies = append(e.Strategies, strategyData)
return nil
}
// CalculateStatsAndPlot results
func (e *HistEval) CalculateStatsAndPlot(scriptName string) {
evalJSON, err := json.Marshal(e)
if err != nil {
log.Fatal("json marshal error", err)
}
buffer := bytes.Buffer{}
buffer.Write(evalJSON)
// run python script to stat and plot/
cmd := exec.Command(scriptName)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = &buffer
err = cmd.Run()
if err != nil {
log.Printf("Command finished with error: %v", err)
}
}

@ -0,0 +1,179 @@
package histanal
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"path/filepath"
"github.com/curusarn/resh/pkg/records"
)
type deviceRecords struct {
Name string
Records []records.EnrichedRecord
}
type userRecords struct {
Name string
Devices []deviceRecords
}
// HistLoad loads history
type HistLoad struct {
UsersRecords []userRecords
skipFailedCmds bool
sanitizedInput bool
debugRecords float64
}
func (e *HistLoad) preprocessDeviceRecords(device deviceRecords) deviceRecords {
sessionIDs := map[string]uint64{}
var nextID uint64
nextID = 1 // start with 1 because 0 won't get saved to json
for k, record := range device.Records {
id, found := sessionIDs[record.SessionID]
if found == false {
id = nextID
sessionIDs[record.SessionID] = id
nextID++
}
device.Records[k].SeqSessionID = id
// assert
if record.Sanitized != e.sanitizedInput {
if e.sanitizedInput {
log.Fatal("ASSERT failed: '--sanitized-input' is present but data is not sanitized")
}
log.Fatal("ASSERT failed: data is sanitized but '--sanitized-input' is not present")
}
device.Records[k].SeqSessionID = id
if e.debugRecords > 0 && rand.Float64() < e.debugRecords {
device.Records[k].DebugThisRecord = true
}
}
// sort.SliceStable(device.Records, func(x, y int) bool {
// if device.Records[x].SeqSessionID == device.Records[y].SeqSessionID {
// return device.Records[x].RealtimeAfterLocal < device.Records[y].RealtimeAfterLocal
// }
// return device.Records[x].SeqSessionID < device.Records[y].SeqSessionID
// })
// iterate from back and mark last record of each session
sessionIDSet := map[string]bool{}
for i := len(device.Records) - 1; i >= 0; i-- {
var record *records.EnrichedRecord
record = &device.Records[i]
if sessionIDSet[record.SessionID] {
continue
}
sessionIDSet[record.SessionID] = true
record.LastRecordOfSession = true
}
return device
}
// enrich records and add sequential session ID
func (e *HistLoad) preprocessRecords() {
for i := range e.UsersRecords {
for j := range e.UsersRecords[i].Devices {
e.UsersRecords[i].Devices[j] = e.preprocessDeviceRecords(e.UsersRecords[i].Devices[j])
}
}
}
func (e *HistLoad) loadHistoryRecordsBatchMode(fname string, dataRootPath string) []userRecords {
var records []userRecords
info, err := os.Stat(dataRootPath)
if err != nil {
log.Fatal("Error: Directory", dataRootPath, "does not exist - exiting! (", err, ")")
}
if info.IsDir() == false {
log.Fatal("Error:", dataRootPath, "is not a directory - exiting!")
}
users, err := ioutil.ReadDir(dataRootPath)
if err != nil {
log.Fatal("Could not read directory:", dataRootPath)
}
fmt.Println("Listing users in <", dataRootPath, ">...")
for _, user := range users {
userRecords := userRecords{Name: user.Name()}
userFullPath := filepath.Join(dataRootPath, user.Name())
if user.IsDir() == false {
log.Println("Warn: Unexpected file (not a directory) <", userFullPath, "> - skipping.")
continue
}
fmt.Println()
fmt.Printf("*- %s\n", user.Name())
devices, err := ioutil.ReadDir(userFullPath)
if err != nil {
log.Fatal("Could not read directory:", userFullPath)
}
for _, device := range devices {
deviceRecords := deviceRecords{Name: device.Name()}
deviceFullPath := filepath.Join(userFullPath, device.Name())
if device.IsDir() == false {
log.Println("Warn: Unexpected file (not a directory) <", deviceFullPath, "> - skipping.")
continue
}
fmt.Printf(" \\- %s\n", device.Name())
files, err := ioutil.ReadDir(deviceFullPath)
if err != nil {
log.Fatal("Could not read directory:", deviceFullPath)
}
for _, file := range files {
fileFullPath := filepath.Join(deviceFullPath, file.Name())
if file.Name() == fname {
fmt.Printf(" \\- %s - loading ...", file.Name())
// load the data
deviceRecords.Records = e.loadHistoryRecords(fileFullPath)
fmt.Println(" OK ✓")
} else {
fmt.Printf(" \\- %s - skipped\n", file.Name())
}
}
userRecords.Devices = append(userRecords.Devices, deviceRecords)
}
records = append(records, userRecords)
}
return records
}
func (e *HistLoad) loadHistoryRecords(fname string) []records.EnrichedRecord {
file, err := os.Open(fname)
if err != nil {
log.Fatal("Open() resh history file error:", err)
}
defer file.Close()
var recs []records.EnrichedRecord
scanner := bufio.NewScanner(file)
for scanner.Scan() {
record := records.Record{}
fallbackRecord := records.FallbackRecord{}
line := scanner.Text()
err = json.Unmarshal([]byte(line), &record)
if err != nil {
err = json.Unmarshal([]byte(line), &fallbackRecord)
if err != nil {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = records.ConvertRecord(&fallbackRecord)
}
if e.sanitizedInput == false {
if record.CmdLength != 0 {
log.Fatal("Assert failed - 'cmdLength' is set in raw data. Maybe you want to use '--sanitized-input' option?")
}
record.CmdLength = len(record.CmdLine)
}
if record.CmdLength == 0 {
log.Fatal("Assert failed - 'cmdLength' is unset in the data. This should not happen.")
}
recs = append(recs, record.Enrich())
}
return recs
}

@ -1,25 +1,25 @@
package main
package strat
import "github.com/curusarn/resh/pkg/records"
type strategyDirectorySensitive struct {
type DirectorySensitive struct {
history map[string][]string
lastPwd string
}
func (s *strategyDirectorySensitive) init() {
func (s *DirectorySensitive) Init() {
s.history = map[string][]string{}
}
func (s *strategyDirectorySensitive) GetTitleAndDescription() (string, string) {
func (s *DirectorySensitive) GetTitleAndDescription() (string, string) {
return "directory sensitive (recent)", "Use recent commands executed is the same directory"
}
func (s *strategyDirectorySensitive) GetCandidates() []string {
func (s *DirectorySensitive) GetCandidates() []string {
return s.history[s.lastPwd]
}
func (s *strategyDirectorySensitive) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *DirectorySensitive) AddHistoryRecord(record *records.EnrichedRecord) error {
// work on history for PWD
pwd := record.Pwd
// remove previous occurance of record
@ -34,7 +34,8 @@ func (s *strategyDirectorySensitive) AddHistoryRecord(record *records.EnrichedRe
return nil
}
func (s *strategyDirectorySensitive) ResetHistory() error {
func (s *DirectorySensitive) ResetHistory() error {
s.Init()
s.history = map[string][]string{}
return nil
}

@ -0,0 +1,24 @@
package strat
import "github.com/curusarn/resh/pkg/records"
type Dummy struct {
history []string
}
func (s *Dummy) GetTitleAndDescription() (string, string) {
return "dummy", "Return empty candidate list"
}
func (s *Dummy) GetCandidates() []string {
return nil
}
func (s *Dummy) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history = append(s.history, record.CmdLine)
return nil
}
func (s *Dummy) ResetHistory() error {
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"math"
@ -8,14 +8,14 @@ import (
"github.com/curusarn/resh/pkg/records"
)
type strategyDynamicRecordDistance struct {
type DynamicRecordDistance struct {
history []records.EnrichedRecord
distParams records.DistParams
DistParams records.DistParams
pwdHistogram map[string]int
realPwdHistogram map[string]int
gitOriginHistogram map[string]int
maxDepth int
label string
MaxDepth int
Label string
}
type strDynDistEntry struct {
@ -23,36 +23,36 @@ type strDynDistEntry struct {
distance float64
}
func (s *strategyDynamicRecordDistance) init() {
func (s *DynamicRecordDistance) Init() {
s.history = nil
s.pwdHistogram = map[string]int{}
s.realPwdHistogram = map[string]int{}
s.gitOriginHistogram = map[string]int{}
}
func (s *strategyDynamicRecordDistance) GetTitleAndDescription() (string, string) {
return "dynamic record distance (depth:" + strconv.Itoa(s.maxDepth) + ";" + s.label + ")", "Use TF-IDF record distance to recommend commands"
func (s *DynamicRecordDistance) GetTitleAndDescription() (string, string) {
return "dynamic record distance (depth:" + strconv.Itoa(s.MaxDepth) + ";" + s.Label + ")", "Use TF-IDF record distance to recommend commands"
}
func (s *strategyDynamicRecordDistance) idf(count int) float64 {
func (s *DynamicRecordDistance) idf(count int) float64 {
return math.Log(float64(len(s.history)) / float64(count))
}
func (s *strategyDynamicRecordDistance) GetCandidates(strippedRecord records.EnrichedRecord) []string {
func (s *DynamicRecordDistance) GetCandidates(strippedRecord records.EnrichedRecord) []string {
if len(s.history) == 0 {
return nil
}
var mapItems []strDynDistEntry
for i, record := range s.history {
if s.maxDepth != 0 && i > s.maxDepth {
if s.MaxDepth != 0 && i > s.MaxDepth {
break
}
distParams := records.DistParams{
Pwd: s.distParams.Pwd * s.idf(s.pwdHistogram[strippedRecord.PwdAfter]),
RealPwd: s.distParams.RealPwd * s.idf(s.realPwdHistogram[strippedRecord.RealPwdAfter]),
Git: s.distParams.Git * s.idf(s.gitOriginHistogram[strippedRecord.GitOriginRemote]),
Time: s.distParams.Time,
SessionID: s.distParams.SessionID,
Pwd: s.DistParams.Pwd * s.idf(s.pwdHistogram[strippedRecord.PwdAfter]),
RealPwd: s.DistParams.RealPwd * s.idf(s.realPwdHistogram[strippedRecord.RealPwdAfter]),
Git: s.DistParams.Git * s.idf(s.gitOriginHistogram[strippedRecord.GitOriginRemote]),
Time: s.DistParams.Time,
SessionID: s.DistParams.SessionID,
}
distance := record.DistanceTo(strippedRecord, distParams)
mapItems = append(mapItems, strDynDistEntry{record.CmdLine, distance})
@ -70,7 +70,7 @@ func (s *strategyDynamicRecordDistance) GetCandidates(strippedRecord records.Enr
return hist
}
func (s *strategyDynamicRecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *DynamicRecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error {
// append record to front
s.history = append([]records.EnrichedRecord{*record}, s.history...)
s.pwdHistogram[record.Pwd]++
@ -79,7 +79,7 @@ func (s *strategyDynamicRecordDistance) AddHistoryRecord(record *records.Enriche
return nil
}
func (s *strategyDynamicRecordDistance) ResetHistory() error {
s.init()
func (s *DynamicRecordDistance) ResetHistory() error {
s.Init()
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"sort"
@ -6,7 +6,7 @@ import (
"github.com/curusarn/resh/pkg/records"
)
type strategyFrequent struct {
type Frequent struct {
history map[string]int
}
@ -15,15 +15,15 @@ type strFrqEntry struct {
count int
}
func (s *strategyFrequent) init() {
func (s *Frequent) init() {
s.history = map[string]int{}
}
func (s *strategyFrequent) GetTitleAndDescription() (string, string) {
func (s *Frequent) GetTitleAndDescription() (string, string) {
return "frequent", "Use frequent commands"
}
func (s *strategyFrequent) GetCandidates() []string {
func (s *Frequent) GetCandidates() []string {
var mapItems []strFrqEntry
for cmdLine, count := range s.history {
mapItems = append(mapItems, strFrqEntry{cmdLine, count})
@ -36,12 +36,12 @@ func (s *strategyFrequent) GetCandidates() []string {
return hist
}
func (s *strategyFrequent) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *Frequent) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history[record.CmdLine]++
return nil
}
func (s *strategyFrequent) ResetHistory() error {
func (s *Frequent) ResetHistory() error {
s.init()
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"sort"
@ -8,8 +8,8 @@ import (
"github.com/mb-14/gomarkov"
)
type strategyMarkovChainCmd struct {
order int
type MarkovChainCmd struct {
Order int
history []strMarkCmdHistoryEntry
historyCmds []string
}
@ -24,24 +24,24 @@ type strMarkCmdEntry struct {
transProb float64
}
func (s *strategyMarkovChainCmd) init() {
func (s *MarkovChainCmd) Init() {
s.history = nil
s.historyCmds = nil
}
func (s *strategyMarkovChainCmd) GetTitleAndDescription() (string, string) {
return "command-based markov chain (order " + strconv.Itoa(s.order) + ")", "Use command-based markov chain to recommend commands"
func (s *MarkovChainCmd) GetTitleAndDescription() (string, string) {
return "command-based markov chain (order " + strconv.Itoa(s.Order) + ")", "Use command-based markov chain to recommend commands"
}
func (s *strategyMarkovChainCmd) GetCandidates() []string {
if len(s.history) < s.order {
func (s *MarkovChainCmd) GetCandidates() []string {
if len(s.history) < s.Order {
var hist []string
for _, item := range s.history {
hist = append(hist, item.cmdLine)
}
return hist
}
chain := gomarkov.NewChain(s.order)
chain := gomarkov.NewChain(s.Order)
chain.Add(s.historyCmds)
@ -52,7 +52,7 @@ func (s *strategyMarkovChainCmd) GetCandidates() []string {
continue
}
cmdsSet[cmd] = true
prob, _ := chain.TransitionProbability(cmd, s.historyCmds[len(s.historyCmds)-s.order:])
prob, _ := chain.TransitionProbability(cmd, s.historyCmds[len(s.historyCmds)-s.Order:])
entries = append(entries, strMarkCmdEntry{cmd: cmd, transProb: prob})
}
sort.Slice(entries, func(i int, j int) bool { return entries[i].transProb > entries[j].transProb })
@ -78,14 +78,14 @@ func (s *strategyMarkovChainCmd) GetCandidates() []string {
return hist
}
func (s *strategyMarkovChainCmd) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *MarkovChainCmd) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history = append(s.history, strMarkCmdHistoryEntry{cmdLine: record.CmdLine, cmd: record.Command})
s.historyCmds = append(s.historyCmds, record.Command)
// s.historySet[record.CmdLine] = true
return nil
}
func (s *strategyMarkovChainCmd) ResetHistory() error {
s.init()
func (s *MarkovChainCmd) ResetHistory() error {
s.Init()
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"sort"
@ -8,8 +8,8 @@ import (
"github.com/mb-14/gomarkov"
)
type strategyMarkovChain struct {
order int
type MarkovChain struct {
Order int
history []string
}
@ -18,19 +18,19 @@ type strMarkEntry struct {
transProb float64
}
func (s *strategyMarkovChain) init() {
func (s *MarkovChain) Init() {
s.history = nil
}
func (s *strategyMarkovChain) GetTitleAndDescription() (string, string) {
return "markov chain (order " + strconv.Itoa(s.order) + ")", "Use markov chain to recommend commands"
func (s *MarkovChain) GetTitleAndDescription() (string, string) {
return "markov chain (order " + strconv.Itoa(s.Order) + ")", "Use markov chain to recommend commands"
}
func (s *strategyMarkovChain) GetCandidates() []string {
if len(s.history) < s.order {
func (s *MarkovChain) GetCandidates() []string {
if len(s.history) < s.Order {
return s.history
}
chain := gomarkov.NewChain(s.order)
chain := gomarkov.NewChain(s.Order)
chain.Add(s.history)
@ -41,7 +41,7 @@ func (s *strategyMarkovChain) GetCandidates() []string {
continue
}
cmdLinesSet[cmdLine] = true
prob, _ := chain.TransitionProbability(cmdLine, s.history[len(s.history)-s.order:])
prob, _ := chain.TransitionProbability(cmdLine, s.history[len(s.history)-s.Order:])
entries = append(entries, strMarkEntry{cmdLine: cmdLine, transProb: prob})
}
sort.Slice(entries, func(i int, j int) bool { return entries[i].transProb > entries[j].transProb })
@ -58,13 +58,13 @@ func (s *strategyMarkovChain) GetCandidates() []string {
return hist
}
func (s *strategyMarkovChain) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *MarkovChain) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history = append(s.history, record.CmdLine)
// s.historySet[record.CmdLine] = true
return nil
}
func (s *strategyMarkovChain) ResetHistory() error {
s.init()
func (s *MarkovChain) ResetHistory() error {
s.Init()
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"math/rand"
@ -7,27 +7,27 @@ import (
"github.com/curusarn/resh/pkg/records"
)
type strategyRandom struct {
candidatesSize int
type Random struct {
CandidatesSize int
history []string
historySet map[string]bool
}
func (s *strategyRandom) init() {
func (s *Random) Init() {
s.history = nil
s.historySet = map[string]bool{}
}
func (s *strategyRandom) GetTitleAndDescription() (string, string) {
func (s *Random) GetTitleAndDescription() (string, string) {
return "random", "Use random commands"
}
func (s *strategyRandom) GetCandidates() []string {
func (s *Random) GetCandidates() []string {
seed := time.Now().UnixNano()
rand.Seed(seed)
var candidates []string
candidateSet := map[string]bool{}
for len(candidates) < s.candidatesSize && len(candidates)*2 < len(s.historySet) {
for len(candidates) < s.CandidatesSize && len(candidates)*2 < len(s.historySet) {
x := rand.Intn(len(s.history))
candidate := s.history[x]
if candidateSet[candidate] == false {
@ -39,13 +39,13 @@ func (s *strategyRandom) GetCandidates() []string {
return candidates
}
func (s *strategyRandom) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *Random) AddHistoryRecord(record *records.EnrichedRecord) error {
s.history = append([]string{record.CmdLine}, s.history...)
s.historySet[record.CmdLine] = true
return nil
}
func (s *strategyRandom) ResetHistory() error {
s.init()
func (s *Random) ResetHistory() error {
s.Init()
return nil
}

@ -1,23 +1,23 @@
package main
package strat
import "github.com/curusarn/resh/pkg/records"
type strategyRecentBash struct {
type RecentBash struct {
histfile []string
histfileSnapshot map[string][]string
history map[string][]string
}
func (s *strategyRecentBash) init() {
func (s *RecentBash) Init() {
s.histfileSnapshot = map[string][]string{}
s.history = map[string][]string{}
}
func (s *strategyRecentBash) GetTitleAndDescription() (string, string) {
func (s *RecentBash) GetTitleAndDescription() (string, string) {
return "recent (bash-like)", "Behave like bash"
}
func (s *strategyRecentBash) GetCandidates(strippedRecord records.EnrichedRecord) []string {
func (s *RecentBash) GetCandidates(strippedRecord records.EnrichedRecord) []string {
// populate the local history from histfile
if s.histfileSnapshot[strippedRecord.SessionID] == nil {
s.histfileSnapshot[strippedRecord.SessionID] = s.histfile
@ -25,7 +25,7 @@ func (s *strategyRecentBash) GetCandidates(strippedRecord records.EnrichedRecord
return append(s.history[strippedRecord.SessionID], s.histfileSnapshot[strippedRecord.SessionID]...)
}
func (s *strategyRecentBash) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *RecentBash) AddHistoryRecord(record *records.EnrichedRecord) error {
// remove previous occurance of record
for i, cmd := range s.history[record.SessionID] {
if cmd == record.CmdLine {
@ -44,7 +44,7 @@ func (s *strategyRecentBash) AddHistoryRecord(record *records.EnrichedRecord) er
return nil
}
func (s *strategyRecentBash) ResetHistory() error {
s.init()
func (s *RecentBash) ResetHistory() error {
s.Init()
return nil
}

@ -1,20 +1,20 @@
package main
package strat
import "github.com/curusarn/resh/pkg/records"
type strategyRecent struct {
type Recent struct {
history []string
}
func (s *strategyRecent) GetTitleAndDescription() (string, string) {
func (s *Recent) GetTitleAndDescription() (string, string) {
return "recent", "Use recent commands"
}
func (s *strategyRecent) GetCandidates() []string {
func (s *Recent) GetCandidates() []string {
return s.history
}
func (s *strategyRecent) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *Recent) AddHistoryRecord(record *records.EnrichedRecord) error {
// remove previous occurance of record
for i, cmd := range s.history {
if cmd == record.CmdLine {
@ -26,7 +26,7 @@ func (s *strategyRecent) AddHistoryRecord(record *records.EnrichedRecord) error
return nil
}
func (s *strategyRecent) ResetHistory() error {
func (s *Recent) ResetHistory() error {
s.history = nil
return nil
}

@ -1,4 +1,4 @@
package main
package strat
import (
"sort"
@ -7,11 +7,11 @@ import (
"github.com/curusarn/resh/pkg/records"
)
type strategyRecordDistance struct {
type RecordDistance struct {
history []records.EnrichedRecord
distParams records.DistParams
maxDepth int
label string
DistParams records.DistParams
MaxDepth int
Label string
}
type strDistEntry struct {
@ -19,24 +19,24 @@ type strDistEntry struct {
distance float64
}
func (s *strategyRecordDistance) init() {
func (s *RecordDistance) Init() {
s.history = nil
}
func (s *strategyRecordDistance) GetTitleAndDescription() (string, string) {
return "record distance (depth:" + strconv.Itoa(s.maxDepth) + ";" + s.label + ")", "Use record distance to recommend commands"
func (s *RecordDistance) GetTitleAndDescription() (string, string) {
return "record distance (depth:" + strconv.Itoa(s.MaxDepth) + ";" + s.Label + ")", "Use record distance to recommend commands"
}
func (s *strategyRecordDistance) GetCandidates(strippedRecord records.EnrichedRecord) []string {
func (s *RecordDistance) GetCandidates(strippedRecord records.EnrichedRecord) []string {
if len(s.history) == 0 {
return nil
}
var mapItems []strDistEntry
for i, record := range s.history {
if s.maxDepth != 0 && i > s.maxDepth {
if s.MaxDepth != 0 && i > s.MaxDepth {
break
}
distance := record.DistanceTo(strippedRecord, s.distParams)
distance := record.DistanceTo(strippedRecord, s.DistParams)
mapItems = append(mapItems, strDistEntry{record.CmdLine, distance})
}
sort.SliceStable(mapItems, func(i int, j int) bool { return mapItems[i].distance < mapItems[j].distance })
@ -52,13 +52,13 @@ func (s *strategyRecordDistance) GetCandidates(strippedRecord records.EnrichedRe
return hist
}
func (s *strategyRecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error {
func (s *RecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error {
// append record to front
s.history = append([]records.EnrichedRecord{*record}, s.history...)
return nil
}
func (s *strategyRecordDistance) ResetHistory() error {
s.init()
func (s *RecordDistance) ResetHistory() error {
s.Init()
return nil
}

@ -0,0 +1,44 @@
package strat
import (
"github.com/curusarn/resh/pkg/records"
)
type ISimpleStrategy interface {
GetTitleAndDescription() (string, string)
GetCandidates() []string
AddHistoryRecord(record *records.EnrichedRecord) error
ResetHistory() error
}
type IStrategy interface {
GetTitleAndDescription() (string, string)
GetCandidates(r records.EnrichedRecord) []string
AddHistoryRecord(record *records.EnrichedRecord) error
ResetHistory() error
}
type simpleStrategyWrapper struct {
strategy ISimpleStrategy
}
// NewSimpleStrategyWrapper returns IStrategy created by wrapping given ISimpleStrategy
func NewSimpleStrategyWrapper(strategy ISimpleStrategy) *simpleStrategyWrapper {
return &simpleStrategyWrapper{strategy: strategy}
}
func (s *simpleStrategyWrapper) GetTitleAndDescription() (string, string) {
return s.strategy.GetTitleAndDescription()
}
func (s *simpleStrategyWrapper) GetCandidates(r records.EnrichedRecord) []string {
return s.strategy.GetCandidates()
}
func (s *simpleStrategyWrapper) AddHistoryRecord(r *records.EnrichedRecord) error {
return s.strategy.AddHistoryRecord(r)
}
func (s *simpleStrategyWrapper) ResetHistory() error {
return s.strategy.ResetHistory()
}
Loading…
Cancel
Save