Skip to content

Commit

Permalink
Merge pull request #8 from tosuke/default-value
Browse files Browse the repository at this point in the history
feat: default value support
  • Loading branch information
Arthur1 authored Mar 19, 2024
2 parents 663eeca + 8d1fe3f commit 9ebfcab
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 52 deletions.
63 changes: 42 additions & 21 deletions query/valuekey/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ var commandExecRE = regexp.MustCompile(`\A\$\((.*)\)\z`)

// Query represents ...
type Query struct {
KeyPrefix string `yaml:"keyPrefix"`
ValueKey map[string]string `yaml:"valueKey"`
SQL string `yaml:"sql"`
Params []interface{} `yaml:"params"`
Service string `yaml:"service,omitempty"`
Time string `yaml:"time"`
KeyPrefix string `yaml:"keyPrefix"`
ValueKey map[string]string `yaml:"valueKey"`
DefaultValue map[string]float64 `yaml:"defaultValue,omitempty"`
SQL string `yaml:"sql"`
Params []interface{} `yaml:"params"`
Service string `yaml:"service,omitempty"`
Time string `yaml:"time"`
}

// Execute is ...
Expand All @@ -43,10 +44,22 @@ func (q *Query) ExecuteWithContext(ctx context.Context, db *sql.DB, logger logr.
return nil, err
}

metrics := make([]*mackerel.MetricValue, 0, len(q.ValueKey))
metricCap := max(len(q.ValueKey), len(q.DefaultValue))
metrics := make([]*mackerel.MetricValue, 0, metricCap)
metricNames := make(map[string]struct{}, metricCap)
now := nowFunc().Unix()

