Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions experiment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,11 @@ experiments:
timeout: 30s
metricsBrief: true
runtime: urunc

memory:
workloads:
default:
image: docker.io/library/nginx:alpine
other:
- image: harbor.nbfc.io/nubificus/urunc/nginx-qemu-unikraft-initrd:latest
runtime: urunc
4 changes: 4 additions & 0 deletions internal/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
runtimeCPU "github.com/urunc-dev/evaluation_suite/internal/runtime/cpu"
runtimeHTTPReadiness "github.com/urunc-dev/evaluation_suite/internal/runtime/httpreadiness"
runtimeLifecycle "github.com/urunc-dev/evaluation_suite/internal/runtime/lifecycle"
runtimeMemory "github.com/urunc-dev/evaluation_suite/internal/runtime/memory"
runtimeNetwork "github.com/urunc-dev/evaluation_suite/internal/runtime/network"
runtimeStorage "github.com/urunc-dev/evaluation_suite/internal/runtime/storage"
)
Expand Down Expand Up @@ -91,6 +92,9 @@ func NewRunCommand() *cobra.Command {
func(trial plan.Trial) (harnessruntime.Adapter, error) {
return runtimeCPU.NewAdapter(), nil
},
func(trial plan.Trial) (harnessruntime.Adapter, error) {
return runtimeMemory.NewAdapter(), nil
},
)

orch := orchestrator.New(adapterFactories...)
Expand Down
225 changes: 225 additions & 0 deletions internal/runtime/memory/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package memory

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"

harnessruntime "github.com/urunc-dev/evaluation_suite/internal/runtime"
)

type commandRunner func(context.Context, ...string) ([]byte, error)

// Adapter measures the container cgroup and its containerd shim using only
// nerdctl and Linux proc/cgroup files.
type Adapter struct {
run commandRunner
procRoot string
cgroupRoot string
}

type CgroupMetrics struct {
CurrentBytes uint64 `json:"currentBytes"`
PeakBytes uint64 `json:"peakBytes"`
Stat map[string]uint64 `json:"stat"`
}

type ShimMetrics struct {
PID int `json:"pid"`
PSSBytes uint64 `json:"pssBytes"`
USSBytes uint64 `json:"ussBytes"`
RSSBytes uint64 `json:"rssBytes"`
}

type Metrics struct {
ContainerID string `json:"containerId"`
ContainerPID int `json:"containerPid"`
CgroupPath string `json:"cgroupPath"`
Cgroup CgroupMetrics `json:"cgroup"`
Shim ShimMetrics `json:"shim"`
}

func NewAdapter() *Adapter {
return &Adapter{
run: runNerdctl,
procRoot: "/proc",
cgroupRoot: "/sys/fs/cgroup",
}
}

func (a *Adapter) ExperimentName() string { return "memory" }

func (a *Adapter) Prepare(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
startedAt := time.Now()
err := pullImage(ctx, tc.Trial.Image)
result := stageResult(harnessruntime.StagePrepare, "Pull memory benchmark image", tc, startedAt, nil)
if err != nil {
return result, fmt.Errorf("pull image %s: %w", tc.Trial.Image, err)
}
return result, nil
}

func (a *Adapter) CreateTask(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
startedAt := time.Now()
return stageResult(
harnessruntime.StageCreate,
"Create memory benchmark container", tc, startedAt, nil,
), nil
}

func (a *Adapter) StartTask(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
startedAt := time.Now()

args := []string{"run", "-it", "--name", tc.Trial.ID, "--runtime", tc.Trial.RuntimeHandler, tc.Trial.Image}

cmd := exec.CommandContext(ctx, "nerdctl", args...)
cmd.Stdin = os.Stdin

// Detach from the parent's terminal/session.
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}

if err := cmd.Start(); err != nil {
_ = a.cleanupbenchmarkresources(ctx, tc)
return stageResult(harnessruntime.StageStart, "Start memory benchmark container", tc, startedAt, nil), fmt.Errorf("start container: %w", err)
}

go func() { _ = cmd.Wait() }()

return stageResult(harnessruntime.StageStart, "Start memory benchmark container", tc, startedAt, nil), nil

}

func (a *Adapter) WaitReady(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
startedAt := time.Now()

time.Sleep(2 * time.Second)

metrics, err := a.collect(ctx, tc.Trial.ID)
result := stageResult(harnessruntime.StageWaitReady, "Collect memory metrics", tc, startedAt, metrics)
if err != nil {
_ = a.cleanupbenchmarkresources(ctx, tc)
return result, fmt.Errorf("collect memory metrics: %w", err)
}

return result, nil
}

