diff --git a/cmd/debug.go b/cmd/debug.go index 87779a0..7311636 100644 --- a/cmd/debug.go +++ b/cmd/debug.go @@ -24,5 +24,6 @@ func newCmdDebug(f factory.Factory) *cobra.Command { debugCmd.AddCommand(debug.HostInfoCommand(f)) debugCmd.AddCommand(debug.DownloadCommand(f)) debugCmd.AddCommand(debug.CleanCacheCommand(f)) + debugCmd.AddCommand(debug.GOpsCommand(f)) return debugCmd } diff --git a/cmd/debug/gops.go b/cmd/debug/gops.go new file mode 100644 index 0000000..1c8c27d --- /dev/null +++ b/cmd/debug/gops.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 ysicing(ysicing.me, ysicing@ysicing.cloud) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// License that can be found in the LICENSE file. + +package debug + +import ( + "os" + "strconv" + + "github.com/spf13/cobra" + tops "github.com/ysicing/tiga/internal/pkg/gops" + "github.com/ysicing/tiga/pkg/factory" +) + +func GOpsCommand(f factory.Factory) *cobra.Command { + cmd := &cobra.Command{ + Use: "gops", + Short: "gops is a tool to list and diagnose Go processes.", + Run: func(cmd *cobra.Command, args []string) { + if len(os.Args) > 3 { + _, err := strconv.Atoi(os.Args[3]) + if err == nil { + f.GetLog().Infof("fetch pid %s process info", os.Args[3]) + tops.ProcessInfo(os.Args[3:]) // shift off the command name + return + } + } + f.GetLog().Info("fetch all process info") + tops.Processes() + }, + } + cmd.AddCommand(treeCommand()) + cmd.AddCommand(processCommand()) + return cmd +} + +// treeCommand displays a process tree. +func treeCommand() *cobra.Command { + return &cobra.Command{ + Use: "tree", + Short: "Display parent-child tree for Go processes.", + Run: func(cmd *cobra.Command, args []string) { + tops.DisplayProcessTree() + }, + } +} + +// processCommand displays information about a Go process. +func processCommand() *cobra.Command { + return &cobra.Command{ + Use: "process", + Aliases: []string{"pid", "proc"}, + Short: "Prints information about a Go process.", + RunE: func(cmd *cobra.Command, args []string) error { + tops.ProcessInfo(args) + return nil + }, + } +} diff --git a/go.mod b/go.mod index 0a57249..68466f0 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/cockroachdb/errors v1.9.1 github.com/containerd/continuity v0.3.0 github.com/ergoapi/util v0.3.24 + github.com/google/gops v0.3.27 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 github.com/loft-sh/utils v0.0.18 @@ -21,10 +22,12 @@ require ( github.com/muesli/roff v0.1.0 github.com/olekukonko/tablewriter v0.0.5 github.com/opencontainers/go-digest v1.0.0 + github.com/shirou/gopsutil/v3 v3.23.1 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/ulikunitz/xz v0.5.11 + github.com/xlab/treeprint v1.1.0 go.etcd.io/bbolt v1.3.7 gopkg.in/natefinch/lumberjack.v2 v2.2.1 k8s.io/apimachinery v0.27.1 @@ -54,6 +57,7 @@ require ( github.com/getsentry/sentry-go v0.20.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.4 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -77,6 +81,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -92,10 +97,13 @@ require ( github.com/otiai10/copy v1.11.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/xlab/treeprint v1.1.0 // indirect + github.com/tklauser/go-sysconf v0.3.11 // indirect + github.com/tklauser/numcpus v0.6.0 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect golang.org/x/crypto v0.9.0 // indirect @@ -121,6 +129,7 @@ require ( k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230308215209-15aac26d736a // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + rsc.io/goversion v1.2.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.2 // indirect sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect diff --git a/go.sum b/go.sum index 57328bc..236a8ce 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= @@ -153,6 +155,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M= @@ -163,6 +166,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gops v0.3.27 h1:BDdWfedShsBbeatZ820oA4DbVOC8yJ4NI8xAlDFWfgI= +github.com/google/gops v0.3.27/go.mod h1:lYqabmfnq4Q6UumWNx96Hjup5BDAVc8zmfIy0SkNCSk= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -231,6 +236,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/loft-sh/utils v0.0.18 h1:VEfvjMBR8V0dk6R2qOFMwuk3mFzlBqsFK23PqBCbV7E= github.com/loft-sh/utils v0.0.18/go.mod h1:n2L3X4i7d8kb2NF+q5duKa41N+N6fBde6XY2AolgSBI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -315,6 +322,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= @@ -331,6 +340,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4= +github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -361,6 +372,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -386,6 +401,8 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= @@ -400,8 +417,6 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -435,8 +450,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= @@ -458,11 +472,13 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -476,6 +492,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -591,6 +609,8 @@ k8s.io/kubectl v0.27.1 h1:9T5c5KdpburYiW8XKQSH0Uly1kMNE90aGSnbYUZNdcA= k8s.io/kubectl v0.27.1/go.mod h1:QsAkSmrRsKTPlAFzF8kODGDl4p35BIwQnc9XFhkcsy8= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= +rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.2 h1:kejWfLeJhUsTGioDoFNJET5LQe/ajzXhJGYoU+pJsiA= diff --git a/internal/pkg/gops/processes.go b/internal/pkg/gops/processes.go new file mode 100644 index 0000000..09179b9 --- /dev/null +++ b/internal/pkg/gops/processes.go @@ -0,0 +1,190 @@ +// Copyright (c) 2023 ysicing(ysicing.me, ysicing@ysicing.cloud) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// License that can be found in the LICENSE file. + +package gops + +import ( + "bytes" + "fmt" + "log" + "math" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/shirou/gopsutil/v3/process" + + "github.com/google/gops/goprocess" +) + +var develRe = regexp.MustCompile(`devel\s+\+\w+`) + +func Processes() { + ps := goprocess.FindAll() + + var maxPID, maxPPID, maxExec, maxVersion int + for i, p := range ps { + ps[i].BuildVersion = shortenVersion(p.BuildVersion) + maxPID = max(maxPID, len(strconv.Itoa(p.PID))) + maxPPID = max(maxPPID, len(strconv.Itoa(p.PPID))) + maxExec = max(maxExec, len(p.Exec)) + maxVersion = max(maxVersion, len(ps[i].BuildVersion)) + } + + for _, p := range ps { + buf := bytes.NewBuffer(nil) + pid := strconv.Itoa(p.PID) + fmt.Fprint(buf, pad(pid, maxPID)) + fmt.Fprint(buf, " ") + ppid := strconv.Itoa(p.PPID) + fmt.Fprint(buf, pad(ppid, maxPPID)) + fmt.Fprint(buf, " ") + fmt.Fprint(buf, pad(p.Exec, maxExec)) + if p.Agent { + fmt.Fprint(buf, "*") + } else { + fmt.Fprint(buf, " ") + } + fmt.Fprint(buf, " ") + fmt.Fprint(buf, pad(p.BuildVersion, maxVersion)) + fmt.Fprint(buf, " ") + fmt.Fprint(buf, p.Path) + fmt.Fprintln(buf) + buf.WriteTo(os.Stdout) + } +} + +func shortenVersion(v string) string { + if !strings.HasPrefix(v, "devel") { + return v + } + results := develRe.FindAllString(v, 1) + if len(results) == 0 { + return v + } + return results[0] +} + +func max(i, j int) int { + if i > j { + return i + } + return j +} + +func pad(s string, total int) string { + if len(s) >= total { + return s + } + return s + strings.Repeat(" ", total-len(s)) +} + +// ProcessInfo takes arguments starting with pid|:addr and grabs all kinds of +// useful Go process information. +func ProcessInfo(args []string) { + pid, _ := strconv.Atoi(args[0]) + var period time.Duration + var err error + if len(args) >= 2 { + period, err = time.ParseDuration(args[1]) + if err != nil { + secs, _ := strconv.Atoi(args[1]) + period = time.Duration(secs) * time.Second + } + } + processInfo(pid, period) +} + +func processInfo(pid int, period time.Duration) { + if period < 0 { + log.Fatalf("Cannot determine CPU usage for negative duration %v", period) + } + p, err := process.NewProcess(int32(pid)) + if err != nil { + log.Fatalf("Cannot read process info: %v", err) + } + if v, err := p.Parent(); err == nil { + fmt.Printf("parent PID:\t%v\n", v.Pid) + } + if v, err := p.NumThreads(); err == nil { + fmt.Printf("threads:\t%v\n", v) + } + if v, err := p.MemoryPercent(); err == nil { + fmt.Printf("memory usage:\t%.3f%%\n", v) + } + if v, err := p.CPUPercent(); err == nil { + fmt.Printf("cpu usage:\t%.3f%%\n", v) + } + if period > 0 { + if v, err := cpuPercentWithinTime(p, period); err == nil { + fmt.Printf("cpu usage (%v):\t%.3f%%\n", period, v) + } + } + if v, err := p.Username(); err == nil { + fmt.Printf("username:\t%v\n", v) + } + if v, err := p.Cmdline(); err == nil { + fmt.Printf("cmd+args:\t%v\n", v) + } + if v, err := elapsedTime(p); err == nil { + fmt.Printf("elapsed time:\t%v\n", v) + } + if v, err := p.Connections(); err == nil { + if len(v) > 0 { + for _, conn := range v { + fmt.Printf("local/remote:\t%v:%v <-> %v:%v (%v)\n", + conn.Laddr.IP, conn.Laddr.Port, conn.Raddr.IP, conn.Raddr.Port, conn.Status) + } + } + } +} + +// cpuPercentWithinTime return how many percent of the CPU time this process uses within given time duration +func cpuPercentWithinTime(p *process.Process, t time.Duration) (float64, error) { + cput, err := p.Times() + if err != nil { + return 0, err + } + time.Sleep(t) + cput2, err := p.Times() + if err != nil { + return 0, err + } + return 100 * (cput2.Total() - cput.Total()) / t.Seconds(), nil +} + +// elapsedTime shows the elapsed time of the process indicating how long the +// process has been running for. +func elapsedTime(p *process.Process) (string, error) { + crtTime, err := p.CreateTime() + if err != nil { + return "", err + } + etime := time.Since(time.Unix(crtTime/1000, 0)) + return fmtEtimeDuration(etime), nil +} + +// fmtEtimeDuration formats etime's duration based on ps' format: +// [[DD-]hh:]mm:ss +// format specification: http://linuxcommand.org/lc3_man_pages/ps1.html +func fmtEtimeDuration(d time.Duration) string { + days := d / (24 * time.Hour) + hours := d % (24 * time.Hour) + minutes := hours % time.Hour + seconds := math.Mod(minutes.Seconds(), 60) + var b strings.Builder + if days > 0 { + fmt.Fprintf(&b, "%02d-", days) + } + if days > 0 || hours/time.Hour > 0 { + fmt.Fprintf(&b, "%02d:", hours/time.Hour) + } + fmt.Fprintf(&b, "%02d:", minutes/time.Minute) + fmt.Fprintf(&b, "%02.0f", seconds) + return b.String() +} diff --git a/internal/pkg/gops/tree.go b/internal/pkg/gops/tree.go new file mode 100644 index 0000000..e6055ca --- /dev/null +++ b/internal/pkg/gops/tree.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 ysicing(ysicing.me, ysicing@ysicing.cloud) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// License that can be found in the LICENSE file. + +package gops + +import ( + "fmt" + "sort" + "strconv" + + "github.com/google/gops/goprocess" + "github.com/xlab/treeprint" +) + +// DisplayProcessTree displays a tree of all the running Go processes. +func DisplayProcessTree() { + ps := goprocess.FindAll() + sort.Slice(ps, func(i, j int) bool { + return ps[i].PPID < ps[j].PPID + }) + pstree := make(map[int][]goprocess.P, len(ps)) + for _, p := range ps { + pstree[p.PPID] = append(pstree[p.PPID], p) + } + tree := treeprint.New() + tree.SetValue("...") + seen := map[int]bool{} + for _, p := range ps { + constructProcessTree(p.PPID, p, pstree, seen, tree) + } + fmt.Println(tree.String()) +} + +// constructProcessTree constructs the process tree in a depth-first fashion. +func constructProcessTree(ppid int, process goprocess.P, pstree map[int][]goprocess.P, seen map[int]bool, tree treeprint.Tree) { + if seen[ppid] { + return + } + seen[ppid] = true + if ppid != process.PPID { + output := strconv.Itoa(ppid) + " (" + process.Exec + ")" + " {" + process.BuildVersion + "}" + if process.Agent { + tree = tree.AddMetaBranch("*", output) + } else { + tree = tree.AddBranch(output) + } + } else { + tree = tree.AddBranch(ppid) + } + for index := range pstree[ppid] { + process := pstree[ppid][index] + constructProcessTree(process.PID, process, pstree, seen, tree) + } +} diff --git a/internal/pkg/ssh/ssh.go b/internal/pkg/ssh/ssh.go deleted file mode 100644 index 06d987f..0000000 --- a/internal/pkg/ssh/ssh.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2023 ysicing(ysicing.me, ysicing@ysicing.cloud) All rights reserved. -// Use of this source code is covered by the following dual licenses: -// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0) -// (2) Affero General Public License 3.0 (AGPL 3.0) -// License that can be found in the LICENSE file. - -package ssh - -import ( - "bytes" - "context" - "github.com/cockroachdb/errors" - "golang.org/x/crypto/ssh" - "io" - "k8s.io/apimachinery/pkg/util/wait" - "time" -) - -var defaultBackoff = wait.Backoff{ - Duration: 15 * time.Second, - Factor: 1, - Steps: 5, -} - -type SSH struct { - SSHPort string `json:"ssh-port,omitempty" yaml:"ssh-port,omitempty" default:"22"` - SSHUser string `json:"ssh-user,omitempty" yaml:"ssh-user,omitempty" default:"root"` - SSHPassword string `json:"ssh-password,omitempty" yaml:"ssh-password,omitempty"` - SSHKey string `json:"ssh-key,omitempty" yaml:"ssh-key,omitempty" wrangler:"writeOnly,nullable"` -} - -type Node struct { - SSH `json:",inline"` - PublicIPAddress []string `json:"public-ip-address,omitempty" yaml:"public-ip-address,omitempty"` -} - -// SSHDialer is a dialer that uses SSH to connect to a remote host. -type SSHDialer struct { - sshKey string - sshCert string - sshAddress string - username string - password string - passphrase string - - Stdin io.ReadCloser - Stdout io.Writer - Stderr io.Writer - Writer io.Writer - - Height int - Weight int - - Term string - Modes ssh.TerminalModes - - ctx context.Context - conn *ssh.Client - session *ssh.Session - cmd *bytes.Buffer - - err error -} - -func NewSSHDialer(n *Node, timeout bool) (*SSHDialer, error) { - if len(n.PublicIPAddress) == 0 { - return nil, errors.New("no ip address") - } - d := &SSHDialer{ - username: n.SSHUser, - password: n.SSHPassword, - passphrase: n.SSHPassword, - sshKey: n.SSHKey, - ctx: context.Background(), - } - return d, nil -} - -// Dial handshake with ssh address. -func (d *SSHDialer) Dial(t bool) (*ssh.Client, error) { - timeout := defaultBackoff.Duration - if !t { - timeout = 0 - } - - cfg, err := utils.GetSSHConfig(d.username, d.sshKey, d.passphrase, d.sshCert, d.password, timeout, d.useSSHAgentAuth) - if err != nil { - return nil, err - } - // establish connection with SSH server. - return ssh.Dial("tcp", d.sshAddress, cfg) -}