for _, r := range rows {
vs := make(map[string]any, metricCap)

for k, v := range q.DefaultValue {
vk, err := replaceValueKey(k, r)
if err != nil {
logger.Info(err.Error(), "query", q)
continue
}
vs[vk] = v
}
for k, v := range q.ValueKey {
var err error

Expand All @@ -63,29 +76,37 @@ func (q *Query) ExecuteWithContext(ctx context.Context, db *sql.DB, logger logr.
if value == nil {
continue
}
vs[vk] = value
}

t := now
if q.Time != "" {
value, ok := r[q.Time]
if !ok {
return nil, fmt.Errorf("%q not exists in columns", q.Time)
}
t, ok = value.(int64)
if !ok {
return nil, fmt.Errorf("failed to convert %q to int64", q.Time)
}
}
for k, v := range vs {
name := k
if q.KeyPrefix != "" {
vk = fmt.Sprintf("%s.%s", q.KeyPrefix, vk)
name = fmt.Sprintf("%s.%s", q.KeyPrefix, name)
}

t := now
if q.Time != "" {
value, ok := r[q.Time]
if !ok {
return nil, fmt.Errorf("%q not exists in columns", q.Time)
}
t, ok = value.(int64)
if !ok {
return nil, fmt.Errorf("failed to convert %q to int64", q.Time)
}
if _, has := metricNames[name]; has {
continue
} else {
metricNames[name] = struct{}{}
}

mv := mackerel.MetricValue{
Name: vk,
Value: value,
Name: name,
Value: v,
Time: t,
}

metrics = append(metrics, &mv)
}
}
Expand Down
160 changes: 129 additions & 31 deletions query/valuekey/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"io"
"log"
"os"
"slices"
"strings"
"testing"
"time"

Expand All @@ -21,39 +23,135 @@ func TestMain(m *testing.M) {
}

func TestQueryExecute(t *testing.T) {
logger := stdr.New(log.New(io.Discard, "", 0))
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal("sqlmock.New: ", err)
}
t.Cleanup(func() {
db.Close()
})
columns := []string{"agent_version", "host_num"}
mock.ExpectQuery("SELECT (.+) FROM (.+)").WillReturnRows(sqlmock.NewRows(columns).AddRow("0.1.0", 10))

q := &Query{
KeyPrefix: "agent",
ValueKey: map[string]string{
"versions.#{agent_version}": "host_num",
t.Parallel()
testCases := map[string]struct {
query *Query
want []*mackerel.MetricValue
}{
"basic": {
query: &Query{
KeyPrefix: "agent",
ValueKey: map[string]string{
"versions.#{agent_version}": "host_num",
},
SQL: "SELECT * FROM dummy",
},
want: []*mackerel.MetricValue{
{
Name: "agent.versions.0_1_0",
Time: nowFunc().Unix(),
Value: int64(10),
},
},
},
SQL: "SELECT * FROM dummy",
}
values, err := q.Execute(db, logger)
if err != nil {
t.Errorf("Execute: got %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("ExpectationsWereMet: got %v", err)
}
want := []*mackerel.MetricValue{
{
Name: "agent.versions.0_1_0",
Time: nowFunc().Unix(),
Value: int64(10),
"defaultValue": {
query: &Query{
KeyPrefix: "agent",
ValueKey: map[string]string{
"versions.#{agent_version}": "host_num",
},
DefaultValue: map[string]float64{
"versions.0_1_1": 0.0,
},
SQL: "SELECT * FROM dummy",
},
want: []*mackerel.MetricValue{
{
Name: "agent.versions.0_1_0",
Time: nowFunc().Unix(),
Value: int64(10),
},
{
Name: "agent.versions.0_1_1",
Time: nowFunc().Unix(),
Value: float64(0.0),
},
},
},
"defaultValue_template": {
query: &Query{
KeyPrefix: "agent",
ValueKey: map[string]string{
"versions.#{agent_version}": "host_num",
},
DefaultValue: map[string]float64{
"versions.#{agent_version}": 0.0,
},
SQL: "SELECT * FROM dummy",
},
want: []*mackerel.MetricValue{
{
Name: "agent.versions.0_1_0",
Time: nowFunc().Unix(),
Value: int64(10),
},
{
Name: "agent.versions.0_1_1",
Time: nowFunc().Unix(),
Value: float64(0.0),
},
{
Name: "agent.versions.0_1_2",
Time: nowFunc().Unix(),
Value: float64(0.0),
},
},
},
"defaultValue_non_exist_key": {
query: &Query{
KeyPrefix: "agent",
ValueKey: map[string]string{
"versions.#{agent_version}": "host_num",
},
DefaultValue: map[string]float64{
"versions.0_2_0": 1.0,
},
SQL: "SELECT * FROM dummy",
},
want: []*mackerel.MetricValue{
{
Name: "agent.versions.0_1_0",
Time: nowFunc().Unix(),
Value: int64(10),
},
{
Name: "agent.versions.0_2_0",
Time: nowFunc().Unix(),
Value: float64(1.0),
},
},
},
}
if diff := cmp.Diff(want, values); diff != "" {
t.Errorf("Execute: (-want, +got)\n%s", diff)

for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
logger := stdr.New(log.New(io.Discard, "", 0))
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal("sqlmock.New: ", err)
}
t.Cleanup(func() {
db.Close()
})
columns := []string{"agent_version", "host_num"}
rows := sqlmock.NewRows(columns).AddRow("0.1.0", 10).AddRow("0.1.1", nil).AddRow("0.1.2", nil)
mock.ExpectQuery("SELECT (.+) FROM (.+)").WillReturnRows(rows)

values, err := tc.query.Execute(db, logger)
if err != nil {
t.Errorf("Execute: got %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("ExpectationsWereMet: got %v", err)
}
slices.SortStableFunc(values, func(a, b *mackerel.MetricValue) int {
return strings.Compare(a.Name, b.Name)
})
if diff := cmp.Diff(tc.want, values); diff != "" {
t.Errorf("Execute: (-want, +got)\n%s", diff)
}
})
}
}

0 comments on commit 9ebfcab

Please sign in to comment.