Skip to content

Commit 31b917c

Browse files
authored
Merge pull request #341 from kaleido-io/metrics
Metrics endpoint to support charting
2 parents 95b0ad3 + 4f4bb84 commit 31b917c

File tree

14 files changed

+645
-1
lines changed

14 files changed

+645
-1
lines changed

docs/swagger/swagger.yaml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,61 @@ paths:
931931
description: Success
932932
default:
933933
description: ""
934+
/namespaces/{ns}/charts/histogram/{collection}:
935+
get:
936+
description: 'TODO: Description'
937+
operationId: getChartHistogram
938+
parameters:
939+
- description: 'TODO: Description'
940+
in: path
941+
name: ns
942+
required: true
943+
schema:
944+
example: default
945+
type: string
946+
- description: 'TODO: Description'
947+
in: path
948+
name: collection
949+
required: true
950+
schema:
951+
type: string
952+
- description: Start time of the data to be fetched
953+
in: query
954+
name: startTime
955+
schema:
956+
type: string
957+
- description: End time of the data to be fetched
958+
in: query
959+
name: endTime
960+
schema:
961+
type: string
962+
- description: Number of buckets between start time and end time
963+
in: query
964+
name: buckets
965+
schema:
966+
type: string
967+
- description: Server-side request timeout (millseconds, or set a custom suffix
968+
like 10s)
969+
in: header
970+
name: Request-Timeout
971+
schema:
972+
default: 120s
973+
type: string
974+
responses:
975+
"200":
976+
content:
977+
application/json:
978+
schema:
979+
items:
980+
properties:
981+
count:
982+
type: string
983+
timestamp: {}
984+
type: object
985+
type: array
986+
description: Success
987+
default:
988+
description: ""
934989
/namespaces/{ns}/data:
935990
get:
936991
description: 'TODO: Description'
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright © 2021 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package apiserver
18+
19+
import (
20+
"net/http"
21+
"strconv"
22+
23+
"github.com/hyperledger/firefly/internal/config"
24+
"github.com/hyperledger/firefly/internal/i18n"
25+
"github.com/hyperledger/firefly/internal/oapispec"
26+
"github.com/hyperledger/firefly/pkg/database"
27+
"github.com/hyperledger/firefly/pkg/fftypes"
28+
)
29+
30+
var getChartHistogram = &oapispec.Route{
31+
Name: "getChartHistogram",
32+
Path: "namespaces/{ns}/charts/histogram/{collection}",
33+
Method: http.MethodGet,
34+
PathParams: []*oapispec.PathParam{
35+
{Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD},
36+
{Name: "collection", Description: i18n.MsgTBD},
37+
},
38+
QueryParams: []*oapispec.QueryParam{
39+
{Name: "startTime", Description: i18n.MsgHistogramStartTimeParam, IsBool: false},
40+
{Name: "endTime", Description: i18n.MsgHistogramEndTimeParam, IsBool: false},
41+
{Name: "buckets", Description: i18n.MsgHistogramBucketsParam, IsBool: false},
42+
},
43+
FilterFactory: nil,
44+
Description: i18n.MsgTBD,
45+
JSONInputValue: nil,
46+
JSONOutputValue: func() interface{} { return []*fftypes.ChartHistogram{} },
47+
JSONOutputCodes: []int{http.StatusOK},
48+
JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) {
49+
startTime, err := fftypes.ParseString(r.QP["startTime"])
50+
if err != nil {
51+
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "startTime")
52+
}
53+
endTime, err := fftypes.ParseString(r.QP["endTime"])
54+
if err != nil {
55+
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "endTime")
56+
}
57+
buckets, err := strconv.ParseInt(r.QP["buckets"], 10, 64)
58+
if err != nil {
59+
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "buckets")
60+
}
61+
return r.Or.GetChartHistogram(r.Ctx, r.PP["ns"], startTime.UnixNano(), endTime.UnixNano(), buckets, database.CollectionName(r.PP["collection"]))
62+
},
63+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright © 2021 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package apiserver
18+
19+
import (
20+
"net/http/httptest"
21+
"testing"
22+
23+
"github.com/hyperledger/firefly/pkg/database"
24+
"github.com/hyperledger/firefly/pkg/fftypes"
25+
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/mock"
27+
)
28+
29+
func TestGetChartHistogramBadStartTime(t *testing.T) {
30+
_, r := newTestAPIServer()
31+
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=abc&endTime=456&buckets=30", nil)
32+
req.Header.Set("Content-Type", "application/json; charset=utf-8")
33+
res := httptest.NewRecorder()
34+
35+
r.ServeHTTP(res, req)
36+
37+
assert.Equal(t, 400, res.Result().StatusCode)
38+
}
39+
40+
func TestGetChartHistogramBadEndTime(t *testing.T) {
41+
_, r := newTestAPIServer()
42+
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=123&endTime=abc&buckets=30", nil)
43+
req.Header.Set("Content-Type", "application/json; charset=utf-8")
44+
res := httptest.NewRecorder()
45+
46+
r.ServeHTTP(res, req)
47+
48+
assert.Equal(t, 400, res.Result().StatusCode)
49+
}
50+
51+
func TestGetChartHistogramBadBuckets(t *testing.T) {
52+
_, r := newTestAPIServer()
53+
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=123&endTime=456&buckets=abc", nil)
54+
req.Header.Set("Content-Type", "application/json; charset=utf-8")
55+
res := httptest.NewRecorder()
56+
57+
r.ServeHTTP(res, req)
58+
59+
assert.Equal(t, 400, res.Result().StatusCode)
60+
}
61+
62+
func TestGetChartHistogramSuccess(t *testing.T) {
63+
o, r := newTestAPIServer()
64+
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=1234567890&endTime=1234567891&buckets=30", nil)
65+
req.Header.Set("Content-Type", "application/json; charset=utf-8")
66+
res := httptest.NewRecorder()
67+
68+
startTime, _ := fftypes.ParseString("1234567890")
69+
endtime, _ := fftypes.ParseString("1234567891")
70+
71+
o.On("GetChartHistogram", mock.Anything, "mynamespace", startTime.UnixNano(), endtime.UnixNano(), int64(30), database.CollectionName("test")).
72+
Return([]*fftypes.ChartHistogram{}, nil)
73+
r.ServeHTTP(res, req)
74+
75+
assert.Equal(t, 200, res.Result().StatusCode)
76+
}

