mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
console: ability to display L2 bps in addition to L3 bps
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="inline-flex items-center gap-2">
|
<div class="inline-flex items-center gap-1">
|
||||||
<span class="text-sm">{{ label }}</span>
|
<span class="text-sm">{{ label }}</span>
|
||||||
<div
|
<div
|
||||||
class="inline-flex rounded-md shadow-sm dark:shadow-white/10"
|
class="inline-flex rounded-md shadow-sm dark:shadow-white/10"
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
v-for="({ name, label: blabel }, idx) in choices"
|
v-for="({ name, label: blabel }, idx) in choices"
|
||||||
:key="name"
|
:key="name"
|
||||||
:for="id(name)"
|
:for="id(name)"
|
||||||
class="cursor-pointer first:rounded-l-lg last:rounded-r-lg focus-within:z-10 focus-within:ring-2 focus-within:ring-blue-300 dark:focus-within:ring-blue-800"
|
class="cursor-pointer first:rounded-l-md last:rounded-r-md focus-within:z-10 focus-within:ring-2 focus-within:ring-blue-300 dark:focus-within:ring-blue-800"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="id(name)"
|
:id="id(name)"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
'rounded-l-md border-l': idx === 0,
|
'rounded-l-md border-l': idx === 0,
|
||||||
'rounded-r-md border-r': idx === choices.length - 1,
|
'rounded-r-md border-r': idx === choices.length - 1,
|
||||||
}"
|
}"
|
||||||
class="border-t border-b border-gray-200 bg-white py-0.5 px-2 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 peer-checked:bg-blue-700 peer-checked:bg-blue-700 peer-checked:font-bold peer-checked:text-white peer-checked:hover:bg-blue-800 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:hover:text-white peer-checked:dark:bg-blue-600 peer-checked:dark:hover:bg-blue-700"
|
class="border-t border-b border-gray-200 bg-white py-0.5 px-1 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-blue-700 peer-checked:bg-blue-700 peer-checked:bg-blue-700 peer-checked:text-white peer-checked:hover:bg-blue-800 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:hover:text-white peer-checked:dark:bg-blue-600 peer-checked:dark:hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
{{ blabel }}
|
{{ blabel }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ const table = computed(() => {
|
|||||||
data.average[idx],
|
data.average[idx],
|
||||||
data["95th"][idx],
|
data["95th"][idx],
|
||||||
].map((d) => ({
|
].map((d) => ({
|
||||||
value: formatXps(d) + data.units,
|
value: formatXps(d) + data.units.slice(-3),
|
||||||
classNames: "text-right tabular-nums",
|
classNames: "text-right tabular-nums",
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@@ -132,7 +132,7 @@ const table = computed(() => {
|
|||||||
...rows.map((r) => ({ value: r })),
|
...rows.map((r) => ({ value: r })),
|
||||||
// Average
|
// Average
|
||||||
{
|
{
|
||||||
value: formatXps(data.xps[idx]) + data.units,
|
value: formatXps(data.xps[idx]) + data.units.slice(-3),
|
||||||
classNames: "text-right tabular-nums",
|
classNames: "text-right tabular-nums",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<aside
|
<aside
|
||||||
class="transition-height transition-width w-full shrink-0 duration-100 lg:h-auto"
|
class="transition-height transition-width w-full shrink-0 duration-100 lg:h-auto"
|
||||||
:class="open ? 'h-80 lg:w-64' : 'h-4 lg:w-4'"
|
:class="open ? 'h-80 lg:w-72' : 'h-4 lg:w-4'"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="absolute z-30 translate-x-4 transition-transform lg:translate-y-4"
|
class="absolute z-30 translate-x-4 transition-transform lg:translate-y-4"
|
||||||
:class="
|
:class="
|
||||||
open
|
open
|
||||||
? 'translate-y-80 rotate-180 lg:translate-x-64'
|
? 'translate-y-80 rotate-180 lg:translate-x-72'
|
||||||
: 'translate-y-4 lg:translate-x-0'
|
: 'translate-y-4 lg:translate-x-0'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
<InputChoice
|
<InputChoice
|
||||||
v-model="units"
|
v-model="units"
|
||||||
:choices="[
|
:choices="[
|
||||||
{ label: 'ᵇ⁄ₛ', name: 'bps' },
|
{ label: 'L3ᵇ⁄ₛ', name: 'l3bps' },
|
||||||
|
{ label: 'L2ᵇ⁄ₛ', name: 'l2bps' },
|
||||||
{ label: 'ᵖ⁄ₛ', name: 'pps' },
|
{ label: 'ᵖ⁄ₛ', name: 'pps' },
|
||||||
]"
|
]"
|
||||||
label="Unit"
|
label="Unit"
|
||||||
@@ -134,7 +135,7 @@ const graphType = ref(graphTypeList[0]);
|
|||||||
const timeRange = ref({});
|
const timeRange = ref({});
|
||||||
const dimensions = ref([]);
|
const dimensions = ref([]);
|
||||||
const filter = ref({});
|
const filter = ref({});
|
||||||
const units = ref("bps");
|
const units = ref("l3bps");
|
||||||
|
|
||||||
const options = computed(() => ({
|
const options = computed(() => ({
|
||||||
// Common to all graph types
|
// Common to all graph types
|
||||||
@@ -168,7 +169,7 @@ watch(
|
|||||||
limit = 10,
|
limit = 10,
|
||||||
points /* eslint-disable-line no-unused-vars */,
|
points /* eslint-disable-line no-unused-vars */,
|
||||||
filter: _filter = "InIfBoundary = external",
|
filter: _filter = "InIfBoundary = external",
|
||||||
units: _units = "bps",
|
units: _units = "l3bps",
|
||||||
} = modelValue;
|
} = modelValue;
|
||||||
|
|
||||||
// Dispatch values in refs
|
// Dispatch values in refs
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
<span v-if="request.units" class="min-w-[4 shrink-0 py-0.5">
|
<span v-if="request.units" class="min-w-[4 shrink-0 py-0.5">
|
||||||
<HashtagIcon class="inline h-4 px-1 align-middle" />
|
<HashtagIcon class="inline h-4 px-1 align-middle" />
|
||||||
<span class="align-middle">{{
|
<span class="align-middle">{{
|
||||||
{ bps: "ᵇ⁄ₛ", pps: "ᵖ⁄ₛ" }[request.units] || requests.units
|
{ l3bps: "L3ᵇ⁄ₛ", l2bps: "L2ᵇ⁄ₛ", pps: "ᵖ⁄ₛ" }[request.units] ||
|
||||||
|
requests.units
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type graphHandlerInput struct {
|
|||||||
Dimensions []queryColumn `json:"dimensions"` // group by ...
|
Dimensions []queryColumn `json:"dimensions"` // group by ...
|
||||||
Limit int `json:"limit" binding:"min=1,max=50"` // limit product of dimensions
|
Limit int `json:"limit" binding:"min=1,max=50"` // limit product of dimensions
|
||||||
Filter queryFilter `json:"filter"` // where ...
|
Filter queryFilter `json:"filter"` // where ...
|
||||||
Units string `json:"units" binding:"required,oneof=pps bps"`
|
Units string `json:"units" binding:"required,oneof=pps l2bps l3bps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// graphHandlerOutput describes the output for the /graph endpoint.
|
// graphHandlerOutput describes the output for the /graph endpoint.
|
||||||
@@ -50,10 +50,13 @@ func (input graphHandlerInput) toSQL() (string, error) {
|
|||||||
fields := []string{
|
fields := []string{
|
||||||
`toStartOfInterval(TimeReceived, INTERVAL slot second) AS time`,
|
`toStartOfInterval(TimeReceived, INTERVAL slot second) AS time`,
|
||||||
}
|
}
|
||||||
if input.Units == "pps" {
|
switch input.Units {
|
||||||
|
case "pps":
|
||||||
fields = append(fields, `SUM(Packets*SamplingRate/slot) AS xps`)
|
fields = append(fields, `SUM(Packets*SamplingRate/slot) AS xps`)
|
||||||
} else {
|
case "l3bps":
|
||||||
fields = append(fields, `SUM(Bytes*SamplingRate*8/slot) AS xps`)
|
fields = append(fields, `SUM(Bytes*SamplingRate*8/slot) AS xps`)
|
||||||
|
case "l2bps":
|
||||||
|
fields = append(fields, `SUM((Bytes+18*Packets)*SamplingRate*8/slot) AS xps`)
|
||||||
}
|
}
|
||||||
selectFields := []string{}
|
selectFields := []string{}
|
||||||
dimensions := []string{}
|
dimensions := []string{}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func TestGraphQuerySQL(t *testing.T) {
|
|||||||
Points: 100,
|
Points: 100,
|
||||||
Dimensions: []queryColumn{},
|
Dimensions: []queryColumn{},
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "bps",
|
Units: "l3bps",
|
||||||
},
|
},
|
||||||
Expected: `
|
Expected: `
|
||||||
WITH
|
WITH
|
||||||
@@ -41,6 +41,27 @@ SELECT
|
|||||||
FROM {table}
|
FROM {table}
|
||||||
WHERE {timefilter}
|
WHERE {timefilter}
|
||||||
GROUP BY time, dimensions
|
GROUP BY time, dimensions
|
||||||
|
ORDER BY time`,
|
||||||
|
}, {
|
||||||
|
Description: "no dimensions, no filters, l2 bps",
|
||||||
|
Input: graphHandlerInput{
|
||||||
|
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
||||||
|
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||||
|
Points: 100,
|
||||||
|
Dimensions: []queryColumn{},
|
||||||
|
Filter: queryFilter{},
|
||||||
|
Units: "l2bps",
|
||||||
|
},
|
||||||
|
Expected: `
|
||||||
|
WITH
|
||||||
|
intDiv(864, {resolution})*{resolution} AS slot
|
||||||
|
SELECT
|
||||||
|
toStartOfInterval(TimeReceived, INTERVAL slot second) AS time,
|
||||||
|
SUM((Bytes+18*Packets)*SamplingRate*8/slot) AS xps,
|
||||||
|
emptyArrayString() AS dimensions
|
||||||
|
FROM {table}
|
||||||
|
WHERE {timefilter}
|
||||||
|
GROUP BY time, dimensions
|
||||||
ORDER BY time`,
|
ORDER BY time`,
|
||||||
}, {
|
}, {
|
||||||
Description: "no dimensions, no filters, pps",
|
Description: "no dimensions, no filters, pps",
|
||||||
@@ -71,7 +92,7 @@ ORDER BY time`,
|
|||||||
Points: 100,
|
Points: 100,
|
||||||
Dimensions: []queryColumn{},
|
Dimensions: []queryColumn{},
|
||||||
Filter: queryFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
Filter: queryFilter{"DstCountry = 'FR' AND SrcCountry = 'US'"},
|
||||||
Units: "bps",
|
Units: "l3bps",
|
||||||
},
|
},
|
||||||
Expected: `
|
Expected: `
|
||||||
WITH
|
WITH
|
||||||
@@ -96,7 +117,7 @@ ORDER BY time`,
|
|||||||
queryColumnInIfProvider,
|
queryColumnInIfProvider,
|
||||||
},
|
},
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "bps",
|
Units: "l3bps",
|
||||||
},
|
},
|
||||||
Expected: `
|
Expected: `
|
||||||
WITH
|
WITH
|
||||||
@@ -171,7 +192,7 @@ func TestGraphHandler(t *testing.T) {
|
|||||||
"limit": 20,
|
"limit": 20,
|
||||||
"dimensions": []string{"ExporterName", "InIfProvider"},
|
"dimensions": []string{"ExporterName", "InIfProvider"},
|
||||||
"filter": "DstCountry = 'FR' AND SrcCountry = 'US'",
|
"filter": "DstCountry = 'FR' AND SrcCountry = 'US'",
|
||||||
"units": "bps",
|
"units": "l3bps",
|
||||||
},
|
},
|
||||||
JSONOutput: gin.H{
|
JSONOutput: gin.H{
|
||||||
// Sorted by sum of bps
|
// Sorted by sum of bps
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type sankeyHandlerInput struct {
|
|||||||
Dimensions []queryColumn `json:"dimensions" binding:"required,min=2"` // group by ...
|
Dimensions []queryColumn `json:"dimensions" binding:"required,min=2"` // group by ...
|
||||||
Limit int `json:"limit" binding:"min=1,max=50"` // limit product of dimensions
|
Limit int `json:"limit" binding:"min=1,max=50"` // limit product of dimensions
|
||||||
Filter queryFilter `json:"filter"` // where ...
|
Filter queryFilter `json:"filter"` // where ...
|
||||||
Units string `json:"units" binding:"required,oneof=pps bps"`
|
Units string `json:"units" binding:"required,oneof=pps l3bps l2bps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// sankeyHandlerOutput describes the output for the /sankey endpoint.
|
// sankeyHandlerOutput describes the output for the /sankey endpoint.
|
||||||
@@ -58,10 +58,13 @@ func (input sankeyHandlerInput) toSQL() (string, error) {
|
|||||||
dimensions = append(dimensions, column.String())
|
dimensions = append(dimensions, column.String())
|
||||||
}
|
}
|
||||||
fields := []string{}
|
fields := []string{}
|
||||||
if input.Units == "pps" {
|
switch input.Units {
|
||||||
|
case "pps":
|
||||||
fields = append(fields, `SUM(Packets*SamplingRate/range) AS xps`)
|
fields = append(fields, `SUM(Packets*SamplingRate/range) AS xps`)
|
||||||
} else {
|
case "l3bps":
|
||||||
fields = append(fields, `SUM(Bytes*SamplingRate*8/range) AS xps`)
|
fields = append(fields, `SUM(Bytes*SamplingRate*8/range) AS xps`)
|
||||||
|
case "l2bps":
|
||||||
|
fields = append(fields, `SUM((Bytes+18*Packets)*SamplingRate*8/range) AS xps`)
|
||||||
}
|
}
|
||||||
fields = append(fields, fmt.Sprintf("[%s] AS dimensions", strings.Join(arrayFields, ",\n ")))
|
fields = append(fields, fmt.Sprintf("[%s] AS dimensions", strings.Join(arrayFields, ",\n ")))
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,14 @@ func TestSankeyQuerySQL(t *testing.T) {
|
|||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Description: "two dimensions, no filters, bps",
|
Description: "two dimensions, no filters, l3 bps",
|
||||||
Input: sankeyHandlerInput{
|
Input: sankeyHandlerInput{
|
||||||
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
||||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||||
Dimensions: []queryColumn{queryColumnSrcAS, queryColumnExporterName},
|
Dimensions: []queryColumn{queryColumnSrcAS, queryColumnExporterName},
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "bps",
|
Units: "l3bps",
|
||||||
},
|
},
|
||||||
Expected: `
|
Expected: `
|
||||||
WITH
|
WITH
|
||||||
@@ -42,6 +42,28 @@ SELECT
|
|||||||
FROM {table}
|
FROM {table}
|
||||||
WHERE {timefilter}
|
WHERE {timefilter}
|
||||||
GROUP BY dimensions
|
GROUP BY dimensions
|
||||||
|
ORDER BY xps DESC`,
|
||||||
|
}, {
|
||||||
|
Description: "two dimensions, no filters, l2 bps",
|
||||||
|
Input: sankeyHandlerInput{
|
||||||
|
Start: time.Date(2022, 04, 10, 15, 45, 10, 0, time.UTC),
|
||||||
|
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||||
|
Dimensions: []queryColumn{queryColumnSrcAS, queryColumnExporterName},
|
||||||
|
Limit: 5,
|
||||||
|
Filter: queryFilter{},
|
||||||
|
Units: "l2bps",
|
||||||
|
},
|
||||||
|
Expected: `
|
||||||
|
WITH
|
||||||
|
(SELECT MAX(TimeReceived) - MIN(TimeReceived) FROM {table} WHERE {timefilter}) AS range,
|
||||||
|
rows AS (SELECT SrcAS, ExporterName FROM {table} WHERE {timefilter} GROUP BY SrcAS, ExporterName ORDER BY SUM(Bytes) DESC LIMIT 5)
|
||||||
|
SELECT
|
||||||
|
SUM((Bytes+18*Packets)*SamplingRate*8/range) AS xps,
|
||||||
|
[if(SrcAS IN (SELECT SrcAS FROM rows), concat(toString(SrcAS), ': ', dictGetOrDefault('asns', 'name', SrcAS, '???')), 'Other'),
|
||||||
|
if(ExporterName IN (SELECT ExporterName FROM rows), ExporterName, 'Other')] AS dimensions
|
||||||
|
FROM {table}
|
||||||
|
WHERE {timefilter}
|
||||||
|
GROUP BY dimensions
|
||||||
ORDER BY xps DESC`,
|
ORDER BY xps DESC`,
|
||||||
}, {
|
}, {
|
||||||
Description: "two dimensions, no filters, pps",
|
Description: "two dimensions, no filters, pps",
|
||||||
@@ -73,7 +95,7 @@ ORDER BY xps DESC`,
|
|||||||
Dimensions: []queryColumn{queryColumnSrcAS, queryColumnExporterName},
|
Dimensions: []queryColumn{queryColumnSrcAS, queryColumnExporterName},
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
Filter: queryFilter{"DstCountry = 'FR'"},
|
Filter: queryFilter{"DstCountry = 'FR'"},
|
||||||
Units: "bps",
|
Units: "l3bps",
|
||||||
},
|
},
|
||||||
Expected: `
|
Expected: `
|
||||||
WITH
|
WITH
|
||||||
@@ -157,7 +179,7 @@ func TestSankeyHandler(t *testing.T) {
|
|||||||
"dimensions": []string{"SrcAS", "InIfProvider", "ExporterName"},
|
"dimensions": []string{"SrcAS", "InIfProvider", "ExporterName"},
|
||||||
"limit": 10,
|
"limit": 10,
|
||||||
"filter": "DstCountry = 'FR'",
|
"filter": "DstCountry = 'FR'",
|
||||||
"units": "bps",
|
"units": "l3bps",
|
||||||
},
|
},
|
||||||
JSONOutput: gin.H{
|
JSONOutput: gin.H{
|
||||||
// Raw data
|
// Raw data
|
||||||
|
|||||||
Reference in New Issue
Block a user