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
-}