Skip to content

Commit b21b610

Browse files
authored
Accept list of docker compose files (#26)
* accept list of docker compose files * allow tools to be a single string * allow single string in collectors settings * ignore missing docker containers * rename tools log file
1 parent 0d4cafb commit b21b610

12 files changed

+124
-16
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424

2525
### Benchi ###
2626
/results
27+
/benchi

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ and tool combination). Each benchmark run folder will contain:
6969

7070
- `infra_NAME.log`: Log file containing the output of the infrastructure docker
7171
containers, split per infra service.
72-
- `tools.log`: Log file containing the output of the tools docker containers.
72+
- `tool_NAME.log`: Log file containing the output of the tool docker containers.
7373
- `COLLECTOR.csv`: Raw metrics collected using the corresponding
7474
[metrics collector](#collectors).
7575

config/config.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ServiceConfig struct {
3333
// DockerCompose is the path to the Docker Compose file for the service. If
3434
// it's a relative path, it will be resolved relative to the configuration
3535
// file.
36-
DockerCompose string `yaml:"compose"`
36+
DockerCompose StringList `yaml:"compose"`
3737
}
3838

3939
// Infrastructure represents a map of service configurations for the infrastructure.
@@ -51,7 +51,7 @@ type MetricsCollector struct {
5151
Settings map[string]any `yaml:"settings"`
5252
// Tools is a list of tools for which the collector is applicable. If empty,\
5353
// the collector will be applied to all tools.
54-
Tools []string `yaml:"tools"`
54+
Tools StringList `yaml:"tools"`
5555
}
5656

5757
// Test represents the configuration for a test.
@@ -129,5 +129,5 @@ type TestHook struct {
129129
Run string `yaml:"run"`
130130
// Tools is a list of tools for which the hook is applicable. If empty, the
131131
// hook will be applied to all tools.
132-
Tools []string `yaml:"tools"`
132+
Tools StringList `yaml:"tools"`
133133
}

config/string_list.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright © 2025 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"fmt"
19+
20+
"gopkg.in/yaml.v3"
21+
)
22+
23+
// StringList represents a YAML field that can be either a string or a slice of strings.
24+
type StringList []string
25+
26+
// UnmarshalYAML implements the yaml.Unmarshaler interface.
27+
func (s *StringList) UnmarshalYAML(value *yaml.Node) error {
28+
var single string
29+
var multi []string
30+
31+
// Try to unmarshal as a single string
32+
if err := value.Decode(&single); err == nil {
33+
*s = []string{single}
34+
return nil
35+
}
36+
37+
// Try to unmarshal as a slice of strings
38+
if err := value.Decode(&multi); err == nil {
39+
*s = multi
40+
return nil
41+
}
42+
43+
return fmt.Errorf("failed to unmarshal StringList")
44+
}

config/string_list_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright © 2025 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"reflect"
19+
"testing"
20+
21+
"gopkg.in/yaml.v3"
22+
)
23+
24+
func TestStringOrSlice_UnmarshalYAML(t *testing.T) {
25+
testCases := []struct {
26+
input string
27+
want StringList
28+
}{{
29+
input: `"single"`,
30+
want: StringList{"single"},
31+
}, {
32+
input: `["one", "two", "three"]`,
33+
want: StringList{"one", "two", "three"},
34+
}, {
35+
input: `- foo
36+
- bar
37+
- baz`,
38+
want: StringList{"foo", "bar", "baz"},
39+
}}
40+
41+
for _, tc := range testCases {
42+
t.Run(tc.input, func(t *testing.T) {
43+
var result StringList
44+
err := yaml.Unmarshal([]byte(tc.input), &result)
45+
if err != nil {
46+
t.Fatalf("unexpected error: %v", err)
47+
}
48+
if !reflect.DeepEqual(result, tc.want) {
49+
t.Errorf("got: %v, want: %v", result, tc.want)
50+
}
51+
})
52+
}
53+
}

