Skip to content
Open
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
60 changes: 60 additions & 0 deletions .github/workflows/metrics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (c) 2023-2026, Nubificus LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: metrics

on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 1' # every Monday at midnight

jobs:
measure:
runs-on: [self-hosted, linux, amd64]
strategy:
fail-fast: false
matrix:
include:
- unikernel: solo5-hvt
image: harbor.nbfc.io/nubificus/urunc/redis-hvt-rumprun:latest
- unikernel: unikraft-nginx
image: harbor.nbfc.io/nubificus/urunc/nginx-unikraft-fc-initrd:latest

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup timestamping shim
run: |
sudo tee /usr/local/bin/containerd-shim-uruncts-v2 > /dev/null << 'EOT'
#!/bin/bash
URUNC_TIMESTAMPS=1 /usr/local/bin/containerd-shim-urunc-v2 $@
EOT
sudo chmod +x /usr/local/bin/containerd-shim-uruncts-v2

- name: Run metrics measurement
run: |
cd script/performance
sudo python3 measure_to_csv.py \
5 \
${{ matrix.image }} \
metrics-${{ matrix.unikernel }}.csv

- name: Upload CSV artifact
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: metrics-${{ matrix.unikernel }}
path: script/performance/metrics-${{ matrix.unikernel }}.csv
if-no-files-found: warn
27 changes: 20 additions & 7 deletions script/performance/__modules__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ def sorted(self) -> List[Timestamp]:
return temp


def parseSingleContainerTimestamps(filename: str, containerID: str) -> List[str]:
def parseSingleContainerTimestamps(
filename: str, containerID: str
) -> List[str]:
with open(filename, 'r') as f:
data = f.readlines()
return [line for line in data if containerID in line]
Expand All @@ -127,8 +129,19 @@ def emptyFile(filename: str) -> None:
open(filename, "w").close()


def spawnContainer() -> str:
command = "nerdctl run --name redis-test -d --snapshotter devmapper --runtime io.containerd.uruncts.v2 harbor.nbfc.io/nubificus/urunc/redis-hvt-rumprun:latest"
def spawnContainer(
image: str = (
"harbor.nbfc.io/nubificus/urunc/redis-hvt-rumprun:latest"
),
name: str = "redis-test",
snapshotter: str = "devmapper",
runtime: str = "io.containerd.uruncts.v2"
) -> str:
command = (
f"nerdctl run --name {name} -d"
f" --snapshotter {snapshotter}"
f" --runtime {runtime} {image}"
)
cmdParts = command.split(" ")
cmd = run(cmdParts,
stdout=PIPE,
Expand All @@ -138,15 +151,15 @@ def spawnContainer() -> str:
return containerID


def deleteContainer() -> bool:
command = "nerdctl rm --force redis-test"
def deleteContainer(name: str = "redis-test") -> bool:
command = f"nerdctl rm --force {name}"
cmdParts = command.split(" ")
cmd = run(cmdParts,
stdout=PIPE,
text=True)
response = cmd.stdout
containerID = response.splitlines()[-1]
return containerID == "redis-test"
result = response.splitlines()[-1]
return result == name


def myprint(msg: str):
Expand Down
99 changes: 99 additions & 0 deletions script/performance/measure_to_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright (c) 2023-2026, Nubificus LTD
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __modules__ import (
emptyFile, spawnContainer, deleteContainer,
parseSingleContainerTimestamps, TimestampSeries, myprint
)
from sys import argv
from time import sleep
import csv

LOGFILE = "/tmp/urunc.zlog"
DELAY = 2

# Key phase definitions: (start_tsID, end_tsID, column_name)
PHASES = [
("TS00", "TS11", "create_ns"), # full create phase
("TS12", "TS18", "start_ns"), # full start phase
("TS16", "TS17", "network_ns"), # network setup
("TS17", "TS18", "disk_ns"), # disk setup
]


def get_phase_duration(series, start_id, end_id):
ts_map = {ts.tsID: ts for ts in series.sorted}
if start_id in ts_map and end_id in ts_map:
return ts_map[end_id].timestamp - ts_map[start_id].timestamp
return "N/A"


def main():
if len(argv) != 4:
print("Error: Missing arguments!")
print("")
print("Usage:")
print(f"\t{argv[0]} <ITERATIONS> <IMAGE> <OUTPUT_CSV>")
print("")
print("Example:")
print(f"\t{argv[0]} 5 "
"harbor.nbfc.io/nubificus/urunc/"
"redis-hvt-rumprun:latest metrics.csv")
exit(1)

iterations = int(argv[1])
image = argv[2]
output_file = argv[3]
name = "urunc-metrics-test"

myprint(f"Collecting metrics for {iterations} iterations")
myprint(f"Image: {image}")
sleep(2)

emptyFile(LOGFILE)
container_ids = []

for i in range(iterations):
myprint(f"Running iteration {i+1} of {iterations}")
container_id = spawnContainer(image=image, name=name)
container_ids.append(container_id)
sleep(DELAY)
success = deleteContainer(name=name)
if not success:
print("Error removing container.")
exit(1)

myprint("Writing CSV...")

with open(output_file, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["containerID", "create_ns", "start_ns",
"network_ns", "disk_ns"])
for container_id in container_ids:
data = parseSingleContainerTimestamps(
filename=LOGFILE, containerID=container_id)
if not data:
continue
series = TimestampSeries(data=data)
row = [container_id]
for start_id, end_id, _ in PHASES:
row.append(get_phase_duration(series, start_id, end_id))
writer.writerow(row)

myprint(f"Saved metrics to {output_file}")
emptyFile(LOGFILE)


if __name__ == "__main__":
main()