├── doc └── psutilsql.gif ├── psutilsql.go ├── .github ├── dependabot.yml └── workflows │ ├── test.yml │ └── release.yml ├── doc.go ├── psutilsql_test.go ├── .gitignore ├── cmd ├── psutilsql │ └── main.go ├── version.go ├── query.go ├── table.go ├── winservices.go ├── docker.go ├── net.go ├── load.go ├── complete.go ├── process.go ├── disk.go ├── host.go ├── mem.go ├── cpu.go └── root.go ├── Makefile ├── docker_query.go ├── winservices_query.go ├── LICENSE ├── net_query_test.go ├── load_query.go ├── docker_query_test.go ├── mem_query.go ├── net_query.go ├── disk_query.go ├── table_query.go ├── importer.go ├── process_query_test.go ├── mem_query_test.go ├── host_query.go ├── load_query_test.go ├── cpu_query.go ├── .goreleaser.yaml ├── go.mod ├── process_query.go ├── disk_query_test.go ├── host_query_test.go ├── query.go ├── process_other.go ├── process_linux.go ├── cpu_query_test.go ├── process_wrpper.go ├── README.md └── go.sum /doc/psutilsql.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noborus/psutilsql/HEAD/doc/psutilsql.gif -------------------------------------------------------------------------------- /psutilsql.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotSupport = errors.New("not support") 7 | ErrNoSuchTable = errors.New("no such table") 8 | ) 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package psutilsql execute SQL queries on system information. 2 | // 3 | // psutilsql can process gopsutil results in SQL and 4 | // output them in various formats. 5 | package psutilsql 6 | -------------------------------------------------------------------------------- /psutilsql_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | func nullWriter() trdsql.Writer { 10 | return trdsql.NewWriter(trdsql.OutStream(io.Discard)) 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | /psutilsql 14 | dist/ 15 | -------------------------------------------------------------------------------- /cmd/psutilsql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/noborus/psutilsql/cmd" 4 | 5 | // version represents the version 6 | var version = "dev" 7 | 8 | // revision set "git rev-parse --short HEAD" 9 | var revision = "HEAD" 10 | 11 | func main() { 12 | cmd.Execute(version, revision) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // versionCmd represents the version command 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print the version number of psutilsql", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Printf("psutilsql version %s rev:%s\n", Version, Revision) 15 | }, 16 | } 17 | 18 | func init() { 19 | rootCmd.AddCommand(versionCmd) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/query.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // queryCmd represents the query command 9 | var queryCmd = &cobra.Command{ 10 | Use: "query", 11 | Short: "SQL query command", 12 | RunE: func(cmd *cobra.Command, args []string) error { 13 | if len(args) < 1 { 14 | return ErrNoQuery 15 | } 16 | return psutilsql.QueryExec(args[0], outFormat()) 17 | }, 18 | } 19 | 20 | func init() { 21 | rootCmd.AddCommand(queryCmd) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/table.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // tableCmd represents the table command 10 | var tableCmd = &cobra.Command{ 11 | Use: "table", 12 | Short: "table list", 13 | RunE: func(cmd *cobra.Command, args []string) error { 14 | var table string 15 | if len(args) > 0 { 16 | table = args[0] 17 | } 18 | return psutilsql.PSTableQuery(table, Query, outFormat()) 19 | }, 20 | } 21 | 22 | func init() { 23 | rootCmd.AddCommand(tableCmd) 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | strategy: 7 | matrix: 8 | go-version: ['stable', 'oldstable'] 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{ matrix.os }} 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Setup Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | id: go 19 | 20 | - name: Build 21 | run: make 22 | 23 | - name: test 24 | run: make test -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY_NAME := psutilsql 2 | SRCS := $(shell git ls-files '*.go') 3 | LDFLAGS := "-X main.version=$(shell git describe --tags --abbrev=0 --always) -X main.revision=$(shell git rev-parse --short HEAD)" 4 | 5 | all: build 6 | 7 | test: $(SRCS) 8 | go test ./... 9 | 10 | build: $(BINARY_NAME) 11 | 12 | $(BINARY_NAME): $(SRCS) 13 | go build -ldflags $(LDFLAGS) -o $(BINARY_NAME) ./cmd/psutilsql 14 | 15 | install: 16 | go install -ldflags $(LDFLAGS) ./cmd/psutilsql 17 | 18 | clean: 19 | rm -f $(BINARY_NAME) 20 | 21 | .PHONY: all test build install clean 22 | -------------------------------------------------------------------------------- /cmd/winservices.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package cmd 5 | 6 | import ( 7 | "github.com/noborus/psutilsql" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // winservicesCmd represents the winservices command 13 | var winservicesCmd = &cobra.Command{ 14 | Use: "winservices", 15 | Short: "winservices information", 16 | Long: `winservices information. 17 | `, 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | return psutilsql.WinservicesQuery(Query, outFormat()) 20 | }, 21 | } 22 | 23 | func init() { 24 | rootCmd.AddCommand(winservicesCmd) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/docker.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // dockerCmd represents the docker command 10 | var dockerCmd = &cobra.Command{ 11 | Use: "docker", 12 | Short: "docker information", 13 | Long: `docker information 14 | 15 | +-------------+------+-------+--------+---------+ 16 | | ContainerID | Name | Image | Status | Running | 17 | +-------------+------+-------+--------+---------+ 18 | `, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | return psutilsql.DockerQuery(Query, outFormat()) 21 | }, 22 | } 23 | 24 | func init() { 25 | rootCmd.AddCommand(dockerCmd) 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | jobs: 7 | goreleaser: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Setup Go 15 | uses: actions/setup-go@v4 16 | with: 17 | go-version: 'stable' 18 | - name: Run GoReleaser 19 | uses: goreleaser/goreleaser-action@v4 20 | with: 21 | version: latest 22 | args: release --clean 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /docker_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/docker" 6 | ) 7 | 8 | // DockerReader returns docker.GetDockerStat result as trdsql.SliceReader. 9 | func DockerReader() (*trdsql.SliceReader, error) { 10 | v, err := docker.GetDockerStat() 11 | if err != nil { 12 | return nil, err 13 | } 14 | return trdsql.NewSliceReader(psDocker, v), nil 15 | } 16 | 17 | // DockerQuery executes SQL on docker.GetDockerStat. 18 | func DockerQuery(query string, w trdsql.Writer) error { 19 | reader, err := DockerReader() 20 | if err != nil { 21 | return err 22 | } 23 | defaultQuery := "SELECT * FROM " + psDocker 24 | if query == "" { 25 | query = defaultQuery 26 | } 27 | return readerExec(reader, query, w) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/net.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // netCmd represents the net command 10 | var netCmd = &cobra.Command{ 11 | Use: "net", 12 | Short: "net information", 13 | Long: `net information. 14 | +----+--------+------+---------+-----------+---------+-----------+--------+------+-----+ 15 | | Fd | Family | Type | LaddrIP | LaddrPort | RaddrIP | RaddrPort | status | Uids | Pid | 16 | +----+--------+------+---------+-----------+---------+-----------+--------+------+-----+ 17 | `, 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | return psutilsql.NetQuery(Query, outFormat()) 20 | }, 21 | } 22 | 23 | func init() { 24 | // netCmd.PersistentFlags().BoolP("conntrack", "t", false, "connection tracking") 25 | rootCmd.AddCommand(netCmd) 26 | } 27 | -------------------------------------------------------------------------------- /winservices_query.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package psutilsql 5 | 6 | import ( 7 | "github.com/noborus/trdsql" 8 | "github.com/shirou/gopsutil/v4/winservices" 9 | ) 10 | 11 | // WinservicesReader returns winservices.ListServices as trdsql.SliceReader. 12 | func WinservicesReader() (*trdsql.SliceReader, error) { 13 | v, err := winservices.ListServices() 14 | if err != nil { 15 | return nil, err 16 | } 17 | return trdsql.NewSliceReader("winservices", v), nil 18 | } 19 | 20 | // WinservicesQuery executes SQL on winservices.ListServices. 21 | func WinservicesQuery(query string, w trdsql.Writer) error { 22 | reader, err := WinservicesReader() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | defaultQuery := "SELECT * FROM " + psWinservices 28 | if query == "" { 29 | query = defaultQuery 30 | } 31 | return readerExec(reader, query, w) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/load.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // loadCmd represents the load command 9 | var loadCmd = &cobra.Command{ 10 | Use: "load", 11 | Short: "load information", 12 | Long: `load information. 13 | 14 | CPU load average 15 | +-------+-------+--------+ 16 | | Load1 | Load5 | Load15 | 17 | +-------+-------+--------+ 18 | 19 | Option misc returnes miscellaneous host-wide statistics 20 | +------------+--------------+--------------+------+ 21 | | ProcsTotal | ProcsRunning | ProcsBlocked | Ctxt | 22 | +------------+--------------+--------------+------+ 23 | `, 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | var err error 26 | var misc bool 27 | if misc, err = cmd.PersistentFlags().GetBool("misc"); err != nil { 28 | return err 29 | } 30 | return psutilsql.LoadQuery(misc, Query, outFormat()) 31 | }, 32 | } 33 | 34 | func init() { 35 | loadCmd.PersistentFlags().BoolP("misc", "m", false, "miscellaneous host-wide statistics") 36 | rootCmd.AddCommand(loadCmd) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/complete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // completeCmd represents the complete command 10 | var completeCmd = &cobra.Command{ 11 | Use: "completion", 12 | Short: "Generates bash/zsh completion scripts", 13 | RunE: func(cmd *cobra.Command, args []string) error { 14 | return cmd.Help() 15 | }, 16 | } 17 | 18 | func completionBashCmd() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: "bash", 21 | Short: "Generates bash completion scripts", 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | return rootCmd.GenBashCompletion(os.Stdout) 24 | }, 25 | } 26 | return cmd 27 | } 28 | 29 | func completionZshCmd() *cobra.Command { 30 | cmd := &cobra.Command{ 31 | Use: "zsh", 32 | Short: "Generates zsh completion scripts", 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | return rootCmd.GenZshCompletion(os.Stdout) 35 | }, 36 | } 37 | return cmd 38 | } 39 | 40 | func init() { 41 | completeCmd.AddCommand(completionBashCmd()) 42 | completeCmd.AddCommand(completionZshCmd()) 43 | rootCmd.AddCommand(completeCmd) 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Noboru Saito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /net_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | func TestNetReader(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "testTrue", 16 | wantErr: false, 17 | }, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | _, err := NetReader() 22 | if (err != nil) != tt.wantErr { 23 | t.Errorf("NetReader() error = %v, wantErr %v", err, tt.wantErr) 24 | return 25 | } 26 | }) 27 | } 28 | } 29 | 30 | func TestNetQuery(t *testing.T) { 31 | type args struct { 32 | query string 33 | w trdsql.Writer 34 | } 35 | tests := []struct { 36 | name string 37 | args args 38 | wantErr bool 39 | }{ 40 | { 41 | name: "test1", 42 | args: args{w: nullWriter()}, 43 | wantErr: false, 44 | }, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | if err := NetQuery(tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 49 | t.Errorf("NetQuery() error = %v, wantErr %v", err, tt.wantErr) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /load_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/load" 6 | ) 7 | 8 | // LoadAvgReader returns load.Avg result as trdsql.SliceReader. 9 | func LoadAvgReader() (*trdsql.SliceReader, error) { 10 | v, err := load.Avg() 11 | if err != nil { 12 | return nil, err 13 | } 14 | return trdsql.NewSliceReader(psLoadAvg, v), nil 15 | } 16 | 17 | // LoadMiscReader returns load.Misc result as trdsql.SliceReader. 18 | func LoadMiscReader() (*trdsql.SliceReader, error) { 19 | v, err := load.Misc() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return trdsql.NewSliceReader(psLoadMisc, v), nil 24 | } 25 | 26 | // LoadQuery executes SQL on Load.Avg or Load.Misc. 27 | func LoadQuery(misc bool, query string, w trdsql.Writer) error { 28 | if misc { 29 | defaultQuery := "SELECT * FROM " + psLoadMisc 30 | if query == "" { 31 | query = defaultQuery 32 | } 33 | reader, err := LoadMiscReader() 34 | if err != nil { 35 | return err 36 | } 37 | return readerExec(reader, query, w) 38 | } 39 | defaultQuery := "SELECT * FROM " + psLoadAvg 40 | if query == "" { 41 | query = defaultQuery 42 | } 43 | reader, err := LoadAvgReader() 44 | if err != nil { 45 | return err 46 | } 47 | return readerExec(reader, query, w) 48 | } 49 | -------------------------------------------------------------------------------- /docker_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/noborus/trdsql" 8 | ) 9 | 10 | func TestDockerReader(t *testing.T) { 11 | if runtime.GOOS != "linux" { 12 | t.Skip("skipping specific test") 13 | } 14 | tests := []struct { 15 | name string 16 | wantErr bool 17 | }{ 18 | { 19 | name: "test1", 20 | wantErr: false, 21 | }, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | _, err := DockerReader() 26 | if (err != nil) != tt.wantErr { 27 | t.Errorf("DockerReader() error = %v, wantErr %v", err, tt.wantErr) 28 | return 29 | } 30 | }) 31 | } 32 | } 33 | 34 | func TestDockerQuery(t *testing.T) { 35 | if runtime.GOOS != "linux" { 36 | t.Skip("skipping specific test") 37 | } 38 | type args struct { 39 | query string 40 | w trdsql.Writer 41 | } 42 | tests := []struct { 43 | name string 44 | args args 45 | wantErr bool 46 | }{ 47 | { 48 | name: "test1", 49 | args: args{w: nullWriter()}, 50 | wantErr: false, 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | if err := DockerQuery(tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 56 | t.Errorf("DockerQuery() error = %v, wantErr %v", err, tt.wantErr) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /mem_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/mem" 6 | ) 7 | 8 | // VirtualMemoryReader returns mem.VirtualMemory result as trdsql.SliceReader. 9 | func VirtualMemoryReader() (*trdsql.SliceReader, error) { 10 | v, err := mem.VirtualMemory() 11 | if err != nil { 12 | return nil, err 13 | } 14 | return trdsql.NewSliceReader(psVirtualMemory, v), nil 15 | } 16 | 17 | // SwapMemoryReader returns mem.SwapMemory result as trdsql.SliceReader. 18 | func SwapMemoryReader() (*trdsql.SliceReader, error) { 19 | v, err := mem.SwapMemory() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return trdsql.NewSliceReader(psSwapMemory, v), nil 24 | } 25 | 26 | // MEMQuery executes SQL on mem.VirtualMemory or mem.SwapMemory. 27 | func MEMQuery(memory bool, query string, w trdsql.Writer) error { 28 | if memory { 29 | defaultQuery := "SELECT * FROM " + psVirtualMemory 30 | if query == "" { 31 | query = defaultQuery 32 | } 33 | reader, err := VirtualMemoryReader() 34 | if err != nil { 35 | return err 36 | } 37 | return readerExec(reader, query, w) 38 | } 39 | 40 | defaultQuery := "SELECT * FROM " + psSwapMemory 41 | if query == "" { 42 | query = defaultQuery 43 | } 44 | reader, err := SwapMemoryReader() 45 | if err != nil { 46 | return err 47 | } 48 | return readerExec(reader, query, w) 49 | } 50 | -------------------------------------------------------------------------------- /cmd/process.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // processCmd represents the process command 9 | var processCmd = &cobra.Command{ 10 | Use: "process", 11 | Short: "process information", 12 | Long: `process information. 13 | +-----+------+-----+-----+--------+-------+------+-----+-----+------+-------+--------+------+---------+ 14 | | pid | name | CPU | MEM | STATUS | START | USER | RSS | VMS | Data | Stack | locked | Swap | COMMAND | 15 | +-----+------+-----+-----+--------+-------+------+-----+-----+------+-------+--------+------+---------+ 16 | 17 | Option ex gets the result of platform dependend memory information. 18 | +-----+------+-----+-----+--------+-------+------+-----+-----+--------+------+-----+------+-------+---------+ 19 | | pid | name | CPU | MEM | STATUS | START | USER | RSS | VMS | Shared | Text | Lib | Data | Dirty | COMMAND | 20 | +-----+------+-----+-----+--------+-------+------+-----+-----+--------+------+-----+------+-------+---------+ 21 | `, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | var err error 24 | var ex bool 25 | if ex, err = cmd.PersistentFlags().GetBool("ex"); err != nil { 26 | return err 27 | } 28 | return psutilsql.ProcessQuery(ex, Query, outFormat()) 29 | }, 30 | } 31 | 32 | func init() { 33 | processCmd.PersistentFlags().BoolP("ex", "", false, "memory info ex") 34 | rootCmd.AddCommand(processCmd) 35 | } 36 | -------------------------------------------------------------------------------- /net_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/net" 6 | ) 7 | 8 | // NetReader returns net.Connections result as trdsql.SliceReader. 9 | func NetReader() (*trdsql.SliceReader, error) { 10 | conns, err := net.Connections("all") 11 | if err != nil { 12 | return nil, err 13 | } 14 | type wrapConnection struct { 15 | Fd uint32 16 | Family uint32 17 | Type uint32 18 | LaddrIP string 19 | LaddrPort uint32 20 | RaddrIP string 21 | RaddrPort uint32 22 | status string 23 | Uids []int32 24 | Pid int32 25 | } 26 | data := make([]wrapConnection, len(conns)) 27 | for i, conn := range conns { 28 | c := wrapConnection{} 29 | c.Fd = conn.Fd 30 | c.Family = conn.Family 31 | c.Type = conn.Type 32 | c.LaddrIP = conn.Laddr.IP 33 | c.LaddrPort = conn.Laddr.Port 34 | c.RaddrIP = conn.Raddr.IP 35 | c.RaddrPort = conn.Raddr.Port 36 | c.status = conn.Status 37 | c.Uids = conn.Uids 38 | c.Pid = conn.Pid 39 | data[i] = c 40 | } 41 | return trdsql.NewSliceReader(psNet, data), nil 42 | } 43 | 44 | // NetQuery executes SQL on net.Connections. 45 | func NetQuery(query string, w trdsql.Writer) error { 46 | reader, err := NetReader() 47 | if err != nil { 48 | return err 49 | } 50 | defaultQuery := "SELECT * FROM " + psNet 51 | if query == "" { 52 | query = defaultQuery 53 | } 54 | return readerExec(reader, query, w) 55 | } 56 | -------------------------------------------------------------------------------- /disk_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/disk" 6 | ) 7 | 8 | // DiskPartitionReader returns disk.Partitions result as trdsql.SliceReader. 9 | func DiskPartitionReader(all bool) (*trdsql.SliceReader, error) { 10 | v, err := disk.Partitions(all) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return trdsql.NewSliceReader(psDiskPartition, v), nil 15 | } 16 | 17 | // DiskUsageReader returns disk.Usage result as trdsql.SliceReader. 18 | func DiskUsageReader(usage string) (*trdsql.SliceReader, error) { 19 | v, err := disk.Usage(usage) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return trdsql.NewSliceReader(psDiskUsage, v), nil 24 | } 25 | 26 | // DiskPartitionQuery executes SQL on disk.Partitions. 27 | func DiskPartitionQuery(all bool, query string, w trdsql.Writer) error { 28 | reader, err := DiskPartitionReader(all) 29 | if err != nil { 30 | return err 31 | } 32 | defaultQuery := "SELECT * FROM " + psDiskPartition 33 | if query == "" { 34 | query = defaultQuery 35 | } 36 | return readerExec(reader, query, w) 37 | } 38 | 39 | // DiskUsageQuery executes SQL on disk.Usage. 40 | func DiskUsageQuery(usage string, query string, w trdsql.Writer) error { 41 | reader, err := DiskUsageReader(usage) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | defaultQuery := "SELECT * FROM " + psDiskUsage 47 | if query == "" { 48 | query = defaultQuery 49 | } 50 | return readerExec(reader, query, w) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/disk.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // diskCmd represents the disk command 10 | var diskCmd = &cobra.Command{ 11 | Use: "disk", 12 | Short: "DISK information", 13 | Long: `DISK information 14 | 15 | Disk partition information. 16 | +--------+------------+--------+------+ 17 | | Device | Mountpoint | Fstype | Opts | 18 | +--------+------------+--------+------+ 19 | 20 | Option usage gets the result of disk usage information. 21 | +------+--------+-------+------+------+-------------+-------------+------------+------------+-------------------+ 22 | | Path | Fstype | Total | Free | Used | UsedPercent | InodesTotal | InodesUsed | InodesFree | InodesUsedPercent | 23 | +------+--------+-------+------+------+-------------+-------------+------------+------------+-------------------+ 24 | 25 | `, 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | var err error 28 | var all bool 29 | if all, err = cmd.PersistentFlags().GetBool("all"); err != nil { 30 | return err 31 | } 32 | var usage string 33 | if usage, err = cmd.PersistentFlags().GetString("usage"); err != nil { 34 | return err 35 | } 36 | if usage != "" { 37 | return psutilsql.DiskUsageQuery(usage, Query, outFormat()) 38 | } 39 | return psutilsql.DiskPartitionQuery(all, Query, outFormat()) 40 | }, 41 | } 42 | 43 | func init() { 44 | diskCmd.PersistentFlags().BoolP("all", "a", false, "all disk") 45 | diskCmd.PersistentFlags().StringP("usage", "u", "", "file system usage") 46 | 47 | rootCmd.AddCommand(diskCmd) 48 | } 49 | -------------------------------------------------------------------------------- /table_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | // TableReader return table name as trdsql.SliceReader. 10 | func TableReader() (*trdsql.SliceReader, error) { 11 | type tableNames struct { 12 | name string 13 | } 14 | tables := []tableNames{ 15 | {name: psCPUTime}, 16 | {name: psCPUInfo}, 17 | {name: psCPUPercent}, 18 | {name: psDiskPartition}, 19 | {name: psDiskUsage}, 20 | {name: psDocker}, 21 | {name: psHostInfo}, 22 | {name: psHostUser}, 23 | {name: psHostTemperature}, 24 | {name: psLoadAvg}, 25 | {name: psLoadMisc}, 26 | {name: psVirtualMemory}, 27 | {name: psSwapMemory}, 28 | {name: psNet}, 29 | {name: psProcess}, 30 | {name: psProcessEx}, 31 | } 32 | if runtime.GOOS == "windows" { 33 | tables = append(tables, tableNames{name: psWinservices}) 34 | } 35 | return trdsql.NewSliceReader("pstable", tables), nil 36 | } 37 | 38 | func definitionQuery(tableName string, w trdsql.Writer) error { 39 | reader := psutilReader(tableName) 40 | if reader == nil { 41 | return ErrNoSuchTable 42 | } 43 | tn, err := reader.TableName() 44 | if err != nil { 45 | return err 46 | } 47 | query := "SELECT * FROM " + tn + " LIMIT 0" 48 | return readerExec(reader, query, w) 49 | } 50 | 51 | // PSTableQuery executes SQL on tables. 52 | func PSTableQuery(tableName string, query string, w trdsql.Writer) error { 53 | if len(tableName) > 0 { 54 | return definitionQuery(tableName, w) 55 | } 56 | reader, err := TableReader() 57 | if err != nil { 58 | return err 59 | } 60 | defaultQuery := "SELECT * FROM pstable ORDER BY name" 61 | if query == "" { 62 | query = defaultQuery 63 | } 64 | return readerExec(reader, query, w) 65 | } 66 | -------------------------------------------------------------------------------- /importer.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | // MultiImporter is a structure for importing multiple readers. 10 | type MultiImporter struct { 11 | readers []Reader 12 | } 13 | 14 | // Reader is an interface that can be passed to MultiImporter. 15 | type Reader interface { 16 | TableName() (string, error) 17 | Names() ([]string, error) 18 | Types() ([]string, error) 19 | PreReadRow() [][]any 20 | ReadRow([]any) ([]any, error) 21 | } 22 | 23 | // NewMultiImporter takes multiple readers as arguments and returns a MultiImporter. 24 | func NewMultiImporter(readers ...Reader) (*MultiImporter, error) { 25 | r := make([]Reader, len(readers)) 26 | copy(r, readers) 27 | return &MultiImporter{ 28 | readers: readers, 29 | }, nil 30 | } 31 | 32 | // ImportContext executes import. 33 | func (i *MultiImporter) ImportContext(ctx context.Context, db *trdsql.DB, query string) (string, error) { 34 | for _, r := range i.readers { 35 | names, err := r.Names() 36 | if err != nil { 37 | return query, err 38 | } 39 | types, err := r.Types() 40 | if err != nil { 41 | return query, err 42 | } 43 | tableName, err := r.TableName() 44 | if err != nil { 45 | return query, err 46 | } 47 | err = db.CreateTableContext(ctx, tableName, names, types, true) 48 | if err != nil { 49 | return query, err 50 | } 51 | err = db.ImportContext(ctx, tableName, names, r) 52 | if err != nil { 53 | return query, err 54 | } 55 | } 56 | return query, nil 57 | } 58 | 59 | // Import executes import. 60 | func (i *MultiImporter) Import(db *trdsql.DB, query string) (string, error) { 61 | ctx := context.Background() 62 | return i.ImportContext(ctx, db, query) 63 | } 64 | -------------------------------------------------------------------------------- /cmd/host.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // hostCmd represents the host command 9 | var hostCmd = &cobra.Command{ 10 | Use: "host", 11 | Short: "host information", 12 | Long: `host information. 13 | 14 | +----------+--------+----------+-------+----+----------+----------------+-----------------+---------------+----------------------+--------------------+--------+ 15 | | Hostname | Uptime | BootTime | Procs | OS | Platform | PlatformFamily | PlatformVersion | KernelVersion | VirtualizationSystem | VirtualizationRole | HostID | 16 | +----------+--------+----------+-------+----+----------+----------------+-----------------+---------------+----------------------+--------------------+--------+ 17 | 18 | Option user gets the result of user information. 19 | +---------+----------+------+------------+ 20 | | User | Terminal | Host | Started | 21 | +---------+----------+------+------------+ 22 | 23 | Option temperatures gets the result of SensorsTemperatures 24 | +-----------+-------------+ 25 | | SensorKey | Temperature | 26 | +-----------+-------------+ 27 | `, 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | var err error 30 | var tempera, users bool 31 | if tempera, err = cmd.PersistentFlags().GetBool("temperatures"); err != nil { 32 | return err 33 | } 34 | if users, err = cmd.PersistentFlags().GetBool("users"); err != nil { 35 | return err 36 | } 37 | 38 | return psutilsql.HostQuery(tempera, users, Query, outFormat()) 39 | }, 40 | } 41 | 42 | func init() { 43 | hostCmd.PersistentFlags().BoolP("temperatures", "t", false, "SensorsTemperatures") 44 | hostCmd.PersistentFlags().BoolP("users", "u", false, "user information") 45 | 46 | rootCmd.AddCommand(hostCmd) 47 | } 48 | -------------------------------------------------------------------------------- /process_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/noborus/trdsql" 8 | ) 9 | 10 | func TestNewProcessReader(t *testing.T) { 11 | type args struct { 12 | ex bool 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | wantErr bool 18 | }{ 19 | { 20 | name: "test1", 21 | args: args{ex: false}, 22 | wantErr: false, 23 | }, 24 | { 25 | name: "testEx", 26 | args: args{ex: true}, 27 | wantErr: false, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | if tt.name == "testEx" && runtime.GOOS != "linux" { 33 | t.Skip("skipping specific test") 34 | } 35 | _, err := NewProcessReader(tt.args.ex) 36 | if (err != nil) != tt.wantErr { 37 | t.Errorf("NewProcessReader() error = %v, wantErr %v", err, tt.wantErr) 38 | return 39 | } 40 | }) 41 | } 42 | } 43 | 44 | func TestProcessQuery(t *testing.T) { 45 | type args struct { 46 | ex bool 47 | query string 48 | w trdsql.Writer 49 | } 50 | tests := []struct { 51 | name string 52 | args args 53 | wantErr bool 54 | }{ 55 | { 56 | name: "test1", 57 | args: args{ex: false, w: nullWriter()}, 58 | wantErr: false, 59 | }, 60 | { 61 | name: "testEx", 62 | args: args{ex: true, w: nullWriter()}, 63 | wantErr: false, 64 | }, 65 | } 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | if tt.name == "testEx" && runtime.GOOS != "linux" { 69 | t.Skip("skipping specific test") 70 | } 71 | if err := ProcessQuery(tt.args.ex, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 72 | t.Errorf("ProcessQuery() error = %v, wantErr %v", err, tt.wantErr) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mem_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | func TestVirtualMemoryReader(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "testTrue", 16 | wantErr: false, 17 | }, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | _, err := VirtualMemoryReader() 22 | if (err != nil) != tt.wantErr { 23 | t.Errorf("VirtualMemoryReader() error = %v, wantErr %v", err, tt.wantErr) 24 | return 25 | } 26 | }) 27 | } 28 | } 29 | 30 | func TestSwapMemoryReader(t *testing.T) { 31 | tests := []struct { 32 | name string 33 | wantErr bool 34 | }{ 35 | { 36 | name: "testTrue", 37 | wantErr: false, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | _, err := SwapMemoryReader() 43 | if (err != nil) != tt.wantErr { 44 | t.Errorf("SwapMemoryReader() error = %v, wantErr %v", err, tt.wantErr) 45 | return 46 | } 47 | }) 48 | } 49 | } 50 | 51 | func TestMEMQuery(t *testing.T) { 52 | type args struct { 53 | memory bool 54 | query string 55 | w trdsql.Writer 56 | } 57 | tests := []struct { 58 | name string 59 | args args 60 | wantErr bool 61 | }{ 62 | { 63 | name: "testVirtual", 64 | args: args{memory: true, w: nullWriter()}, 65 | wantErr: false, 66 | }, 67 | { 68 | name: "testSwap", 69 | args: args{memory: false, w: nullWriter()}, 70 | wantErr: false, 71 | }, 72 | } 73 | for _, tt := range tests { 74 | t.Run(tt.name, func(t *testing.T) { 75 | if err := MEMQuery(tt.args.memory, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 76 | t.Errorf("MEMQuery() error = %v, wantErr %v", err, tt.wantErr) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /host_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/host" 6 | "github.com/shirou/gopsutil/v4/sensors" 7 | ) 8 | 9 | // HostInfoReader returns host.Info result as trdsql.SliceReader. 10 | func HostInfoReader() (*trdsql.SliceReader, error) { 11 | v, err := host.Info() 12 | if err != nil { 13 | return nil, err 14 | } 15 | return trdsql.NewSliceReader(psHostInfo, v), nil 16 | } 17 | 18 | // HostUsersReader returns host.Users result as trdsql.SliceReader. 19 | func HostUsersReader() (*trdsql.SliceReader, error) { 20 | v, err := host.Users() 21 | if err != nil { 22 | return nil, err 23 | } 24 | return trdsql.NewSliceReader(psHostUser, v), nil 25 | } 26 | 27 | // HostTemperatureReader returns host.SensorsTemperatures result as trdsql.SliceReader. 28 | func HostTemperatureReader() (*trdsql.SliceReader, error) { 29 | v, err := sensors.SensorsTemperatures() 30 | if err != nil { 31 | return nil, err 32 | } 33 | return trdsql.NewSliceReader(psHostTemperature, v), nil 34 | } 35 | 36 | // HostQuery executes SQL on host.Info or host.Users or host.SensorsTemperatures. 37 | func HostQuery(tempera bool, users bool, query string, w trdsql.Writer) error { 38 | if tempera { 39 | reader, err := HostTemperatureReader() 40 | if err != nil { 41 | return err 42 | } 43 | defaultQuery := "SELECT * FROM " + psHostTemperature 44 | if query == "" { 45 | query = defaultQuery 46 | } 47 | return readerExec(reader, query, w) 48 | } else if users { 49 | reader, err := HostUsersReader() 50 | if err != nil { 51 | return err 52 | } 53 | defaultQuery := "SELECT * FROM " + psHostUser 54 | if query == "" { 55 | query = defaultQuery 56 | } 57 | return readerExec(reader, query, w) 58 | } 59 | reader, err := HostInfoReader() 60 | if err != nil { 61 | return err 62 | } 63 | defaultQuery := "SELECT * FROM " + psHostInfo 64 | if query == "" { 65 | query = defaultQuery 66 | } 67 | return readerExec(reader, query, w) 68 | } 69 | -------------------------------------------------------------------------------- /load_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/noborus/trdsql" 8 | ) 9 | 10 | func TestLoadAvgReader(t *testing.T) { 11 | if runtime.GOOS == "windows" { 12 | t.Skip("skipping specific test") 13 | } 14 | tests := []struct { 15 | name string 16 | wantErr bool 17 | }{ 18 | { 19 | name: "testTrue", 20 | wantErr: false, 21 | }, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | _, err := LoadAvgReader() 26 | if (err != nil) != tt.wantErr { 27 | t.Errorf("LoadAvgReader() error = %v, wantErr %v", err, tt.wantErr) 28 | return 29 | } 30 | }) 31 | } 32 | } 33 | 34 | func TestLoadMiscReader(t *testing.T) { 35 | if runtime.GOOS == "windows" { 36 | t.Skip("skipping specific test") 37 | } 38 | tests := []struct { 39 | name string 40 | wantErr bool 41 | }{ 42 | { 43 | name: "testTrue", 44 | wantErr: false, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | _, err := LoadMiscReader() 50 | if (err != nil) != tt.wantErr { 51 | t.Errorf("LoadMiscReader() error = %v, wantErr %v", err, tt.wantErr) 52 | return 53 | } 54 | }) 55 | } 56 | } 57 | 58 | func TestLoadQuery(t *testing.T) { 59 | if runtime.GOOS == "windows" { 60 | t.Skip("skipping specific test") 61 | } 62 | type args struct { 63 | misc bool 64 | query string 65 | w trdsql.Writer 66 | } 67 | tests := []struct { 68 | name string 69 | args args 70 | wantErr bool 71 | }{ 72 | { 73 | name: "testLoadAvg", 74 | args: args{misc: false, w: nullWriter()}, 75 | wantErr: false, 76 | }, 77 | { 78 | name: "testLoadMisc", 79 | args: args{misc: true, w: nullWriter()}, 80 | wantErr: false, 81 | }, 82 | } 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | if err := LoadQuery(tt.args.misc, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 86 | t.Errorf("LoadQuery() error = %v, wantErr %v", err, tt.wantErr) 87 | } 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /cpu_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "github.com/noborus/trdsql" 5 | "github.com/shirou/gopsutil/v4/cpu" 6 | ) 7 | 8 | // CPUTimeReader returns cpu.Times result as trdsql.SliceReader. 9 | func CPUTimeReader(total bool) (*trdsql.SliceReader, error) { 10 | v, err := cpu.Times(!total) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return trdsql.NewSliceReader(psCPUTime, v), nil 15 | } 16 | 17 | // CPUInfoReader returns cpu.Info result as trdsql.SliceReader. 18 | func CPUInfoReader() (*trdsql.SliceReader, error) { 19 | v, err := cpu.Info() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return trdsql.NewSliceReader(psCPUInfo, v), nil 24 | } 25 | 26 | // CPUPercentReader returns cpu.Percent result as trdsql.SliceReader. 27 | func CPUPercentReader(total bool) (*trdsql.SliceReader, error) { 28 | v, err := cpu.Percent(0, !total) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return trdsql.NewSliceReader(psCPUPercent, v), nil 33 | } 34 | 35 | // CPUTimeQuery executes SQL on cpu.Time. 36 | func CPUTimeQuery(total bool, query string, w trdsql.Writer) error { 37 | reader, err := CPUTimeReader(total) 38 | if err != nil { 39 | return err 40 | } 41 | defaultQuery := "SELECT * FROM " + psCPUTime + " ORDER BY cpu" 42 | if query == "" { 43 | query = defaultQuery 44 | } 45 | return readerExec(reader, query, w) 46 | } 47 | 48 | // CPUInfoQuery executes SQL on cpu.Info. 49 | func CPUInfoQuery(query string, w trdsql.Writer) error { 50 | reader, err := CPUInfoReader() 51 | if err != nil { 52 | return err 53 | } 54 | defaultQuery := "SELECT * FROM " + psCPUInfo + " ORDER BY cpu" 55 | if query == "" { 56 | query = defaultQuery 57 | } 58 | return readerExec(reader, query, w) 59 | } 60 | 61 | // CPUPercentQuery executes SQL on cpu.Percent. 62 | func CPUPercentQuery(total bool, query string, w trdsql.Writer) error { 63 | reader, err := CPUPercentReader(total) 64 | if err != nil { 65 | return err 66 | } 67 | defaultQuery := "SELECT * FROM " + psCPUPercent 68 | if query == "" { 69 | query = defaultQuery 70 | } 71 | return readerExec(reader, query, w) 72 | } 73 | -------------------------------------------------------------------------------- /cmd/mem.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | // memCmd represents the mem command 9 | var memCmd = &cobra.Command{ 10 | Use: "mem", 11 | Short: "memory information", 12 | Long: `memory information. 13 | 14 | VirtualMemory information 15 | +-------+-----------+------+-------------+------+--------+----------+-------+---------+---------+--------+-----------+-------+--------------+--------+------+--------------+------------+------------+------------+-------------+-------------+-----------+----------+----------+---------+-----------+----------+--------+--------------+-------------+--------------+----------------+---------------+--------------+ 16 | | Total | Available | Used | UsedPercent | Free | Active | Inactive | Wired | Laundry | Buffers | Cached | Writeback | Dirty | WritebackTmp | Shared | Slab | SReclaimable | SUnreclaim | PageTables | SwapCached | CommitLimit | CommittedAS | HighTotal | HighFree | LowTotal | LowFree | SwapTotal | SwapFree | Mapped | VMallocTotal | VMallocUsed | VMallocChunk | HugePagesTotal | HugePagesFree | HugePageSize | 17 | +-------+-----------+------+-------------+------+--------+----------+-------+---------+---------+--------+-----------+-------+--------------+--------+------+--------------+------------+------------+------------+-------------+-------------+-----------+----------+----------+---------+-----------+----------+--------+--------------+-------------+--------------+----------------+---------------+--------------+ 18 | 19 | Option swap gets the result of swap information 20 | +-------+------+------+-------------+-----+------+------+-------+---------+ 21 | | Total | Used | Free | UsedPercent | Sin | Sout | PgIn | PgOut | PgFault | 22 | +-------+------+------+-------------+-----+------+------+-------+---------+ 23 | `, 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | var err error 26 | var swap bool 27 | if swap, err = cmd.PersistentFlags().GetBool("swap"); err != nil { 28 | return err 29 | } 30 | return psutilsql.MEMQuery(!swap, Query, outFormat()) 31 | }, 32 | } 33 | 34 | func init() { 35 | memCmd.PersistentFlags().BoolP("swap", "s", false, "swap memory") 36 | rootCmd.AddCommand(memCmd) 37 | } 38 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | id: "psutilsql" 17 | ldflags: 18 | - -s -w -X main.version={{ .Version }} -X main.revision={{ .Commit }} 19 | main: ./cmd/psutilsql 20 | ignore: 21 | - goos: windows 22 | goarch: "386" 23 | archives: 24 | - format: tar.gz 25 | # this name template makes the OS and Arch compatible with the results of uname. 26 | name_template: >- 27 | {{ .ProjectName }}_{{ .Version }}_ 28 | {{- title .Os }}_ 29 | {{- if eq .Arch "amd64" }}x86_64 30 | {{- else if eq .Arch "386" }}i386 31 | {{- else }}{{ .Arch }}{{ end }} 32 | {{- if .Arm }}v{{ .Arm }}{{ end }} 33 | # use zip for windows archives 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | checksum: 38 | name_template: 'checksums.txt' 39 | snapshot: 40 | name_template: "{{ incpatch .Version }}-next" 41 | changelog: 42 | sort: asc 43 | filters: 44 | exclude: 45 | - '^docs:' 46 | - '^test:' 47 | nfpms: 48 | - 49 | package_name: "psutilsql" 50 | homepage: "https://github.com/noborus/psutilsql" 51 | maintainer: "Noboru Saito " 52 | description: "CLI tool that can be processed by SQL using" 53 | license: "MIT" 54 | formats: 55 | - deb 56 | - rpm 57 | brews: 58 | - 59 | name: psutilsql 60 | repository: 61 | owner: noborus 62 | name: homebrew-tap 63 | token: "{{ .Env.TAP_GITHUB_TOKEN }}" 64 | commit_author: 65 | name: noborus 66 | email: noborusai@gmail.com 67 | homepage: https://github.com/noborus/psutilsql 68 | description: "CLI tool that can be processed by SQL using" 69 | # The lines beneath this are called `modelines`. See `:help modeline` 70 | # Feel free to remove those if you don't want/use them. 71 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 72 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 73 | -------------------------------------------------------------------------------- /cmd/cpu.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/noborus/psutilsql" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | // cpuCmd represents the cpu command 10 | var cpuCmd = &cobra.Command{ 11 | Use: "cpu", 12 | Short: "CPU information", 13 | Long: `CPU information 14 | 15 | Option times (default) gets the result of cpu.Times(). 16 | +-----+------+--------+------+------+--------+-----+---------+-------+-------+-----------+ 17 | | CPU | User | System | Idle | Nice | Iowait | Irq | Softirq | Steal | Guest | GuestNice | 18 | +-----+------+--------+------+------+--------+-----+---------+-------+-------+-----------+ 19 | 20 | Option info gets the result of cpu.Info(). 21 | +-----+----------+--------+-------+----------+------------+--------+-------+-----------+-----+-----------+-------+-----------+ 22 | | CPU | VendorID | Family | Model | Stepping | PhysicalID | CoreID | Cores | ModelName | Mhz | CacheSize | Flags | Microcode | 23 | +-----+----------+--------+-------+----------+------------+--------+-------+-----------+-----+-----------+-------+-----------+ 24 | 25 | Option percent gets the result of cpu.Percent 26 | 27 | Option total gets the result of the total on one row. 28 | `, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | var err error 31 | var per, info, time, total bool 32 | 33 | if total, err = cmd.PersistentFlags().GetBool("total"); err != nil { 34 | return err 35 | } 36 | if per, err = cmd.PersistentFlags().GetBool("percent"); err != nil { 37 | return err 38 | } 39 | if info, err = cmd.PersistentFlags().GetBool("info"); err != nil { 40 | return err 41 | } 42 | if time, err = cmd.PersistentFlags().GetBool("time"); err != nil { 43 | return err 44 | } 45 | 46 | if per { 47 | return psutilsql.CPUPercentQuery(total, Query, outFormat()) 48 | } 49 | if info { 50 | return psutilsql.CPUInfoQuery(Query, outFormat()) 51 | } 52 | if time { 53 | return psutilsql.CPUTimeQuery(total, Query, outFormat()) 54 | } 55 | return psutilsql.CPUTimeQuery(total, Query, outFormat()) 56 | }, 57 | } 58 | 59 | func init() { 60 | rootCmd.AddCommand(cpuCmd) 61 | cpuCmd.PersistentFlags().BoolP("info", "i", false, "CPU info") 62 | cpuCmd.PersistentFlags().BoolP("percent", "p", false, "CPU Percent") 63 | cpuCmd.PersistentFlags().BoolP("total", "t", false, "Per CPU or total") 64 | cpuCmd.PersistentFlags().BoolP("time", "", true, "CPU Time") 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/noborus/psutilsql 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/noborus/trdsql v1.1.0 9 | github.com/shirou/gopsutil/v4 v4.25.10 10 | github.com/spf13/cobra v1.10.1 11 | ) 12 | 13 | require ( 14 | filippo.io/edwards25519 v1.1.0 // indirect 15 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect 16 | github.com/dustin/go-humanize v1.0.1 // indirect 17 | github.com/ebitengine/purego v0.9.0 // indirect 18 | github.com/go-ole/go-ole v1.3.0 // indirect 19 | github.com/go-sql-driver/mysql v1.9.3 // indirect 20 | github.com/goccy/go-yaml v1.18.0 // indirect 21 | github.com/google/uuid v1.6.0 // indirect 22 | github.com/iancoleman/orderedmap v0.3.0 // indirect 23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 24 | github.com/itchyny/gojq v0.12.17 // indirect 25 | github.com/itchyny/timefmt-go v0.1.6 // indirect 26 | github.com/jwalton/gchalk v1.3.0 // indirect 27 | github.com/jwalton/go-supportscolor v1.2.0 // indirect 28 | github.com/klauspost/compress v1.18.0 // indirect 29 | github.com/lib/pq v1.10.9 // indirect 30 | github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect 31 | github.com/mattn/go-isatty v0.0.20 // indirect 32 | github.com/mattn/go-runewidth v0.0.16 // indirect 33 | github.com/mattn/go-sqlite3 v1.14.32 // indirect 34 | github.com/multiprocessio/go-sqlite3-stdlib v0.0.0-20220822170115-9f6825a1cd25 // indirect 35 | github.com/ncruces/go-strftime v0.1.9 // indirect 36 | github.com/noborus/guesswidth v0.4.0 // indirect 37 | github.com/noborus/sqlss v0.1.0 // indirect 38 | github.com/noborus/tbln v0.0.2 // indirect 39 | github.com/olekukonko/tablewriter v0.0.5 // indirect 40 | github.com/pierrec/lz4/v4 v4.1.22 // indirect 41 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect 42 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 43 | github.com/rivo/uniseg v0.4.7 // indirect 44 | github.com/spf13/pflag v1.0.9 // indirect 45 | github.com/tklauser/go-sysconf v0.3.15 // indirect 46 | github.com/tklauser/numcpus v0.10.0 // indirect 47 | github.com/ulikunitz/xz v0.5.15 // indirect 48 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 49 | golang.org/x/crypto v0.41.0 // indirect 50 | golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect 51 | golang.org/x/sys v0.37.0 // indirect 52 | golang.org/x/term v0.34.0 // indirect 53 | gonum.org/v1/gonum v0.16.0 // indirect 54 | modernc.org/libc v1.66.8 // indirect 55 | modernc.org/mathutil v1.7.1 // indirect 56 | modernc.org/memory v1.11.0 // indirect 57 | modernc.org/sqlite v1.38.2 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /process_query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "io" 5 | "runtime" 6 | 7 | "github.com/noborus/trdsql" 8 | "github.com/shirou/gopsutil/v4/process" 9 | ) 10 | 11 | // NewProcessReader returns process.Processes result as ProcessReader. 12 | func NewProcessReader(ex bool) (*ProcessReader, error) { 13 | pr := &ProcessReader{} 14 | pr.tableName = psProcess 15 | columns := []pColumnNum{PID, NAME, CPU, MEM, STATUS, START, USER, MEMORYINFO, COMMAND} 16 | if ex { 17 | pr.tableName = psProcessEx 18 | if runtime.GOOS == "linux" { 19 | columns = []pColumnNum{PID, NAME, CPU, MEM, STATUS, START, USER, MEMORYINFOEX, COMMAND} 20 | } else { 21 | return nil, ErrNotSupport 22 | } 23 | } 24 | for _, cn := range columns { 25 | col := processColumn[cn] 26 | pr.names = append(pr.names, col.names...) 27 | pr.types = append(pr.types, col.types...) 28 | pr.funcs = append(pr.funcs, col.getFunc) 29 | } 30 | 31 | processes, err := process.Processes() 32 | if err != nil { 33 | return nil, err 34 | } 35 | pr.data = make([][]any, len(processes)) 36 | for i, p := range processes { 37 | pr.data[i] = []any{} 38 | for _, getFunc := range pr.funcs { 39 | v := getFunc(p) 40 | pr.data[i] = append(pr.data[i], v...) 41 | } 42 | } 43 | 44 | return pr, nil 45 | } 46 | 47 | // The ProcessReader structure represents a process 48 | // and satisfies the trdsql.Reader interface. 49 | type ProcessReader struct { 50 | tableName string 51 | names []string 52 | types []string 53 | funcs []func(p *process.Process) []any 54 | data [][]any 55 | } 56 | 57 | // TableName returns TableName. 58 | func (p *ProcessReader) TableName() (string, error) { 59 | return p.tableName, nil 60 | } 61 | 62 | // Names returns column names. 63 | func (p *ProcessReader) Names() ([]string, error) { 64 | return p.names, nil 65 | } 66 | 67 | // Types returns column types. 68 | func (p *ProcessReader) Types() ([]string, error) { 69 | return p.types, nil 70 | } 71 | 72 | // PreReadRow is returns entity of the data. 73 | func (p *ProcessReader) PreReadRow() [][]any { 74 | return p.data 75 | } 76 | 77 | // ReadRow only returns EOF. 78 | func (p *ProcessReader) ReadRow(row []any) ([]any, error) { 79 | return nil, io.EOF 80 | } 81 | 82 | // ProcessQuery executes SQL on process.Processes. 83 | func ProcessQuery(ex bool, query string, w trdsql.Writer) error { 84 | reader, err := NewProcessReader(ex) 85 | if err != nil { 86 | return err 87 | } 88 | defaultQuery := "SELECT * FROM " + psProcess + " ORDER BY pid" 89 | if ex { 90 | defaultQuery = "SELECT * FROM " + psProcessEx + " ORDER BY pid" 91 | } 92 | if query == "" { 93 | query = defaultQuery 94 | } 95 | 96 | return readerExec(reader, query, w) 97 | } 98 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strings" 7 | 8 | "github.com/noborus/psutilsql" 9 | 10 | "github.com/noborus/trdsql" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | ErrNoQuery = errors.New("require query") 16 | ) 17 | 18 | // rootCmd represents the base command when called without any subcommands 19 | var rootCmd = &cobra.Command{ 20 | Use: "psutilsql", 21 | Short: "SQL for running processes and system utilization", 22 | Long: `SQL for running processes and system utilization. 23 | 24 | SQL can be executed on the information acquired using gopsutil library. 25 | Default SQL is provided, so you can omit SQL if you select a command.`, 26 | RunE: func(c *cobra.Command, args []string) error { 27 | if len(Query) == 0 && len(args) == 0 { 28 | return ErrNoQuery 29 | } 30 | return psutilsql.QueryExec(Query, outFormat()) 31 | }, 32 | } 33 | 34 | var ( 35 | // Version represents the version 36 | Version string 37 | // Revision set "git rev-parse --short HEAD" 38 | Revision string 39 | ) 40 | 41 | // Execute adds all child commands to the root command and sets flags appropriately. 42 | // This is called by main.main(). It only needs to happen once to the rootCmd. 43 | func Execute(version string, revision string) { 44 | Version = version 45 | Revision = revision 46 | cmd, _, err := rootCmd.Find(os.Args[1:]) 47 | if err != nil || cmd == nil { 48 | // Not found 49 | args := append([]string{"query"}, os.Args[1:]...) 50 | rootCmd.SetArgs(args) 51 | } 52 | if err := rootCmd.Execute(); err != nil { 53 | rootCmd.SetOutput(os.Stderr) 54 | os.Exit(1) 55 | } 56 | } 57 | 58 | // OutFormat is an output format specification. 59 | var OutFormat string 60 | 61 | // Header is an output header specification(CSV and RAW only). 62 | var Header bool 63 | 64 | // Delimiter is a delimiter specification (CSV ans RAW only). 65 | var Delimiter string 66 | 67 | // Query is SQL specification. 68 | var Query string 69 | 70 | func init() { 71 | // Cobra also supports local flags, which will only run 72 | // when this action is called directly. 73 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 74 | rootCmd.PersistentFlags().StringVarP(&OutFormat, "OutFormat", "o", "AT", "output format=[AT|CSV|LTSV|JSON|JSONL|TBLN|RAW|MD|VF|YAML]") 75 | rootCmd.PersistentFlags().StringVarP(&Delimiter, "Delimiter", "d", ",", "output delimiter (CSV only)") 76 | rootCmd.PersistentFlags().BoolVarP(&Header, "Header", "O", false, "output header (CSV only)") 77 | rootCmd.PersistentFlags().StringVarP(&Query, "Query", "q", "", "query") 78 | } 79 | 80 | func outFormat() trdsql.Writer { 81 | format := trdsql.OutputFormat(strings.ToUpper(OutFormat)) 82 | w := trdsql.NewWriter( 83 | trdsql.OutFormat(format), 84 | trdsql.OutDelimiter(Delimiter), 85 | trdsql.OutHeader(Header), 86 | ) 87 | return w 88 | } 89 | -------------------------------------------------------------------------------- /disk_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | func TestDiskPartitionReader(t *testing.T) { 10 | type args struct { 11 | all bool 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | wantErr bool 17 | }{ 18 | { 19 | name: "testTrue", 20 | args: args{all: true}, 21 | wantErr: false, 22 | }, 23 | { 24 | name: "testFalse", 25 | args: args{all: false}, 26 | wantErr: false, 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | _, err := DiskPartitionReader(tt.args.all) 32 | if (err != nil) != tt.wantErr { 33 | t.Errorf("DiskPartitionReader() error = %v, wantErr %v", err, tt.wantErr) 34 | return 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestDiskUsageReader(t *testing.T) { 41 | type args struct { 42 | usage string 43 | } 44 | tests := []struct { 45 | name string 46 | args args 47 | wantErr bool 48 | }{ 49 | { 50 | name: "test1", 51 | args: args{usage: "/"}, 52 | wantErr: false, 53 | }, 54 | } 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | _, err := DiskUsageReader(tt.args.usage) 58 | if (err != nil) != tt.wantErr { 59 | t.Errorf("DiskUsageReader() error = %v, wantErr %v", err, tt.wantErr) 60 | return 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestDiskPartitionQuery(t *testing.T) { 67 | type args struct { 68 | all bool 69 | query string 70 | w trdsql.Writer 71 | } 72 | tests := []struct { 73 | name string 74 | args args 75 | wantErr bool 76 | }{ 77 | { 78 | name: "testFalse", 79 | args: args{all: false, w: nullWriter()}, 80 | wantErr: false, 81 | }, 82 | { 83 | name: "testTrue", 84 | args: args{all: true, w: nullWriter()}, 85 | wantErr: false, 86 | }, 87 | } 88 | for _, tt := range tests { 89 | t.Run(tt.name, func(t *testing.T) { 90 | if err := DiskPartitionQuery(tt.args.all, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 91 | t.Errorf("DiskPartitionQuery() error = %v, wantErr %v", err, tt.wantErr) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | func TestDiskUsageQuery(t *testing.T) { 98 | type args struct { 99 | usage string 100 | query string 101 | w trdsql.Writer 102 | } 103 | tests := []struct { 104 | name string 105 | args args 106 | wantErr bool 107 | }{ 108 | { 109 | name: "test1", 110 | args: args{usage: "/", w: nullWriter()}, 111 | wantErr: false, 112 | }, 113 | } 114 | for _, tt := range tests { 115 | t.Run(tt.name, func(t *testing.T) { 116 | if err := DiskUsageQuery(tt.args.usage, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 117 | t.Errorf("DiskUsageQuery() error = %v, wantErr %v", err, tt.wantErr) 118 | } 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /host_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | "github.com/noborus/trdsql" 9 | ) 10 | 11 | func TestHostInfoReader(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | wantErr bool 15 | }{ 16 | { 17 | name: "test1", 18 | wantErr: false, 19 | }, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | _, err := HostInfoReader() 24 | if (err != nil) != tt.wantErr { 25 | t.Errorf("HostInfoReader() error = %v, wantErr %v", err, tt.wantErr) 26 | return 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func TestHostUsersReader(t *testing.T) { 33 | if utmpSkip() { 34 | t.Skip("skipping tests that use utmp") 35 | } 36 | tests := []struct { 37 | name string 38 | wantErr bool 39 | }{ 40 | { 41 | name: "test1", 42 | wantErr: false, 43 | }, 44 | } 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | _, err := HostUsersReader() 48 | if (err != nil) != tt.wantErr { 49 | t.Errorf("HostUsersReader() error = %v, wantErr %v", err, tt.wantErr) 50 | return 51 | } 52 | }) 53 | } 54 | } 55 | 56 | func TestHostTemperatureReader(t *testing.T) { 57 | if runtime.GOOS != "linux" { 58 | t.Skip("skipping specific test") 59 | } 60 | tests := []struct { 61 | name string 62 | wantErr bool 63 | }{ 64 | { 65 | name: "test1", 66 | wantErr: false, 67 | }, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | _, err := HostTemperatureReader() 72 | if (err != nil) != tt.wantErr { 73 | t.Errorf("HostTemperatureReader() error = %v, wantErr %v", err, tt.wantErr) 74 | return 75 | } 76 | }) 77 | } 78 | } 79 | 80 | func TestHostQuery(t *testing.T) { 81 | type args struct { 82 | tempera bool 83 | users bool 84 | query string 85 | w trdsql.Writer 86 | } 87 | tests := []struct { 88 | name string 89 | args args 90 | wantErr bool 91 | }{ 92 | { 93 | name: "testInfo", 94 | args: args{tempera: false, users: false, w: nullWriter()}, 95 | wantErr: false, 96 | }, 97 | { 98 | name: "testUsers", 99 | args: args{tempera: false, users: true, w: nullWriter()}, 100 | wantErr: false, 101 | }, 102 | { 103 | name: "testTempera", 104 | args: args{tempera: true, users: false, w: nullWriter()}, 105 | wantErr: false, 106 | }, 107 | } 108 | for _, tt := range tests { 109 | t.Run(tt.name, func(t *testing.T) { 110 | if tt.name == "testTempera" && runtime.GOOS != "linux" { 111 | t.Skip("skipping specific test") 112 | } 113 | if utmpSkip() { 114 | t.Skip("skipping tests that use utmp") 115 | } 116 | if err := HostQuery(tt.args.tempera, tt.args.users, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 117 | t.Errorf("HostQuery() error = %v, wantErr %v", err, tt.wantErr) 118 | } 119 | }) 120 | } 121 | } 122 | 123 | func utmpSkip() bool { 124 | _, err := os.Stat("/var/run/utmp") 125 | return os.IsNotExist(err) 126 | } 127 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | const ( 10 | psCPUTime = "cputime" 11 | psCPUInfo = "cpuinfo" 12 | psCPUPercent = "cpupercent" 13 | psDiskPartition = "diskpartition" 14 | psDiskUsage = "diskusage" 15 | psDocker = "docker" 16 | psHostInfo = "hostinfo" 17 | psHostUser = "hostuser" 18 | psHostTemperature = "hosttemperature" 19 | psLoadAvg = "loadavg" 20 | psLoadMisc = "loadmisc" 21 | psVirtualMemory = "virtualmemory" 22 | psSwapMemory = "swapmemory" 23 | psNet = "net" 24 | psProcess = "process" 25 | psProcessEx = "processex" 26 | psWinservices = "winservices" 27 | ) 28 | 29 | func psutilReader(tableName string) Reader { 30 | var err error 31 | var reader Reader 32 | switch strings.ToLower(tableName) { 33 | case psCPUTime: 34 | reader, err = CPUTimeReader(false) 35 | case psCPUInfo: 36 | reader, err = CPUInfoReader() 37 | case psCPUPercent: 38 | reader, err = CPUPercentReader(false) 39 | case psDiskPartition: 40 | reader, err = DiskPartitionReader(true) 41 | case psDiskUsage: 42 | reader, err = DiskUsageReader("/") 43 | case psDocker: 44 | reader, err = DockerReader() 45 | case psHostInfo: 46 | reader, err = HostInfoReader() 47 | case psHostUser: 48 | reader, err = HostUsersReader() 49 | case psHostTemperature: 50 | reader, err = HostTemperatureReader() 51 | case psLoadAvg: 52 | reader, err = LoadAvgReader() 53 | case psLoadMisc: 54 | reader, err = LoadMiscReader() 55 | case psVirtualMemory: 56 | reader, err = VirtualMemoryReader() 57 | case psSwapMemory: 58 | reader, err = SwapMemoryReader() 59 | case psNet: 60 | reader, err = NetReader() 61 | case psProcess: 62 | reader, err = NewProcessReader(false) 63 | case psProcessEx: 64 | reader, err = NewProcessReader(true) 65 | case "pstable": 66 | reader, err = TableReader() 67 | default: 68 | reader = nil 69 | } 70 | if err != nil { 71 | return nil 72 | } 73 | return reader 74 | } 75 | 76 | func readerExec(reader Reader, query string, writer trdsql.Writer) error { 77 | importer, err := NewMultiImporter(reader) 78 | if err != nil { 79 | return err 80 | } 81 | trd := trdsql.NewTRDSQL(importer, trdsql.NewExporter(writer)) 82 | err = trd.Exec(query) 83 | return err 84 | } 85 | 86 | // QueryExec actually executes the passed query and writes it to the writer. 87 | func QueryExec(query string, writer trdsql.Writer) error { 88 | parsedQuery := trdsql.SQLFields(query) 89 | tables, _ := trdsql.TableNames(parsedQuery) 90 | if tables == nil { 91 | return nil 92 | } 93 | var readers []Reader 94 | for _, table := range tables { 95 | reader := psutilReader(table) 96 | if reader != nil { 97 | readers = append(readers, reader) 98 | } 99 | } 100 | importer, err := NewMultiImporter(readers...) 101 | if err != nil { 102 | return err 103 | } 104 | trd := trdsql.NewTRDSQL(importer, trdsql.NewExporter(writer)) 105 | err = trd.Exec(query) 106 | return err 107 | } 108 | -------------------------------------------------------------------------------- /process_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package psutilsql 5 | 6 | var processColumn = map[pColumnNum]pColumn{ 7 | PID: { 8 | names: []string{"pid"}, 9 | types: []string{"int"}, 10 | getFunc: getPid, 11 | }, 12 | NAME: { 13 | names: []string{"name"}, 14 | types: []string{"text"}, 15 | getFunc: getName, 16 | }, 17 | CPU: { 18 | names: []string{"CPU"}, 19 | types: []string{"float"}, 20 | getFunc: cpuPercent, 21 | }, 22 | MEM: { 23 | names: []string{"MEM"}, 24 | types: []string{"float"}, 25 | getFunc: memPercent, 26 | }, 27 | STATUS: { 28 | names: []string{"STATUS"}, 29 | types: []string{"text"}, 30 | getFunc: status, 31 | }, 32 | START: { 33 | names: []string{"START"}, 34 | types: []string{"timestamp"}, 35 | getFunc: createTime, 36 | }, 37 | USER: { 38 | names: []string{"USER"}, 39 | types: []string{"text"}, 40 | getFunc: getUser, 41 | }, 42 | CWD: { 43 | names: []string{"Cwd"}, 44 | types: []string{"text"}, 45 | getFunc: cwd, 46 | }, 47 | EXE: { 48 | names: []string{"Exe"}, 49 | types: []string{"text"}, 50 | getFunc: exe, 51 | }, 52 | TERMINAL: { 53 | names: []string{"Terminal"}, 54 | types: []string{"text"}, 55 | getFunc: terminal, 56 | }, 57 | IONICE: { 58 | names: []string{"IONice"}, 59 | types: []string{"int"}, 60 | getFunc: ioNice, 61 | }, 62 | NICE: { 63 | names: []string{"Nice"}, 64 | types: []string{"int"}, 65 | getFunc: nice, 66 | }, 67 | NUMFDS: { 68 | names: []string{"NumFDs"}, 69 | types: []string{"int"}, 70 | getFunc: numFDs, 71 | }, 72 | NUMTHREADS: { 73 | names: []string{"NumThreads"}, 74 | types: []string{"int"}, 75 | getFunc: numThreads, 76 | }, 77 | PPID: { 78 | names: []string{"pPid"}, 79 | types: []string{"int"}, 80 | getFunc: ppid, 81 | }, 82 | TGID: { 83 | names: []string{"Tgid"}, 84 | types: []string{"int"}, 85 | getFunc: tgid, 86 | }, 87 | UIDS: { 88 | names: []string{"Uids"}, 89 | types: []string{"text"}, 90 | getFunc: uids, 91 | }, 92 | GIDS: { 93 | names: []string{"Gids"}, 94 | types: []string{"text"}, 95 | getFunc: gids, 96 | }, 97 | MEMORYINFO: { 98 | names: []string{"RSS", "VMS", "Data", "Stack", "locked", "Swap"}, 99 | types: []string{"int", "int", "int", "int", "int", "int"}, 100 | getFunc: memoryInfo, 101 | }, 102 | IOCOUNTERS: { 103 | names: []string{"ReadCount", "WriteCount", "ReadBytes", "WriteBytes"}, 104 | types: []string{"int", "int", "int", "int"}, 105 | getFunc: ioCounters, 106 | }, 107 | FOREGROUND: { 108 | names: []string{"Foreground"}, 109 | types: []string{"bool"}, 110 | getFunc: foreGround, 111 | }, 112 | BACKGROUND: { 113 | names: []string{"Background"}, 114 | types: []string{"bool"}, 115 | getFunc: backGround, 116 | }, 117 | ISRUNNING: { 118 | names: []string{"IsRunning"}, 119 | types: []string{"bool"}, 120 | getFunc: isRunning, 121 | }, 122 | 123 | COMMAND: { 124 | names: []string{"COMMAND"}, 125 | types: []string{"text"}, 126 | getFunc: cmdLine, 127 | }, 128 | } 129 | -------------------------------------------------------------------------------- /process_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package psutilsql 5 | 6 | import ( 7 | "github.com/shirou/gopsutil/v4/process" 8 | ) 9 | 10 | var processColumn = map[pColumnNum]pColumn{ 11 | PID: { 12 | names: []string{"pid"}, 13 | types: []string{"int"}, 14 | getFunc: getPid, 15 | }, 16 | NAME: { 17 | names: []string{"name"}, 18 | types: []string{"text"}, 19 | getFunc: getName, 20 | }, 21 | CPU: { 22 | names: []string{"CPU"}, 23 | types: []string{"float"}, 24 | getFunc: cpuPercent, 25 | }, 26 | MEM: { 27 | names: []string{"MEM"}, 28 | types: []string{"float"}, 29 | getFunc: memPercent, 30 | }, 31 | STATUS: { 32 | names: []string{"STATUS"}, 33 | types: []string{"text"}, 34 | getFunc: status, 35 | }, 36 | START: { 37 | names: []string{"START"}, 38 | types: []string{"timestamp"}, 39 | getFunc: createTime, 40 | }, 41 | USER: { 42 | names: []string{"USER"}, 43 | types: []string{"text"}, 44 | getFunc: getUser, 45 | }, 46 | CWD: { 47 | names: []string{"Cwd"}, 48 | types: []string{"text"}, 49 | getFunc: cwd, 50 | }, 51 | EXE: { 52 | names: []string{"Exe"}, 53 | types: []string{"text"}, 54 | getFunc: exe, 55 | }, 56 | TERMINAL: { 57 | names: []string{"Terminal"}, 58 | types: []string{"text"}, 59 | getFunc: terminal, 60 | }, 61 | IONICE: { 62 | names: []string{"IONice"}, 63 | types: []string{"int"}, 64 | getFunc: ioNice, 65 | }, 66 | NICE: { 67 | names: []string{"Nice"}, 68 | types: []string{"int"}, 69 | getFunc: nice, 70 | }, 71 | NUMFDS: { 72 | names: []string{"NumFDs"}, 73 | types: []string{"int"}, 74 | getFunc: numFDs, 75 | }, 76 | NUMTHREADS: { 77 | names: []string{"NumThreads"}, 78 | types: []string{"int"}, 79 | getFunc: numThreads, 80 | }, 81 | PPID: { 82 | names: []string{"pPid"}, 83 | types: []string{"int"}, 84 | getFunc: ppid, 85 | }, 86 | TGID: { 87 | names: []string{"Tgid"}, 88 | types: []string{"int"}, 89 | getFunc: tgid, 90 | }, 91 | UIDS: { 92 | names: []string{"Uids"}, 93 | types: []string{"text"}, 94 | getFunc: uids, 95 | }, 96 | GIDS: { 97 | names: []string{"Gids"}, 98 | types: []string{"text"}, 99 | getFunc: gids, 100 | }, 101 | MEMORYINFOEX: { 102 | names: []string{"RSS", "VMS", "Shared", "Text", "Lib", "Data", "Dirty"}, 103 | types: []string{"int", "int", "int", "int", "int", "int", "int"}, 104 | getFunc: memoryInfoEx, 105 | }, 106 | MEMORYINFO: { 107 | names: []string{"RSS", "VMS", "Data", "Stack", "locked", "Swap"}, 108 | types: []string{"int", "int", "int", "int", "int", "int"}, 109 | getFunc: memoryInfo, 110 | }, 111 | IOCOUNTERS: { 112 | names: []string{"ReadCount", "WriteCount", "ReadBytes", "WriteBytes"}, 113 | types: []string{"int", "int", "int", "int"}, 114 | getFunc: ioCounters, 115 | }, 116 | FOREGROUND: { 117 | names: []string{"Foreground"}, 118 | types: []string{"bool"}, 119 | getFunc: foreGround, 120 | }, 121 | BACKGROUND: { 122 | names: []string{"Background"}, 123 | types: []string{"bool"}, 124 | getFunc: backGround, 125 | }, 126 | ISRUNNING: { 127 | names: []string{"IsRunning"}, 128 | types: []string{"bool"}, 129 | getFunc: isRunning, 130 | }, 131 | COMMAND: { 132 | names: []string{"COMMAND"}, 133 | types: []string{"text"}, 134 | getFunc: cmdLine, 135 | }, 136 | } 137 | 138 | func memoryInfoEx(p *process.Process) []any { 139 | mx, err := p.MemoryInfoEx() 140 | if err != nil { 141 | return []any{0, 0, 0, 0, 0, 0, 0} 142 | } 143 | return []any{ 144 | mx.RSS, 145 | mx.VMS, 146 | mx.Shared, 147 | mx.Text, 148 | mx.Lib, 149 | mx.Data, 150 | mx.Dirty, 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /cpu_query_test.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/noborus/trdsql" 7 | ) 8 | 9 | func TestCPUTimeReader(t *testing.T) { 10 | type args struct { 11 | total bool 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | wantErr bool 17 | }{ 18 | { 19 | name: "testTrue", 20 | args: args{total: true}, 21 | wantErr: false, 22 | }, 23 | { 24 | name: "testFalse", 25 | args: args{total: false}, 26 | wantErr: false, 27 | }, 28 | } 29 | for _, tt := range tests { 30 | t.Run(tt.name, func(t *testing.T) { 31 | _, err := CPUTimeReader(tt.args.total) 32 | if (err != nil) != tt.wantErr { 33 | t.Errorf("CPUTimeReader() error = %v, wantErr %v", err, tt.wantErr) 34 | return 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestCPUInfoReader(t *testing.T) { 41 | tests := []struct { 42 | name string 43 | wantErr bool 44 | }{ 45 | { 46 | name: "test1", 47 | wantErr: false, 48 | }, 49 | } 50 | for _, tt := range tests { 51 | t.Run(tt.name, func(t *testing.T) { 52 | _, err := CPUInfoReader() 53 | if (err != nil) != tt.wantErr { 54 | t.Errorf("CPUInfoReader() error = %v, wantErr %v", err, tt.wantErr) 55 | return 56 | } 57 | }) 58 | } 59 | } 60 | 61 | func TestCPUPercentReader(t *testing.T) { 62 | type args struct { 63 | total bool 64 | } 65 | tests := []struct { 66 | name string 67 | args args 68 | wantErr bool 69 | }{ 70 | { 71 | name: "testTrue", 72 | args: args{total: true}, 73 | wantErr: false, 74 | }, 75 | { 76 | name: "testFalse", 77 | args: args{total: false}, 78 | wantErr: false, 79 | }, 80 | } 81 | for _, tt := range tests { 82 | t.Run(tt.name, func(t *testing.T) { 83 | _, err := CPUPercentReader(tt.args.total) 84 | if (err != nil) != tt.wantErr { 85 | t.Errorf("CPUPercentReader() error = %v, wantErr %v", err, tt.wantErr) 86 | return 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func TestCPUTimeQuery(t *testing.T) { 93 | type args struct { 94 | total bool 95 | query string 96 | w trdsql.Writer 97 | } 98 | tests := []struct { 99 | name string 100 | args args 101 | wantErr bool 102 | }{ 103 | { 104 | name: "test1", 105 | args: args{total: false, w: nullWriter()}, 106 | wantErr: false, 107 | }, 108 | { 109 | name: "testTotal", 110 | args: args{total: true, w: nullWriter()}, 111 | wantErr: false, 112 | }, 113 | } 114 | for _, tt := range tests { 115 | t.Run(tt.name, func(t *testing.T) { 116 | if err := CPUTimeQuery(tt.args.total, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 117 | t.Errorf("CPUTimeQuery() error = %v, wantErr %v", err, tt.wantErr) 118 | } 119 | }) 120 | } 121 | } 122 | 123 | func TestCPUInfoQuery(t *testing.T) { 124 | type args struct { 125 | query string 126 | w trdsql.Writer 127 | } 128 | tests := []struct { 129 | name string 130 | args args 131 | wantErr bool 132 | }{ 133 | { 134 | name: "test1", 135 | args: args{w: nullWriter()}, 136 | wantErr: false, 137 | }, 138 | } 139 | for _, tt := range tests { 140 | t.Run(tt.name, func(t *testing.T) { 141 | if err := CPUInfoQuery(tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 142 | t.Errorf("CPUInfoQuery() error = %v, wantErr %v", err, tt.wantErr) 143 | } 144 | }) 145 | } 146 | } 147 | 148 | func TestCPUPercentQuery(t *testing.T) { 149 | type args struct { 150 | total bool 151 | query string 152 | w trdsql.Writer 153 | } 154 | tests := []struct { 155 | name string 156 | args args 157 | wantErr bool 158 | }{ 159 | { 160 | name: "test1", 161 | args: args{total: false, w: nullWriter()}, 162 | wantErr: false, 163 | }, 164 | { 165 | name: "testTotal", 166 | args: args{total: true, w: nullWriter()}, 167 | wantErr: false, 168 | }, 169 | } 170 | for _, tt := range tests { 171 | t.Run(tt.name, func(t *testing.T) { 172 | if err := CPUPercentQuery(tt.args.total, tt.args.query, tt.args.w); (err != nil) != tt.wantErr { 173 | t.Errorf("CPUPercentQuery() error = %v, wantErr %v", err, tt.wantErr) 174 | } 175 | }) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /process_wrpper.go: -------------------------------------------------------------------------------- 1 | package psutilsql 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/shirou/gopsutil/v4/process" 10 | ) 11 | 12 | type pColumnNum int 13 | 14 | const ( 15 | PID pColumnNum = iota 16 | NAME 17 | CPU 18 | MEM 19 | STATUS 20 | START 21 | USER 22 | CWD 23 | EXE 24 | TERMINAL 25 | IONICE 26 | NICE 27 | NUMFDS 28 | NUMTHREADS 29 | PPID 30 | TGID 31 | UIDS 32 | GIDS 33 | MEMORYINFOEX 34 | MEMORYINFO 35 | IOCOUNTERS 36 | FOREGROUND 37 | BACKGROUND 38 | ISRUNNING 39 | COMMAND 40 | ) 41 | 42 | type pColumn struct { 43 | names []string 44 | types []string 45 | getFunc func(p *process.Process) []any 46 | } 47 | 48 | func getPid(p *process.Process) []any { 49 | return []any{p.Pid} 50 | } 51 | 52 | func strWrap(v string, err error) []any { 53 | if err != nil { 54 | return []any{""} 55 | } 56 | return []any{v} 57 | } 58 | 59 | func getName(p *process.Process) []any { 60 | return strWrap(p.Name()) 61 | } 62 | 63 | func status(p *process.Process) []any { 64 | status, err := p.Status() 65 | if err != nil { 66 | return []any{""} 67 | } 68 | result := make([]any, len(status)) 69 | for i, v := range status { 70 | result[i] = v 71 | } 72 | return result 73 | } 74 | 75 | func cmdLine(p *process.Process) []any { 76 | return strWrap(p.Cmdline()) 77 | } 78 | 79 | func getUser(p *process.Process) []any { 80 | return strWrap(p.Username()) 81 | } 82 | 83 | func cwd(p *process.Process) []any { 84 | return strWrap(p.Cwd()) 85 | } 86 | 87 | func exe(p *process.Process) []any { 88 | return strWrap(p.Exe()) 89 | } 90 | 91 | func terminal(p *process.Process) []any { 92 | return strWrap(p.Terminal()) 93 | } 94 | 95 | func numWrap(v any, err error) []any { 96 | if err != nil { 97 | return []any{0} 98 | } 99 | return []any{v} 100 | } 101 | 102 | func cpuPercent(p *process.Process) []any { 103 | return numWrap(p.CPUPercent()) 104 | } 105 | 106 | func memPercent(p *process.Process) []any { 107 | return numWrap(p.MemoryPercent()) 108 | } 109 | 110 | func ioNice(p *process.Process) []any { 111 | return numWrap(p.IOnice()) 112 | } 113 | 114 | func nice(p *process.Process) []any { 115 | return numWrap(p.Nice()) 116 | } 117 | 118 | func numFDs(p *process.Process) []any { 119 | return numWrap(p.NumFDs()) 120 | } 121 | 122 | func numThreads(p *process.Process) []any { 123 | return numWrap(p.NumThreads()) 124 | } 125 | 126 | func ppid(p *process.Process) []any { 127 | return numWrap(p.Ppid()) 128 | } 129 | 130 | func tgid(p *process.Process) []any { 131 | return numWrap(p.Tgid()) 132 | } 133 | 134 | func sliceWrap(v []uint32, err error) []any { 135 | if err != nil { 136 | return []any{0} 137 | } 138 | s := make([]string, len(v)) 139 | for i, vv := range v { 140 | s[i] = fmt.Sprint(vv) 141 | } 142 | return []any{strings.Join(s, ",")} 143 | } 144 | 145 | func uids(p *process.Process) []any { 146 | return sliceWrap(p.Uids()) 147 | } 148 | 149 | func gids(p *process.Process) []any { 150 | return sliceWrap(p.Gids()) 151 | } 152 | 153 | func boolWrap(v bool, err error) []any { 154 | if err != nil { 155 | return []any{""} 156 | } 157 | return []any{strconv.FormatBool(v)} 158 | } 159 | 160 | func foreGround(p *process.Process) []any { 161 | return boolWrap(p.Foreground()) 162 | } 163 | 164 | func backGround(p *process.Process) []any { 165 | return boolWrap(p.Background()) 166 | } 167 | 168 | func isRunning(p *process.Process) []any { 169 | return boolWrap(p.IsRunning()) 170 | } 171 | 172 | func createTime(p *process.Process) []any { 173 | c, err := p.CreateTime() 174 | if err != nil { 175 | return []any{0} 176 | } 177 | return []any{time.Unix(c/1000, 0)} 178 | } 179 | 180 | func memoryInfo(p *process.Process) []any { 181 | mx, err := p.MemoryInfo() 182 | if err != nil { 183 | return []any{0, 0, 0, 0, 0, 0} 184 | } 185 | return []any{ 186 | mx.RSS, 187 | mx.VMS, 188 | mx.Data, 189 | mx.Stack, 190 | mx.Locked, 191 | mx.Swap, 192 | } 193 | } 194 | 195 | func ioCounters(p *process.Process) []any { 196 | io, err := p.IOCounters() 197 | if err != nil { 198 | return []any{0, 0, 0, 0} 199 | } 200 | return []any{ 201 | io.ReadCount, 202 | io.WriteCount, 203 | io.ReadBytes, 204 | io.WriteBytes, 205 | } 206 | } 207 | 208 | /* 209 | func pageFaults(p *process.Process) []any { 210 | pf, err := p.PageFaults 211 | if err != nil { 212 | return []any{0, 0, 0, 0} 213 | } 214 | return []any{ 215 | pf.MinorFaults, 216 | pf.MajorFaults, 217 | pf.ChildMinorFaults, 218 | pf.ChildMajorFaults, 219 | } 220 | } 221 | */ 222 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psutilsql 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/noborus/psutilsql)](https://pkg.go.dev/github.com/noborus/psutilsql) 4 | 5 | CLI tool that can be processed by SQL using [gopsutil](https://github.com/shirou/gopsutil) library. 6 | 7 | SQL input/output is handled by [trdsql](https://github.com/noborus/trdsql). 8 | Therefore, CSV, JSON, LTSV, MarkDown, Raw, Vertical, and TBLN can be selected as the output format. 9 | 10 | ![psutilsql.gif](doc/psutilsql.gif) 11 | 12 | ## install 13 | 14 | ```console 15 | go get -u github.com/noborus/psutilsql... 16 | ``` 17 | 18 | psutilsql depends on [go-sqlite3](https://github.com/mattn/go-sqlite3). 19 | Therefore, gcc is required to build. 20 | 21 | ## Usage 22 | 23 | ```console 24 | psutilsql command 25 | ``` 26 | 27 | ### SQL 28 | 29 | The query command(\ can be omitted) can execute SQL. 30 | 31 | ```console 32 | $ psutilsql query "SELECT Total,Used,Free FROM virtualmemory" 33 | or 34 | $ psutilsql "SELECT Total,Used,Free FROM virtualmemory" 35 | 36 | +-------------+------------+------------+ 37 | | Total | Used | Free | 38 | +-------------+------------+------------+ 39 | | 16687091712 | 6468083712 | 2399399936 | 40 | +-------------+------------+------------+ 41 | ``` 42 | 43 | #### Table list 44 | 45 | List of table names that can be used. 46 | 47 | Displayed with the following command: 48 | 49 | ```console 50 | psutilsql table 51 | ``` 52 | 53 | | name | 54 | |-----------------| 55 | | cpuinfo | 56 | | cpupercent | 57 | | cputime | 58 | | diskpartition | 59 | | diskusage | 60 | | docker | 61 | | hostinfo | 62 | | hosttemperature | 63 | | hostuser | 64 | | loadavg | 65 | | loadmisc | 66 | | net | 67 | | process | 68 | | processex | 69 | | swapmemory | 70 | | virtualmemory | 71 | 72 | ### Command 73 | 74 | Display values using command and options without using SQL. 75 | 76 | ```console 77 | $ psutilsql host --users 78 | +---------+----------+------+------------+ 79 | | User | Terminal | Host | Started | 80 | +---------+----------+------+------------+ 81 | | noborus | tty7 | :0 | 1564096509 | 82 | +---------+----------+------+------------+ 83 | ``` 84 | 85 | ```console 86 | $ psutilsql --help 87 | SQL for running processes and system utilization. 88 | 89 | SQL can be executed on the information acquired using gopsutil library. 90 | Default SQL is provided, so you can omit SQL if you select a command. 91 | 92 | Usage: 93 | psutilsql [flags] 94 | psutilsql [command] 95 | 96 | Available Commands: 97 | completion Generates bash/zsh completion scripts 98 | cpu CPU information 99 | disk DISK information 100 | docker docker information 101 | help Help about any command 102 | host host information 103 | load load information 104 | mem memory information 105 | net net information 106 | process process information 107 | query SQL query command 108 | table table list 109 | version Print the version number of psutilsql 110 | 111 | Flags: 112 | -d, --Delimiter string output delimiter (CSV only) (default ",") 113 | -O, --Header output header (CSV only) 114 | -o, --OutFormat string output format=[AT|CSV|LTSV|JSON|JSONL|TBLN|RAW|MD|VF|YAML] (default "AT") 115 | -q, --Query string query 116 | -h, --help help for psutilsql 117 | -t, --toggle Help message for toggle 118 | 119 | Use "psutilsql [command] --help" for more information about a command. 120 | ``` 121 | 122 | ### cpu 123 | 124 | --time: cpu time(default) 125 | 126 | | CPU | User | System | Idle | Nice | Iowait | Irq | Softirq | Steal | Guest | GuestNice | 127 | |-----|------|--------|------|------|--------|-----|---------|-------|-------|-----------| 128 | 129 | --info, -i: cpu info 130 | 131 | | CPU | VendorID | Family | Model | Stepping | PhysicalID | CoreID | Cores | ModelName | Mhz | CacheSize | Flags | Microcode | 132 | |-----|----------|--------|-------|----------|------------|--------|-------|-----------|-----|-----------|-------|-----------| 133 | 134 | --percent,-p: cpu percent 135 | 136 | ### disk 137 | 138 | --partition: disk partition(default) 139 | 140 | | Device | Mountpoint | Fstype | Opts | 141 | |--------|------------|--------|------| 142 | 143 | --usage [disk]: disk usage 144 | 145 | | Path | Fstype | Total | Free | Used | UsedPercent | InodesTotal | InodesUsed | InodesFree | InodesUsedPercent | 146 | |------|--------|-------|------|------|-------------|-------------|------------|------------|-------------------| 147 | 148 | ### docker 149 | 150 | | ContainerID | Name | Image | Status | Running | 151 | |-------------|------|-------|--------|---------| 152 | 153 | ### host 154 | 155 | --info: host information(default) 156 | 157 | | Hostname | Uptime | BootTime | Procs | OS | Platform | PlatformFamily | PlatformVersion | KernelVersion | VirtualizationSystem | VirtualizationRole | HostID | 158 | |----------|--------|----------|-------|----|----------|----------------|-----------------|---------------|----------------------|--------------------|--------| 159 | 160 | --user,-u: user information 161 | 162 | | User | Terminal | Host | Started | 163 | |------|----------|------|---------| 164 | 165 | --temperatures, -t: SensorsTemperatures 166 | 167 | | SensorKey | Temperature | 168 | |-----------|-------------| 169 | 170 | ### load 171 | 172 | | Load1 | Load5 | Load15 | 173 | |-------|-------|--------| 174 | 175 | --misc,-m: miscellaneous host-wide statistics 176 | 177 | | ProcsTotal | ProcsRunning | ProcsBlocked | Ctxt | 178 | |------------|--------------|--------------|------| 179 | 180 | ### mem 181 | 182 | VirtualMemory(default) 183 | 184 | | Total | Available | Used | UsedPercent | Free | Active | Inactive | Wired | Laundry | Buffers | Cached | Writeback | Dirty | WritebackTmp | Shared | Slab | SReclaimable | SUnreclaim | PageTables | SwapCached | CommitLimit | CommittedAS | HighTotal | HighFree | LowTotal | LowFree | SwapTotal | SwapFree | Mapped | VMallocTotal | VMallocUsed | VMallocChunk | HugePagesTotal | HugePagesFree | HugePageSize | 185 | |-------|-----------|------|-------------|------|--------|----------|-------|---------|---------|--------|-----------|-------|--------------|--------|------|--------------|------------|------------|------------|-------------|-------------|-----------|----------|----------|---------|-----------|----------|--------|--------------|-------------|--------------|----------------|---------------|--------------| 186 | 187 | --swap, -s: SwapMemory 188 | 189 | | Total | Used | Free | UsedPercent | Sin | Sout | PgIn | PgOut | PgFault | 190 | |-------|------|------|-------------|-----|------|------|-------|---------| 191 | 192 | ### net 193 | 194 | | Fd | Family | Type | LaddrIP | LaddrPort | RaddrIP | RaddrPort | status | Uids | Pid | 195 | |----|--------|------|---------|-----------|---------|-----------|--------|------|-----| 196 | 197 | ### process 198 | 199 | | pid | name | CPU | MEM | STATUS | START | USER | RSS | VMS | Data | Stack | locked | Swap | COMMAND | 200 | |-----|------|-----|-----|--------|-------|------|-----|-----|------|-------|--------|------|---------| 201 | 202 | --ex: memory info ex 203 | 204 | | pid | name | CPU | MEM | STATUS | START | USER | RSS | VMS | Shared | Text | Lib | Data | Dirty | COMMAND | 205 | |-----|------|-----|-----|--------|-------|------|-----|-----|--------|------|-----|------|-------|---------| 206 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= 4 | github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 10 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 11 | github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= 12 | github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 13 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 14 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 15 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 16 | github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= 17 | github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 18 | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 19 | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 20 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 21 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 22 | github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= 23 | github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 24 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 25 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 26 | github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= 27 | github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= 28 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 29 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 30 | github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg= 31 | github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY= 32 | github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= 33 | github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= 34 | github.com/jwalton/gchalk v1.3.0 h1:uTfAaNexN8r0I9bioRTksuT8VGjrPs9YIXR1PQbtX/Q= 35 | github.com/jwalton/gchalk v1.3.0/go.mod h1:ytRlj60R9f7r53IAElbpq4lVuPOPNg2J4tJcCxtFqr8= 36 | github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= 37 | github.com/jwalton/go-supportscolor v1.2.0 h1:g6Ha4u7Vm3LIsQ5wmeBpS4gazu0UP1DRDE8y6bre4H8= 38 | github.com/jwalton/go-supportscolor v1.2.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= 39 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 40 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 41 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 42 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 43 | github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= 44 | github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 45 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 46 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 47 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 48 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 49 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 50 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 51 | github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= 52 | github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 53 | github.com/multiprocessio/go-sqlite3-stdlib v0.0.0-20220822170115-9f6825a1cd25 h1:bnhGk2UFFPqylhxTEffs1ehDRn4bEZsEoDH53Z4HqA8= 54 | github.com/multiprocessio/go-sqlite3-stdlib v0.0.0-20220822170115-9f6825a1cd25/go.mod h1:RrGEZqqiyEcLyTVLDSgtNZVLqJykj0F4vwuuqvMdT60= 55 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= 56 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 57 | github.com/noborus/guesswidth v0.4.0 h1:+PPh+Z+GM4mKmVrhYR4lpjeyBuLMSVo2arM+VErdHIc= 58 | github.com/noborus/guesswidth v0.4.0/go.mod h1:ghA6uh9RcK+uSmaDDmBMj/tRZ3BSpspDP6DMF5Xk3bc= 59 | github.com/noborus/sqlss v0.1.0 h1:GSrOeBuswHaBn6enegZeMVudPPeyVoYo+LCapTc+b7Q= 60 | github.com/noborus/sqlss v0.1.0/go.mod h1:34KdYx3QxMFfD05RhUi7Uw5M1i6KOBQ1NHtMIuNVnWM= 61 | github.com/noborus/tbln v0.0.2 h1:pQIv+ZO38KPz52FOuhs/W3inpgmd5qwL8XFDqI+KKyY= 62 | github.com/noborus/tbln v0.0.2/go.mod h1:kS3WhEDRJhNwF3+aRGl9iaUzu/r3lExDagcPPENtNQ0= 63 | github.com/noborus/trdsql v1.1.0 h1:Yf3ThX3cuEGFR6/DR+fa5Vxa+qJglcbhAIIYvWJOblY= 64 | github.com/noborus/trdsql v1.1.0/go.mod h1:lgl2mIhA9wH1eflNYorasdu1LYCLzlgwUiQH2blokN8= 65 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 66 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 67 | github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= 68 | github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 69 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= 72 | github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 73 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 74 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 75 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 76 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 77 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 78 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 79 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 80 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= 81 | github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= 82 | github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= 83 | github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= 84 | github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= 85 | github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= 86 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 87 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 88 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 89 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 90 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 91 | github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= 92 | github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= 93 | github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= 94 | github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= 95 | github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= 96 | github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 97 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 98 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 99 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 100 | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 101 | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 102 | golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= 103 | golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= 104 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 105 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 106 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 107 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 108 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 109 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 110 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 111 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 114 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 116 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 117 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 118 | golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 119 | golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 120 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 121 | golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 122 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 123 | golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= 124 | golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= 125 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 126 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 127 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 128 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 129 | gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= 130 | gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 131 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 132 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 133 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 134 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 135 | modernc.org/cc/v4 v4.26.4 h1:jPhG8oNjtTYuP2FA4YefTJ/wioNUGALmGuEWt7SUR6s= 136 | modernc.org/cc/v4 v4.26.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= 137 | modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= 138 | modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= 139 | modernc.org/fileutil v1.3.28 h1:Vp156KUA2nPu9F1NEv036x9UGOjg2qsi5QlWTjZmtMk= 140 | modernc.org/fileutil v1.3.28/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= 141 | modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= 142 | modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= 143 | modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= 144 | modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= 145 | modernc.org/libc v1.66.8 h1:/awsvTnyN/sNjvJm6S3lb7KZw5WV4ly/sBEG7ZUzmIE= 146 | modernc.org/libc v1.66.8/go.mod h1:aVdcY7udcawRqauu0HukYYxtBSizV+R80n/6aQe9D5k= 147 | modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 148 | modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= 149 | modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= 150 | modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 151 | modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= 152 | modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= 153 | modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= 154 | modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= 155 | modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= 156 | modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= 157 | modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= 158 | modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= 159 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 160 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 161 | --------------------------------------------------------------------------------