feat: make widget graph configureable

This commit is contained in:
Marvin Gaube
2023-11-03 10:14:54 +01:00
committed by Vincent Bernat
parent d152dab89e
commit 6cbdfafd67
4 changed files with 99 additions and 37 deletions

View File

@@ -26,6 +26,8 @@ type Configuration struct {
DimensionsLimit int `validate:"min=10"`
// CacheTTL tells how long to keep the most costly requests in cache.
CacheTTL time.Duration `validate:"min=5s"`
// HomepageGraphFilter defines the filtering string to use for the homepage graph
HomepageGraphFilter string
}
// VisualizeOptionsConfiguration defines options for the "visualize" tab.
@@ -55,9 +57,10 @@ func DefaultConfiguration() Configuration {
Dimensions: []query.Column{query.NewColumn("SrcAS")},
Limit: 10,
},
HomepageTopWidgets: []string{"src-as", "src-port", "protocol", "src-country", "etype"},
DimensionsLimit: 50,
CacheTTL: 30 * time.Minute,
HomepageTopWidgets: []string{"src-as", "src-port", "protocol", "src-country", "etype"},
DimensionsLimit: 50,
CacheTTL: 30 * time.Minute,
HomepageGraphFilter: "InIfBoundary = 'external'",
}
}

View File

@@ -772,6 +772,10 @@ The console itself accepts the following keys:
- `homepage-top-widgets` to define the widgets to display on the home page
- `dimensions-limit` to set the upper limit of the number of returned dimensions
- `cache-ttl` sets the time costly requests are kept in cache
- `homepage-graph-filter` sets the filter for the graph on the
homepage (default: `InIfBoundary = 'external'`).
This is a SQL expression, passed into the clickhouse query directly.
It can also be empty, in which case the sum of all flows captured will be displayed.
Here is an example:

View File

