mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
console: use common/schema for dimensions
This is a bit less type-safe. We could keep type safety by redefining all the consts in `query_consts.go` in `common/schema`, but this is pointless as the goal is to have arbitrary dimensions at some point.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,7 +5,6 @@
|
|||||||
*.pb.go
|
*.pb.go
|
||||||
mock_*.go
|
mock_*.go
|
||||||
|
|
||||||
/console/frontend/data/fields.json
|
|
||||||
/console/data/frontend/
|
/console/data/frontend/
|
||||||
|
|
||||||
*~
|
*~
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -16,8 +16,7 @@ export CGO_ENABLED=0
|
|||||||
|
|
||||||
FLOW_VERSION := $(shell sed -n 's/^const CurrentSchemaVersion = //p' inlet/flow/schemas.go)
|
FLOW_VERSION := $(shell sed -n 's/^const CurrentSchemaVersion = //p' inlet/flow/schemas.go)
|
||||||
GENERATED_JS = \
|
GENERATED_JS = \
|
||||||
console/frontend/node_modules \
|
console/frontend/node_modules
|
||||||
console/frontend/data/fields.json
|
|
||||||
GENERATED_GO = \
|
GENERATED_GO = \
|
||||||
inlet/flow/decoder/flow-ANY.pb.go \
|
inlet/flow/decoder/flow-ANY.pb.go \
|
||||||
common/clickhousedb/mocks/mock_driver.go \
|
common/clickhousedb/mocks/mock_driver.go \
|
||||||
@@ -99,10 +98,6 @@ console/filter/parser.go: console/filter/parser.peg | $(PIGEON) ; $(info $(M) ge
|
|||||||
console/frontend/node_modules: console/frontend/package.json console/frontend/package-lock.json
|
console/frontend/node_modules: console/frontend/package.json console/frontend/package-lock.json
|
||||||
console/frontend/node_modules: ; $(info $(M) fetching node modules…)
|
console/frontend/node_modules: ; $(info $(M) fetching node modules…)
|
||||||
$Q (cd console/frontend ; npm ci --silent --no-audit --no-fund) && touch $@
|
$Q (cd console/frontend ; npm ci --silent --no-audit --no-fund) && touch $@
|
||||||
console/frontend/data/fields.json: console/query_consts.go ; $(info $(M) generate list of selectable fields…)
|
|
||||||
$Q sed -En -e 's/^\tqueryColumn([a-zA-Z0-9]+)( .*|$$)/ "\1"/p' $< \
|
|
||||||
| sed -E -e '$$ ! s/$$/,/' -e '1s/^ */[/' -e '$$s/$$/]/' > $@
|
|
||||||
$Q test -s $@
|
|
||||||
console/data/frontend: $(GENERATED_JS)
|
console/data/frontend: $(GENERATED_JS)
|
||||||
console/data/frontend: $(shell $(LSFILES) console/frontend 2> /dev/null)
|
console/data/frontend: $(shell $(LSFILES) console/frontend 2> /dev/null)
|
||||||
console/data/frontend: ; $(info $(M) building console frontend…)
|
console/data/frontend: ; $(info $(M) building console frontend…)
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ const (
|
|||||||
SkipAliasedColumns
|
SkipAliasedColumns
|
||||||
// SkipTimeReceived skips the time received column
|
// SkipTimeReceived skips the time received column
|
||||||
SkipTimeReceived
|
SkipTimeReceived
|
||||||
|
// SkipNotDimension skips columns that cannot be used as a dimension
|
||||||
|
SkipNotDimension
|
||||||
// UseTransformFromType uses the type from TransformFrom if any
|
// UseTransformFromType uses the type from TransformFrom if any
|
||||||
UseTransformFromType
|
UseTransformFromType
|
||||||
// SubstituteGenerates changes the column name to use the default generated value
|
// SubstituteGenerates changes the column name to use the default generated value
|
||||||
@@ -80,6 +82,9 @@ func (schema Schema) iterate(fn func(column Column), options ...TableOption) {
|
|||||||
if slices.Contains(options, SkipAliasedColumns) && column.Alias != "" {
|
if slices.Contains(options, SkipAliasedColumns) && column.Alias != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if slices.Contains(options, SkipNotDimension) && column.NotSelectable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if slices.Contains(options, UseTransformFromType) && column.TransformFrom != nil {
|
if slices.Contains(options, UseTransformFromType) && column.TransformFrom != nil {
|
||||||
for _, ocol := range column.TransformFrom {
|
for _, ocol := range column.TransformFrom {
|
||||||
// We assume we only need to use name/type
|
// We assume we only need to use name/type
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ var Flows = Schema{
|
|||||||
},
|
},
|
||||||
Columns: buildMapFromColumns([]Column{
|
Columns: buildMapFromColumns([]Column{
|
||||||
{
|
{
|
||||||
Name: "TimeReceived",
|
Name: "TimeReceived",
|
||||||
Type: "DateTime",
|
Type: "DateTime",
|
||||||
Codec: "DoubleDelta, LZ4",
|
Codec: "DoubleDelta, LZ4",
|
||||||
|
NotSelectable: true,
|
||||||
},
|
},
|
||||||
{Name: "SamplingRate", Type: "UInt64"},
|
{Name: "SamplingRate", Type: "UInt64", NotSelectable: true},
|
||||||
{Name: "ExporterAddress", Type: "LowCardinality(IPv6)"},
|
{Name: "ExporterAddress", Type: "LowCardinality(IPv6)"},
|
||||||
{Name: "ExporterName", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterName", Type: "LowCardinality(String)", NotSortingKey: true},
|
||||||
{Name: "ExporterGroup", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterGroup", Type: "LowCardinality(String)", NotSortingKey: true},
|
||||||
@@ -45,9 +46,10 @@ var Flows = Schema{
|
|||||||
Type: "IPv6",
|
Type: "IPv6",
|
||||||
MainOnly: true,
|
MainOnly: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetMask",
|
Name: "SrcNetMask",
|
||||||
Type: "UInt8",
|
Type: "UInt8",
|
||||||
MainOnly: true,
|
MainOnly: true,
|
||||||
|
NotSelectable: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetPrefix",
|
Name: "SrcNetPrefix",
|
||||||
Type: "String",
|
Type: "String",
|
||||||
@@ -110,7 +112,8 @@ END`,
|
|||||||
{Name: "DstLargeCommunities.LocalData1", Type: "Array(UInt32)"},
|
{Name: "DstLargeCommunities.LocalData1", Type: "Array(UInt32)"},
|
||||||
{Name: "DstLargeCommunities.LocalData2", Type: "Array(UInt32)"},
|
{Name: "DstLargeCommunities.LocalData2", Type: "Array(UInt32)"},
|
||||||
},
|
},
|
||||||
TransformTo: "arrayMap((asn, l1, l2) -> ((bitShiftLeft(CAST(asn, 'UInt128'), 64) + bitShiftLeft(CAST(l1, 'UInt128'), 32)) + CAST(l2, 'UInt128')), `DstLargeCommunities.ASN`, `DstLargeCommunities.LocalData1`, `DstLargeCommunities.LocalData2`)",
|
TransformTo: "arrayMap((asn, l1, l2) -> ((bitShiftLeft(CAST(asn, 'UInt128'), 64) + bitShiftLeft(CAST(l1, 'UInt128'), 32)) + CAST(l2, 'UInt128')), `DstLargeCommunities.ASN`, `DstLargeCommunities.LocalData1`, `DstLargeCommunities.LocalData2`)",
|
||||||
|
NotSelectable: true,
|
||||||
},
|
},
|
||||||
{Name: "InIfName", Type: "LowCardinality(String)"},
|
{Name: "InIfName", Type: "LowCardinality(String)"},
|
||||||
{Name: "InIfDescription", Type: "String", NotSortingKey: true},
|
{Name: "InIfDescription", Type: "String", NotSortingKey: true},
|
||||||
@@ -121,12 +124,13 @@ END`,
|
|||||||
{Name: "EType", Type: "UInt32"},
|
{Name: "EType", Type: "UInt32"},
|
||||||
{Name: "Proto", Type: "UInt32"},
|
{Name: "Proto", Type: "UInt32"},
|
||||||
{Name: "SrcPort", Type: "UInt32", MainOnly: true},
|
{Name: "SrcPort", Type: "UInt32", MainOnly: true},
|
||||||
{Name: "Bytes", Type: "UInt64", NotSortingKey: true},
|
{Name: "Bytes", Type: "UInt64", NotSortingKey: true, NotSelectable: true},
|
||||||
{Name: "Packets", Type: "UInt64", NotSortingKey: true},
|
{Name: "Packets", Type: "UInt64", NotSortingKey: true, NotSelectable: true},
|
||||||
{
|
{
|
||||||
Name: "PacketSize",
|
Name: "PacketSize",
|
||||||
Type: "UInt64",
|
Type: "UInt64",
|
||||||
Alias: "intDiv(Bytes, Packets)",
|
Alias: "intDiv(Bytes, Packets)",
|
||||||
|
NotSelectable: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "PacketSizeBucket",
|
Name: "PacketSizeBucket",
|
||||||
Type: "LowCardinality(String)",
|
Type: "LowCardinality(String)",
|
||||||
|
|||||||
@@ -36,4 +36,7 @@ type Column struct {
|
|||||||
GenerateFrom string
|
GenerateFrom string
|
||||||
TransformFrom []Column
|
TransformFrom []Column
|
||||||
TransformTo string
|
TransformTo string
|
||||||
|
|
||||||
|
// For the console.
|
||||||
|
NotSelectable bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"akvorado/common/schema"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ func DefaultConfiguration() Configuration {
|
|||||||
Start: "6 hours ago",
|
Start: "6 hours ago",
|
||||||
End: "now",
|
End: "now",
|
||||||
Filter: "InIfBoundary = external",
|
Filter: "InIfBoundary = external",
|
||||||
Dimensions: []queryColumn{queryColumnSrcAS},
|
Dimensions: []queryColumn{"SrcAS"},
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
},
|
},
|
||||||
HomepageTopWidgets: []string{"src-as", "src-port", "protocol", "src-country", "etype"},
|
HomepageTopWidgets: []string{"src-as", "src-port", "protocol", "src-country", "etype"},
|
||||||
@@ -65,5 +67,6 @@ func (c *Component) configHandlerFunc(gc *gin.Context) {
|
|||||||
"defaultVisualizeOptions": c.config.DefaultVisualizeOptions,
|
"defaultVisualizeOptions": c.config.DefaultVisualizeOptions,
|
||||||
"dimensionsLimit": c.config.DimensionsLimit,
|
"dimensionsLimit": c.config.DimensionsLimit,
|
||||||
"homepageTopWidgets": c.config.HomepageTopWidgets,
|
"homepageTopWidgets": c.config.HomepageTopWidgets,
|
||||||
|
"dimensions": schema.Flows.SelectColumns(schema.SkipNotDimension),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,57 @@ func TestConfigHandler(t *testing.T) {
|
|||||||
"dimensions": []string{"SrcAS"},
|
"dimensions": []string{"SrcAS"},
|
||||||
"limit": 10,
|
"limit": 10,
|
||||||
},
|
},
|
||||||
"dimensionsLimit": 50,
|
|
||||||
"homepageTopWidgets": []string{"src-as", "src-port", "protocol", "src-country", "etype"},
|
"homepageTopWidgets": []string{"src-as", "src-port", "protocol", "src-country", "etype"},
|
||||||
|
"dimensionsLimit": 50,
|
||||||
|
"dimensions": []string{"ExporterAddress",
|
||||||
|
"ExporterName",
|
||||||
|
"ExporterGroup",
|
||||||
|
"ExporterRole",
|
||||||
|
"ExporterSite",
|
||||||
|
"ExporterRegion",
|
||||||
|
"ExporterTenant",
|
||||||
|
"SrcAddr",
|
||||||
|
"DstAddr",
|
||||||
|
"SrcNetPrefix",
|
||||||
|
"DstNetPrefix",
|
||||||
|
"SrcAS",
|
||||||
|
"DstAS",
|
||||||
|
"SrcNetName",
|
||||||
|
"DstNetName",
|
||||||
|
"SrcNetRole",
|
||||||
|
"DstNetRole",
|
||||||
|
"SrcNetSite",
|
||||||
|
"DstNetSite",
|
||||||
|
"SrcNetRegion",
|
||||||
|
"DstNetRegion",
|
||||||
|
"SrcNetTenant",
|
||||||
|
"DstNetTenant",
|
||||||
|
"SrcCountry",
|
||||||
|
"DstCountry",
|
||||||
|
"DstASPath",
|
||||||
|
"Dst1stAS",
|
||||||
|
"Dst2ndAS",
|
||||||
|
"Dst3rdAS",
|
||||||
|
"DstCommunities",
|
||||||
|
"InIfName",
|
||||||
|
"OutIfName",
|
||||||
|
"InIfDescription",
|
||||||
|
"OutIfDescription",
|
||||||
|
"InIfSpeed",
|
||||||
|
"OutIfSpeed",
|
||||||
|
"InIfConnectivity",
|
||||||
|
"OutIfConnectivity",
|
||||||
|
"InIfProvider",
|
||||||
|
"OutIfProvider",
|
||||||
|
"InIfBoundary",
|
||||||
|
"OutIfBoundary",
|
||||||
|
"EType",
|
||||||
|
"Proto",
|
||||||
|
"SrcPort",
|
||||||
|
"DstPort",
|
||||||
|
"PacketSizeBucket",
|
||||||
|
"ForwardingStatus",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -45,13 +45,12 @@ func ReverseColumnDirection(name string) string {
|
|||||||
// in predicate code blocks.
|
// in predicate code blocks.
|
||||||
func (c *current) acceptColumn() (string, error) {
|
func (c *current) acceptColumn() (string, error) {
|
||||||
name := string(c.text)
|
name := string(c.text)
|
||||||
for pair := schema.Flows.Columns.Front(); pair != nil; pair = pair.Next() {
|
for _, columnName := range schema.Flows.Columns.Keys() {
|
||||||
column := pair.Value
|
if strings.EqualFold(name, columnName) {
|
||||||
if strings.EqualFold(name, column.Name) {
|
|
||||||
if c.globalStore["meta"].(*Meta).ReverseDirection {
|
if c.globalStore["meta"].(*Meta).ReverseDirection {
|
||||||
return ReverseColumnDirection(column.Name), nil
|
return ReverseColumnDirection(columnName), nil
|
||||||
}
|
}
|
||||||
return column.Name, nil
|
return columnName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("unknown column %q", name)
|
return "", fmt.Errorf("unknown column %q", name)
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import { dataColor } from "@/utils";
|
|||||||
import InputString from "@/components/InputString.vue";
|
import InputString from "@/components/InputString.vue";
|
||||||
import InputListBox from "@/components/InputListBox.vue";
|
import InputListBox from "@/components/InputListBox.vue";
|
||||||
import { ServerConfigKey } from "@/components/ServerConfigProvider.vue";
|
import { ServerConfigKey } from "@/components/ServerConfigProvider.vue";
|
||||||
import fields from "@data/fields.json";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -75,7 +74,7 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const serverConfiguration = inject(ServerConfigKey)!;
|
const serverConfiguration = inject(ServerConfigKey)!;
|
||||||
const selectedDimensions = ref<Array<typeof dimensions[0]>>([]);
|
const selectedDimensions = ref<Array<typeof dimensions.value[0]>>([]);
|
||||||
const dimensionsError = computed(() => {
|
const dimensionsError = computed(() => {
|
||||||
if (selectedDimensions.value.length < props.minDimensions) {
|
if (selectedDimensions.value.length < props.minDimensions) {
|
||||||
return "At least two dimensions are required";
|
return "At least two dimensions are required";
|
||||||
@@ -99,25 +98,27 @@ const limitError = computed(() => {
|
|||||||
});
|
});
|
||||||
const hasErrors = computed(() => !!limitError.value || !!dimensionsError.value);
|
const hasErrors = computed(() => !!limitError.value || !!dimensionsError.value);
|
||||||
|
|
||||||
const dimensions = fields.map((v, idx) => ({
|
const dimensions = computed(() =>
|
||||||
id: idx + 1,
|
serverConfiguration.value?.dimensions.map((v, idx) => ({
|
||||||
name: v,
|
id: idx + 1,
|
||||||
color: dataColor(
|
name: v,
|
||||||
["Exporter", "Src", "Dst", "In", "Out", ""]
|
color: dataColor(
|
||||||
.map((p) => v.startsWith(p))
|
["Exporter", "Src", "Dst", "In", "Out", ""]
|
||||||
.indexOf(true)
|
.map((p) => v.startsWith(p))
|
||||||
),
|
.indexOf(true)
|
||||||
}));
|
),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const removeDimension = (dimension: typeof dimensions[0]) => {
|
const removeDimension = (dimension: typeof dimensions.value[0]) => {
|
||||||
selectedDimensions.value = selectedDimensions.value.filter(
|
selectedDimensions.value = selectedDimensions.value.filter(
|
||||||
(d) => d !== dimension
|
(d) => d !== dimension
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => [props.modelValue, dimensions.value] as const,
|
||||||
(value) => {
|
([value, dimensions]) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
limit.value = value.limit.toString();
|
limit.value = value.limit.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type ServerConfig = {
|
|||||||
dimensions: string[];
|
dimensions: string[];
|
||||||
limit: number;
|
limit: number;
|
||||||
};
|
};
|
||||||
|
dimensions: string[];
|
||||||
dimensionsLimit: number;
|
dimensionsLimit: number;
|
||||||
homepageTopWidgets: string[];
|
homepageTopWidgets: string[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "data/*.json"],
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"]
|
||||||
"@data/*": ["./data/*"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
"@data": fileURLToPath(new URL("./data", import.meta.url)),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ func TestGraphInputReverseDirection(t *testing.T) {
|
|||||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||||
Points: 100,
|
Points: 100,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
queryColumnInIfProvider,
|
"InIfProvider",
|
||||||
},
|
},
|
||||||
Filter: queryFilter{
|
Filter: queryFilter{
|
||||||
Filter: "DstCountry = 'FR' AND SrcCountry = 'US'",
|
Filter: "DstCountry = 'FR' AND SrcCountry = 'US'",
|
||||||
@@ -36,8 +36,8 @@ func TestGraphInputReverseDirection(t *testing.T) {
|
|||||||
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
End: time.Date(2022, 04, 11, 15, 45, 10, 0, time.UTC),
|
||||||
Points: 100,
|
Points: 100,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
queryColumnOutIfProvider,
|
"OutIfProvider",
|
||||||
},
|
},
|
||||||
Filter: queryFilter{
|
Filter: queryFilter{
|
||||||
Filter: "SrcCountry = 'FR' AND DstCountry = 'US'",
|
Filter: "SrcCountry = 'FR' AND DstCountry = 'US'",
|
||||||
@@ -120,8 +120,8 @@ func TestGraphPreviousPeriod(t *testing.T) {
|
|||||||
Start: start,
|
Start: start,
|
||||||
End: end,
|
End: end,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterAddress,
|
"ExporterAddress",
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := input.previousPeriod()
|
got := input.previousPeriod()
|
||||||
@@ -328,8 +328,8 @@ ORDER BY time WITH FILL
|
|||||||
Points: 100,
|
Points: 100,
|
||||||
Limit: 20,
|
Limit: 20,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
queryColumnInIfProvider,
|
"InIfProvider",
|
||||||
},
|
},
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "l3bps",
|
Units: "l3bps",
|
||||||
@@ -360,8 +360,8 @@ ORDER BY time WITH FILL
|
|||||||
Points: 100,
|
Points: 100,
|
||||||
Limit: 20,
|
Limit: 20,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
queryColumnInIfProvider,
|
"InIfProvider",
|
||||||
},
|
},
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "l3bps",
|
Units: "l3bps",
|
||||||
@@ -409,8 +409,8 @@ ORDER BY time WITH FILL
|
|||||||
Points: 100,
|
Points: 100,
|
||||||
Limit: 20,
|
Limit: 20,
|
||||||
Dimensions: []queryColumn{
|
Dimensions: []queryColumn{
|
||||||
queryColumnExporterName,
|
"ExporterName",
|
||||||
queryColumnInIfProvider,
|
"InIfProvider",
|
||||||
},
|
},
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "l3bps",
|
Units: "l3bps",
|
||||||
|
|||||||
@@ -9,50 +9,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"akvorado/common/helpers"
|
"akvorado/common/helpers"
|
||||||
|
"akvorado/common/schema"
|
||||||
"akvorado/console/filter"
|
"akvorado/console/filter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type queryColumn int
|
type queryColumn string
|
||||||
|
|
||||||
func (qc queryColumn) MarshalText() ([]byte, error) {
|
func (qc queryColumn) MarshalText() ([]byte, error) {
|
||||||
got, ok := queryColumnMap.LoadValue(qc)
|
return []byte(qc), nil
|
||||||
if ok {
|
|
||||||
return []byte(got), nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("unknown field")
|
|
||||||
}
|
}
|
||||||
func (qc queryColumn) String() string {
|
func (qc queryColumn) String() string {
|
||||||
got, _ := queryColumnMap.LoadValue(qc)
|
return string(qc)
|
||||||
return got
|
|
||||||
}
|
}
|
||||||
func (qc *queryColumn) UnmarshalText(input []byte) error {
|
func (qc *queryColumn) UnmarshalText(input []byte) error {
|
||||||
got, ok := queryColumnMap.LoadKey(string(input))
|
name := string(input)
|
||||||
if ok {
|
if column, ok := schema.Flows.Columns.Get(name); ok && !column.NotSelectable {
|
||||||
*qc = got
|
*qc = queryColumn(name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("unknown field")
|
return errors.New("unknown field")
|
||||||
}
|
}
|
||||||
|
|
||||||
// queryColumnsRequiringMainTable lists query columns only present in
|
|
||||||
// the main table. Also check filter/parser.peg.
|
|
||||||
var queryColumnsRequiringMainTable = map[queryColumn]struct{}{
|
|
||||||
queryColumnSrcAddr: {},
|
|
||||||
queryColumnDstAddr: {},
|
|
||||||
queryColumnSrcNetPrefix: {},
|
|
||||||
queryColumnDstNetPrefix: {},
|
|
||||||
queryColumnSrcPort: {},
|
|
||||||
queryColumnDstPort: {},
|
|
||||||
queryColumnDstASPath: {},
|
|
||||||
queryColumnDstCommunities: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireMainTable(qcs []queryColumn, qf queryFilter) bool {
|
func requireMainTable(qcs []queryColumn, qf queryFilter) bool {
|
||||||
if qf.MainTableRequired {
|
if qf.MainTableRequired {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, qc := range qcs {
|
for _, qc := range qcs {
|
||||||
if _, ok := queryColumnsRequiringMainTable[qc]; ok {
|
if column, ok := schema.Flows.Columns.Get(string(qc)); ok && column.MainOnly {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,21 +81,21 @@ func (qf *queryFilter) UnmarshalText(input []byte) error {
|
|||||||
func (qc queryColumn) toSQLSelect() string {
|
func (qc queryColumn) toSQLSelect() string {
|
||||||
var strValue string
|
var strValue string
|
||||||
switch qc {
|
switch qc {
|
||||||
case queryColumnExporterAddress, queryColumnSrcAddr, queryColumnDstAddr:
|
case "ExporterAddress", "SrcAddr", "DstAddr":
|
||||||
strValue = fmt.Sprintf("replaceRegexpOne(IPv6NumToString(%s), '^::ffff:', '')", qc)
|
strValue = fmt.Sprintf("replaceRegexpOne(IPv6NumToString(%s), '^::ffff:', '')", qc)
|
||||||
case queryColumnSrcAS, queryColumnDstAS, queryColumnDst1stAS, queryColumnDst2ndAS, queryColumnDst3rdAS:
|
case "SrcAS", "DstAS", "Dst1stAS", "Dst2ndAS", "Dst3rdAS":
|
||||||
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
|
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
|
||||||
qc, qc)
|
qc, qc)
|
||||||
case queryColumnEType:
|
case "EType":
|
||||||
strValue = fmt.Sprintf(`if(EType = %d, 'IPv4', if(EType = %d, 'IPv6', '???'))`,
|
strValue = fmt.Sprintf(`if(EType = %d, 'IPv4', if(EType = %d, 'IPv6', '???'))`,
|
||||||
helpers.ETypeIPv4, helpers.ETypeIPv6)
|
helpers.ETypeIPv4, helpers.ETypeIPv6)
|
||||||
case queryColumnProto:
|
case "Proto":
|
||||||
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
|
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
|
||||||
case queryColumnInIfSpeed, queryColumnOutIfSpeed, queryColumnSrcPort, queryColumnDstPort, queryColumnForwardingStatus, queryColumnInIfBoundary, queryColumnOutIfBoundary:
|
case "InIfSpeed", "OutIfSpeed", "SrcPort", "DstPort", "ForwardingStatus", "InIfBoundary", "OutIfBoundary":
|
||||||
strValue = fmt.Sprintf("toString(%s)", qc)
|
strValue = fmt.Sprintf("toString(%s)", qc)
|
||||||
case queryColumnDstASPath:
|
case "DstASPath":
|
||||||
strValue = `arrayStringConcat(DstASPath, ' ')`
|
strValue = `arrayStringConcat(DstASPath, ' ')`
|
||||||
case queryColumnDstCommunities:
|
case "DstCommunities":
|
||||||
strValue = `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`
|
strValue = `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`
|
||||||
default:
|
default:
|
||||||
strValue = qc.String()
|
strValue = qc.String()
|
||||||
@@ -122,19 +105,15 @@ func (qc queryColumn) toSQLSelect() string {
|
|||||||
|
|
||||||
// reverseDirection reverse the direction of a column (src/dst, in/out)
|
// reverseDirection reverse the direction of a column (src/dst, in/out)
|
||||||
func (qc queryColumn) reverseDirection() queryColumn {
|
func (qc queryColumn) reverseDirection() queryColumn {
|
||||||
value, ok := queryColumnMap.LoadKey(filter.ReverseColumnDirection(qc.String()))
|
return queryColumn(filter.ReverseColumnDirection(string(qc)))
|
||||||
if !ok {
|
|
||||||
panic("unknown reverse column")
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixQueryColumnName fix capitalization of the provided column name
|
// fixQueryColumnName fix capitalization of the provided column name
|
||||||
func fixQueryColumnName(name string) string {
|
func fixQueryColumnName(name string) string {
|
||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
for _, target := range queryColumnMap.Values() {
|
for _, k := range schema.Flows.Columns.Keys() {
|
||||||
if strings.ToLower(target) == name {
|
if strings.ToLower(k) == name {
|
||||||
return target
|
return k
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Free Mobile
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
package console
|
|
||||||
|
|
||||||
import "akvorado/common/helpers/bimap"
|
|
||||||
|
|
||||||
const (
|
|
||||||
queryColumnExporterAddress queryColumn = iota + 1
|
|
||||||
queryColumnExporterName
|
|
||||||
queryColumnExporterGroup
|
|
||||||
queryColumnExporterRole
|
|
||||||
queryColumnExporterSite
|
|
||||||
queryColumnExporterRegion
|
|
||||||
queryColumnExporterTenant
|
|
||||||
queryColumnSrcAS
|
|
||||||
queryColumnSrcNetName
|
|
||||||
queryColumnSrcNetRole
|
|
||||||
queryColumnSrcNetSite
|
|
||||||
queryColumnSrcNetRegion
|
|
||||||
queryColumnSrcNetTenant
|
|
||||||
queryColumnSrcCountry
|
|
||||||
queryColumnInIfName
|
|
||||||
queryColumnInIfDescription
|
|
||||||
queryColumnInIfSpeed
|
|
||||||
queryColumnInIfConnectivity
|
|
||||||
queryColumnInIfProvider
|
|
||||||
queryColumnInIfBoundary
|
|
||||||
queryColumnEType
|
|
||||||
queryColumnProto
|
|
||||||
queryColumnSrcPort
|
|
||||||
queryColumnSrcAddr
|
|
||||||
queryColumnSrcNetPrefix
|
|
||||||
queryColumnDstAS
|
|
||||||
queryColumnDstASPath
|
|
||||||
queryColumnDst1stAS
|
|
||||||
queryColumnDst2ndAS
|
|
||||||
queryColumnDst3rdAS
|
|
||||||
queryColumnDstCommunities
|
|
||||||
queryColumnDstNetName
|
|
||||||
queryColumnDstNetRole
|
|
||||||
queryColumnDstNetSite
|
|
||||||
queryColumnDstNetRegion
|
|
||||||
queryColumnDstNetTenant
|
|
||||||
queryColumnDstCountry
|
|
||||||
queryColumnOutIfName
|
|
||||||
queryColumnOutIfDescription
|
|
||||||
queryColumnOutIfSpeed
|
|
||||||
queryColumnOutIfConnectivity
|
|
||||||
queryColumnOutIfProvider
|
|
||||||
queryColumnOutIfBoundary
|
|
||||||
queryColumnDstAddr
|
|
||||||
queryColumnDstNetPrefix
|
|
||||||
queryColumnDstPort
|
|
||||||
queryColumnForwardingStatus
|
|
||||||
queryColumnPacketSizeBucket
|
|
||||||
)
|
|
||||||
|
|
||||||
var queryColumnMap = bimap.New(map[queryColumn]string{
|
|
||||||
queryColumnExporterAddress: "ExporterAddress",
|
|
||||||
queryColumnExporterName: "ExporterName",
|
|
||||||
queryColumnExporterGroup: "ExporterGroup",
|
|
||||||
queryColumnExporterRole: "ExporterRole",
|
|
||||||
queryColumnExporterSite: "ExporterSite",
|
|
||||||
queryColumnExporterRegion: "ExporterRegion",
|
|
||||||
queryColumnExporterTenant: "ExporterTenant",
|
|
||||||
queryColumnSrcAddr: "SrcAddr",
|
|
||||||
queryColumnDstAddr: "DstAddr",
|
|
||||||
queryColumnSrcNetPrefix: "SrcNetPrefix",
|
|
||||||
queryColumnDstNetPrefix: "DstNetPrefix",
|
|
||||||
queryColumnSrcAS: "SrcAS",
|
|
||||||
queryColumnDstAS: "DstAS",
|
|
||||||
queryColumnDstASPath: "DstASPath",
|
|
||||||
queryColumnDst1stAS: "Dst1stAS",
|
|
||||||
queryColumnDst2ndAS: "Dst2ndAS",
|
|
||||||
queryColumnDst3rdAS: "Dst3rdAS",
|
|
||||||
queryColumnDstCommunities: "DstCommunities",
|
|
||||||
queryColumnSrcNetName: "SrcNetName",
|
|
||||||
queryColumnDstNetName: "DstNetName",
|
|
||||||
queryColumnSrcNetRole: "SrcNetRole",
|
|
||||||
queryColumnDstNetRole: "DstNetRole",
|
|
||||||
queryColumnSrcNetSite: "SrcNetSite",
|
|
||||||
queryColumnDstNetSite: "DstNetSite",
|
|
||||||
queryColumnSrcNetRegion: "SrcNetRegion",
|
|
||||||
queryColumnDstNetRegion: "DstNetRegion",
|
|
||||||
queryColumnSrcNetTenant: "SrcNetTenant",
|
|
||||||
queryColumnDstNetTenant: "DstNetTenant",
|
|
||||||
queryColumnSrcCountry: "SrcCountry",
|
|
||||||
queryColumnDstCountry: "DstCountry",
|
|
||||||
queryColumnInIfName: "InIfName",
|
|
||||||
queryColumnOutIfName: "OutIfName",
|
|
||||||
queryColumnInIfDescription: "InIfDescription",
|
|
||||||
queryColumnOutIfDescription: "OutIfDescription",
|
|
||||||
queryColumnInIfSpeed: "InIfSpeed",
|
|
||||||
queryColumnOutIfSpeed: "OutIfSpeed",
|
|
||||||
queryColumnInIfConnectivity: "InIfConnectivity",
|
|
||||||
queryColumnOutIfConnectivity: "OutIfConnectivity",
|
|
||||||
queryColumnInIfProvider: "InIfProvider",
|
|
||||||
queryColumnOutIfProvider: "OutIfProvider",
|
|
||||||
queryColumnInIfBoundary: "InIfBoundary",
|
|
||||||
queryColumnOutIfBoundary: "OutIfBoundary",
|
|
||||||
queryColumnEType: "EType",
|
|
||||||
queryColumnProto: "Proto",
|
|
||||||
queryColumnSrcPort: "SrcPort",
|
|
||||||
queryColumnDstPort: "DstPort",
|
|
||||||
queryColumnForwardingStatus: "ForwardingStatus",
|
|
||||||
queryColumnPacketSizeBucket: "PacketSizeBucket",
|
|
||||||
})
|
|
||||||
@@ -16,14 +16,14 @@ func TestRequireMainTable(t *testing.T) {
|
|||||||
Expected bool
|
Expected bool
|
||||||
}{
|
}{
|
||||||
{[]queryColumn{}, queryFilter{}, false},
|
{[]queryColumn{}, queryFilter{}, false},
|
||||||
{[]queryColumn{queryColumnSrcAS}, queryFilter{}, false},
|
{[]queryColumn{"SrcAS"}, queryFilter{}, false},
|
||||||
{[]queryColumn{queryColumnExporterAddress}, queryFilter{}, false},
|
{[]queryColumn{"ExporterAddress"}, queryFilter{}, false},
|
||||||
{[]queryColumn{queryColumnSrcPort}, queryFilter{}, true},
|
{[]queryColumn{"SrcPort"}, queryFilter{}, true},
|
||||||
{[]queryColumn{queryColumnSrcAddr}, queryFilter{}, true},
|
{[]queryColumn{"SrcAddr"}, queryFilter{}, true},
|
||||||
{[]queryColumn{queryColumnDstPort}, queryFilter{}, true},
|
{[]queryColumn{"DstPort"}, queryFilter{}, true},
|
||||||
{[]queryColumn{queryColumnDstAddr}, queryFilter{}, true},
|
{[]queryColumn{"DstAddr"}, queryFilter{}, true},
|
||||||
{[]queryColumn{queryColumnSrcAS, queryColumnDstAddr}, queryFilter{}, true},
|
{[]queryColumn{"SrcAS", "DstAddr"}, queryFilter{}, true},
|
||||||
{[]queryColumn{queryColumnDstAddr, queryColumnSrcAS}, queryFilter{}, true},
|
{[]queryColumn{"DstAddr", "SrcAS"}, queryFilter{}, true},
|
||||||
{[]queryColumn{}, queryFilter{MainTableRequired: true}, true},
|
{[]queryColumn{}, queryFilter{MainTableRequired: true}, true},
|
||||||
}
|
}
|
||||||
for idx, tc := range cases {
|
for idx, tc := range cases {
|
||||||
@@ -34,40 +34,65 @@ func TestRequireMainTable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalQueryColumn(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Input string
|
||||||
|
Expected string
|
||||||
|
Error bool
|
||||||
|
}{
|
||||||
|
{"DstAddr", "DstAddr", false},
|
||||||
|
{"TimeReceived", "", true},
|
||||||
|
{"Nothing", "", true},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
var qc queryColumn
|
||||||
|
err := qc.UnmarshalText([]byte(tc.Input))
|
||||||
|
if err != nil && !tc.Error {
|
||||||
|
t.Fatalf("UnmarshalText(%q) error:\n%+v", tc.Input, err)
|
||||||
|
}
|
||||||
|
if err == nil && tc.Error {
|
||||||
|
t.Fatalf("UnmarshalText(%q) did not error", tc.Input)
|
||||||
|
}
|
||||||
|
if diff := helpers.Diff(qc, tc.Expected); diff != "" {
|
||||||
|
t.Fatalf("UnmarshalText(%q) (-got, +want):\n%s", tc.Input, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQueryColumnSQLSelect(t *testing.T) {
|
func TestQueryColumnSQLSelect(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Input queryColumn
|
Input queryColumn
|
||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Input: queryColumnSrcAddr,
|
Input: "SrcAddr",
|
||||||
Expected: `replaceRegexpOne(IPv6NumToString(SrcAddr), '^::ffff:', '')`,
|
Expected: `replaceRegexpOne(IPv6NumToString(SrcAddr), '^::ffff:', '')`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnDstAS,
|
Input: "DstAS",
|
||||||
Expected: `concat(toString(DstAS), ': ', dictGetOrDefault('asns', 'name', DstAS, '???'))`,
|
Expected: `concat(toString(DstAS), ': ', dictGetOrDefault('asns', 'name', DstAS, '???'))`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnDst2ndAS,
|
Input: "Dst2ndAS",
|
||||||
Expected: `concat(toString(Dst2ndAS), ': ', dictGetOrDefault('asns', 'name', Dst2ndAS, '???'))`,
|
Expected: `concat(toString(Dst2ndAS), ': ', dictGetOrDefault('asns', 'name', Dst2ndAS, '???'))`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnProto,
|
Input: "Proto",
|
||||||
Expected: `dictGetOrDefault('protocols', 'name', Proto, '???')`,
|
Expected: `dictGetOrDefault('protocols', 'name', Proto, '???')`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnEType,
|
Input: "EType",
|
||||||
Expected: `if(EType = 2048, 'IPv4', if(EType = 34525, 'IPv6', '???'))`,
|
Expected: `if(EType = 2048, 'IPv4', if(EType = 34525, 'IPv6', '???'))`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnOutIfSpeed,
|
Input: "OutIfSpeed",
|
||||||
Expected: `toString(OutIfSpeed)`,
|
Expected: `toString(OutIfSpeed)`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnExporterName,
|
Input: "ExporterName",
|
||||||
Expected: `ExporterName`,
|
Expected: `ExporterName`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnPacketSizeBucket,
|
Input: "PacketSizeBucket",
|
||||||
Expected: `PacketSizeBucket`,
|
Expected: `PacketSizeBucket`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnDstASPath,
|
Input: "DstASPath",
|
||||||
Expected: `arrayStringConcat(DstASPath, ' ')`,
|
Expected: `arrayStringConcat(DstASPath, ' ')`,
|
||||||
}, {
|
}, {
|
||||||
Input: queryColumnDstCommunities,
|
Input: "DstCommunities",
|
||||||
Expected: `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`,
|
Expected: `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func TestSankeyQuerySQL(t *testing.T) {
|
|||||||
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{"SrcAS", "ExporterName"},
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "l3bps",
|
Units: "l3bps",
|
||||||
@@ -49,7 +49,7 @@ ORDER BY xps DESC
|
|||||||
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{"SrcAS", "ExporterName"},
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "l2bps",
|
Units: "l2bps",
|
||||||
@@ -74,7 +74,7 @@ ORDER BY xps DESC
|
|||||||
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{"SrcAS", "ExporterName"},
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Filter: queryFilter{},
|
Filter: queryFilter{},
|
||||||
Units: "pps",
|
Units: "pps",
|
||||||
@@ -98,7 +98,7 @@ ORDER BY xps DESC
|
|||||||
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{"SrcAS", "ExporterName"},
|
||||||
Limit: 10,
|
Limit: 10,
|
||||||
Filter: queryFilter{Filter: "DstCountry = 'FR'"},
|
Filter: queryFilter{Filter: "DstCountry = 'FR'"},
|
||||||
Units: "l3bps",
|
Units: "l3bps",
|
||||||
|
|||||||
Reference in New Issue
Block a user