diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f150ae1..e0f8a8bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9e1083f..816c3a44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.21' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 with: diff --git a/go.mod b/go.mod index ade894c2..999b1109 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/robinovitch61/wander -go 1.20 +go 1.21 require ( github.com/atotto/clipboard v0.1.4 @@ -15,6 +15,7 @@ require ( github.com/itchyny/gojq v0.12.13 github.com/olekukonko/tablewriter v0.0.5 github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 ) @@ -27,7 +28,7 @@ require ( github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/hashicorp/cronexpr v1.1.1 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -51,10 +52,9 @@ require ( github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect golang.org/x/crypto v0.10.0 // indirect - golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect + golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.9.0 // indirect golang.org/x/term v0.9.0 // indirect @@ -62,3 +62,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/hashicorp/nomad/api v0.0.0-20230619092614-e29ad68c588d => github.com/robinovitch61/nomad/api v0.0.0-20231113195354-920278517b17 diff --git a/go.sum b/go.sum index 529e6449..4b7e21fe 100644 --- a/go.sum +++ b/go.sum @@ -151,6 +151,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -221,6 +223,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robinovitch61/nomad/api v0.0.0-20231113195354-920278517b17 h1:ZcvdHmPsF4rBfNFfJVUC7xJKBQ/ayzj5QnxsfA81oII= +github.com/robinovitch61/nomad/api v0.0.0-20231113195354-920278517b17/go.mod h1:ijDwa6o1uG1jFSq6kERiX2PamKGpZzTmo0XOFNeFZgw= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -282,6 +286,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 h1:/yRP+0AN7mf5DkD3BAI6TOFnd51gEoDEb8o35jIFtgw= +golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/internal/tui/nomad/alltasks.go b/internal/tui/nomad/alltasks.go index d3d8cf4a..ec51e0fb 100644 --- a/internal/tui/nomad/alltasks.go +++ b/internal/tui/nomad/alltasks.go @@ -1,18 +1,18 @@ package nomad import ( - "encoding/json" - tea "github.com/charmbracelet/bubbletea" - "github.com/hashicorp/nomad/api" - "github.com/robinovitch61/wander/internal/tui/components/page" - "github.com/robinovitch61/wander/internal/tui/formatter" - "github.com/robinovitch61/wander/internal/tui/message" - "sort" + "encoding/json" + tea "github.com/charmbracelet/bubbletea" + "github.com/hashicorp/nomad/api" + "github.com/robinovitch61/wander/internal/tui/components/page" + "github.com/robinovitch61/wander/internal/tui/formatter" + "github.com/robinovitch61/wander/internal/tui/message" + "sort" ) func FetchAllTasks(client api.Client, columns []string) tea.Cmd { return func() tea.Msg { - allocations, _, err := client.Allocations().List(&api.QueryOptions{}) + allocations, _, err := client.Allocations().List(&api.QueryOptions{Resources: true}) if err != nil { return message.ErrMsg{Err: err} } @@ -24,9 +24,12 @@ func FetchAllTasks(client api.Client, columns []string) tea.Cmd { return message.ErrMsg{Err: err} } + resources := getAllocatedResources(alloc) for taskName, task := range alloc.TaskStates { + taskResources := getTaskResources(resources, taskName) taskRowEntries = append(taskRowEntries, taskRowEntry{ FullAllocationAsJSON: string(allocAsJSON), + NodeID: alloc.NodeID, JobID: alloc.JobID, ID: alloc.ID, TaskGroup: alloc.TaskGroup, @@ -35,6 +38,9 @@ func FetchAllTasks(client api.Client, columns []string) tea.Cmd { State: task.State, StartedAt: task.StartedAt.UTC(), FinishedAt: task.FinishedAt.UTC(), + CpuShares: taskResources.CpuShares, + Memory: taskResources.MemoryMB, + MaxMemory: taskResources.MaxMemoryMB, }) } } @@ -67,6 +73,7 @@ func FetchAllTasks(client api.Client, columns []string) tea.Cmd { func getTaskRowFromColumns(row taskRowEntry, columns []string) []string { knownColMap := map[string]string{ + "Node ID": formatter.ShortAllocID(row.NodeID), "Job": row.JobID, "Alloc ID": formatter.ShortAllocID(row.ID), "Task Group": row.TaskGroup, @@ -76,6 +83,9 @@ func getTaskRowFromColumns(row taskRowEntry, columns []string) []string { "Started": formatter.FormatTime(row.StartedAt), "Finished": formatter.FormatTime(row.FinishedAt), "Uptime": getUptime(row.State, row.StartedAt.UnixNano()), + "CPU": row.CpuShares, + "Memory": row.Memory, + "Max Memory": row.MaxMemory, } var rowEntries []string diff --git a/internal/tui/nomad/jobtasks.go b/internal/tui/nomad/jobtasks.go index 7df3df4e..f39b4989 100644 --- a/internal/tui/nomad/jobtasks.go +++ b/internal/tui/nomad/jobtasks.go @@ -1,18 +1,27 @@ package nomad import ( - "encoding/json" - tea "github.com/charmbracelet/bubbletea" - "github.com/hashicorp/nomad/api" - "github.com/robinovitch61/wander/internal/tui/components/page" - "github.com/robinovitch61/wander/internal/tui/formatter" - "github.com/robinovitch61/wander/internal/tui/message" - "sort" + "encoding/json" + "fmt" + tea "github.com/charmbracelet/bubbletea" + "github.com/hashicorp/nomad/api" + "github.com/robinovitch61/wander/internal/dev" + "github.com/robinovitch61/wander/internal/tui/components/page" + "github.com/robinovitch61/wander/internal/tui/formatter" + "github.com/robinovitch61/wander/internal/tui/message" + "sort" ) func FetchTasksForJob(client api.Client, jobID, jobNamespace string, columns []string) tea.Cmd { return func() tea.Msg { - allocationsForJob, _, err := client.Jobs().Allocations(jobID, true, &api.QueryOptions{Namespace: jobNamespace}) + allocationsForJob, _, err := client.Jobs().Allocations( + jobID, + true, + &api.QueryOptions{ + Namespace: jobNamespace, + Resources: true, + }, + ) if err != nil { return message.ErrMsg{Err: err} } @@ -24,8 +33,12 @@ func FetchTasksForJob(client api.Client, jobID, jobNamespace string, columns []s return message.ErrMsg{Err: err} } + resources := getAllocatedResources(alloc) + dev.Debug(fmt.Sprintf("Allocated resources for alloc %s: %v", alloc.ID, resources)) for taskName, task := range alloc.TaskStates { + taskResources := getTaskResources(resources, taskName) jobTaskRowEntries = append(jobTaskRowEntries, taskRowEntry{ + NodeID: alloc.NodeID, JobID: alloc.JobID, FullAllocationAsJSON: string(allocAsJSON), ID: alloc.ID, @@ -35,6 +48,9 @@ func FetchTasksForJob(client api.Client, jobID, jobNamespace string, columns []s State: task.State, StartedAt: task.StartedAt.UTC(), FinishedAt: task.FinishedAt.UTC(), + CpuShares: taskResources.CpuShares, + Memory: taskResources.MemoryMB, + MaxMemory: taskResources.MaxMemoryMB, }) } } @@ -64,6 +80,7 @@ func FetchTasksForJob(client api.Client, jobID, jobNamespace string, columns []s func getJobTaskRowFromColumns(row taskRowEntry, columns []string) []string { knownColMap := map[string]string{ + "Node ID": formatter.ShortAllocID(row.NodeID), "Job": row.JobID, "Alloc ID": formatter.ShortAllocID(row.ID), "Task Group": row.TaskGroup, @@ -73,6 +90,9 @@ func getJobTaskRowFromColumns(row taskRowEntry, columns []string) []string { "Started": formatter.FormatTime(row.StartedAt), "Finished": formatter.FormatTime(row.FinishedAt), "Uptime": getUptime(row.State, row.StartedAt.UnixNano()), + "CPU": row.CpuShares, + "Memory": row.Memory, + "Max Memory": row.MaxMemory, } var rowEntries []string diff --git a/internal/tui/nomad/util.go b/internal/tui/nomad/util.go index ed27d010..7fd727d0 100644 --- a/internal/tui/nomad/util.go +++ b/internal/tui/nomad/util.go @@ -2,6 +2,7 @@ package nomad import ( "encoding/json" + "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/gorilla/websocket" "github.com/hashicorp/nomad/api" @@ -17,9 +18,10 @@ import ( const keySeparator = "|【=◈︿◈=】|" type taskRowEntry struct { - FullAllocationAsJSON string - JobID, ID, TaskGroup, Name, TaskName, State string - StartedAt, FinishedAt time.Time + FullAllocationAsJSON string + NodeID, JobID, ID, TaskGroup, Name, TaskName, State string + StartedAt, FinishedAt time.Time + CpuShares, Memory, MaxMemory string } func toTaskKey(state, fullAllocationAsJSON, taskName string) string { @@ -100,3 +102,31 @@ func getUptime(status string, startTime int64) string { } return uptime } + +func getAllocatedResources(alloc *api.AllocationListStub) map[string]*api.AllocatedTaskResources { + resources := alloc.AllocatedResources + allocTaskResources := make(map[string]*api.AllocatedTaskResources) + if resources != nil { + allocTaskResources = resources.Tasks + } + return allocTaskResources +} + +type taskResources struct { + CpuShares, MemoryMB, MaxMemoryMB string +} + +func getTaskResources(allocTaskResources map[string]*api.AllocatedTaskResources, taskName string) taskResources { + if allocated, ok := allocTaskResources[taskName]; ok { + maxMemMB := "unbounded" + if allocated.Memory.MemoryMaxMB > 0 { + maxMemMB = fmt.Sprintf("%d MB", allocated.Memory.MemoryMaxMB) + } + return taskResources{ + CpuShares: fmt.Sprintf("%d MHz", allocated.Cpu.CpuShares), + MemoryMB: fmt.Sprintf("%d MB", allocated.Memory.MemoryMB), + MaxMemoryMB: maxMemMB, + } + } + return taskResources{} +}