example/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The output folder will contain logs and results:
3838
`kafka-to-kafka` test and the `conduit` tool.
3939
- `infra_kafka.log`: Log file containing the output of the kafka infrastructure
4040
docker containers.
41-
- `tools.log`: Log file containing the output of the tools docker containers
41+
- `tool_conduit.log`: Log file containing the output of the tool docker containers
4242
(conduit).
4343
- `conduit.csv`: Metrics collected using the [Conduit](../README.md#conduit) collector.
4444
- `docker.csv`: Metrics collected using the [Docker](../README.md##docker) collector.

metrics/conduit/config.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ package conduit
1717
import (
1818
"fmt"
1919

20+
"github.com/conduitio/benchi/config"
2021
"github.com/go-viper/mapstructure/v2"
2122
)
2223

2324
type Config struct {
2425
// Pipelines is a list of pipelines to monitor.
25-
Pipelines []string `yaml:"pipelines"`
26+
Pipelines config.StringList `yaml:"pipelines"`
2627
}
2728

2829
func parseConfig(settings map[string]any) (Config, error) {

metrics/docker/collector.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ func (c *Collector) Configure(settings map[string]any) error {
8787
}
8888

8989
func (c *Collector) Run(ctx context.Context) error {
90-
out := make(chan stats, 100+len(c.cfg.Containers))
90+
out := make(chan stats, 4*len(c.cfg.Containers))
9191
var wg sync.WaitGroup
9292

9393
for _, container := range c.cfg.Containers {
9494
wg.Add(1)
9595
go func() {
9696
defer wg.Done()
97-
collect(ctx, c.logger, c.dockerClient, container, out)
97+
logger := c.logger.With("container", container)
98+
collect(ctx, logger, c.dockerClient, container, out)
9899
}()
99100
}
100101

@@ -110,6 +111,7 @@ func (c *Collector) Run(ctx context.Context) error {
110111
if firstError == nil {
111112
firstError = s.Err
112113
}
114+
continue
113115
}
114116

115117
c.storeStatsEntry(s.statsEntry)

metrics/docker/config.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ package docker
1717
import (
1818
"fmt"
1919

20+
"github.com/conduitio/benchi/config"
2021
"github.com/go-viper/mapstructure/v2"
2122
)
2223

2324
type Config struct {
2425
// Containers is a list of containers to monitor.
25-
Containers []string `yaml:"containers"`
26+
Containers config.StringList `yaml:"containers"`
2627
}
2728

2829
func parseConfig(settings map[string]any) (Config, error) {

metrics/docker/stats_helpers.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
"github.com/docker/docker/api/types/container"
3030
"github.com/docker/docker/client"
31+
"github.com/docker/docker/errdefs"
3132
)
3233

3334
// statsEntry represents the statistics data collected from a container.
@@ -63,10 +64,14 @@ func collect(
6364
containerName string,
6465
out chan<- stats,
6566
) {
66-
logger.Debug("Collecting stats", "container", containerName)
67+
logger.Debug("Collecting stats")
6768

6869
response, err := cli.ContainerStats(ctx, containerName, true)
6970
if err != nil {
71+
if errdefs.IsNotFound(err) {
72+
logger.Warn("Container not found, metrics won't be collected for container", "error", err)
73+
return
74+
}
7075
out <- stats{Container: containerName, Err: err}
7176
return
7277
}
@@ -94,7 +99,7 @@ func collect(
9499
if err == io.EOF {
95100
break
96101
}
97-
out <- stats{Err: err}
102+
out <- stats{Container: containerName, Err: err}
98103
time.Sleep(100 * time.Millisecond)
99104
continue
100105
}

metrics/kafka/config.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ package kafka
1717
import (
1818
"fmt"
1919

20+
"github.com/conduitio/benchi/config"
2021
"github.com/go-viper/mapstructure/v2"
2122
)
2223

2324
type Config struct {
2425
// Topics is a list of topics to monitor.
25-
Topics []string `yaml:"topics"`
26+
Topics config.StringList `yaml:"topics"`
2627
}
2728

2829
func parseConfig(settings map[string]any) (Config, error) {

runner.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ func (r *TestRunner) runTool(ctx context.Context) (err error) {
603603
return nil
604604
}
605605

606-
logPath := filepath.Join(r.resultsDir, "tools.log")
606+
logPath := filepath.Join(r.resultsDir, fmt.Sprintf("tool_%s.log", r.tool))
607607

608608
err = r.dockerComposeUpWait(ctx, logger, paths, r.toolContainers, logPath)
609609
if err != nil {
@@ -1011,9 +1011,9 @@ func (r *TestRunner) pullHookImages(ctx context.Context, logger *slog.Logger, ho
10111011
}
10121012

10131013
func collectDockerComposeFiles(cfgs ...config.ServiceConfig) []string {
1014-
paths := make([]string, len(cfgs))
1015-
for i, cfg := range cfgs {
1016-
paths[i] = cfg.DockerCompose
1014+
paths := make([]string, 0, len(cfgs))
1015+
for _, cfg := range cfgs {
1016+
paths = append(paths, cfg.DockerCompose...)
10171017
}
10181018
return paths
10191019
}

0 commit comments

Comments
 (0)