From f6b7bb80fe2571f5eb7bb76e3ac9e57a88852b9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:22:15 +0000 Subject: [PATCH 1/2] Initial plan From 154a46a05d28886a7605fbe6a96301a704f156dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:28:17 +0000 Subject: [PATCH 2/2] Remove unused metrics feature --- README.md | 2 +- internal/index.html | 22 +---------- internal/metrics/procfs.go | 35 ----------------- internal/metrics/ps.go | 40 ------------------- internal/proc/container.go | 19 --------- internal/proc/host.go | 13 ------- internal/proc/kubernetes.go | 77 +------------------------------------ internal/proc/noop.go | 6 --- internal/proc/proc.go | 2 - internal/run.go | 23 ----------- internal/task_node.go | 2 - internal/types/metrics.go | 5 --- 12 files changed, 3 insertions(+), 243 deletions(-) delete mode 100644 internal/metrics/procfs.go delete mode 100644 internal/metrics/ps.go delete mode 100644 internal/types/metrics.go diff --git a/README.md b/README.md index 6c6d40d..f03acf4 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ With Kit, you can define and manage complex workflows in a single `tasks.yaml` f - **Auto-restart** - Automatically restart services on failure - **File watching** - Re-run tasks when files change - **Port forwarding** - Forward ports from services to host -- **Web UI** - Visualize your workflow and monitor task status with real-time metrics +- **Web UI** - Visualize your workflow and monitor task status ## Quick Start diff --git a/internal/index.html b/internal/index.html index 89a4247..6cb14d7 100644 --- a/internal/index.html +++ b/internal/index.html @@ -114,11 +114,6 @@ color: #c9d1d9; } - #metrics { - color: #8b949e; - margin-left: 16px; - } - /* Task message in log header */ #message { margin-left: 16px; @@ -176,8 +171,7 @@
Click on a task to see logs - - +
Auto-scrolling
@@ -396,7 +390,6 @@ // Get references to DOM elements const status = document.getElementById("status"); const name = document.getElementById("name"); - const metrics = document.getElementById("metrics"); const message = document.getElementById("message"); const logs = document.getElementById("logs"); const logsContainer = document.getElementById("log-container"); @@ -503,12 +496,6 @@ return brightColors[code - 90]; }; - const formatMetrics = (metrics) => { - if (!metrics) return ""; - - return `${(metrics.mem / 1024 / 1024).toFixed(0)}Mi`; - }; - // Fetch the initial graph data from the server fetch("/dag") .then((response) => response.json()) @@ -560,7 +547,6 @@ id: node.name, label: node.name, phase: nodePhase, - metrics: node.metrics, message: node.message || "", ports: node.task?.ports || "", icon: iconDataUrl, @@ -632,9 +618,6 @@ // Update the log header with node information name.textContent = nodeId; - metrics.textContent = nodeData.metrics - ? formatMetrics(nodeData.metrics) - : ""; message.textContent = nodeData.message ? nodeData.message : ""; @@ -709,7 +692,6 @@ if ( existingNode.data("phase") !== node.phase || existingNode.data("message") !== node.message || - existingNode.data("metrics") !== node.metrics || existingNode.data("parent") !== (node.task && node.task.group ? `group:${node.task.group}` @@ -717,7 +699,6 @@ ) { // Update node data existingNode.data("phase", node.phase); - existingNode.data("metrics", node.metrics); existingNode.data("message", node.message); existingNode.data( "ports", @@ -767,7 +748,6 @@ id: node.name, label: node.name, phase: node.phase, - metrics: node.metrics, message: node.message || "", ports: node.task?.ports || "", icon: iconDataUrl, diff --git a/internal/metrics/procfs.go b/internal/metrics/procfs.go deleted file mode 100644 index 3f6d2db..0000000 --- a/internal/metrics/procfs.go +++ /dev/null @@ -1,35 +0,0 @@ -package metrics - -import ( - "fmt" - "os" - "strconv" - "strings" - - "github.com/kitproj/kit/internal/types" -) - -// GetProcFSCommand returns the cat command to read /proc/pid/statm -func GetProcFSCommand(pid int) []string { - return []string{"cat", fmt.Sprintf("/proc/%d/statm", pid)} -} - -// ParseProcFSOutput parses /proc/pid/statm output for memory usage only -func ParseProcFSOutput(output string) (*types.Metrics, error) { - fields := strings.Fields(strings.TrimSpace(output)) - if len(fields) < 2 { - return nil, fmt.Errorf("unexpected /proc/pid/statm output: insufficient fields") - } - - // Field 1 (0-indexed): RSS (resident pages) - rssPages, err := strconv.ParseInt(fields[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse RSS from /proc/pid/statm: %w", err) - } - - // Convert pages to bytes using actual system page size - systemPageSize := int64(os.Getpagesize()) - memoryBytes := uint64(rssPages * systemPageSize) - - return &types.Metrics{Mem: memoryBytes}, nil -} diff --git a/internal/metrics/ps.go b/internal/metrics/ps.go deleted file mode 100644 index 0af9ba1..0000000 --- a/internal/metrics/ps.go +++ /dev/null @@ -1,40 +0,0 @@ -package metrics - -import ( - "fmt" - "strconv" - "strings" - - "github.com/kitproj/kit/internal/types" -) - -func GetPSCommand(pid int) []string { - return []string{"ps", "-o", "rss", "-p", strconv.Itoa(pid)} -} - -func ParsePSOutput(output string) (*types.Metrics, error) { - lines := strings.Split(strings.TrimSpace(output), "\n") - if len(lines) < 2 { - return nil, fmt.Errorf("unexpected ps output: %s", output) - } - - var totalMemory uint64 - - for i := 1; i < len(lines); i++ { - fields := strings.Fields(lines[i]) - if len(fields) < 1 { - continue - } - - rssMemoryKB, err := strconv.ParseInt(fields[0], 10, 64) - if err != nil { - continue - } - - totalMemory += uint64(rssMemoryKB * 1024) - } - - return &types.Metrics{ - Mem: totalMemory, - }, nil -} diff --git a/internal/proc/container.go b/internal/proc/container.go index 0044df4..c5441dd 100644 --- a/internal/proc/container.go +++ b/internal/proc/container.go @@ -26,7 +26,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/registry" "github.com/docker/go-connections/nat" - "github.com/kitproj/kit/internal/metrics" "github.com/kitproj/kit/internal/types" v1 "github.com/opencontainers/image-spec/specs-go/v1" "k8s.io/utils/strings/slices" @@ -309,24 +308,6 @@ func ignoreNotExist(err error) error { } -func (c *container) GetMetrics(ctx context.Context) (*types.Metrics, error) { - // Initialize Docker client if not already done - - command := metrics.GetProcFSCommand(1) // PID 1 - - // Use Docker API for exec instead of command line - output, err := c.execInContainer(ctx, command) - if err != nil { - return nil, fmt.Errorf("docker exec failed for container %s: %w", c.name, err) - } - - metrics, err := metrics.ParseProcFSOutput(string(output)) - if err != nil { - return nil, fmt.Errorf("failed to parse process metrics for container %s: %w", c.name, err) - } - return metrics, nil -} - func (c *container) execInContainer(ctx context.Context, command []string) ([]byte, error) { // Create exec configuration execConfig := dockertypes.ExecConfig{ diff --git a/internal/proc/host.go b/internal/proc/host.go index 28576b9..0bccd62 100644 --- a/internal/proc/host.go +++ b/internal/proc/host.go @@ -11,7 +11,6 @@ import ( "syscall" "time" - "github.com/kitproj/kit/internal/metrics" "github.com/kitproj/kit/internal/types" ) @@ -88,15 +87,3 @@ func ignoreProcessFinishedErr(err error) error { } return nil } - -func (h *host) GetMetrics(ctx context.Context) (*types.Metrics, error) { - command := metrics.GetPSCommand(h.pid) - cmd := exec.CommandContext(ctx, command[0], command[1:]...) - - output, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("failed to get process metrics for pid %d: %w, output: %s", h.pid, err, string(output)) - } - - return metrics.ParsePSOutput(string(output)) -} diff --git a/internal/proc/kubernetes.go b/internal/proc/kubernetes.go index c69a32f..0335471 100644 --- a/internal/proc/kubernetes.go +++ b/internal/proc/kubernetes.go @@ -14,11 +14,9 @@ import ( "os" "path/filepath" "sort" - "strings" "sync" "time" - "github.com/kitproj/kit/internal/metrics" "github.com/kitproj/kit/internal/types" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -113,7 +111,7 @@ func (k *k8s) Run(ctx context.Context, stdout io.Writer, stderr io.Writer) error return fmt.Errorf("failed to create clientset: %w", err) } - // Store clientset and config for later use in metrics + // Store clientset and config for later use k.clientset = clientset k.restConfig = config @@ -441,79 +439,6 @@ func sortUnstructureds(uns []*unstructured.Unstructured) { }) } -func (k *k8s) GetMetrics(ctx context.Context) (*types.Metrics, error) { - sum := &types.Metrics{} - for _, podKey := range k.pods { - parts := strings.SplitN(podKey, "/", 2) - namespace := parts[0] - podName := parts[1] - metrics, err := k.getMetrics(ctx, namespace, podName) - if err != nil { - return nil, fmt.Errorf("failed to get metrics for pod %s: %w", podKey, err) - } - sum.Mem += metrics.Mem - } - return sum, nil -} - -func (k *k8s) getMetrics(ctx context.Context, namespace, podName string) (*types.Metrics, error) { - // First, get the list of containers in the pod - containers, err := k.getContainersInPod(ctx, namespace, podName) - if err != nil { - return nil, fmt.Errorf("failed to get containers for pod %s/%s: %w", namespace, podName, err) - } - - totalMemory := uint64(0) - - // Iterate through each container and sum their memory usage - for _, containerName := range containers { - containerMetrics, err := k.getContainerMetrics(ctx, namespace, podName, containerName) - if err != nil { - // Log the error but continue with other containers - k.log.Printf("failed to get metrics for container %s in pod %s/%s: %v", containerName, namespace, podName, err) - continue - } - totalMemory += containerMetrics.Mem - } - - return &types.Metrics{Mem: totalMemory}, nil -} - -func (k *k8s) getContainersInPod(ctx context.Context, namespace, podName string) ([]string, error) { - // Use Kubernetes API to get pod details and extract container names - pod, err := k.clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to get pod %s/%s: %w", namespace, podName, err) - } - - var containerNames []string - for _, container := range pod.Spec.Containers { - containerNames = append(containerNames, container.Name) - } - - if len(containerNames) == 0 { - return nil, fmt.Errorf("no containers found in pod %s/%s", namespace, podName) - } - - return containerNames, nil -} - -func (k *k8s) getContainerMetrics(ctx context.Context, namespace, podName, containerName string) (*types.Metrics, error) { - command := metrics.GetProcFSCommand(1) // PID 1 - - // Use Kubernetes API to execute command in container - output, err := k.execInContainer(ctx, namespace, podName, containerName, command) - if err != nil { - return nil, fmt.Errorf("failed to exec in container %s/%s/%s: %w", namespace, podName, containerName, err) - } - - metrics, err := metrics.ParseProcFSOutput(string(output)) - if err != nil { - return nil, fmt.Errorf("failed to parse process metrics for container %s: %w", containerName, err) - } - return metrics, nil -} - func (k *k8s) execInContainer(ctx context.Context, namespace, podName, containerName string, command []string) ([]byte, error) { req := k.clientset.CoreV1().RESTClient().Post(). Resource("pods"). diff --git a/internal/proc/noop.go b/internal/proc/noop.go index db815a3..3c6ce2b 100644 --- a/internal/proc/noop.go +++ b/internal/proc/noop.go @@ -3,8 +3,6 @@ package proc import ( "context" "io" - - "github.com/kitproj/kit/internal/types" ) type noop struct{} @@ -12,7 +10,3 @@ type noop struct{} func (n noop) Run(ctx context.Context, stdout, stderr io.Writer) error { return nil } - -func (n noop) GetMetrics(ctx context.Context) (*types.Metrics, error) { - return &types.Metrics{}, nil -} diff --git a/internal/proc/proc.go b/internal/proc/proc.go index 1cfaafb..a80ccd8 100644 --- a/internal/proc/proc.go +++ b/internal/proc/proc.go @@ -11,8 +11,6 @@ import ( type Interface interface { // Run runs the process. Run(ctx context.Context, stdout, stderr io.Writer) error - // GetMetrics returns current resource metrics for this process - GetMetrics(ctx context.Context) (*types.Metrics, error) } func New(name string, t types.Task, log *log.Logger, spec types.Spec) Interface { diff --git a/internal/run.go b/internal/run.go index 865b995..22c0d96 100644 --- a/internal/run.go +++ b/internal/run.go @@ -484,29 +484,6 @@ func RunSubgraph(ctx context.Context, cancel context.CancelFunc, port int, openB out = io.MultiWriter(out, buf) } - go func() { - ticker := time.NewTicker(20 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if node.Phase != "running" && node.Phase != "stalled" { - continue - } - metrics, err := p.GetMetrics(ctx) - if err != nil { - logger.Printf("failed to get metrics: %v", err) - continue - } - node.Metrics = metrics - statusEvents <- node - } - } - }() - err = p.Run(ctx, out, out) // if the task was cancelled, we don't want to restart it, this is normal exit if errors.Is(ctx.Err(), context.Canceled) { diff --git a/internal/task_node.go b/internal/task_node.go index b752b4d..47d7169 100644 --- a/internal/task_node.go +++ b/internal/task_node.go @@ -24,8 +24,6 @@ type TaskNode struct { Phase string `json:"phase"` // the message for the task phase, e.g. "exit code 1' Message string `json:"message,omitempty"` - // metrics for resource usage tracking - Metrics *types.Metrics `json:"metrics,omitempty"` // cancel function cancel func() // a mutex diff --git a/internal/types/metrics.go b/internal/types/metrics.go deleted file mode 100644 index 7ea3750..0000000 --- a/internal/types/metrics.go +++ /dev/null @@ -1,5 +0,0 @@ -package types - -type Metrics struct { - Mem uint64 `json:"mem"` // Memory usage in bytes -}