func (a *Adapter) Stop(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
return a.commandStage(ctx, harnessruntime.StageStop, "Stop memory benchmark container", tc, "stop", tc.Trial.ID)
}

func (a *Adapter) DeleteTask(ctx context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
return a.commandStage(ctx, harnessruntime.StageDelete, "Remove memory benchmark container", tc, "rm", "--force", tc.Trial.ID)
}

func (a *Adapter) Cleanup(_ context.Context, tc harnessruntime.TrialContext) (harnessruntime.StageResult, error) {
return stageResult(harnessruntime.StageCleanup, "Cleanup memory benchmark container", tc, time.Now(), nil), nil
}

func (a *Adapter) cleanupbenchmarkresources(ctx context.Context, tc harnessruntime.TrialContext) error {
_, err := a.run(ctx, "rm", "--force", tc.Trial.ID)
return err
}

func (a *Adapter) commandStage(
ctx context.Context,
stage harnessruntime.Stage,
description string,
tc harnessruntime.TrialContext,
args ...string,
) (harnessruntime.StageResult, error) {
startedAt := time.Now()
output, err := a.run(ctx, args...)
result := stageResult(stage, description, tc, startedAt, map[string]any{
"output": strings.TrimSpace(string(output)),
})
if err != nil {
return result, fmt.Errorf("nerdctl %s: %w", strings.Join(args, " "), err)
}
return result, nil
}

func (a *Adapter) collect(ctx context.Context, name string) (Metrics, error) {
pidText, err := a.run(ctx, "inspect", "--format", "{{.State.Pid}}", name)
if err != nil {
return Metrics{}, fmt.Errorf("inspect container PID: %w", err)
}
containerPID, err := strconv.Atoi(strings.TrimSpace(string(pidText)))
if err != nil || containerPID <= 0 {
return Metrics{}, fmt.Errorf("invalid container PID %q", strings.TrimSpace(string(pidText)))
}

idText, err := a.run(ctx, "inspect", "--format", "{{.Id}}", name)
if err != nil {
return Metrics{}, fmt.Errorf("inspect container ID: %w", err)
}
containerID := strings.TrimSpace(string(idText))
if containerID == "" {
return Metrics{}, errors.New("nerdctl returned an empty container ID")
}

cgroupPath, err := readCgroupPath(filepath.Join(a.procRoot, strconv.Itoa(containerPID), "cgroup"))
if err != nil {
return Metrics{}, err
}
cgroupDir := filepath.Join(a.cgroupRoot, strings.TrimPrefix(cgroupPath, "/"))
cgroup, err := readCgroupMetrics(cgroupDir)
if err != nil {
return Metrics{}, err
}

shimPID, err := findShimPID(a.procRoot, containerID)
if err != nil {
return Metrics{}, err
}
shim, err := readShimMetrics(filepath.Join(a.procRoot, strconv.Itoa(shimPID), "smaps_rollup"), shimPID)
if err != nil {
fmt.Println(err)
return Metrics{}, err
}

return Metrics{
ContainerID: containerID,
ContainerPID: containerPID,
CgroupPath: cgroupDir,
Cgroup: cgroup,
Shim: shim,
}, nil
}

func runNerdctl(ctx context.Context, args ...string) ([]byte, error) {
cmd := exec.CommandContext(ctx, "nerdctl", args...)
cmd.Stdin = os.Stdin
output, err := cmd.CombinedOutput()
if err != nil {
return output, fmt.Errorf("%w: %s", err, strings.TrimSpace(string(output)))
}
return output, nil
}

func stageResult(
stage harnessruntime.Stage,
description string,
tc harnessruntime.TrialContext,
startedAt time.Time,
data any,
) harnessruntime.StageResult {
finishedAt := time.Now()
return harnessruntime.StageResult{
Stage: stage,
StartedAt: startedAt,
FinishedAt: finishedAt,
Duration: finishedAt.Sub(startedAt),
Description: fmt.Sprintf("%s: trial=%s runtime=%s handler=%s image=%s", description, tc.Trial.ID, tc.Trial.RuntimeName, tc.Trial.RuntimeHandler, tc.Trial.Image),
Data: data,
}
}
Loading
Loading