Skip to content

Commit 3a59e87

Browse files
committed
fix: chart subcommand not rendering correctly
1 parent b125e2e commit 3a59e87

17 files changed

+225
-63
lines changed

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ It provides the following information:
8787
- `(20/s)` (attempted) rate,
8888
- `avg: 72ns, min: 125ns, max: 27.590042ms` average, min and max iteration times.
8989

90+
### Chart Command
91+
92+
The `chart` command allows you to plot a chart of the test scenarios that would be triggered over time with the provided run function. It supports various subcommands corresponding to different trigger modes.
93+
94+
Usage:
95+
```
96+
f1 chart <subcommand>
97+
```
98+
99+
Flags:
100+
- `--chart-start`: Optional start time for the chart (default: current time in RFC3339 format)
101+
- `--chart-duration`: Duration for the chart (default: 10 minutes)
102+
- `--filename`: Filename for optional detailed chart, e.g. `<trigger_mode>.png`
103+
104+
The command generates an ASCII graph in the console and optionally creates a detailed PNG chart if a filename is provided.
105+
106+
Example:
107+
```
108+
f1 chart constant --chart-duration=5m --filename=constant_chart.png
109+
```
110+
111+
This will generate an ASCII graph in the console and save a detailed PNG chart to `constant_chart.png`.
112+
90113
### Environment variables
91114

92115
| Name | Format | Default | Description |

internal/chart/chart_cmd.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func chartCmdExecute(
7070
return fmt.Errorf("creating builder: %w", err)
7171
}
7272