internal/apiserver/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ var routes = []*oapispec.Route{
7777
getTxnOps,
7878
getTxns,
7979

80+
getChartHistogram,
81+
8082
postTokenPool,
8183
postTokenPoolByType,
8284
getTokenPools,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright © 2021 Kaleido, Inc.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package sqlcommon
18+
19+
import (
20+
"context"
21+
"database/sql"
22+
"fmt"
23+
24+
sq "github.com/Masterminds/squirrel"
25+
"github.com/hyperledger/firefly/internal/i18n"
26+
"github.com/hyperledger/firefly/pkg/database"
27+
"github.com/hyperledger/firefly/pkg/fftypes"
28+
)
29+
30+
func (s *SQLCommon) getCaseQueries(ns string, intervals []fftypes.ChartHistogramInterval) (caseQueries []sq.CaseBuilder) {
31+
for _, interval := range intervals {
32+
caseQueries = append(caseQueries, sq.Case().
33+
When(
34+
sq.And{
35+
sq.GtOrEq{"created": interval.StartTime},
36+
sq.Lt{"created": interval.EndTime},
37+
sq.Eq{"namespace": ns},
38+
},
39+
"1",
40+
).
41+
Else("0"))
42+
}
43+
44+
return caseQueries
45+
}
46+
47+
func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection database.CollectionName) (tableName string, err error) {
48+
switch collection {
49+
case database.CollectionName(database.CollectionMessages):
50+
return "messages", nil
51+
case database.CollectionName(database.CollectionTransactions):
52+
return "transactions", nil
53+
case database.CollectionName(database.CollectionOperations):
54+
return "operations", nil
55+
case database.CollectionName(database.CollectionEvents):
56+
return "events", nil
57+
default:
58+
return "", i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection)
59+
}
60+
}
61+
62+
func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogram) ([]*fftypes.ChartHistogram, error) {
63+
results := []interface{}{}
64+
65+
for i := range cols {
66+
results = append(results, &cols[i].Count)
67+
}
68+
err := rows.Scan(results...)
69+
if err != nil {
70+
return nil, i18n.NewError(ctx, i18n.MsgDBReadErr, "histogram")
71+
}
72+
73+
return cols, nil
74+
}
75+
76+
func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals []fftypes.ChartHistogramInterval, collection database.CollectionName) (histogram []*fftypes.ChartHistogram, err error) {
77+
tableName, err := s.getTableNameFromCollection(ctx, collection)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
qb := sq.Select()
83+
84+
for i, caseQuery := range s.getCaseQueries(ns, intervals) {
85+
query, args, _ := caseQuery.ToSql()
86+
87+
histogram = append(histogram, &fftypes.ChartHistogram{
88+
Count: "",
89+
Timestamp: intervals[i].StartTime,
90+
})
91+
92+
qb = qb.Column(sq.Alias(sq.Expr("SUM("+query+")", args...), fmt.Sprintf("case_%d", i)))
93+
}
94+
95+
rows, _, err := s.query(ctx, qb.From(tableName))
96+
if err != nil {
97+
return nil, err
98+
}
99+
defer rows.Close()
100+
101+
if !rows.Next() {
102+
return []*fftypes.ChartHistogram{}, nil
103+
}
104+
105+
return s.histogramResult(ctx, rows, histogram)
106+
}

0 commit comments

Comments
 (0)