Skip to content

Commit 416c0aa

Browse files
committed
dynamic: support nested metric fields
1 parent b9b9559 commit 416c0aa

File tree

2 files changed

+97
-11
lines changed

2 files changed

+97
-11
lines changed

pkg/dynamic/metric.go

+45-11
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,26 @@ import (
55
"reflect"
66
"regexp"
77
"strings"
8+
"sync"
89

910
"github.com/prometheus/client_golang/prometheus"
1011

1112
"github.com/c9s/bbgo/pkg/fixedpoint"
12-
"github.com/c9s/bbgo/pkg/types"
1313
)
1414

1515
var matchFirstCapRE = regexp.MustCompile("(.)([A-Z][a-z]+)")
1616
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
1717

1818
var dynamicStrategyConfigMetrics = map[string]*prometheus.GaugeVec{}
19+
var dynamicStrategyConfigMetricsMutex sync.Mutex
1920

20-
func getOrCreateMetric(id, fieldName string) (*prometheus.GaugeVec, error) {
21+
func getOrCreateMetric(id, fieldName string) (*prometheus.GaugeVec, string, error) {
2122
metricName := id + "_config_" + fieldName
23+
24+
dynamicStrategyConfigMetricsMutex.Lock()
2225
metric, ok := dynamicStrategyConfigMetrics[metricName]
26+
defer dynamicStrategyConfigMetricsMutex.Unlock()
27+
2328
if !ok {
2429
metric = prometheus.NewGaugeVec(
2530
prometheus.GaugeOpts{
@@ -30,11 +35,13 @@ func getOrCreateMetric(id, fieldName string) (*prometheus.GaugeVec, error) {
3035
)
3136

3237
if err := prometheus.Register(metric); err != nil {
33-
return nil, fmt.Errorf("unable to register metrics on field %+v, error: %+v", fieldName, err)
38+
return nil, "", fmt.Errorf("unable to register metrics on field %+v, error: %+v", fieldName, err)
3439
}
40+
41+
dynamicStrategyConfigMetrics[metricName] = metric
3542
}
3643

37-
return metric, nil
44+
return metric, metricName, nil
3845
}
3946

4047
func toSnakeCase(input string) string {
@@ -74,15 +81,31 @@ func castToFloat64(valInf any) (float64, bool) {
7481
return val, true
7582
}
7683

77-
func InitializeConfigMetrics(id, instanceId string, s types.StrategyID) error {
78-
tv := reflect.TypeOf(s).Elem()
79-
sv := reflect.Indirect(reflect.ValueOf(s))
84+
func InitializeConfigMetrics(id, instanceId string, st any) error {
85+
_, err := initializeConfigMetricsWithFieldPrefix(id, instanceId, "", st)
86+
return err
87+
}
88+
89+
func initializeConfigMetricsWithFieldPrefix(id, instanceId, fieldPrefix string, st any) ([]string, error) {
90+
var metricNames []string
91+
tv := reflect.TypeOf(st).Elem()
92+
93+
vv := reflect.ValueOf(st)
94+
if vv.IsNil() {
95+
return nil, nil
96+
}
97+
98+
sv := reflect.Indirect(vv)
8099

81100
symbolField := sv.FieldByName("Symbol")
82101
hasSymbolField := symbolField.IsValid()
83102

84103
for i := 0; i < tv.NumField(); i++ {
85104
field := tv.Field(i)
105+
if !field.IsExported() {
106+
continue
107+
}
108+
86109
jsonTag := field.Tag.Get("json")
87110
if jsonTag == "" {
88111
continue
@@ -93,7 +116,16 @@ func InitializeConfigMetrics(id, instanceId string, s types.StrategyID) error {
93116
continue
94117
}
95118

96-
fieldName := toSnakeCase(tagAttrs[0])
119+
fieldName := fieldPrefix + toSnakeCase(tagAttrs[0])
120+
if field.Type.Kind() == reflect.Pointer && field.Type.Elem().Kind() == reflect.Struct {
121+
subMetricNames, err := initializeConfigMetricsWithFieldPrefix(id, instanceId, fieldName+"_", sv.Field(i).Interface())
122+
if err != nil {
123+
return nil, err
124+
}
125+
126+
metricNames = append(metricNames, subMetricNames...)
127+
continue
128+
}
97129

98130
val := 0.0
99131
valInf := sv.Field(i).Interface()
@@ -107,17 +139,19 @@ func InitializeConfigMetrics(id, instanceId string, s types.StrategyID) error {
107139
symbol = symbolField.String()
108140
}
109141

110-
metric, err := getOrCreateMetric(id, fieldName)
142+
metric, metricName, err := getOrCreateMetric(id, fieldName)
111143
if err != nil {
112-
return err
144+
return nil, err
113145
}
114146

115147
metric.With(prometheus.Labels{
116148
"strategy_type": id,
117149
"strategy_id": instanceId,
118150
"symbol": symbol,
119151
}).Set(val)
152+
153+
metricNames = append(metricNames, metricName)
120154
}
121155

122-
return nil
156+
return metricNames, nil
123157
}

pkg/dynamic/metric_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dynamic
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/c9s/bbgo/pkg/fixedpoint"
9+
. "github.com/c9s/bbgo/pkg/testing/testhelper"
10+
)
11+
12+
func TestInitializeConfigMetrics(t *testing.T) {
13+
type Bar struct {
14+
Enabled bool `json:"enabled"`
15+
}
16+
type Foo struct {
17+
MinMarginLevel fixedpoint.Value `json:"minMarginLevel"`
18+
Bar *Bar `json:"bar"`
19+
20+
// this field should be ignored
21+
ignoredField string
22+
23+
ignoredFieldInt int
24+
}
25+
26+
t.Run("general", func(t *testing.T) {
27+
metricNames, err := initializeConfigMetricsWithFieldPrefix("test", "test-01", "", &Foo{
28+
MinMarginLevel: Number(1.4),
29+
Bar: &Bar{
30+
Enabled: true,
31+
},
32+
})
33+
34+
if assert.NoError(t, err) {
35+
assert.Len(t, metricNames, 2)
36+
assert.Equal(t, "test_config_min_margin_level", metricNames[0])
37+
assert.Equal(t, "test_config_bar_enabled", metricNames[1], "nested struct field as a metric")
38+
}
39+
})
40+
41+
t.Run("nil struct field", func(t *testing.T) {
42+
metricNames, err := initializeConfigMetricsWithFieldPrefix("test", "test-01", "", &Foo{
43+
MinMarginLevel: Number(1.4),
44+
})
45+
46+
if assert.NoError(t, err) {
47+
assert.Len(t, metricNames, 1)
48+
assert.Equal(t, "test_config_min_margin_level", metricNames[0])
49+
}
50+
})
51+
52+
}

0 commit comments

Comments
 (0)