73-
if trig.DryRun == nil {
73+
if trig.Rate == nil {
7474
return fmt.Errorf("%s does not support charting predicted load", cmd.Name())
7575
}
7676

@@ -82,7 +82,7 @@ func chartCmdExecute(
8282
rates := []float64{0.0}
8383
times := []time.Time{current}
8484
for ; current.Add(sampleInterval).Before(end); current = current.Add(sampleInterval) {
85-
rate := trig.DryRun(current)
85+
rate := trig.Rate(current)
8686
rates = append(rates, float64(rate))
8787
times = append(times, current)
8888
}
@@ -95,26 +95,31 @@ func chartCmdExecute(
9595
return nil
9696
}
9797
graph := chart.Chart{
98-
Title: trig.Description,
99-
TitleStyle: chart.StyleTextDefaults(),
100-
Width: 1920,
101-
Height: 1024,
98+
Title: trig.Description,
99+
TitleStyle: chart.Style{
100+
TextWrap: chart.TextWrapWord,
101+
},
102+
Width: 1920,
103+
Height: 1024,
104+
Background: chart.Style{
105+
Padding: chart.Box{
106+
Top: 50,
107+
},
108+
},
102109
YAxis: chart.YAxis{
103-
Name: "Triggered Test Iterations",
104-
NameStyle: chart.StyleTextDefaults(),
105-
Style: chart.StyleTextDefaults(),
106-
AxisType: chart.YAxisSecondary,
110+
Name: "Triggered Test Iterations",
111+
AxisType: chart.YAxisSecondary,
112+
ValueFormatter: chart.IntValueFormatter,
107113
},
108114
XAxis: chart.XAxis{
109115
Name: "Time",
110-
NameStyle: chart.StyleTextDefaults(),
111116
ValueFormatter: chart.TimeMinuteValueFormatter,
112-
Style: chart.StyleTextDefaults(),
113117
},
114118
Series: []chart.Series{
115119
chart.TimeSeries{
116120
Style: chart.Style{
117-
StrokeColor: chart.GetDefaultColor(0).WithAlpha(64),
121+
StrokeColor: chart.GetDefaultColor(0),
122+
StrokeWidth: 2.0,
118123
},
119124
Name: "testing",
120125
XValues: times,
Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,41 @@
11
package chart_test
22

33
import (
4-
"strconv"
5-
"testing"
6-
"time"
7-
84
"github.com/stretchr/testify/assert"
5+
"os"
6+
"strings"
7+
"testing"
98

109
"github.com/form3tech-oss/f1/v2/internal/chart"
10+
"github.com/form3tech-oss/f1/v2/internal/log"
1111
"github.com/form3tech-oss/f1/v2/internal/trigger"
1212
"github.com/form3tech-oss/f1/v2/internal/ui"
1313
)
1414

1515
type ChartTestStage struct {
16-
t *testing.T
17-
assert *assert.Assertions
18-
err error
19-
args []string
16+
t *testing.T
17+
assert *assert.Assertions
18+
err error
19+
args []string
20+
output *ui.Output
21+
results *lineStringBuilder
22+
expectedOutput string
2023
}
2124

2225
func NewChartTestStage(t *testing.T) (*ChartTestStage, *ChartTestStage, *ChartTestStage) {
2326
t.Helper()
2427

28+
sb := newLineStringBuilder()
29+
p := ui.Printer{
30+
Writer: sb,
31+
ErrWriter: sb,
32+
}
33+
2534
stage := &ChartTestStage{
26-
t: t,
27-
assert: assert.New(t),
35+
t: t,
36+
assert: assert.New(t),
37+
output: ui.NewOutput(log.NewDiscardLogger(), &p, true, true),
38+
results: sb,
2839
}
2940
return stage, stage, stage
3041
}
@@ -34,8 +45,7 @@ func (s *ChartTestStage) and() *ChartTestStage {
3445
}
3546

3647
func (s *ChartTestStage) i_execute_the_chart_command() *ChartTestStage {
37-
outputer := ui.NewDiscardOutput()
38-
cmd := chart.Cmd(trigger.GetBuilders(outputer), outputer)
48+
cmd := chart.Cmd(trigger.GetBuilders(s.output), s.output)
3949
cmd.SetArgs(s.args)
4050
s.err = cmd.Execute()
4151
return s
@@ -46,8 +56,16 @@ func (s *ChartTestStage) the_command_is_successful() *ChartTestStage {
4656
return s
4757
}
4858

59+
func (s *ChartTestStage) the_output_is_correct() *ChartTestStage {
60+
s.assert.Equal(s.expectedOutput, s.results.String())
61+
return s
62+
}
63+
4964
func (s *ChartTestStage) the_load_style_is_constant() *ChartTestStage {
5065
s.args = append(s.args, "constant", "--rate", "10/s", "--distribution", "none")
66+
f, err := os.ReadFile("../testdata/expected-constant-chart-output.txt")
67+
s.assert.NoError(err)
68+
s.expectedOutput = string(f)
5169
return s
5270
}
5371

@@ -56,27 +74,55 @@ func (s *ChartTestStage) jitter_is_applied() *ChartTestStage {
5674
return s
5775
}
5876

59-
func (s *ChartTestStage) the_load_style_is_staged(stages string) *ChartTestStage {
60-
s.args = append(s.args, "staged", "--stages", stages, "--distribution", "none")
77+
func (s *ChartTestStage) the_load_style_is_staged() *ChartTestStage {
78+
s.args = append(s.args, "staged", "--stages", "5m:100,2m:0,10s:100", "--distribution", "none")
79+
f, err := os.ReadFile("../testdata/expected-staged-chart-output.txt")
80+
s.assert.NoError(err)
81+
s.expectedOutput = string(f)
6182
return s
6283
}
6384

6485
func (s *ChartTestStage) the_load_style_is_ramp() *ChartTestStage {
6586
s.args = append(s.args, "ramp", "--start-rate", "0/s", "--end-rate", "10/s", "--ramp-duration", "10s", "--chart-duration", "10s", "--distribution", "none")
87+
f, err := os.ReadFile("../testdata/expected-ramp-chart-output.txt")
88+
s.assert.NoError(err)
89+
s.expectedOutput = string(f)
6690
return s
6791
}
6892

69-
func (s *ChartTestStage) the_load_style_is_gaussian_with_a_volume_of(volume int) *ChartTestStage {
70-
s.args = append(s.args, "gaussian", "--peak", "5m", "--repeat", "10m", "--volume", strconv.Itoa(volume), "--standard-deviation", "1m", "--distribution", "none")
93+
func (s *ChartTestStage) the_load_style_is_gaussian_with_a_volume_of() *ChartTestStage {
94+
s.args = append(s.args, "gaussian", "--peak", "5m", "--repeat", "10m", "--volume", "100000", "--standard-deviation", "1m", "--distribution", "none")
95+
f, err := os.ReadFile("../testdata/expected-gaussian-chart-output.txt")
96+
s.assert.NoError(err)
97+
s.expectedOutput = string(f)
7198
return s
7299
}
73100

74101
func (s *ChartTestStage) the_chart_starts_at_a_fixed_time() *ChartTestStage {
75-
s.args = append(s.args, "--chart-start", time.Now().Truncate(10*time.Minute).Format(time.RFC3339))
102+
s.args = append(s.args, "--chart-start", "2024-09-19T17:00:00Z")
76103
return s
77104
}
78105

79-
func (s *ChartTestStage) the_load_style_is_defined_in_the_config_file(filename string) *ChartTestStage {
80-
s.args = append(s.args, "file", filename, "--chart-duration", "5s")
106+
func (s *ChartTestStage) the_load_style_is_defined_in_the_config_file() *ChartTestStage {
107+
s.args = append(s.args, "file", "../testdata/config-file.yaml", "--chart-duration", "5s")
108+
f, err := os.ReadFile("../testdata/expected-file-chart-output.txt")
109+
s.assert.NoError(err)
110+
s.expectedOutput = string(f)
81111
return s
82112
}
113+
114+
type lineStringBuilder struct {
115+
sb *strings.Builder
116+
}
117+
118+
func newLineStringBuilder() *lineStringBuilder {
119+
return &lineStringBuilder{sb: &strings.Builder{}}
120+
}
121+
122+
func (l *lineStringBuilder) Write(p []byte) (n int, err error) {
123+
return l.sb.Write(p)
124+
}
125+
126+
func (l *lineStringBuilder) String() string {
127+
return strings.Replace(l.sb.String(), "\\n", "\n", -1)
128+
}

internal/chart/chart_cmd_test.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ func TestChartConstantNoJitter(t *testing.T) {
3232
i_execute_the_chart_command()
3333

3434
then.
35-
the_command_is_successful()
35+
the_command_is_successful().and().
36+
the_output_is_correct()
3637
}
3738

3839
func TestChartStaged(t *testing.T) {
@@ -41,13 +42,14 @@ func TestChartStaged(t *testing.T) {
4142
given, when, then := NewChartTestStage(t)
4243

4344
given.
44-
the_load_style_is_staged("5m:100,2m:0,10s:100")
45+
the_load_style_is_staged()
4546

4647
when.
4748
i_execute_the_chart_command()
4849

4950
then.
50-
the_command_is_successful()
51+
the_command_is_successful().and().
52+
the_output_is_correct()
5153
}
5254

5355
func TestChartGaussian(t *testing.T) {
@@ -56,14 +58,15 @@ func TestChartGaussian(t *testing.T) {
5658
given, when, then := NewChartTestStage(t)
5759

5860
given.
59-
the_load_style_is_gaussian_with_a_volume_of(100000).and().
61+
the_load_style_is_gaussian_with_a_volume_of().and().
6062
the_chart_starts_at_a_fixed_time()
6163

6264
when.
6365
i_execute_the_chart_command()
6466

6567
then.
66-
the_command_is_successful()
68+
the_command_is_successful().and().
69+
the_output_is_correct()
6770
}
6871

6972
func TestChartGaussianWithJitter(t *testing.T) {
@@ -72,7 +75,7 @@ func TestChartGaussianWithJitter(t *testing.T) {
7275
given, when, then := NewChartTestStage(t)
7376

7477
given.
75-
the_load_style_is_gaussian_with_a_volume_of(100000).and().
78+
the_load_style_is_gaussian_with_a_volume_of().and().
7679
jitter_is_applied().and().
7780
the_chart_starts_at_a_fixed_time()
7881

@@ -95,7 +98,8 @@ func TestChartRamp(t *testing.T) {
9598
i_execute_the_chart_command()
9699

97100
then.
98-
the_command_is_successful()
101+
the_command_is_successful().and().
102+
the_output_is_correct()
99103
}
100104

101105
func TestChartFileConfig(t *testing.T) {
@@ -104,11 +108,12 @@ func TestChartFileConfig(t *testing.T) {
104108
given, when, then := NewChartTestStage(t)
105109

106110
given.
107-
the_load_style_is_defined_in_the_config_file("../testdata/config-file.yaml")
111+
the_load_style_is_defined_in_the_config_file()
108112

109113
when.
110114
i_execute_the_chart_command()
111115

112116
then.
113-
the_command_is_successful()
117+
the_command_is_successful().and().
118+
the_output_is_correct()
114119
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
10.00 ┤╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2+
9.33 ┤│
3+
8.67 ┤│
4+
8.00 ┤│
5+
7.33 ┤│
6+
6.67 ┤│
7+
6.00 ┤│
8+
5.33 ┤│
9+
4.67 ┤│
10+
4.00 ┤│
11+
3.33 ┤│
12+
2.67 ┤│
13+
2.00 ┤│
14+
1.33 ┤│
15+
0.67 ┤│
16+
0.00 ┼╯
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
10.00 ┤╭────────────────╮
2+
9.33 ┤│ │ ╭╮
3+
8.67 ┤│ │ ││
4+
8.00 ┤│ │ │╰╮
5+
7.33 ┤│ │ ╭╯ │
6+
6.67 ┤│ │ │ │
7+
6.00 ┤│ │ ╭╯ │
8+
5.33 ┤│ ╰────────────╮ │ ╰╮
9+
4.67 ┤│ │ │ │
10+
4.00 ┤│ │ ╭╯ │
11+
3.33 ┤│ │ ╭╯ │
12+
2.67 ┤│ │ │ │
13+
2.00 ┤│ │ │ ╰╮
14+
1.33 ┤│ │╭╯ │ ╭───╮
15+
0.67 ┤│ ││ │ │ │
16+
0.00 ┼╯ ╰╯ ╰───────────╯ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
665 ┤ ╭───────╮
2+
621 ┤ ╭──╯ ╰──╮
3+
576 ┤ ╭──╯ ╰──╮
4+
532 ┤ ╭─╯ ╰─╮
5+
488 ┤ ╭╯ ╰╮
6+
443 ┤ ╭─╯ ╰─╮
7+
399 ┤ ╭─╯ ╰─╮
8+
355 ┤ ╭─╯ ╰─╮
9+
310 ┤ ╭─╯ ╰─╮
10+
266 ┤ ╭─╯ ╰─╮
11+
222 ┤ ╭─╯ ╰─╮
12+
177 ┤ ╭─╯ ╰─╮
13+
133 ┤ ╭──╯ ╰──╮
14+
89 ┤ ╭───╯ ╰───╮
15+
44 ┤ ╭──────╯ ╰──────╮
16+
0 ┼───────────────────────────────────────╯ ╰─────────────────────────────────────
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
9.00 ┤ ╭──────────────
2+
8.40 ┤ │
3+
7.80 ┤ ╭───────────────╯
4+
7.20 ┤ ╭───────────────╯
5+
6.60 ┤ │
6+
6.00 ┤ ╭───────────────╯
7+
5.40 ┤ │
8+
4.80 ┤ ╭───────────────╯
9+
4.20 ┤ ╭───────────────╯
10+
3.60 ┤ │
11+
3.00 ┤ ╭───────────────╯
12+
2.40 ┤ │
13+
1.80 ┤ ╭───────────────╯
14+
1.20 ┤ ╭───────────────╯
15+
0.60 ┤ │
16+
0.00 ┼────────────────╯
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
99.00 ┤ ╭────╮
2+
92.40 ┤ ╭────╯ ╰╮
3+
85.80 ┤ ╭─────╯ ╰──╮
4+
79.20 ┤ ╭────╯ ╰─╮
5+
72.60 ┤ ╭────╯ ╰─╮
6+
66.00 ┤ ╭────╯ ╰─╮ ╭╮
7+
59.40 ┤ ╭────╯ ╰─╮ ││
8+
52.80 ┤ ╭─────╯ ╰─╮ ││
9+
46.20 ┤ ╭────╯ ╰─╮ ││
10+
39.60 ┤ ╭────╯ ╰─╮ ││
11+
33.00 ┤ ╭─────╯ ╰──╮ ││
12+
26.40 ┤ ╭───╯ ╰╮ ╭╯│
13+
19.80 ┤ ╭─────╯ ╰──╮ │ │
14+
13.20 ┤ ╭─────╯ ╰─╮ │ │
15+
6.60 ┤ ╭───╯ ╰─╮│ │
16+
0.00 ┼────╯ ╰╯ ╰────────────────────────────────────────────

0 commit comments

Comments
 (0)