mirror of https://github.com/curusarn/resh
Merge pull request #15 from curusarn/dev_3
Restructure the project, improve evaluation, add tests (go, shell)pull/18/head
commit
e4e1ac3e7a
@ -1,4 +1 @@ |
||||
resh-collect |
||||
resh-daemon |
||||
resh-sanitize-history |
||||
resh-evaluate |
||||
bin/* |
||||
|
||||
@ -0,0 +1,152 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"log" |
||||
"os" |
||||
"os/user" |
||||
"path/filepath" |
||||
|
||||
"github.com/curusarn/resh/pkg/histanal" |
||||
"github.com/curusarn/resh/pkg/records" |
||||
"github.com/curusarn/resh/pkg/strat" |
||||
) |
||||
|
||||
// Version from git set during build
|
||||
var Version string |
||||
|
||||
// Revision from git set during build
|
||||
var Revision string |
||||
|
||||
func main() { |
||||
const maxCandidates = 50 |
||||
|
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
historyPath := filepath.Join(dir, ".resh_history.json") |
||||
historyPathBatchMode := filepath.Join(dir, "resh_history.json") |
||||
sanitizedHistoryPath := filepath.Join(dir, "resh_history_sanitized.json") |
||||
// tmpPath := "/tmp/resh-evaluate-tmp.json"
|
||||
|
||||
showVersion := flag.Bool("version", false, "Show version and exit") |
||||
showRevision := flag.Bool("revision", false, "Show git revision and exit") |
||||
input := flag.String("input", "", |
||||
"Input file (default: "+historyPath+"OR"+sanitizedHistoryPath+ |
||||
" depending on --sanitized-input option)") |
||||
// outputDir := flag.String("output", "/tmp/resh-evaluate", "Output directory")
|
||||
sanitizedInput := flag.Bool("sanitized-input", false, |
||||
"Handle input as sanitized (also changes default value for input argument)") |
||||
plottingScript := flag.String("plotting-script", "resh-evaluate-plot.py", "Script to use for plotting") |
||||
inputDataRoot := flag.String("input-data-root", "", |
||||
"Input data root, enables batch mode, looks for files matching --input option") |
||||
slow := flag.Bool("slow", false, |
||||
"Enables strategies that takes a long time (e.g. markov chain strategies).") |
||||
skipFailedCmds := flag.Bool("skip-failed-cmds", false, |
||||
"Skips records with non-zero exit status.") |
||||
debugRecords := flag.Float64("debug", 0, "Debug records - percentage of records that should be debugged.") |
||||
|
||||
flag.Parse() |
||||
|
||||
// handle show{Version,Revision} options
|
||||
if *showVersion == true { |
||||
fmt.Println(Version) |
||||
os.Exit(0) |
||||
} |
||||
if *showRevision == true { |
||||
fmt.Println(Revision) |
||||
os.Exit(0) |
||||
} |
||||
|
||||
// handle batch mode
|
||||
batchMode := false |
||||
if *inputDataRoot != "" { |
||||
batchMode = true |
||||
} |
||||
// set default input
|
||||
if *input == "" { |
||||
if *sanitizedInput { |
||||
*input = sanitizedHistoryPath |
||||
} else if batchMode { |
||||
*input = historyPathBatchMode |
||||
} else { |
||||
*input = historyPath |
||||
} |
||||
} |
||||
|
||||
var evaluator histanal.HistEval |
||||
if batchMode { |
||||
evaluator = histanal.NewHistEvalBatchMode(*input, *inputDataRoot, maxCandidates, *skipFailedCmds, *debugRecords, *sanitizedInput) |
||||
} else { |
||||
evaluator = histanal.NewHistEval(*input, maxCandidates, *skipFailedCmds, *debugRecords, *sanitizedInput) |
||||
} |
||||
|
||||
var simpleStrategies []strat.ISimpleStrategy |
||||
var strategies []strat.IStrategy |
||||
|
||||
// dummy := strategyDummy{}
|
||||
// simpleStrategies = append(simpleStrategies, &dummy)
|
||||
|
||||
simpleStrategies = append(simpleStrategies, &strat.Recent{}) |
||||
|
||||
// frequent := strategyFrequent{}
|
||||
// frequent.init()
|
||||
// simpleStrategies = append(simpleStrategies, &frequent)
|
||||
|
||||
// random := strategyRandom{candidatesSize: maxCandidates}
|
||||
// random.init()
|
||||
// simpleStrategies = append(simpleStrategies, &random)
|
||||
|
||||
directory := strat.DirectorySensitive{} |
||||
directory.Init() |
||||
simpleStrategies = append(simpleStrategies, &directory) |
||||
|
||||
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() |
||||
strategies = append(strategies, &dynamicDistG) |
||||
|
||||
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 := strat.RecentBash{} |
||||
recentBash.Init() |
||||
strategies = append(strategies, &recentBash) |
||||
|
||||
if *slow { |
||||
|
||||
markovCmd := strat.MarkovChainCmd{Order: 1} |
||||
markovCmd.Init() |
||||
|
||||
markovCmd2 := strat.MarkovChainCmd{Order: 2} |
||||
markovCmd2.Init() |
||||
|
||||
markov := strat.MarkovChain{Order: 1} |
||||
markov.Init() |
||||
|
||||
markov2 := strat.MarkovChain{Order: 2} |
||||
markov2.Init() |
||||
|
||||
simpleStrategies = append(simpleStrategies, &markovCmd2, &markovCmd, &markov2, &markov) |
||||
} |
||||
|
||||
for _, strategy := range simpleStrategies { |
||||
strategies = append(strategies, strat.NewSimpleStrategyWrapper(strategy)) |
||||
} |
||||
|
||||
for _, strat := range strategies { |
||||
err := evaluator.Evaluate(strat) |
||||
if err != nil { |
||||
log.Println("Evaluator evaluate() error:", err) |
||||
} |
||||
} |
||||
|
||||
evaluator.CalculateStatsAndPlot(*plottingScript) |
||||
} |
||||
@ -1,247 +0,0 @@ |
||||
package common |
||||
|
||||
import ( |
||||
"log" |
||||
"strconv" |
||||
|
||||
"github.com/mattn/go-shellwords" |
||||
) |
||||
|
||||
// Record representing single executed command with its metadata
|
||||
type Record struct { |
||||
// core
|
||||
CmdLine string `json:"cmdLine"` |
||||
ExitCode int `json:"exitCode"` |
||||
Shell string `json:"shell"` |
||||
Uname string `json:"uname"` |
||||
SessionId string `json:"sessionId"` |
||||
|
||||
// posix
|
||||
Cols string `json:"cols"` |
||||
Lines string `json:"lines"` |
||||
Home string `json:"home"` |
||||
Lang string `json:"lang"` |
||||
LcAll string `json:"lcAll"` |
||||
Login string `json:"login"` |
||||
//Path string `json:"path"`
|
||||
Pwd string `json:"pwd"` |
||||
PwdAfter string `json:"pwdAfter"` |
||||
ShellEnv string `json:"shellEnv"` |
||||
Term string `json:"term"` |
||||
|
||||
// non-posix"`
|
||||
RealPwd string `json:"realPwd"` |
||||
RealPwdAfter string `json:"realPwdAfter"` |
||||
Pid int `json:"pid"` |
||||
SessionPid int `json:"sessionPid"` |
||||
Host string `json:"host"` |
||||
Hosttype string `json:"hosttype"` |
||||
Ostype string `json:"ostype"` |
||||
Machtype string `json:"machtype"` |
||||
Shlvl int `json:"shlvl"` |
||||
|
||||
// before after
|
||||
TimezoneBefore string `json:"timezoneBefore"` |
||||
TimezoneAfter string `json:"timezoneAfter"` |
||||
|
||||
RealtimeBefore float64 `json:"realtimeBefore"` |
||||
RealtimeAfter float64 `json:"realtimeAfter"` |
||||
RealtimeBeforeLocal float64 `json:"realtimeBeforeLocal"` |
||||
RealtimeAfterLocal float64 `json:"realtimeAfterLocal"` |
||||
|
||||
RealtimeDuration float64 `json:"realtimeDuration"` |
||||
RealtimeSinceSessionStart float64 `json:"realtimeSinceSessionStart"` |
||||
RealtimeSinceBoot float64 `json:"realtimeSinceBoot"` |
||||
//Logs []string `json: "logs"`
|
||||
|
||||
GitDir string `json:"gitDir"` |
||||
GitRealDir string `json:"gitRealDir"` |
||||
GitOriginRemote string `json:"gitOriginRemote"` |
||||
MachineId string `json:"machineId"` |
||||
|
||||
OsReleaseId string `json:"osReleaseId"` |
||||
OsReleaseVersionId string `json:"osReleaseVersionId"` |
||||
OsReleaseIdLike string `json:"osReleaseIdLike"` |
||||
OsReleaseName string `json:"osReleaseName"` |
||||
OsReleasePrettyName string `json:"osReleasePrettyName"` |
||||
|
||||
ReshUuid string `json:"reshUuid"` |
||||
ReshVersion string `json:"reshVersion"` |
||||
ReshRevision string `json:"reshRevision"` |
||||
|
||||
// added by sanitizatizer
|
||||
Sanitized bool `json:"sanitized"` |
||||
CmdLength int `json:"cmdLength,omitempty"` |
||||
|
||||
// enriching fields - added "later"
|
||||
FirstWord string `json:"firstWord,omitempty"` |
||||
Invalid bool `json:"invalid,omitempty"` |
||||
SeqSessionID uint64 `json:"seqSessionID,omitempty"` |
||||
} |
||||
|
||||
// FallbackRecord when record is too old and can't be parsed into regular Record
|
||||
type FallbackRecord struct { |
||||
// older version of the record where cols and lines are int
|
||||
|
||||
// core
|
||||
CmdLine string `json:"cmdLine"` |
||||
ExitCode int `json:"exitCode"` |
||||
Shell string `json:"shell"` |
||||
Uname string `json:"uname"` |
||||
SessionId string `json:"sessionId"` |
||||
|
||||
// posix
|
||||
Cols int `json:"cols"` // notice the in type
|
||||
Lines int `json:"lines"` // notice the in type
|
||||
Home string `json:"home"` |
||||
Lang string `json:"lang"` |
||||
LcAll string `json:"lcAll"` |
||||
Login string `json:"login"` |
||||
//Path string `json:"path"`
|
||||
Pwd string `json:"pwd"` |
||||
PwdAfter string `json:"pwdAfter"` |
||||
ShellEnv string `json:"shellEnv"` |
||||
Term string `json:"term"` |
||||
|
||||
// non-posix"`
|
||||
RealPwd string `json:"realPwd"` |
||||
RealPwdAfter string `json:"realPwdAfter"` |
||||
Pid int `json:"pid"` |
||||
SessionPid int `json:"sessionPid"` |
||||
Host string `json:"host"` |
||||
Hosttype string `json:"hosttype"` |
||||
Ostype string `json:"ostype"` |
||||
Machtype string `json:"machtype"` |
||||
Shlvl int `json:"shlvl"` |
||||
|
||||
// before after
|
||||
TimezoneBefore string `json:"timezoneBefore"` |
||||
TimezoneAfter string `json:"timezoneAfter"` |
||||
|
||||
RealtimeBefore float64 `json:"realtimeBefore"` |
||||
RealtimeAfter float64 `json:"realtimeAfter"` |
||||
RealtimeBeforeLocal float64 `json:"realtimeBeforeLocal"` |
||||
RealtimeAfterLocal float64 `json:"realtimeAfterLocal"` |
||||
|
||||
RealtimeDuration float64 `json:"realtimeDuration"` |
||||
RealtimeSinceSessionStart float64 `json:"realtimeSinceSessionStart"` |
||||
RealtimeSinceBoot float64 `json:"realtimeSinceBoot"` |
||||
//Logs []string `json: "logs"`
|
||||
|
||||
GitDir string `json:"gitDir"` |
||||
GitRealDir string `json:"gitRealDir"` |
||||
GitOriginRemote string `json:"gitOriginRemote"` |
||||
MachineId string `json:"machineId"` |
||||
|
||||
OsReleaseId string `json:"osReleaseId"` |
||||
OsReleaseVersionId string `json:"osReleaseVersionId"` |
||||
OsReleaseIdLike string `json:"osReleaseIdLike"` |
||||
OsReleaseName string `json:"osReleaseName"` |
||||
OsReleasePrettyName string `json:"osReleasePrettyName"` |
||||
|
||||
ReshUuid string `json:"reshUuid"` |
||||
ReshVersion string `json:"reshVersion"` |
||||
ReshRevision string `json:"reshRevision"` |
||||
} |
||||
|
||||
// ConvertRecord from FallbackRecord to Record
|
||||
func ConvertRecord(r *FallbackRecord) Record { |
||||
return Record{ |
||||
// core
|
||||
CmdLine: r.CmdLine, |
||||
ExitCode: r.ExitCode, |
||||
Shell: r.Shell, |
||||
Uname: r.Uname, |
||||
SessionId: r.SessionId, |
||||
|
||||
// posix
|
||||
// these two lines are the only reason we are doing this
|
||||
Cols: strconv.Itoa(r.Cols), |
||||
Lines: strconv.Itoa(r.Lines), |
||||
|
||||
Home: r.Home, |
||||
Lang: r.Lang, |
||||
LcAll: r.LcAll, |
||||
Login: r.Login, |
||||
// Path: r.path,
|
||||
Pwd: r.Pwd, |
||||
PwdAfter: r.PwdAfter, |
||||
ShellEnv: r.ShellEnv, |
||||
Term: r.Term, |
||||
|
||||
// non-posix
|
||||
RealPwd: r.RealPwd, |
||||
RealPwdAfter: r.RealPwdAfter, |
||||
Pid: r.Pid, |
||||
SessionPid: r.SessionPid, |
||||
Host: r.Host, |
||||
Hosttype: r.Hosttype, |
||||
Ostype: r.Ostype, |
||||
Machtype: r.Machtype, |
||||
Shlvl: r.Shlvl, |
||||
|
||||
// before after
|
||||
TimezoneBefore: r.TimezoneBefore, |
||||
TimezoneAfter: r.TimezoneAfter, |
||||
|
||||
RealtimeBefore: r.RealtimeBefore, |
||||
RealtimeAfter: r.RealtimeAfter, |
||||
RealtimeBeforeLocal: r.RealtimeBeforeLocal, |
||||
RealtimeAfterLocal: r.RealtimeAfterLocal, |
||||
|
||||
RealtimeDuration: r.RealtimeDuration, |
||||
RealtimeSinceSessionStart: r.RealtimeSinceSessionStart, |
||||
RealtimeSinceBoot: r.RealtimeSinceBoot, |
||||
|
||||
GitDir: r.GitDir, |
||||
GitRealDir: r.GitRealDir, |
||||
GitOriginRemote: r.GitOriginRemote, |
||||
MachineId: r.MachineId, |
||||
|
||||
OsReleaseId: r.OsReleaseId, |
||||
OsReleaseVersionId: r.OsReleaseVersionId, |
||||
OsReleaseIdLike: r.OsReleaseIdLike, |
||||
OsReleaseName: r.OsReleaseName, |
||||
OsReleasePrettyName: r.OsReleasePrettyName, |
||||
|
||||
ReshUuid: r.ReshUuid, |
||||
ReshVersion: r.ReshVersion, |
||||
ReshRevision: r.ReshRevision, |
||||
} |
||||
} |
||||
|
||||
// Enrich - adds additional fields to the record
|
||||
func (r *Record) Enrich() { |
||||
// Get command/first word from commandline
|
||||
r.FirstWord = GetCommandFromCommandLine(r.CmdLine) |
||||
err := r.Validate() |
||||
if err != nil { |
||||
log.Println("Invalid command:", r.CmdLine) |
||||
r.Invalid = true |
||||
} |
||||
r.Invalid = false |
||||
// TODO: Detect and mark simple commands r.Simple
|
||||
} |
||||
|
||||
// Validate - returns error if the record is invalid
|
||||
func (r *Record) Validate() error { |
||||
return nil |
||||
} |
||||
|
||||
// GetCommandFromCommandLine func
|
||||
func GetCommandFromCommandLine(cmdLine string) string { |
||||
args, err := shellwords.Parse(cmdLine) |
||||
if err != nil { |
||||
log.Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )") |
||||
return "<error>" |
||||
} |
||||
if len(args) > 0 { |
||||
return args[0] |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// Config struct
|
||||
type Config struct { |
||||
Port int |
||||
} |
||||
@ -1,340 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"os/exec" |
||||
"os/user" |
||||
"path/filepath" |
||||
"sort" |
||||
|
||||
"github.com/curusarn/resh/common" |
||||
) |
||||
|
||||
// Version from git set during build
|
||||
var Version string |
||||
|
||||
// Revision from git set during build
|
||||
var Revision string |
||||
|
||||
func main() { |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
historyPath := filepath.Join(dir, ".resh_history.json") |
||||
historyPathBatchMode := filepath.Join(dir, "resh_history.json") |
||||
sanitizedHistoryPath := filepath.Join(dir, "resh_history_sanitized.json") |
||||
// tmpPath := "/tmp/resh-evaluate-tmp.json"
|
||||
|
||||
showVersion := flag.Bool("version", false, "Show version and exit") |
||||
showRevision := flag.Bool("revision", false, "Show git revision and exit") |
||||
input := flag.String("input", "", |
||||
"Input file (default: "+historyPath+"OR"+sanitizedHistoryPath+ |
||||
" depending on --sanitized-input option)") |
||||
// outputDir := flag.String("output", "/tmp/resh-evaluate", "Output directory")
|
||||
sanitizedInput := flag.Bool("sanitized-input", false, |
||||
"Handle input as sanitized (also changes default value for input argument)") |
||||
plottingScript := flag.String("plotting-script", "resh-evaluate-plot.py", "Script to use for plotting") |
||||
inputDataRoot := flag.String("input-data-root", "", |
||||
"Input data root, enables batch mode, looks for files matching --input option") |
||||
|
||||
flag.Parse() |
||||
|
||||
// handle show{Version,Revision} options
|
||||
if *showVersion == true { |
||||
fmt.Println(Version) |
||||
os.Exit(0) |
||||
} |
||||
if *showRevision == true { |
||||
fmt.Println(Revision) |
||||
os.Exit(0) |
||||
} |
||||
|
||||
// handle batch mode
|
||||
batchMode := false |
||||
if *inputDataRoot != "" { |
||||
batchMode = true |
||||
} |
||||
// set default input
|
||||
if *input == "" { |
||||
if *sanitizedInput { |
||||
*input = sanitizedHistoryPath |
||||
} else if batchMode { |
||||
*input = historyPathBatchMode |
||||
} else { |
||||
*input = historyPath |
||||
} |
||||
} |
||||
|
||||
evaluator := evaluator{sanitizedInput: *sanitizedInput, maxCandidates: 50, BatchMode: batchMode} |
||||
if batchMode { |
||||
err := evaluator.initBatchMode(*input, *inputDataRoot) |
||||
if err != nil { |
||||
log.Fatal("Evaluator initBatchMode() error:", err) |
||||
} |
||||
} else { |
||||
err := evaluator.init(*input) |
||||
if err != nil { |
||||
log.Fatal("Evaluator init() error:", err) |
||||
} |
||||
} |
||||
|
||||
var strategies []strategy |
||||
|
||||
// dummy := strategyDummy{}
|
||||
// strategies = append(strategies, &dummy)
|
||||
|
||||
recent := strategyRecent{} |
||||
frequent := strategyFrequent{} |
||||
frequent.init() |
||||
directory := strategyDirectorySensitive{} |
||||
directory.init() |
||||
|
||||
strategies = append(strategies, &recent, &frequent, &directory) |
||||
|
||||
for _, strat := range strategies { |
||||
err := evaluator.evaluate(strat) |
||||
if err != nil { |
||||
log.Println("Evaluator evaluate() error:", err) |
||||
} |
||||
} |
||||
|
||||
evaluator.calculateStatsAndPlot(*plottingScript) |
||||
} |
||||
|
||||
type strategy interface { |
||||
GetTitleAndDescription() (string, string) |
||||
GetCandidates() []string |
||||
AddHistoryRecord(record *common.Record) error |
||||
ResetHistory() error |
||||
} |
||||
|
||||
type matchJSON struct { |
||||
Match bool |
||||
Distance int |
||||
CharsRecalled int |
||||
} |
||||
|
||||
type strategyJSON struct { |
||||
Title string |
||||
Description string |
||||
Matches []matchJSON |
||||
} |
||||
|
||||
type deviceRecords struct { |
||||
Name string |
||||
Records []common.Record |
||||
} |
||||
|
||||
type userRecords struct { |
||||
Name string |
||||
Devices []deviceRecords |
||||
} |
||||
|
||||
type evaluator struct { |
||||
sanitizedInput bool |
||||
BatchMode bool |
||||
maxCandidates int |
||||
UsersRecords []userRecords |
||||
Strategies []strategyJSON |
||||
} |
||||
|
||||
func (e *evaluator) initBatchMode(input string, inputDataRoot string) error { |
||||
e.UsersRecords = e.loadHistoryRecordsBatchMode(input, inputDataRoot) |
||||
e.processRecords() |
||||
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.processRecords() |
||||
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) |
||||
} |
||||
} |
||||
|
||||
// enrich records and add them to serializable structure
|
||||
func (e *evaluator) processRecords() { |
||||
for i := range e.UsersRecords { |
||||
for j, device := range e.UsersRecords[i].Devices { |
||||
sessionIDs := map[string]uint64{} |
||||
var nextID uint64 |
||||
nextID = 0 |
||||
for k, record := range e.UsersRecords[i].Devices[j].Records { |
||||
id, found := sessionIDs[record.SessionId] |
||||
if found == false { |
||||
id = nextID |
||||
sessionIDs[record.SessionId] = id |
||||
nextID++ |
||||
} |
||||
record.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") |
||||
} |
||||
|
||||
e.UsersRecords[i].Devices[j].Records[k].Enrich() |
||||
// device.Records = append(device.Records, record)
|
||||
} |
||||
sort.SliceStable(e.UsersRecords[i].Devices[j].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 |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (e *evaluator) evaluate(strategy strategy) error { |
||||
title, description := strategy.GetTitleAndDescription() |
||||
strategyData := strategyJSON{Title: title, Description: description} |
||||
for _, record := range e.UsersRecords[0].Devices[0].Records { |
||||
candidates := strategy.GetCandidates() |
||||
|
||||
matchFound := false |
||||
for i, candidate := range candidates { |
||||
// make an option (--calculate-total) to turn this on/off ?
|
||||
// if i >= e.maxCandidates {
|
||||
// break
|
||||
// }
|
||||
if candidate == record.CmdLine { |
||||
match := matchJSON{Match: true, Distance: i + 1, CharsRecalled: record.CmdLength} |
||||
strategyData.Matches = append(strategyData.Matches, match) |
||||
matchFound = true |
||||
break |
||||
} |
||||
} |
||||
if matchFound == false { |
||||
strategyData.Matches = append(strategyData.Matches, matchJSON{}) |
||||
} |
||||
err := strategy.AddHistoryRecord(&record) |
||||
if err != nil { |
||||
log.Println("Error while evauating", err) |
||||
return err |
||||
} |
||||
} |
||||
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) []common.Record { |
||||
file, err := os.Open(fname) |
||||
if err != nil { |
||||
log.Fatal("Open() resh history file error:", err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
var records []common.Record |
||||
scanner := bufio.NewScanner(file) |
||||
for scanner.Scan() { |
||||
record := common.Record{} |
||||
fallbackRecord := common.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 = common.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.") |
||||
} |
||||
records = append(records, record) |
||||
} |
||||
return records |
||||
} |
||||
@ -1,24 +0,0 @@ |
||||
package main |
||||
|
||||
import "github.com/curusarn/resh/common" |
||||
|
||||
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 *common.Record) error { |
||||
s.history = append(s.history, record.CmdLine) |
||||
return nil |
||||
} |
||||
|
||||
func (s *strategyDummy) ResetHistory() error { |
||||
return nil |
||||
} |
||||
@ -1,32 +0,0 @@ |
||||
package main |
||||
|
||||
import "github.com/curusarn/resh/common" |
||||
|
||||
type strategyRecent struct { |
||||
history []string |
||||
} |
||||
|
||||
func (s *strategyRecent) GetTitleAndDescription() (string, string) { |
||||
return "recent", "Use recent commands" |
||||
} |
||||
|
||||
func (s *strategyRecent) GetCandidates() []string { |
||||
return s.history |
||||
} |
||||
|
||||
func (s *strategyRecent) AddHistoryRecord(record *common.Record) 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 |
||||
} |
||||
|
||||
func (s *strategyRecent) ResetHistory() error { |
||||
s.history = nil |
||||
return nil |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
package cfg |
||||
|
||||
// Config struct
|
||||
type Config struct { |
||||
Port int |
||||
} |
||||
@ -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, records.Enriched(record)) |
||||
} |
||||
return recs |
||||
} |
||||
@ -0,0 +1,391 @@ |
||||
package records |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"log" |
||||
"math" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/mattn/go-shellwords" |
||||
) |
||||
|
||||
// BaseRecord - common base for Record and FallbackRecord
|
||||
type BaseRecord struct { |
||||
// core
|
||||
CmdLine string `json:"cmdLine"` |
||||
ExitCode int `json:"exitCode"` |
||||
Shell string `json:"shell"` |
||||
Uname string `json:"uname"` |
||||
SessionID string `json:"sessionId"` |
||||
|
||||
// posix
|
||||
Home string `json:"home"` |
||||
Lang string `json:"lang"` |
||||
LcAll string `json:"lcAll"` |
||||
Login string `json:"login"` |
||||
//Path string `json:"path"`
|
||||
Pwd string `json:"pwd"` |
||||
PwdAfter string `json:"pwdAfter"` |
||||
ShellEnv string `json:"shellEnv"` |
||||
Term string `json:"term"` |
||||
|
||||
// non-posix"`
|
||||
RealPwd string `json:"realPwd"` |
||||
RealPwdAfter string `json:"realPwdAfter"` |
||||
Pid int `json:"pid"` |
||||
SessionPid int `json:"sessionPid"` |
||||
Host string `json:"host"` |
||||
Hosttype string `json:"hosttype"` |
||||
Ostype string `json:"ostype"` |
||||
Machtype string `json:"machtype"` |
||||
Shlvl int `json:"shlvl"` |
||||
|
||||
// before after
|
||||
TimezoneBefore string `json:"timezoneBefore"` |
||||
TimezoneAfter string `json:"timezoneAfter"` |
||||
|
||||
RealtimeBefore float64 `json:"realtimeBefore"` |
||||
RealtimeAfter float64 `json:"realtimeAfter"` |
||||
RealtimeBeforeLocal float64 `json:"realtimeBeforeLocal"` |
||||
RealtimeAfterLocal float64 `json:"realtimeAfterLocal"` |
||||
|
||||
RealtimeDuration float64 `json:"realtimeDuration"` |
||||
RealtimeSinceSessionStart float64 `json:"realtimeSinceSessionStart"` |
||||
RealtimeSinceBoot float64 `json:"realtimeSinceBoot"` |
||||
//Logs []string `json: "logs"`
|
||||
|
||||
GitDir string `json:"gitDir"` |
||||
GitRealDir string `json:"gitRealDir"` |
||||
GitOriginRemote string `json:"gitOriginRemote"` |
||||
MachineID string `json:"machineId"` |
||||
|
||||
OsReleaseID string `json:"osReleaseId"` |
||||
OsReleaseVersionID string `json:"osReleaseVersionId"` |
||||
OsReleaseIDLike string `json:"osReleaseIdLike"` |
||||
OsReleaseName string `json:"osReleaseName"` |
||||
OsReleasePrettyName string `json:"osReleasePrettyName"` |
||||
|
||||
ReshUUID string `json:"reshUuid"` |
||||
ReshVersion string `json:"reshVersion"` |
||||
ReshRevision string `json:"reshRevision"` |
||||
|
||||
// added by sanitizatizer
|
||||
Sanitized bool `json:"sanitized,omitempty"` |
||||
CmdLength int `json:"cmdLength,omitempty"` |
||||
} |
||||
|
||||
// Record representing single executed command with its metadata
|
||||
type Record struct { |
||||
BaseRecord |
||||
|
||||
Cols string `json:"cols"` |
||||
Lines string `json:"lines"` |
||||
} |
||||
|
||||
// EnrichedRecord - record enriched with additional data
|
||||
type EnrichedRecord struct { |
||||
Record |
||||
|
||||
// enriching fields - added "later"
|
||||
Command string `json:"command"` |
||||
FirstWord string `json:"firstWord"` |
||||
Invalid bool `json:"invalid"` |
||||
SeqSessionID uint64 `json:"seqSessionId"` |
||||
LastRecordOfSession bool `json:"lastRecordOfSession"` |
||||
DebugThisRecord bool `json:"debugThisRecord"` |
||||
Errors []string `json:"errors"` |
||||
// SeqSessionID uint64 `json:"seqSessionId,omitempty"`
|
||||
} |
||||
|
||||
// FallbackRecord when record is too old and can't be parsed into regular Record
|
||||
type FallbackRecord struct { |
||||
BaseRecord |
||||
// older version of the record where cols and lines are int
|
||||
|
||||
Cols int `json:"cols"` // notice the int type
|
||||
Lines int `json:"lines"` // notice the int type
|
||||
} |
||||
|
||||
// ConvertRecord from FallbackRecord to Record
|
||||
func ConvertRecord(r *FallbackRecord) Record { |
||||
return Record{ |
||||
BaseRecord: r.BaseRecord, |
||||
// these two lines are the only reason we are doing this
|
||||
Cols: strconv.Itoa(r.Cols), |
||||
Lines: strconv.Itoa(r.Lines), |
||||
} |
||||
} |
||||
|
||||
// ToString - returns record the json
|
||||
func (r EnrichedRecord) ToString() (string, error) { |
||||
jsonRec, err := json.Marshal(r) |
||||
if err != nil { |
||||
return "marshalling error", err |
||||
} |
||||
return string(jsonRec), nil |
||||
} |
||||
|
||||
// Enriched - returnd enriched record
|
||||
func Enriched(r Record) EnrichedRecord { |
||||
record := EnrichedRecord{Record: r} |
||||
// Get command/first word from commandline
|
||||
var err error |
||||
record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine) |
||||
if err != nil { |
||||
record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error()) |
||||
rec, _ := record.ToString() |
||||
log.Println("Invalid command:", rec) |
||||
record.Invalid = true |
||||
return record |
||||
} |
||||
err = r.Validate() |
||||
if err != nil { |
||||
record.Errors = append(record.Errors, "Validate error:"+err.Error()) |
||||
rec, _ := record.ToString() |
||||
log.Println("Invalid command:", rec) |
||||
record.Invalid = true |
||||
} |
||||
return record |
||||
// TODO: Detect and mark simple commands r.Simple
|
||||
} |
||||
|
||||
// Validate - returns error if the record is invalid
|
||||
func (r *Record) Validate() error { |
||||
if r.CmdLine == "" { |
||||
return errors.New("There is no CmdLine") |
||||
} |
||||
if r.RealtimeBefore == 0 || r.RealtimeAfter == 0 { |
||||
return errors.New("There is no Time") |
||||
} |
||||
if r.RealtimeBeforeLocal == 0 || r.RealtimeAfterLocal == 0 { |
||||
return errors.New("There is no Local Time") |
||||
} |
||||
if r.RealPwd == "" || r.RealPwdAfter == "" { |
||||
return errors.New("There is no Real Pwd") |
||||
} |
||||
if r.Pwd == "" || r.PwdAfter == "" { |
||||
return errors.New("There is no Pwd") |
||||
} |
||||
|
||||
// TimezoneBefore
|
||||
// TimezoneAfter
|
||||
|
||||
// RealtimeDuration
|
||||
// RealtimeSinceSessionStart - TODO: add later
|
||||
// RealtimeSinceBoot - TODO: add later
|
||||
|
||||
// device extras
|
||||
// Host
|
||||
// Hosttype
|
||||
// Ostype
|
||||
// Machtype
|
||||
// OsReleaseID
|
||||
// OsReleaseVersionID
|
||||
// OsReleaseIDLike
|
||||
// OsReleaseName
|
||||
// OsReleasePrettyName
|
||||
|
||||
// session extras
|
||||
// Term
|
||||
// Shlvl
|
||||
|
||||
// static info
|
||||
// Lang
|
||||
// LcAll
|
||||
|
||||
// meta
|
||||
// ReshUUID
|
||||
// ReshVersion
|
||||
// ReshRevision
|
||||
|
||||
// added by sanitizatizer
|
||||
// Sanitized
|
||||
// CmdLength
|
||||
return nil |
||||
} |
||||
|
||||
// SetCmdLine sets cmdLine and related members
|
||||
func (r *EnrichedRecord) SetCmdLine(cmdLine string) { |
||||
r.CmdLine = cmdLine |
||||
r.CmdLength = len(cmdLine) |
||||
r.ExitCode = 0 |
||||
var err error |
||||
r.Command, r.FirstWord, err = GetCommandAndFirstWord(cmdLine) |
||||
if err != nil { |
||||
r.Errors = append(r.Errors, "GetCommandAndFirstWord error:"+err.Error()) |
||||
// log.Println("Invalid command:", r.CmdLine)
|
||||
r.Invalid = true |
||||
} |
||||
} |
||||
|
||||
// Stripped returns record stripped of all info that is not available during prediction
|
||||
func Stripped(r EnrichedRecord) EnrichedRecord { |
||||
// clear the cmd itself
|
||||
r.SetCmdLine("") |
||||
// replace after info with before info
|
||||
r.PwdAfter = r.Pwd |
||||
r.RealPwdAfter = r.RealPwd |
||||
r.TimezoneAfter = r.TimezoneBefore |
||||
r.RealtimeAfter = r.RealtimeBefore |
||||
r.RealtimeAfterLocal = r.RealtimeBeforeLocal |
||||
// clear some more stuff
|
||||
r.RealtimeDuration = 0 |
||||
r.LastRecordOfSession = false |
||||
return r |
||||
} |
||||
|
||||
// GetCommandAndFirstWord func
|
||||
func GetCommandAndFirstWord(cmdLine string) (string, string, error) { |
||||
args, err := shellwords.Parse(cmdLine) |
||||
if err != nil { |
||||
log.Println("shellwords Error:", err, " (cmdLine: <", cmdLine, "> )") |
||||
return "", "", err |
||||
} |
||||
if len(args) == 0 { |
||||
return "", "", nil |
||||
} |
||||
i := 0 |
||||
for true { |
||||
// commands in shell sometimes look like this `variable=something command argument otherArgument --option`
|
||||
// to get the command we skip over tokens that contain '='
|
||||
if strings.ContainsRune(args[i], '=') && len(args) > i+1 { |
||||
i++ |
||||
continue |
||||
} |
||||
return args[i], args[0], nil |
||||
} |
||||
log.Fatal("GetCommandAndFirstWord error: this should not happen!") |
||||
return "ERROR", "ERROR", errors.New("this should not happen - contact developer ;)") |
||||
} |
||||
|
||||
// DistParams is used to supply params to Enrichedrecords.DistanceTo()
|
||||
type DistParams struct { |
||||
ExitCode float64 |
||||
MachineID float64 |
||||
SessionID float64 |
||||
Login float64 |
||||
Shell float64 |
||||
Pwd float64 |
||||
RealPwd float64 |
||||
Git float64 |
||||
Time float64 |
||||
} |
||||
|
||||
// DistanceTo another record
|
||||
func (r *EnrichedRecord) DistanceTo(r2 EnrichedRecord, p DistParams) float64 { |
||||
var dist float64 |
||||
dist = 0 |
||||
|
||||
// lev distance or something? TODO later
|
||||
// CmdLine
|
||||
|
||||
// exit code
|
||||
if r.ExitCode != r2.ExitCode { |
||||
if r.ExitCode == 0 || r2.ExitCode == 0 { |
||||
// one success + one error -> 1
|
||||
dist += 1 * p.ExitCode |
||||
} else { |
||||
// two different errors
|
||||
dist += 0.5 * p.ExitCode |
||||
} |
||||
} |
||||
|
||||
// machine/device
|
||||
if r.MachineID != r2.MachineID { |
||||
dist += 1 * p.MachineID |
||||
} |
||||
// Uname
|
||||
|
||||
// session
|
||||
if r.SessionID != r2.SessionID { |
||||
dist += 1 * p.SessionID |
||||
} |
||||
// Pid - add because of nested shells?
|
||||
// SessionPid
|
||||
|
||||
// user
|
||||
if r.Login != r2.Login { |
||||
dist += 1 * p.Login |
||||
} |
||||
// Home
|
||||
|
||||
// shell
|
||||
if r.Shell != r2.Shell { |
||||
dist += 1 * p.Shell |
||||
} |
||||
// ShellEnv
|
||||
|
||||
// pwd
|
||||
if r.Pwd != r2.Pwd { |
||||
// TODO: compare using hierarchy
|
||||
// TODO: make more important
|
||||
dist += 1 * p.Pwd |
||||
} |
||||
if r.RealPwd != r2.RealPwd { |
||||
// TODO: -||-
|
||||
dist += 1 * p.RealPwd |
||||
} |
||||
// PwdAfter
|
||||
// RealPwdAfter
|
||||
|
||||
// git
|
||||
if r.GitDir != r2.GitDir { |
||||
dist += 1 * p.Git |
||||
} |
||||
if r.GitRealDir != r2.GitRealDir { |
||||
dist += 1 * p.Git |
||||
} |
||||
if r.GitOriginRemote != r2.GitOriginRemote { |
||||
dist += 1 * p.Git |
||||
} |
||||
|
||||
// time
|
||||
// this can actually get negative for differences of less than one second which is fine
|
||||
// distance grows by 1 with every order
|
||||
distTime := math.Log10(math.Abs(r.RealtimeBefore-r2.RealtimeBefore)) * p.Time |
||||
if math.IsNaN(distTime) == false && math.IsInf(distTime, 0) == false { |
||||
dist += distTime |
||||
} |
||||
// RealtimeBeforeLocal
|
||||
// RealtimeAfter
|
||||
// RealtimeAfterLocal
|
||||
|
||||
// TimezoneBefore
|
||||
// TimezoneAfter
|
||||
|
||||
// RealtimeDuration
|
||||
// RealtimeSinceSessionStart - TODO: add later
|
||||
// RealtimeSinceBoot - TODO: add later
|
||||
|
||||
// device extras
|
||||
// Host
|
||||
// Hosttype
|
||||
// Ostype
|
||||
// Machtype
|
||||
// OsReleaseID
|
||||
// OsReleaseVersionID
|
||||
// OsReleaseIDLike
|
||||
// OsReleaseName
|
||||
// OsReleasePrettyName
|
||||
|
||||
// session extras
|
||||
// Term
|
||||
// Shlvl
|
||||
|
||||
// static info
|
||||
// Lang
|
||||
// LcAll
|
||||
|
||||
// meta
|
||||
// ReshUUID
|
||||
// ReshVersion
|
||||
// ReshRevision
|
||||
|
||||
// added by sanitizatizer
|
||||
// Sanitized
|
||||
// CmdLength
|
||||
|
||||
return dist |
||||
} |
||||
@ -0,0 +1,152 @@ |
||||
package records |
||||
|
||||
import ( |
||||
"bufio" |
||||
"encoding/json" |
||||
"log" |
||||
"os" |
||||
"testing" |
||||
) |
||||
|
||||
func GetTestRecords() []Record { |
||||
file, err := os.Open("testdata/resh_history.json") |
||||
if err != nil { |
||||
log.Fatal("Open() resh history file error:", err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
var recs []Record |
||||
scanner := bufio.NewScanner(file) |
||||
for scanner.Scan() { |
||||
record := Record{} |
||||
line := scanner.Text() |
||||
err = json.Unmarshal([]byte(line), &record) |
||||
if err != nil { |
||||
log.Println("Line:", line) |
||||
log.Fatal("Decoding error:", err) |
||||
} |
||||
recs = append(recs, record) |
||||
} |
||||
return recs |
||||
} |
||||
|
||||
func GetTestEnrichedRecords() []EnrichedRecord { |
||||
var recs []EnrichedRecord |
||||
for _, rec := range GetTestRecords() { |
||||
recs = append(recs, Enriched(rec)) |
||||
} |
||||
return recs |
||||
} |
||||
|
||||
func TestToString(t *testing.T) { |
||||
for _, rec := range GetTestEnrichedRecords() { |
||||
_, err := rec.ToString() |
||||
if err != nil { |
||||
t.Error("ToString() failed") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEnriched(t *testing.T) { |
||||
record := Record{BaseRecord: BaseRecord{CmdLine: "cmd arg1 arg2"}} |
||||
enriched := Enriched(record) |
||||
if enriched.FirstWord != "cmd" || enriched.Command != "cmd" { |
||||
t.Error("Enriched() returned reocord w/ wrong Command OR FirstWord") |
||||
} |
||||
} |
||||
|
||||
func TestValidate(t *testing.T) { |
||||
record := EnrichedRecord{} |
||||
if record.Validate() == nil { |
||||
t.Error("Validate() didn't return an error for invalid record") |
||||
} |
||||
record.CmdLine = "cmd arg" |
||||
record.FirstWord = "cmd" |
||||
record.Command = "cmd" |
||||
time := 1234.5678 |
||||
record.RealtimeBefore = time |
||||
record.RealtimeAfter = time |
||||
record.RealtimeBeforeLocal = time |
||||
record.RealtimeAfterLocal = time |
||||
pwd := "/pwd" |
||||
record.Pwd = pwd |
||||
record.PwdAfter = pwd |
||||
record.RealPwd = pwd |
||||
record.RealPwdAfter = pwd |
||||
if record.Validate() != nil { |
||||
t.Error("Validate() returned an error for a valid record") |
||||
} |
||||
} |
||||
|
||||
func TestSetCmdLine(t *testing.T) { |
||||
record := EnrichedRecord{} |
||||
cmdline := "cmd arg1 arg2" |
||||
record.SetCmdLine(cmdline) |
||||
if record.CmdLine != cmdline || record.Command != "cmd" || record.FirstWord != "cmd" { |
||||
t.Error() |
||||
} |
||||
} |
||||
|
||||
func TestStripped(t *testing.T) { |
||||
for _, rec := range GetTestEnrichedRecords() { |
||||
stripped := Stripped(rec) |
||||
|
||||
// there should be no cmdline
|
||||
if stripped.CmdLine != "" || |
||||
stripped.FirstWord != "" || |
||||
stripped.Command != "" { |
||||
t.Error("Stripped() returned record w/ info about CmdLine, Command OR FirstWord") |
||||
} |
||||
// *after* fields should be overwritten by *before* fields
|
||||
if stripped.PwdAfter != stripped.Pwd || |
||||
stripped.RealPwdAfter != stripped.RealPwd || |
||||
stripped.TimezoneAfter != stripped.TimezoneBefore || |
||||
stripped.RealtimeAfter != stripped.RealtimeBefore || |
||||
stripped.RealtimeAfterLocal != stripped.RealtimeBeforeLocal { |
||||
t.Error("Stripped() returned record w/ different *after* and *before* values - *after* fields should be overwritten by *before* fields") |
||||
} |
||||
// there should be no information about duration and session end
|
||||
if stripped.RealtimeDuration != 0 || |
||||
stripped.LastRecordOfSession != false { |
||||
t.Error("Stripped() returned record with too much information") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestGetCommandAndFirstWord(t *testing.T) { |
||||
cmd, stWord, err := GetCommandAndFirstWord("cmd arg1 arg2") |
||||
if err != nil || cmd != "cmd" || stWord != "cmd" { |
||||
t.Error("GetCommandAndFirstWord() returned wrong Command OR FirstWord") |
||||
} |
||||
} |
||||
|
||||
func TestDistanceTo(t *testing.T) { |
||||
paramsFull := DistParams{ |
||||
ExitCode: 1, |
||||
MachineID: 1, |
||||
SessionID: 1, |
||||
Login: 1, |
||||
Shell: 1, |
||||
Pwd: 1, |
||||
RealPwd: 1, |
||||
Git: 1, |
||||
Time: 1, |
||||
} |
||||
paramsZero := DistParams{} |
||||
var prevRec EnrichedRecord |
||||
for _, rec := range GetTestEnrichedRecords() { |
||||
dist := rec.DistanceTo(rec, paramsFull) |
||||
if dist != 0 { |
||||
t.Error("DistanceTo() itself should be always 0") |
||||
} |
||||
dist = rec.DistanceTo(prevRec, paramsFull) |
||||
if dist == 0 { |
||||
t.Error("DistanceTo() between two test records shouldn't be 0") |
||||
} |
||||
dist = rec.DistanceTo(prevRec, paramsZero) |
||||
if dist != 0 { |
||||
t.Error("DistanceTo() should be 0 when DistParams is all zeros") |
||||
} |
||||
prevRec = rec |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
{"cmdLine":"ls","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"d5c0fe70-c80b-4715-87cb-f8d8d5b4c673","cols":"80","lines":"24","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":14560,"sessionPid":14560,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1566762905.173595,"realtimeAfter":1566762905.1894295,"realtimeBeforeLocal":1566770105.173595,"realtimeAfterLocal":1566770105.1894295,"realtimeDuration":0.015834569931030273,"realtimeSinceSessionStart":1.7122540473937988,"realtimeSinceBoot":20766.542254047396,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"find . -name applications","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"c5251955-3a64-4353-952e-08d62a898694","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":3109,"sessionPid":3109,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567420001.2531302,"realtimeAfter":1567420002.4311218,"realtimeBeforeLocal":1567427201.2531302,"realtimeAfterLocal":1567427202.4311218,"realtimeDuration":1.1779916286468506,"realtimeSinceSessionStart":957.4848053455353,"realtimeSinceBoot":2336.594805345535,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"desktop-file-validate curusarn.sync-clipboards.desktop ","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"c5251955-3a64-4353-952e-08d62a898694","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/.local/share/applications","pwdAfter":"/home/simon/.local/share/applications","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/.local/share/applications","realPwdAfter":"/home/simon/.local/share/applications","pid":3109,"sessionPid":3109,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567421748.2965438,"realtimeAfter":1567421748.3068867,"realtimeBeforeLocal":1567428948.2965438,"realtimeAfterLocal":1567428948.3068867,"realtimeDuration":0.010342836380004883,"realtimeSinceSessionStart":2704.528218984604,"realtimeSinceBoot":4083.6382189846036,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"cat /tmp/extensions | grep '.'","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"f044cdbf-fd51-4c37-8528-dcd98fc7b6d9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":6887,"sessionPid":6887,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567461416.6871984,"realtimeAfter":1567461416.7336714,"realtimeBeforeLocal":1567468616.6871984,"realtimeAfterLocal":1567468616.7336714,"realtimeDuration":0.046473026275634766,"realtimeSinceSessionStart":21.45597553253174,"realtimeSinceBoot":43752.03597553253,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"cd git/resh/","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"f044cdbf-fd51-4c37-8528-dcd98fc7b6d9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon/git/resh","pid":6887,"sessionPid":6887,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567461667.8806899,"realtimeAfter":1567461667.8949044,"realtimeBeforeLocal":1567468867.8806899,"realtimeAfterLocal":1567468867.8949044,"realtimeDuration":0.014214515686035156,"realtimeSinceSessionStart":272.64946699142456,"realtimeSinceBoot":44003.229466991426,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"git s","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"f044cdbf-fd51-4c37-8528-dcd98fc7b6d9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":6887,"sessionPid":6887,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567461707.6467602,"realtimeAfter":1567461707.7177293,"realtimeBeforeLocal":1567468907.6467602,"realtimeAfterLocal":1567468907.7177293,"realtimeDuration":0.0709691047668457,"realtimeSinceSessionStart":312.4155373573303,"realtimeSinceBoot":44042.99553735733,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"cat /tmp/extensions | grep '^\\.' | cut -f1 |tr '[:upper:]' '[:lower:]' ","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"f044cdbf-fd51-4c37-8528-dcd98fc7b6d9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":6887,"sessionPid":6887,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567461722.813049,"realtimeAfter":1567461722.8280325,"realtimeBeforeLocal":1567468922.813049,"realtimeAfterLocal":1567468922.8280325,"realtimeDuration":0.014983415603637695,"realtimeSinceSessionStart":327.581826210022,"realtimeSinceBoot":44058.161826210024,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"tig","exitCode":127,"shell":"bash","uname":"Linux","sessionId":"f044cdbf-fd51-4c37-8528-dcd98fc7b6d9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":6887,"sessionPid":6887,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567461906.3896828,"realtimeAfter":1567461906.4084594,"realtimeBeforeLocal":1567469106.3896828,"realtimeAfterLocal":1567469106.4084594,"realtimeDuration":0.018776655197143555,"realtimeSinceSessionStart":511.1584599018097,"realtimeSinceBoot":44241.73845990181,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"752acb916f2a"} |
||||
{"cmdLine":"resh-sanitize-history | jq","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"a3318c80-3521-4b22-aa64-ea0f6c641410","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":14601,"sessionPid":14601,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567547116.2430356,"realtimeAfter":1567547116.7547352,"realtimeBeforeLocal":1567554316.2430356,"realtimeAfterLocal":1567554316.7547352,"realtimeDuration":0.5116996765136719,"realtimeSinceSessionStart":15.841878414154053,"realtimeSinceBoot":30527.201878414155,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"sudo pacman -S ansible","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"64154f2d-a4bc-4463-a690-520080b61ead","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/kristin","pwdAfter":"/home/simon/git/kristin","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/kristin","realPwdAfter":"/home/simon/git/kristin","pid":5663,"sessionPid":5663,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567609042.0166302,"realtimeAfter":1567609076.9726007,"realtimeBeforeLocal":1567616242.0166302,"realtimeAfterLocal":1567616276.9726007,"realtimeDuration":34.95597052574158,"realtimeSinceSessionStart":1617.0794131755829,"realtimeSinceBoot":6120.029413175583,"gitDir":"/home/simon/git/kristin","gitRealDir":"/home/simon/git/kristin","gitOriginRemote":"git@gitlab.com:sucvut/kristin.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"vagrant up","exitCode":1,"shell":"bash","uname":"Linux","sessionId":"64154f2d-a4bc-4463-a690-520080b61ead","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/kristin","pwdAfter":"/home/simon/git/kristin","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/kristin","realPwdAfter":"/home/simon/git/kristin","pid":5663,"sessionPid":5663,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567609090.7359188,"realtimeAfter":1567609098.3125577,"realtimeBeforeLocal":1567616290.7359188,"realtimeAfterLocal":1567616298.3125577,"realtimeDuration":7.57663893699646,"realtimeSinceSessionStart":1665.798701763153,"realtimeSinceBoot":6168.748701763153,"gitDir":"/home/simon/git/kristin","gitRealDir":"/home/simon/git/kristin","gitOriginRemote":"git@gitlab.com:sucvut/kristin.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"sudo modprobe vboxnetflt","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"64154f2d-a4bc-4463-a690-520080b61ead","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/kristin","pwdAfter":"/home/simon/git/kristin","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/kristin","realPwdAfter":"/home/simon/git/kristin","pid":5663,"sessionPid":5663,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567609143.2847652,"realtimeAfter":1567609143.3116078,"realtimeBeforeLocal":1567616343.2847652,"realtimeAfterLocal":1567616343.3116078,"realtimeDuration":0.026842594146728516,"realtimeSinceSessionStart":1718.3475482463837,"realtimeSinceBoot":6221.2975482463835,"gitDir":"/home/simon/git/kristin","gitRealDir":"/home/simon/git/kristin","gitOriginRemote":"git@gitlab.com:sucvut/kristin.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"echo $RANDOM","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"8ddacadc-6e73-483c-b347-4e18df204466","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":31387,"sessionPid":31387,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567727039.6540458,"realtimeAfter":1567727039.6629689,"realtimeBeforeLocal":1567734239.6540458,"realtimeAfterLocal":1567734239.6629689,"realtimeDuration":0.008923053741455078,"realtimeSinceSessionStart":1470.7667458057404,"realtimeSinceBoot":18495.01674580574,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"make resh-evaluate ","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567977478.9672194,"realtimeAfter":1567977479.5449634,"realtimeBeforeLocal":1567984678.9672194,"realtimeAfterLocal":1567984679.5449634,"realtimeDuration":0.5777440071105957,"realtimeSinceSessionStart":5738.577540636063,"realtimeSinceBoot":20980.42754063606,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"cat ~/.resh_history.json | grep \"./resh-eval\" | jq","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"105","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1567986105.3988302,"realtimeAfter":1567986105.4809113,"realtimeBeforeLocal":1567993305.3988302,"realtimeAfterLocal":1567993305.4809113,"realtimeDuration":0.08208107948303223,"realtimeSinceSessionStart":14365.00915145874,"realtimeSinceBoot":29606.85915145874,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"git c \"add sanitized flag to record, add Enrich() to record\"","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568063976.9103937,"realtimeAfter":1568063976.9326868,"realtimeBeforeLocal":1568071176.9103937,"realtimeAfterLocal":1568071176.9326868,"realtimeDuration":0.0222930908203125,"realtimeSinceSessionStart":92236.52071499825,"realtimeSinceBoot":107478.37071499825,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"git s","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568063978.2340608,"realtimeAfter":1568063978.252463,"realtimeBeforeLocal":1568071178.2340608,"realtimeAfterLocal":1568071178.252463,"realtimeDuration":0.0184023380279541,"realtimeSinceSessionStart":92237.84438204765,"realtimeSinceBoot":107479.69438204766,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"git a evaluate/results.go ","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568063989.0446353,"realtimeAfter":1568063989.2452207,"realtimeBeforeLocal":1568071189.0446353,"realtimeAfterLocal":1568071189.2452207,"realtimeDuration":0.20058536529541016,"realtimeSinceSessionStart":92248.65495657921,"realtimeSinceBoot":107490.50495657921,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"sudo pacman -S python-pip","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568072068.3557143,"realtimeAfter":1568072070.7509863,"realtimeBeforeLocal":1568079268.3557143,"realtimeAfterLocal":1568079270.7509863,"realtimeDuration":2.3952720165252686,"realtimeSinceSessionStart":100327.96603560448,"realtimeSinceBoot":115569.81603560448,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"pip3 install matplotlib","exitCode":1,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568072088.5575967,"realtimeAfter":1568072094.372314,"realtimeBeforeLocal":1568079288.5575967,"realtimeAfterLocal":1568079294.372314,"realtimeDuration":5.8147172927856445,"realtimeSinceSessionStart":100348.16791796684,"realtimeSinceBoot":115590.01791796685,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"sudo pip3 install matplotlib","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568072106.138616,"realtimeAfter":1568072115.1124601,"realtimeBeforeLocal":1568079306.138616,"realtimeAfterLocal":1568079315.1124601,"realtimeDuration":8.973844051361084,"realtimeSinceSessionStart":100365.7489373684,"realtimeSinceBoot":115607.5989373684,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"./resh-evaluate --plotting-script evaluate/resh-evaluate-plot.py --input ~/git/resh_private/history_data/simon/dell/resh_history.json ","exitCode":130,"shell":"bash","uname":"Linux","sessionId":"93998b68-ec48-4e48-9e4a-b37b39f5439e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":9463,"sessionPid":9463,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1568076266.9364285,"realtimeAfter":1568076288.1131275,"realtimeBeforeLocal":1568083466.9364285,"realtimeAfterLocal":1568083488.1131275,"realtimeDuration":21.176698923110962,"realtimeSinceSessionStart":104526.54674983025,"realtimeSinceBoot":119768.39674983025,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.1","reshRevision":"737bc0a4df38","cmdLength":0} |
||||
{"cmdLine":"git c \"Add a bunch of useless comments to make linter happy\"","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"04050353-a97d-4435-9248-f47dd08b2f2a","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":14702,"sessionPid":14702,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1569456045.8763022,"realtimeAfter":1569456045.9030173,"realtimeBeforeLocal":1569463245.8763022,"realtimeAfterLocal":1569463245.9030173,"realtimeDuration":0.02671504020690918,"realtimeSinceSessionStart":2289.789242744446,"realtimeSinceBoot":143217.91924274445,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.3","reshRevision":"188d8b420493","sanitized":false} |
||||
{"cmdLine":"fuck","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"a4aadf03-610d-4731-ba94-5b7ce21e7bb9","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":3413,"sessionPid":3413,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1569687682.4250975,"realtimeAfter":1569687682.5877323,"realtimeBeforeLocal":1569694882.4250975,"realtimeAfterLocal":1569694882.5877323,"realtimeDuration":0.16263484954833984,"realtimeSinceSessionStart":264603.49496507645,"realtimeSinceBoot":374854.48496507644,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.3","reshRevision":"188d8b420493","sanitized":false} |
||||
{"cmdLine":"code .","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"87c7ab14-ae51-408d-adbc-fc4f9d28de6e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":31947,"sessionPid":31947,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1569709366.523767,"realtimeAfter":1569709367.516908,"realtimeBeforeLocal":1569716566.523767,"realtimeAfterLocal":1569716567.516908,"realtimeDuration":0.9931409358978271,"realtimeSinceSessionStart":23846.908839941025,"realtimeSinceBoot":396539.888839941,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.3","reshRevision":"188d8b420493","sanitized":false} |
||||
{"cmdLine":"make test","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"87c7ab14-ae51-408d-adbc-fc4f9d28de6e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon/git/resh","pwdAfter":"/home/simon/git/resh","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon/git/resh","realPwdAfter":"/home/simon/git/resh","pid":31947,"sessionPid":31947,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1569709371.89966,"realtimeAfter":1569709377.430194,"realtimeBeforeLocal":1569716571.89966,"realtimeAfterLocal":1569716577.430194,"realtimeDuration":5.530533790588379,"realtimeSinceSessionStart":23852.284733057022,"realtimeSinceBoot":396545.264733057,"gitDir":"/home/simon/git/resh","gitRealDir":"/home/simon/git/resh","gitOriginRemote":"git@github.com:curusarn/resh.git","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.3","reshRevision":"188d8b420493","sanitized":false} |
||||
{"cmdLine":"mkdir ~/git/resh/testdata","exitCode":0,"shell":"bash","uname":"Linux","sessionId":"71529b60-2e7b-4d5b-8dc1-6d0740b58e9e","cols":"211","lines":"56","home":"/home/simon","lang":"en_US.UTF-8","lcAll":"","login":"simon","pwd":"/home/simon","pwdAfter":"/home/simon","shellEnv":"/bin/bash","term":"xterm-256color","realPwd":"/home/simon","realPwdAfter":"/home/simon","pid":21224,"sessionPid":21224,"host":"simon-pc","hosttype":"x86_64","ostype":"linux-gnu","machtype":"x86_64-pc-linux-gnu","shlvl":1,"timezoneBefore":"+0200","timezoneAfter":"+0200","realtimeBefore":1569709838.4642656,"realtimeAfter":1569709838.4718792,"realtimeBeforeLocal":1569717038.4642656,"realtimeAfterLocal":1569717038.4718792,"realtimeDuration":0.007613658905029297,"realtimeSinceSessionStart":9.437154054641724,"realtimeSinceBoot":397011.02715405467,"gitDir":"","gitRealDir":"","gitOriginRemote":"","machineId":"c70365240bc647f09e2490722cc8186b","osReleaseId":"manjaro","osReleaseVersionId":"","osReleaseIdLike":"arch","osReleaseName":"Manjaro Linux","osReleasePrettyName":"Manjaro Linux","reshUuid":"","reshVersion":"1.1.3","reshRevision":"188d8b420493","sanitized":false} |
||||
@ -0,0 +1,29 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,91 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,97 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,56 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,70 @@ |
||||
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 |
||||
} |
||||
@ -0,0 +1,46 @@ |
||||
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() |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
[ "${BASH_SOURCE[0]}" != "scripts/test.sh" ] && echo 'Run this script using `make test`' && exit 1 |
||||
|
||||
for f in scripts/*.sh; do |
||||
echo "Running shellcheck on $f ..." |
||||
shellcheck $f --shell=bash --severity=error || exit 1 |
||||
done |
||||
|
||||
echo "Checking Zsh syntax of scripts/shellrc.sh ..." |
||||
! zsh -n scripts/shellrc.sh && echo "Zsh syntax check failed!" && exit 1 |
||||
|
||||
for sh in bash zsh; do |
||||
echo "Running functions in scripts/shellrc.sh using $sh ..." |
||||
! $sh -c ". scripts/shellrc.sh; __resh_preexec; __resh_precmd" && echo "Error while running functions!" && exit 1 |
||||
done |
||||
|
||||
# TODO: test installation |
||||
|
||||
exit 0 |
||||
Loading…
Reference in new issue