Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the psutil builtins #185

Merged
merged 1 commit into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions env/spreadsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,18 @@ func (s SpreadsheetRow) Type() Type {
return SpreadsheetRowType
}

func (s SpreadsheetRow) ToDict() Dict {
ser := make([]string, len(s.Values))
for i, v := range s.Values {
ser[i] = fmt.Sprint(v)
}
d := NewDict(map[string]any{})
for i, v := range s.Values {
d.Data[s.Uplink.Cols[i]] = v
}
return *d
}

func (s Spreadsheet) GetColumns() List {
lst := make([]any, len(s.Cols))
for i, v := range s.Cols {
Expand Down
2 changes: 1 addition & 1 deletion evaldo/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -7405,7 +7405,6 @@ func RegisterBuiltins(ps *env.ProgramState) {
RegisterBuiltins2(Builtins_html, ps, "html")
RegisterBuiltins2(Builtins_sqlite, ps, "sqlite")
RegisterBuiltins2(Builtins_validation, ps, "validation")
RegisterBuiltins2(Builtins_ps, ps, "ps")
RegisterBuiltins2(Builtins_json, ps, "json")
RegisterBuiltins2(Builtins_stackless, ps, "stackless")
RegisterBuiltins2(Builtins_eyr, ps, "eyr")
Expand All @@ -7425,6 +7424,7 @@ func RegisterBuiltins(ps *env.ProgramState) {
RegisterBuiltins2(Builtins_smtpd, ps, "smtpd")
RegisterBuiltins2(Builtins_mail, ps, "mail")
RegisterBuiltinsInContext(Builtins_math, ps, "math")
RegisterBuiltinsInContext(Builtins_ps, ps, "devops")
// ## Archived modules
// RegisterBuiltins2(Builtins_gtk, ps, "gtk")
// RegisterBuiltins2(Builtins_nats, ps, "nats")
Expand Down
291 changes: 265 additions & 26 deletions evaldo/builtins_psutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,297 @@ package evaldo

import (
"fmt"
"strings"
"time"

"github.com/refaktor/rye/env"

"github.com/shirou/gopsutil/process"

"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/process"
)

func something1() {

fmt.Print("1")
}

// In request we return a raw-map, because it's very inside loop call, this is sparse call, and we get tons of fields, so it would be best
// to turn them to normal Rye map (which is now Env / later Context or something like it), and they query it from Rye.

var Builtins_ps = map[string]*env.Builtin{

"ps\\virtual-memory?": {
"host-info?": {
Argsn: 0,
Doc: "Get information about the host system.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
v, err := host.Info()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "host-info?")
}
r := env.NewDict(make(map[string]any, 10))
r.Data["hostname"] = *env.NewString(v.Hostname)
r.Data["uptime"] = *env.NewInteger(int64(v.Uptime))
r.Data["boot-time"] = *env.NewInteger(int64(v.BootTime))
r.Data["procs"] = *env.NewInteger(int64(v.Procs))
r.Data["os"] = *env.NewString(v.OS)
r.Data["platform"] = *env.NewString(v.Platform)
r.Data["platform-family"] = *env.NewString(v.PlatformFamily)
r.Data["platform-version"] = *env.NewString(v.PlatformVersion)
r.Data["kernel-version"] = *env.NewString(v.KernelVersion)
r.Data["virtualization-system"] = *env.NewString(v.VirtualizationSystem)
return *r
},
},
"users?": {
Argsn: 0,
Doc: "Get information about users as a spreadsheet.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
users, err := host.Users()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "users?")
}
fmt.Println(users)
s := env.NewSpreadsheet([]string{"User", "Terminal", "Host", "Started"})
for _, user := range users {
vals := []any{
*env.NewString(user.User),
*env.NewString(user.Terminal),
*env.NewString(user.Host),
*env.NewInteger(int64(user.Started)),
}
s.AddRow(*env.NewSpreadsheetRow(vals, s))
}
return *s
},
},
"load-avg?": {
Argsn: 0,
Doc: "Get the load average as a dict representing load average over the last 1, 5, and 15 minutes.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
v, err := load.Avg()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "load-avg?")
}
r := env.NewDict(make(map[string]any, 3))
r.Data["1"] = *env.NewDecimal(v.Load1)
r.Data["5"] = *env.NewDecimal(v.Load5)
r.Data["15"] = *env.NewDecimal(v.Load15)
return r
},
},
"virtual-memory?": {
Argsn: 0,
Doc: "Get information about virtual memory usage.",
Fn: func(env1 *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
v, _ := mem.VirtualMemory()
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
v, err := mem.VirtualMemory()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "virtual-memory?")
}
r := env.NewDict(make(map[string]any, 3))
r.Data["total"] = v.Total
r.Data["free"] = v.Free
r.Data["used-percent"] = v.UsedPercent
r.Data["total"] = *env.NewInteger(int64(v.Total))
r.Data["free"] = *env.NewInteger(int64(v.Free))
r.Data["used-percent"] = *env.NewDecimal(v.UsedPercent)
return *r
},
},
"ps\\pids?": {
"disk-usage?": {
Argsn: 0,
Doc: "Get disk usage information as a spreadsheet.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
partitions, err := disk.Partitions(true)
if err != nil {
return MakeBuiltinError(ps, err.Error(), "disk-usage?")
}
s := env.NewSpreadsheet([]string{"Filesystem", "Size", "Used", "Available", "Capacity", "iused", "ifree", "%iused", "Mounted on"})
for _, partition := range partitions {
usage, err := disk.Usage(partition.Mountpoint)
if err != nil {
return MakeBuiltinError(ps, err.Error(), "disk-usage?")
}
vals := []any{
*env.NewString(partition.Device),
*env.NewInteger(int64(usage.Total)),
*env.NewInteger(int64(usage.Used)),
*env.NewInteger(int64(usage.Free)),
*env.NewDecimal(float64(usage.UsedPercent)),
*env.NewInteger(int64(usage.InodesUsed)),
*env.NewInteger(int64(usage.InodesFree)),
*env.NewInteger(int64(usage.InodesUsedPercent)),
*env.NewString(usage.Path),
}
s.AddRow(*env.NewSpreadsheetRow(vals, s))
}
return *s
},
},
"pids?": {
Argsn: 0,
Doc: "Get process pids as a block.",
Fn: func(env1 *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
pids, _ := process.Pids()
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
pids, err := process.Pids()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "pids?")
}

