Skip to content

Commit f63e4c9

Browse files
authored
feat: add run metadata labels to config.json (#69)
Add support for arbitrary key-value labels as metadata on benchmark runs. Labels can be set via YAML config (global.metadata.labels) and/or CLI flags (--metadata.label=key=value). CLI labels merge with config labels, with CLI taking precedence on conflicts.
1 parent 8658f37 commit f63e4c9

7 files changed

Lines changed: 143 additions & 14 deletions

File tree

cmd/benchmarkoor/run.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"os/signal"
88
"path/filepath"
9+
"strings"
910
"syscall"
1011

1112
"github.com/ethpandaops/benchmarkoor/pkg/client"
@@ -22,6 +23,7 @@ import (
2223
var (
2324
limitInstanceIDs []string
2425
limitInstanceClients []string
26+
metadataLabels []string
2527
)
2628

2729
var runCmd = &cobra.Command{
@@ -37,6 +39,8 @@ func init() {
3739
"Limit to instances with these IDs (comma-separated or repeated flag)")
3840
runCmd.Flags().StringSliceVar(&limitInstanceClients, "limit-instance-client", nil,
3941
"Limit to instances with these client types (comma-separated or repeated flag)")
42+
runCmd.Flags().StringSliceVar(&metadataLabels, "metadata.label", nil,
43+
"Add metadata label as key=value (can be repeated)")
4044
}
4145

4246
func runBenchmark(cmd *cobra.Command, args []string) error {
@@ -50,6 +54,20 @@ func runBenchmark(cmd *cobra.Command, args []string) error {
5054
return fmt.Errorf("loading config: %w", err)
5155
}
5256

57+
// Merge CLI metadata labels into config (CLI wins on conflict).
58+
for _, entry := range metadataLabels {
59+
k, v, ok := strings.Cut(entry, "=")
60+
if !ok || k == "" {
61+
return fmt.Errorf("invalid metadata label %q: must be key=value", entry)
62+
}
63+
64+
if cfg.Global.Metadata.Labels == nil {
65+
cfg.Global.Metadata.Labels = make(map[string]string, len(metadataLabels))
66+
}
67+
68+
cfg.Global.Metadata.Labels[k] = v
69+
}
70+
5371
// Validate configuration.
5472
if err := cfg.Validate(); err != nil {
5573
return fmt.Errorf("validating config: %w", err)

pkg/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ type Config struct {
5858
Client ClientConfig `yaml:"client" mapstructure:"client"`
5959
}
6060

61+
// MetadataConfig contains arbitrary metadata labels for a benchmark run.
62+
type MetadataConfig struct {
63+
Labels map[string]string `yaml:"labels,omitempty" mapstructure:"labels" json:"labels,omitempty"`
64+
}
65+
6166
// GlobalConfig contains global application settings.
6267
type GlobalConfig struct {
6368
LogLevel string `yaml:"log_level" mapstructure:"log_level"`
@@ -67,6 +72,7 @@ type GlobalConfig struct {
6772
Directories DirectoriesConfig `yaml:"directories,omitempty" mapstructure:"directories"`
6873
DropCachesPath string `yaml:"drop_caches_path,omitempty" mapstructure:"drop_caches_path"`
6974
GitHubToken string `yaml:"github_token,omitempty" mapstructure:"github_token"`
75+
Metadata MetadataConfig `yaml:"metadata,omitempty" mapstructure:"metadata"`
7076
}
7177

7278
// DirectoriesConfig contains directory path configurations.

pkg/config/config_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,78 @@ client:
11001100
})
11011101
}
11021102

1103+
func TestLoad_MetadataLabels(t *testing.T) {
1104+
t.Run("parses labels from yaml", func(t *testing.T) {
1105+
configContent := `
1106+
global:
1107+
metadata:
1108+
labels:
1109+
env: production
1110+
team: platform
1111+
client:
1112+
config:
1113+
genesis:
1114+
geth: http://example.com/genesis.json
1115+
instances:
1116+
- id: test-instance
1117+
client: geth
1118+
`
1119+
tmpDir := t.TempDir()
1120+
configPath := filepath.Join(tmpDir, "config.yaml")
1121+
require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o644))
1122+
1123+
cfg, err := Load(configPath)
1124+
require.NoError(t, err)
1125+
1126+
require.Len(t, cfg.Global.Metadata.Labels, 2)
1127+
assert.Equal(t, "production", cfg.Global.Metadata.Labels["env"])
1128+
assert.Equal(t, "platform", cfg.Global.Metadata.Labels["team"])
1129+
})
1130+
1131+
t.Run("empty metadata produces no errors", func(t *testing.T) {
1132+
configContent := `
1133+
client:
1134+
config:
1135+
genesis:
1136+
geth: http://example.com/genesis.json
1137+
instances:
1138+
- id: test-instance
1139+
client: geth
1140+
`
1141+
tmpDir := t.TempDir()
1142+
configPath := filepath.Join(tmpDir, "config.yaml")
1143+
require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o644))
1144+
1145+
cfg, err := Load(configPath)
1146+
require.NoError(t, err)
1147+
1148+
assert.Nil(t, cfg.Global.Metadata.Labels)
1149+
})
1150+
1151+
t.Run("empty labels map produces no errors", func(t *testing.T) {
1152+
configContent := `
1153+
global:
1154+
metadata:
1155+
labels: {}
1156+
client:
1157+
config:
1158+
genesis:
1159+
geth: http://example.com/genesis.json
1160+
instances:
1161+
- id: test-instance
1162+
client: geth
1163+
`
1164+
tmpDir := t.TempDir()
1165+
configPath := filepath.Join(tmpDir, "config.yaml")
1166+
require.NoError(t, os.WriteFile(configPath, []byte(configContent), 0o644))
1167+
1168+
cfg, err := Load(configPath)
1169+
require.NoError(t, err)
1170+
1171+
assert.Empty(t, cfg.Global.Metadata.Labels)
1172+
})
1173+
}
1174+
11031175
// createTestTarball creates a minimal .tar.gz file at the given path for testing.
11041176
func createTestTarball(t *testing.T, path string) {
11051177
t.Helper()

pkg/runner/runner.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,19 @@ type StartBlock struct {
8989

9090
// RunConfig contains configuration for a single test run.
9191
type RunConfig struct {
92-
Timestamp int64 `json:"timestamp"`
93-
TimestampEnd int64 `json:"timestamp_end,omitempty"`
94-
SuiteHash string `json:"suite_hash,omitempty"`
95-
SystemResourceCollectionMethod string `json:"system_resource_collection_method,omitempty"`
96-
System *SystemInfo `json:"system"`
97-
Instance *ResolvedInstance `json:"instance"`
98-
StartBlock *StartBlock `json:"start_block,omitempty"`
99-
TestCounts *TestCounts `json:"test_counts,omitempty"`
100-
Status string `json:"status,omitempty"`
101-
TerminationReason string `json:"termination_reason,omitempty"`
102-
ContainerExitCode *int64 `json:"container_exit_code,omitempty"`
103-
ContainerOOMKilled *bool `json:"container_oom_killed,omitempty"`
92+
Timestamp int64 `json:"timestamp"`
93+
TimestampEnd int64 `json:"timestamp_end,omitempty"`
94+
SuiteHash string `json:"suite_hash,omitempty"`
95+
SystemResourceCollectionMethod string `json:"system_resource_collection_method,omitempty"`
96+
System *SystemInfo `json:"system"`
97+
Instance *ResolvedInstance `json:"instance"`
98+
Metadata *config.MetadataConfig `json:"metadata,omitempty"`
99+
StartBlock *StartBlock `json:"start_block,omitempty"`
100+
TestCounts *TestCounts `json:"test_counts,omitempty"`
101+
Status string `json:"status,omitempty"`
102+
TerminationReason string `json:"termination_reason,omitempty"`
103+
ContainerExitCode *int64 `json:"container_exit_code,omitempty"`
104+
ContainerOOMKilled *bool `json:"container_oom_killed,omitempty"`
104105
}
105106

106107
// Run status constants.
@@ -910,6 +911,11 @@ func (r *runner) runContainerLifecycle(
910911
},
911912
}
912913

914+
// Attach metadata labels if configured.
915+
if r.cfg.FullConfig != nil && len(r.cfg.FullConfig.Global.Metadata.Labels) > 0 {
916+
runConfig.Metadata = &r.cfg.FullConfig.Global.Metadata
917+
}
918+
913919
if len(params.GenesisGroups) > 0 {
914920
runConfig.Instance.GenesisGroups = params.GenesisGroups
915921
} else {

ui/src/api/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ export interface RunConfig {
140140
termination_reason?: string
141141
container_exit_code?: number
142142
container_oom_killed?: boolean
143+
metadata?: {
144+
labels?: Record<string, string>
145+
}
143146
}
144147

145148
export interface SystemInfo {

ui/src/components/run-detail/RunConfiguration.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ interface RunConfigurationProps {
88
instance: InstanceConfig
99
system: SystemInfo
1010
startBlock?: StartBlock
11+
metadata?: {
12+
labels?: Record<string, string>
13+
}
1114
}
1215

1316
function CopyButton({ text }: { text: string }) {
@@ -42,7 +45,7 @@ function InfoItem({ label, value }: { label: string; value: string | number }) {
4245
)
4346
}
4447

45-
export function RunConfiguration({ instance, system, startBlock }: RunConfigurationProps) {
48+
export function RunConfiguration({ instance, system, startBlock, metadata }: RunConfigurationProps) {
4649
const [expanded, setExpanded] = useState(false)
4750

4851
const shortImage = instance.image.includes('/') ? instance.image.split('/').pop()! : instance.image
@@ -370,6 +373,7 @@ export function RunConfiguration({ instance, system, startBlock }: RunConfigurat
370373
</dd>
371374
</div>
372375
)}
376+
373377
</div>
374378
</div>
375379

@@ -488,6 +492,26 @@ export function RunConfiguration({ instance, system, startBlock }: RunConfigurat
488492
)}
489493
</div>
490494
)}
495+
496+
{/* Metadata Labels */}
497+
{metadata?.labels && Object.keys(metadata.labels).length > 0 && (
498+
<div className="mt-6 border-t border-gray-200 pt-6 dark:border-gray-700">
499+
<h4 className="mb-3 text-sm/6 font-medium text-gray-900 dark:text-gray-100">Metadata Labels</h4>
500+
<div className="overflow-x-auto rounded-xs bg-gray-100 p-2 dark:bg-gray-900">
501+
<div className="flex flex-col gap-1 font-mono text-xs/5 text-gray-900 dark:text-gray-100">
502+
{Object.entries(metadata.labels).map(([key, value]) => (
503+
<div key={key} className="flex items-start gap-2">
504+
<span className="break-all">
505+
<span className="text-gray-500 dark:text-gray-400">{key}=</span>
506+
{value}
507+
</span>
508+
<CopyButton text={`${key}=${value}`} />
509+
</div>
510+
))}
511+
</div>
512+
</div>
513+
</div>
514+
)}
491515
</div>
492516
</div>
493517
)}

ui/src/pages/RunDetailPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ export function RunDetailPage() {
519519
)}
520520
</div>
521521

522-
<RunConfiguration instance={config.instance} system={config.system} startBlock={config.start_block} />
522+
<RunConfiguration instance={config.instance} system={config.system} startBlock={config.start_block} metadata={config.metadata} />
523523

524524
<FilesPanel
525525
runId={runId}

0 commit comments

Comments
 (0)