mirror of https://github.com/curusarn/resh
parent
2c7947225e
commit
9de1f9d5cc
@ -1,246 +0,0 @@ |
||||
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) |
||||
} |
||||
} |
||||
@ -1,180 +0,0 @@ |
||||
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.Convert(&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) |
||||
} else if record.CmdLength == 0 { |
||||
log.Fatal("Assert failed - 'cmdLength' is unset in the data. This should not happen.") |
||||
} |
||||
if !e.skipFailedCmds || record.ExitCode == 0 { |
||||
recs = append(recs, records.Enriched(record)) |
||||
} |
||||
} |
||||
return recs |
||||
} |
||||
@ -1,47 +0,0 @@ |
||||
package strat |
||||
|
||||
import "github.com/curusarn/resh/pkg/records" |
||||
|
||||
// DirectorySensitive prediction/recommendation strategy
|
||||
type DirectorySensitive struct { |
||||
history map[string][]string |
||||
lastPwd string |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *DirectorySensitive) Init() { |
||||
s.history = map[string][]string{} |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *DirectorySensitive) GetTitleAndDescription() (string, string) { |
||||
return "directory sensitive (recent)", "Use recent commands executed is the same directory" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
func (s *DirectorySensitive) GetCandidates() []string { |
||||
return s.history[s.lastPwd] |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *DirectorySensitive) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
// work on history for PWD
|
||||
pwd := record.Pwd |
||||
// remove previous occurance of record
|
||||
for i, cmd := range s.history[pwd] { |
||||
if cmd == record.CmdLine { |
||||
s.history[pwd] = append(s.history[pwd][:i], s.history[pwd][i+1:]...) |
||||
} |
||||
} |
||||
// append new record
|
||||
s.history[pwd] = append([]string{record.CmdLine}, s.history[pwd]...) |
||||
s.lastPwd = record.PwdAfter |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *DirectorySensitive) ResetHistory() error { |
||||
s.Init() |
||||
s.history = map[string][]string{} |
||||
return nil |
||||
} |
||||
@ -1,29 +0,0 @@ |
||||
package strat |
||||
|
||||
import "github.com/curusarn/resh/pkg/records" |
||||
|
||||
// Dummy prediction/recommendation strategy
|
||||
type Dummy struct { |
||||
history []string |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *Dummy) GetTitleAndDescription() (string, string) { |
||||
return "dummy", "Return empty candidate list" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
func (s *Dummy) GetCandidates() []string { |
||||
return nil |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *Dummy) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
s.history = append(s.history, record.CmdLine) |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *Dummy) ResetHistory() error { |
||||
return nil |
||||
} |
||||
@ -1,91 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"math" |
||||
"sort" |
||||
"strconv" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// DynamicRecordDistance prediction/recommendation strategy
|
||||
type DynamicRecordDistance struct { |
||||
history []records.EnrichedRecord |
||||
DistParams records.DistParams |
||||
pwdHistogram map[string]int |
||||
realPwdHistogram map[string]int |
||||
gitOriginHistogram map[string]int |
||||
MaxDepth int |
||||
Label string |
||||
} |
||||
|
||||
type strDynDistEntry struct { |
||||
cmdLine string |
||||
distance float64 |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *DynamicRecordDistance) Init() { |
||||
s.history = nil |
||||
s.pwdHistogram = map[string]int{} |
||||
s.realPwdHistogram = map[string]int{} |
||||
s.gitOriginHistogram = map[string]int{} |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
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 *DynamicRecordDistance) idf(count int) float64 { |
||||
return math.Log(float64(len(s.history)) / float64(count)) |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
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 { |
||||
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, |
||||
} |
||||
distance := record.DistanceTo(strippedRecord, distParams) |
||||
mapItems = append(mapItems, strDynDistEntry{record.CmdLine, distance}) |
||||
} |
||||
sort.SliceStable(mapItems, func(i int, j int) bool { return mapItems[i].distance < mapItems[j].distance }) |
||||
var hist []string |
||||
histSet := map[string]bool{} |
||||
for _, item := range mapItems { |
||||
if histSet[item.cmdLine] { |
||||
continue |
||||
} |
||||
histSet[item.cmdLine] = true |
||||
hist = append(hist, item.cmdLine) |
||||
} |
||||
return hist |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *DynamicRecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
// append record to front
|
||||
s.history = append([]records.EnrichedRecord{*record}, s.history...) |
||||
s.pwdHistogram[record.Pwd]++ |
||||
s.realPwdHistogram[record.RealPwd]++ |
||||
s.gitOriginHistogram[record.GitOriginRemote]++ |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *DynamicRecordDistance) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,53 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"sort" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// Frequent prediction/recommendation strategy
|
||||
type Frequent struct { |
||||
history map[string]int |
||||
} |
||||
|
||||
type strFrqEntry struct { |
||||
cmdLine string |
||||
count int |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *Frequent) Init() { |
||||
s.history = map[string]int{} |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *Frequent) GetTitleAndDescription() (string, string) { |
||||
return "frequent", "Use frequent commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
func (s *Frequent) GetCandidates() []string { |
||||
var mapItems []strFrqEntry |
||||
for cmdLine, count := range s.history { |
||||
mapItems = append(mapItems, strFrqEntry{cmdLine, count}) |
||||
} |
||||
sort.Slice(mapItems, func(i int, j int) bool { return mapItems[i].count > mapItems[j].count }) |
||||
var hist []string |
||||
for _, item := range mapItems { |
||||
hist = append(hist, item.cmdLine) |
||||
} |
||||
return hist |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *Frequent) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
s.history[record.CmdLine]++ |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *Frequent) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,97 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"sort" |
||||
"strconv" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
"github.com/mb-14/gomarkov" |
||||
) |
||||
|
||||
// MarkovChainCmd prediction/recommendation strategy
|
||||
type MarkovChainCmd struct { |
||||
Order int |
||||
history []strMarkCmdHistoryEntry |
||||
historyCmds []string |
||||
} |
||||
|
||||
type strMarkCmdHistoryEntry struct { |
||||
cmd string |
||||
cmdLine string |
||||
} |
||||
|
||||
type strMarkCmdEntry struct { |
||||
cmd string |
||||
transProb float64 |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *MarkovChainCmd) Init() { |
||||
s.history = nil |
||||
s.historyCmds = nil |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *MarkovChainCmd) GetTitleAndDescription() (string, string) { |
||||
return "command-based markov chain (order " + strconv.Itoa(s.Order) + ")", "Use command-based markov chain to recommend commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
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.Add(s.historyCmds) |
||||
|
||||
cmdsSet := map[string]bool{} |
||||
var entries []strMarkCmdEntry |
||||
for _, cmd := range s.historyCmds { |
||||
if cmdsSet[cmd] { |
||||
continue |
||||
} |
||||
cmdsSet[cmd] = true |
||||
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 }) |
||||
var hist []string |
||||
histSet := map[string]bool{} |
||||
for i := len(s.history) - 1; i >= 0; i-- { |
||||
if histSet[s.history[i].cmdLine] { |
||||
continue |
||||
} |
||||
histSet[s.history[i].cmdLine] = true |
||||
if s.history[i].cmd == entries[0].cmd { |
||||
hist = append(hist, s.history[i].cmdLine) |
||||
} |
||||
} |
||||
// log.Println("################")
|
||||
// log.Println(s.history[len(s.history)-s.order:])
|
||||
// log.Println(" -> ")
|
||||
// x := math.Min(float64(len(hist)), 3)
|
||||
// log.Println(entries[:int(x)])
|
||||
// x = math.Min(float64(len(hist)), 5)
|
||||
// log.Println(hist[:int(x)])
|
||||
// log.Println("################")
|
||||
return hist |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
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 |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *MarkovChainCmd) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,76 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"sort" |
||||
"strconv" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
"github.com/mb-14/gomarkov" |
||||
) |
||||
|
||||
// MarkovChain prediction/recommendation strategy
|
||||
type MarkovChain struct { |
||||
Order int |
||||
history []string |
||||
} |
||||
|
||||
type strMarkEntry struct { |
||||
cmdLine string |
||||
transProb float64 |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *MarkovChain) Init() { |
||||
s.history = nil |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *MarkovChain) GetTitleAndDescription() (string, string) { |
||||
return "markov chain (order " + strconv.Itoa(s.Order) + ")", "Use markov chain to recommend commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
func (s *MarkovChain) GetCandidates() []string { |
||||
if len(s.history) < s.Order { |
||||
return s.history |
||||
} |
||||
chain := gomarkov.NewChain(s.Order) |
||||
|
||||
chain.Add(s.history) |
||||
|
||||
cmdLinesSet := map[string]bool{} |
||||
var entries []strMarkEntry |
||||
for _, cmdLine := range s.history { |
||||
if cmdLinesSet[cmdLine] { |
||||
continue |
||||
} |
||||
cmdLinesSet[cmdLine] = true |
||||
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 }) |
||||
var hist []string |
||||
for _, item := range entries { |
||||
hist = append(hist, item.cmdLine) |
||||
} |
||||
// log.Println("################")
|
||||
// log.Println(s.history[len(s.history)-s.order:])
|
||||
// log.Println(" -> ")
|
||||
// x := math.Min(float64(len(hist)), 5)
|
||||
// log.Println(hist[:int(x)])
|
||||
// log.Println("################")
|
||||
return hist |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *MarkovChain) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
s.history = append(s.history, record.CmdLine) |
||||
// s.historySet[record.CmdLine] = true
|
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *MarkovChain) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,57 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"time" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// Random prediction/recommendation strategy
|
||||
type Random struct { |
||||
CandidatesSize int |
||||
history []string |
||||
historySet map[string]bool |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *Random) Init() { |
||||
s.history = nil |
||||
s.historySet = map[string]bool{} |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *Random) GetTitleAndDescription() (string, string) { |
||||
return "random", "Use random commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
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) { |
||||
x := rand.Intn(len(s.history)) |
||||
candidate := s.history[x] |
||||
if candidateSet[candidate] == false { |
||||
candidateSet[candidate] = true |
||||
candidates = append(candidates, candidate) |
||||
continue |
||||
} |
||||
} |
||||
return candidates |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *Random) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
s.history = append([]string{record.CmdLine}, s.history...) |
||||
s.historySet[record.CmdLine] = true |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *Random) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,56 +0,0 @@ |
||||
package strat |
||||
|
||||
import "github.com/curusarn/resh/pkg/records" |
||||
|
||||
// RecentBash prediction/recommendation strategy
|
||||
type RecentBash struct { |
||||
histfile []string |
||||
histfileSnapshot map[string][]string |
||||
history map[string][]string |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *RecentBash) Init() { |
||||
s.histfileSnapshot = map[string][]string{} |
||||
s.history = map[string][]string{} |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *RecentBash) GetTitleAndDescription() (string, string) { |
||||
return "recent (bash-like)", "Behave like bash" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
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 |
||||
} |
||||
return append(s.history[strippedRecord.SessionID], s.histfileSnapshot[strippedRecord.SessionID]...) |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
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 { |
||||
s.history[record.SessionID] = append(s.history[record.SessionID][:i], s.history[record.SessionID][i+1:]...) |
||||
} |
||||
} |
||||
// append new record
|
||||
s.history[record.SessionID] = append([]string{record.CmdLine}, s.history[record.SessionID]...) |
||||
|
||||
if record.LastRecordOfSession { |
||||
// append history of the session to histfile and clear session history
|
||||
s.histfile = append(s.history[record.SessionID], s.histfile...) |
||||
s.histfileSnapshot[record.SessionID] = nil |
||||
s.history[record.SessionID] = nil |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *RecentBash) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,37 +0,0 @@ |
||||
package strat |
||||
|
||||
import "github.com/curusarn/resh/pkg/records" |
||||
|
||||
// Recent prediction/recommendation strategy
|
||||
type Recent struct { |
||||
history []string |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *Recent) GetTitleAndDescription() (string, string) { |
||||
return "recent", "Use recent commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
func (s *Recent) GetCandidates() []string { |
||||
return s.history |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *Recent) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
// remove previous occurance of record
|
||||
for i, cmd := range s.history { |
||||
if cmd == record.CmdLine { |
||||
s.history = append(s.history[:i], s.history[i+1:]...) |
||||
} |
||||
} |
||||
// append new record
|
||||
s.history = append([]string{record.CmdLine}, s.history...) |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *Recent) ResetHistory() error { |
||||
s.history = nil |
||||
return nil |
||||
} |
||||
@ -1,70 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"sort" |
||||
"strconv" |
||||
|
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// RecordDistance prediction/recommendation strategy
|
||||
type RecordDistance struct { |
||||
history []records.EnrichedRecord |
||||
DistParams records.DistParams |
||||
MaxDepth int |
||||
Label string |
||||
} |
||||
|
||||
type strDistEntry struct { |
||||
cmdLine string |
||||
distance float64 |
||||
} |
||||
|
||||
// Init see name
|
||||
func (s *RecordDistance) Init() { |
||||
s.history = nil |
||||
} |
||||
|
||||
// GetTitleAndDescription see name
|
||||
func (s *RecordDistance) GetTitleAndDescription() (string, string) { |
||||
return "record distance (depth:" + strconv.Itoa(s.MaxDepth) + ";" + s.Label + ")", "Use record distance to recommend commands" |
||||
} |
||||
|
||||
// GetCandidates see name
|
||||
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 { |
||||
break |
||||
} |
||||
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 }) |
||||
var hist []string |
||||
histSet := map[string]bool{} |
||||
for _, item := range mapItems { |
||||
if histSet[item.cmdLine] { |
||||
continue |
||||
} |
||||
histSet[item.cmdLine] = true |
||||
hist = append(hist, item.cmdLine) |
||||
} |
||||
return hist |
||||
} |
||||
|
||||
// AddHistoryRecord see name
|
||||
func (s *RecordDistance) AddHistoryRecord(record *records.EnrichedRecord) error { |
||||
// append record to front
|
||||
s.history = append([]records.EnrichedRecord{*record}, s.history...) |
||||
return nil |
||||
} |
||||
|
||||
// ResetHistory see name
|
||||
func (s *RecordDistance) ResetHistory() error { |
||||
s.Init() |
||||
return nil |
||||
} |
||||
@ -1,46 +0,0 @@ |
||||
package strat |
||||
|
||||
import ( |
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// ISimpleStrategy interface
|
||||
type ISimpleStrategy interface { |
||||
GetTitleAndDescription() (string, string) |
||||
GetCandidates() []string |
||||
AddHistoryRecord(record *records.EnrichedRecord) error |
||||
ResetHistory() error |
||||
} |
||||
|
||||
// IStrategy interface
|
||||
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…
Reference in new issue