pids2 := make([]env.Object, len(pids))
for i, p := range pids {
pids2[i] = env.Integer{int64(p)}
pids2[i] = env.NewInteger(int64(p))
}
return env.NewBlock(*env.NewTSeries(pids2))
},
},
"ps\\process": {
"processes?": {
Argsn: 0,
Doc: "Get information about all processes as a spreadsheet.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
processes, err := process.Processes()
if err != nil {
return MakeBuiltinError(ps, err.Error(), "processes?")
}
s := proccesSpreadsheetBase()
for _, process := range processes {
processSpreadsheetAdd(s, process)
}
return *s
},
},
"process": {
Argsn: 1,
Doc: "TODODOC",
Fn: func(env1 *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
pids, _ := process.Pids()
pids2 := make([]env.Object, len(pids))
for i, p := range pids {
pids2[i] = env.Integer{int64(p)}
Doc: "Get information about process with a given PID.",
Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object {
switch pid := arg0.(type) {
case env.Integer:
process, err := process.NewProcess(int32(pid.Value))
if err != nil {
return MakeBuiltinError(ps, err.Error(), "process")
}
s := proccesSpreadsheetBase()
processSpreadsheetAdd(s, process)
return s.Rows[0].ToDict()
default:
return MakeArgError(ps, 1, []env.Type{env.IntegerType}, "process")
}
return env.NewBlock(*env.NewTSeries(pids2))
},
},
}

func proccesSpreadsheetBase() *env.Spreadsheet {
return env.NewSpreadsheet([]string{
"User",
"PID",
"Status",
"%CPU",
"%MEM",
"VZS",
"RSS",
"Num Threads",
"Num FDs",
"Num Open Files",
"Num Connections",
"Started at",
"CPU Time",
"Command",
})
}

func processSpreadsheetAdd(s *env.Spreadsheet, process *process.Process) {
var status env.String
stat, err := process.Status()
if err == nil {
status = *env.NewString(strings.Join(stat, " "))
} else {
status = *env.NewString("???")
}

var vzs env.Object
var rss env.Object
memInfo, err := process.MemoryInfo()
if err == nil {
vzs = *env.NewInteger(int64(memInfo.VMS))
rss = *env.NewInteger(int64(memInfo.RSS))
} else {
vzs = *env.NewString("???")
rss = *env.NewString("???")
}

var numOpenFiles env.Object
openFiles, err := process.OpenFiles()
if err == nil {
numOpenFiles = *env.NewInteger(int64(len(openFiles)))
} else {
numOpenFiles = *env.NewString("???")
}

var numConnections env.Object
connections, err := process.Connections()
if err == nil {
numConnections = *env.NewInteger(int64(len(connections)))
} else {
numConnections = *env.NewString("???")
}

var startedAt env.Object
createTime, err := process.CreateTime()
if err == nil {
startedAt = *env.NewDate(time.UnixMilli(createTime))
} else {
startedAt = *env.NewString("???")
}

var cpuTime env.Object
times, err := process.Times()
if err == nil {
dur := time.Duration(times.User+times.System) * time.Second
cpuTime = *env.NewString(fmt.Sprintf("%02d:%02d.%02d", int(dur.Minutes()), int(dur.Seconds())%60, int(dur.Milliseconds())%1000))
} else {
cpuTime = *env.NewString("???")
}

vals := []any{
maybeString(process.Username),
process.Pid,
status,
maybeFloat64(process.CPUPercent),
maybeFloat32(process.MemoryPercent),
vzs,
rss,
maybeInt32(process.NumThreads),
maybeInt32(process.NumFDs),
numOpenFiles,
numConnections,
startedAt,
cpuTime,
maybeString(process.Cmdline),
}
s.AddRow(*env.NewSpreadsheetRow(vals, s))
}

func maybeString(f func() (string, error)) env.Object {
s, err := f()
if err != nil {
return *env.NewString("???")
}
return *env.NewString(s)
}

func maybeFloat64(f func() (float64, error)) env.Object {
s, err := f()
if err != nil {
return *env.NewString("???")
}
return *env.NewDecimal(s)
}

func maybeFloat32(f func() (float32, error)) env.Object {
s, err := f()
if err != nil {
return *env.NewString("???")
}
return *env.NewDecimal(float64(s))
}

func maybeInt32(f func() (int32, error)) env.Object {
s, err := f()
if err != nil {
return *env.NewString("???")
}
return *env.NewInteger(int64(s))
}
Loading