@@ -213,6 +213,11 @@ LIMIT 5
}
func (c *Component) widgetGraphHandlerFunc(gc *gin.Context) {
// first step: define which filter to use
filter := c.config.HomepageGraphFilter
if filter != "" {
filter = fmt.Sprintf("AND %s", filter)
}
ctx := c.t.Context(gc.Request.Context())
now := c.d.Clock.Now()
query := c.finalizeQuery(fmt.Sprintf(`
@@ -222,7 +227,7 @@ SELECT
SUM(Bytes*SamplingRate*8/{{ .Interval }})/1000/1000/1000 AS Gbps
FROM {{ .Table }}
WHERE {{ .Timefilter }}
AND InIfBoundary = 'external'
%s
GROUP BY Time
ORDER BY Time WITH FILL
FROM {{ .TimefilterStart }}
@@ -234,7 +239,8 @@ ORDER BY Time WITH FILL
End: now,
MainTableRequired: false,
Points: 200,
})))
}),
filter))
gc.Header("X-SQL-Query", query)
results := []struct {

View File

@@ -230,23 +230,13 @@ func TestWidgetTop(t *testing.T) {
}
func TestWidgetGraph(t *testing.T) {
_, h, mockConn, mockClock := NewMock(t, DefaultConfiguration())
base := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
mockClock.Set(base.Add(24 * time.Hour))
expected := []struct {
Time time.Time `json:"t"`
Gbps float64 `json:"gbps"`
testcases := []struct {
config Configuration
query string
}{
{base, 25.3},
{base.Add(time.Minute), 27.8},
{base.Add(2 * time.Minute), 26.4},
{base.Add(3 * time.Minute), 29.2},
{base.Add(4 * time.Minute), 0},
{base.Add(5 * time.Minute), 24.7},
}
mockConn.EXPECT().
Select(gomock.Any(), gomock.Any(), strings.TrimSpace(`
{
config: DefaultConfiguration(),
query: `
SELECT
toStartOfInterval(TimeReceived + INTERVAL 144 second, INTERVAL 432 second) - INTERVAL 144 second AS Time,
SUM(Bytes*SamplingRate*8/432)/1000/1000/1000 AS Gbps
@@ -257,23 +247,82 @@ GROUP BY Time
ORDER BY Time WITH FILL
FROM toDateTime('2009-11-10 23:00:00', 'UTC')
TO toDateTime('2009-11-11 23:00:00', 'UTC') + INTERVAL 1 second
STEP 432`)).
SetArg(1, expected).
Return(nil)
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
STEP 432`,
},
{
URL: "/api/v0/console/widget/graph",
JSONOutput: gin.H{
"data": []gin.H{
{"t": "2009-11-10T23:00:00Z", "gbps": 25.3},
{"t": "2009-11-10T23:01:00Z", "gbps": 27.8},
{"t": "2009-11-10T23:02:00Z", "gbps": 26.4},
{"t": "2009-11-10T23:03:00Z", "gbps": 29.2},
{"t": "2009-11-10T23:04:00Z", "gbps": 0},
{"t": "2009-11-10T23:05:00Z", "gbps": 24.7},
config: func() Configuration {
c := DefaultConfiguration()
c.HomepageGraphFilter = ""
return c
}(),
query: `
SELECT
toStartOfInterval(TimeReceived + INTERVAL 144 second, INTERVAL 432 second) - INTERVAL 144 second AS Time,
SUM(Bytes*SamplingRate*8/432)/1000/1000/1000 AS Gbps
FROM flows
WHERE TimeReceived BETWEEN toDateTime('2009-11-10 23:00:00', 'UTC') AND toDateTime('2009-11-11 23:00:00', 'UTC')
GROUP BY Time
ORDER BY Time WITH FILL
FROM toDateTime('2009-11-10 23:00:00', 'UTC')
TO toDateTime('2009-11-11 23:00:00', 'UTC') + INTERVAL 1 second
STEP 432`,
},
{
config: func() Configuration {
c := DefaultConfiguration()
c.HomepageGraphFilter = "OutIfBoundary = 'external'"
return c
}(),
query: `
SELECT
toStartOfInterval(TimeReceived + INTERVAL 144 second, INTERVAL 432 second) - INTERVAL 144 second AS Time,
SUM(Bytes*SamplingRate*8/432)/1000/1000/1000 AS Gbps
FROM flows
WHERE TimeReceived BETWEEN toDateTime('2009-11-10 23:00:00', 'UTC') AND toDateTime('2009-11-11 23:00:00', 'UTC')
AND OutIfBoundary = 'external'
GROUP BY Time
ORDER BY Time WITH FILL
FROM toDateTime('2009-11-10 23:00:00', 'UTC')
TO toDateTime('2009-11-11 23:00:00', 'UTC') + INTERVAL 1 second
STEP 432`,
},
}
for _, tcase := range testcases {
_, h, mockConn, mockClock := NewMock(t, tcase.config)
base := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
mockClock.Set(base.Add(24 * time.Hour))
expected := []struct {
Time time.Time `json:"t"`
Gbps float64 `json:"gbps"`
}{
{base, 25.3},
{base.Add(time.Minute), 27.8},
{base.Add(2 * time.Minute), 26.4},
{base.Add(3 * time.Minute), 29.2},
{base.Add(4 * time.Minute), 0},
{base.Add(5 * time.Minute), 24.7},
}
mockConn.EXPECT().
Select(gomock.Any(), gomock.Any(), strings.TrimSpace(tcase.query)).
SetArg(1, expected).
Return(nil)
helpers.TestHTTPEndpoints(t, h.LocalAddr(), helpers.HTTPEndpointCases{
{
URL: "/api/v0/console/widget/graph",
JSONOutput: gin.H{
"data": []gin.H{
{"t": "2009-11-10T23:00:00Z", "gbps": 25.3},
{"t": "2009-11-10T23:01:00Z", "gbps": 27.8},
{"t": "2009-11-10T23:02:00Z", "gbps": 26.4},
{"t": "2009-11-10T23:03:00Z", "gbps": 29.2},
{"t": "2009-11-10T23:04:00Z", "gbps": 0},
{"t": "2009-11-10T23:05:00Z", "gbps": 24.7},
},
},
},
},
})
})
}
}