mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
common/schema: namespace column members by subsystem
Otherwise, it will be a mess once we introduce IPFIX/sFlow specific fields
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration defines how we connect to a Clickhouse database
|
// Configuration defines how we connect to a ClickHouse database
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
// Servers define the list of clickhouse servers to connect to (with ports)
|
// Servers define the list of clickhouse servers to connect to (with ports)
|
||||||
Servers []string `validate:"min=1,dive,listen"`
|
Servers []string `validate:"min=1,dive,listen"`
|
||||||
@@ -23,7 +23,7 @@ type Configuration struct {
|
|||||||
DialTimeout time.Duration `validate:"min=100ms"`
|
DialTimeout time.Duration `validate:"min=100ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfiguration represents the default configuration for connecting to Clickhouse
|
// DefaultConfiguration represents the default configuration for connecting to ClickHouse
|
||||||
func DefaultConfiguration() Configuration {
|
func DefaultConfiguration() Configuration {
|
||||||
return Configuration{
|
return Configuration{
|
||||||
Servers: []string{"127.0.0.1:9000"},
|
Servers: []string{"127.0.0.1:9000"},
|
||||||
|
|||||||
@@ -10,106 +10,101 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String turns a column into a declaration for ClickHouse
|
// ClickHouseDefinition turns a column into a declaration for ClickHouse
|
||||||
func (column Column) String() string {
|
func (column Column) ClickHouseDefinition() string {
|
||||||
result := []string{fmt.Sprintf("`%s`", column.Name), column.Type}
|
result := []string{fmt.Sprintf("`%s`", column.Name), column.ClickHouseType}
|
||||||
if column.Codec != "" {
|
if column.ClickHouseCodec != "" {
|
||||||
result = append(result, fmt.Sprintf("CODEC(%s)", column.Codec))
|
result = append(result, fmt.Sprintf("CODEC(%s)", column.ClickHouseCodec))
|
||||||
}
|
}
|
||||||
if column.Alias != "" {
|
if column.ClickHouseAlias != "" {
|
||||||
result = append(result, fmt.Sprintf("ALIAS %s", column.Alias))
|
result = append(result, fmt.Sprintf("ALIAS %s", column.ClickHouseAlias))
|
||||||
}
|
}
|
||||||
return strings.Join(result, " ")
|
return strings.Join(result, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableOption is an option to alter the values returned by Table() and Columns().
|
// ClickHouseTableOption is an option to alter the values returned by ClickHouseCreateTable() and ClickHouseSelectColumns().
|
||||||
type TableOption int
|
type ClickHouseTableOption int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SkipMainOnlyColumns skips the columns for the main flows table only.
|
// ClickHouseSkipMainOnlyColumns skips the columns for the main flows table only.
|
||||||
SkipMainOnlyColumns TableOption = iota
|
ClickHouseSkipMainOnlyColumns ClickHouseTableOption = iota
|
||||||
// SkipGeneratedColumns skips the columns with a GenerateFrom value
|
// ClickHouseSkipGeneratedColumns skips the columns with a GenerateFrom value
|
||||||
SkipGeneratedColumns
|
ClickHouseSkipGeneratedColumns
|
||||||
// SkipTransformColumns skips the columns with a TransformFrom value
|
// ClickHouseSkipTransformColumns skips the columns with a TransformFrom value
|
||||||
SkipTransformColumns
|
ClickHouseSkipTransformColumns
|
||||||
// SkipAliasedColumns skips the columns with a Alias value
|
// ClickHouseSkipAliasedColumns skips the columns with a Alias value
|
||||||
SkipAliasedColumns
|
ClickHouseSkipAliasedColumns
|
||||||
// SkipTimeReceived skips the time received column
|
// ClickHouseSkipTimeReceived skips the time received column
|
||||||
SkipTimeReceived
|
ClickHouseSkipTimeReceived
|
||||||
// SkipNotDimension skips columns that cannot be used as a dimension
|
// ClickHouseUseTransformFromType uses the type from TransformFrom if any
|
||||||
SkipNotDimension
|
ClickHouseUseTransformFromType
|
||||||
// UseTransformFromType uses the type from TransformFrom if any
|
// ClickHouseSubstituteGenerates changes the column name to use the default generated value
|
||||||
UseTransformFromType
|
ClickHouseSubstituteGenerates
|
||||||
// SubstituteGenerates changes the column name to use the default generated value
|
// ClickHouseSubstituteTransforms changes the column name to use the transformed value
|
||||||
SubstituteGenerates
|
ClickHouseSubstituteTransforms
|
||||||
// SubstituteTransforms changes the column name to use the transformed value
|
|
||||||
SubstituteTransforms
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateTable returns the columns for the CREATE TABLE clause in ClickHouse.
|
// ClickHouseCreateTable returns the columns for the CREATE TABLE clause in ClickHouse.
|
||||||
func (schema Schema) CreateTable(options ...TableOption) string {
|
func (schema Schema) ClickHouseCreateTable(options ...ClickHouseTableOption) string {
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
schema.iterate(func(column Column) {
|
schema.clickhouseIterate(func(column Column) {
|
||||||
lines = append(lines, column.String())
|
lines = append(lines, column.ClickHouseDefinition())
|
||||||
}, options...)
|
}, options...)
|
||||||
return strings.Join(lines, ",\n")
|
return strings.Join(lines, ",\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectColumns returns the column for the SELECT clause in ClickHouse.
|
// ClickHouseSelectColumns returns the columns matching the options for use in SELECT
|
||||||
func (schema Schema) SelectColumns(options ...TableOption) []string {
|
func (schema Schema) ClickHouseSelectColumns(options ...ClickHouseTableOption) []string {
|
||||||
cols := []string{}
|
cols := []string{}
|
||||||
schema.iterate(func(column Column) {
|
schema.clickhouseIterate(func(column Column) {
|
||||||
cols = append(cols, column.Name)
|
cols = append(cols, column.Name)
|
||||||
}, options...)
|
}, options...)
|
||||||
return cols
|
return cols
|
||||||
}
|
}
|
||||||
|
|
||||||
func (schema Schema) iterate(fn func(column Column), options ...TableOption) {
|
func (schema Schema) clickhouseIterate(fn func(column Column), options ...ClickHouseTableOption) {
|
||||||
for pair := schema.Columns.Front(); pair != nil; pair = pair.Next() {
|
for pair := schema.Columns.Front(); pair != nil; pair = pair.Next() {
|
||||||
column := pair.Value
|
column := pair.Value
|
||||||
if slices.Contains(options, SkipTimeReceived) && column.Name == "TimeReceived" {
|
if slices.Contains(options, ClickHouseSkipTimeReceived) && column.Name == "TimeReceived" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SkipMainOnlyColumns) && column.MainOnly {
|
if slices.Contains(options, ClickHouseSkipMainOnlyColumns) && column.MainOnly {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SkipGeneratedColumns) && column.GenerateFrom != "" {
|
if slices.Contains(options, ClickHouseSkipGeneratedColumns) && column.ClickHouseGenerateFrom != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SkipTransformColumns) && column.TransformFrom != nil {
|
if slices.Contains(options, ClickHouseSkipTransformColumns) && column.ClickHouseTransformFrom != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SkipAliasedColumns) && column.Alias != "" {
|
if slices.Contains(options, ClickHouseSkipAliasedColumns) && column.ClickHouseAlias != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SkipNotDimension) && column.NotSelectable {
|
if slices.Contains(options, ClickHouseUseTransformFromType) && column.ClickHouseTransformFrom != nil {
|
||||||
continue
|
for _, ocol := range column.ClickHouseTransformFrom {
|
||||||
}
|
|
||||||
if slices.Contains(options, UseTransformFromType) && column.TransformFrom != nil {
|
|
||||||
for _, ocol := range column.TransformFrom {
|
|
||||||
// We assume we only need to use name/type
|
// We assume we only need to use name/type
|
||||||
column.Name = ocol.Name
|
column.Name = ocol.Name
|
||||||
column.Type = ocol.Type
|
column.ClickHouseType = ocol.ClickHouseType
|
||||||
fn(column)
|
fn(column)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SubstituteGenerates) && column.GenerateFrom != "" {
|
if slices.Contains(options, ClickHouseSubstituteGenerates) && column.ClickHouseGenerateFrom != "" {
|
||||||
column.Name = fmt.Sprintf("%s AS %s", column.GenerateFrom, column.Name)
|
column.Name = fmt.Sprintf("%s AS %s", column.ClickHouseGenerateFrom, column.Name)
|
||||||
}
|
}
|
||||||
if slices.Contains(options, SubstituteTransforms) && column.TransformFrom != nil {
|
if slices.Contains(options, ClickHouseSubstituteTransforms) && column.ClickHouseTransformFrom != nil {
|
||||||
column.Name = fmt.Sprintf("%s AS %s", column.TransformTo, column.Name)
|
column.Name = fmt.Sprintf("%s AS %s", column.ClickHouseTransformTo, column.Name)
|
||||||
}
|
}
|
||||||
fn(column)
|
fn(column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortingKeys returns the list of sorting keys, prefixed by the primary keys.
|
// ClickHouseSortingKeys returns the list of sorting keys, prefixed by the primary keys.
|
||||||
func (schema Schema) SortingKeys() []string {
|
func (schema Schema) ClickHouseSortingKeys() []string {
|
||||||
cols := append([]string{}, schema.PrimaryKeys...)
|
cols := append([]string{}, schema.ClickHousePrimaryKeys...)
|
||||||
for pair := schema.Columns.Front(); pair != nil; pair = pair.Next() {
|
for pair := schema.Columns.Front(); pair != nil; pair = pair.Next() {
|
||||||
column := pair.Value
|
column := pair.Value
|
||||||
if column.NotSortingKey || column.MainOnly {
|
if column.ClickHouseNotSortingKey || column.MainOnly {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !slices.Contains(cols, column.Name) {
|
if !slices.Contains(cols, column.Name) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// will be duplicated as Dst/OutIf during init. That's not the case for columns
|
// will be duplicated as Dst/OutIf during init. That's not the case for columns
|
||||||
// in `PrimaryKeys'.
|
// in `PrimaryKeys'.
|
||||||
var Flows = Schema{
|
var Flows = Schema{
|
||||||
PrimaryKeys: []string{
|
ClickHousePrimaryKeys: []string{
|
||||||
"TimeReceived",
|
"TimeReceived",
|
||||||
"ExporterAddress",
|
"ExporterAddress",
|
||||||
"EType",
|
"EType",
|
||||||
@@ -28,113 +28,113 @@ var Flows = Schema{
|
|||||||
},
|
},
|
||||||
Columns: buildMapFromColumns([]Column{
|
Columns: buildMapFromColumns([]Column{
|
||||||
{
|
{
|
||||||
Name: "TimeReceived",
|
Name: "TimeReceived",
|
||||||
Type: "DateTime",
|
ClickHouseType: "DateTime",
|
||||||
Codec: "DoubleDelta, LZ4",
|
ClickHouseCodec: "DoubleDelta, LZ4",
|
||||||
NotSelectable: true,
|
ConsoleNotDimension: true,
|
||||||
},
|
},
|
||||||
{Name: "SamplingRate", Type: "UInt64", NotSelectable: true},
|
{Name: "SamplingRate", ClickHouseType: "UInt64", ConsoleNotDimension: true},
|
||||||
{Name: "ExporterAddress", Type: "LowCardinality(IPv6)"},
|
{Name: "ExporterAddress", ClickHouseType: "LowCardinality(IPv6)"},
|
||||||
{Name: "ExporterName", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterName", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "ExporterGroup", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterGroup", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "ExporterRole", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterRole", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "ExporterSite", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterSite", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "ExporterRegion", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterRegion", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "ExporterTenant", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "ExporterTenant", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{
|
{
|
||||||
Name: "SrcAddr",
|
Name: "SrcAddr",
|
||||||
Type: "IPv6",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "IPv6",
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetMask",
|
Name: "SrcNetMask",
|
||||||
Type: "UInt8",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "UInt8",
|
||||||
NotSelectable: true,
|
ConsoleNotDimension: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetPrefix",
|
Name: "SrcNetPrefix",
|
||||||
Type: "String",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "String",
|
||||||
Alias: `CASE
|
ClickHouseAlias: `CASE
|
||||||
WHEN EType = 0x800 THEN concat(replaceRegexpOne(IPv6CIDRToRange(SrcAddr, (96 + SrcNetMask)::UInt8).1::String, '^::ffff:', ''), '/', SrcNetMask::String)
|
WHEN EType = 0x800 THEN concat(replaceRegexpOne(IPv6CIDRToRange(SrcAddr, (96 + SrcNetMask)::UInt8).1::String, '^::ffff:', ''), '/', SrcNetMask::String)
|
||||||
WHEN EType = 0x86dd THEN concat(IPv6CIDRToRange(SrcAddr, SrcNetMask).1::String, '/', SrcNetMask::String)
|
WHEN EType = 0x86dd THEN concat(IPv6CIDRToRange(SrcAddr, SrcNetMask).1::String, '/', SrcNetMask::String)
|
||||||
ELSE ''
|
ELSE ''
|
||||||
END`,
|
END`,
|
||||||
},
|
},
|
||||||
{Name: "SrcAS", Type: "UInt32"},
|
{Name: "SrcAS", ClickHouseType: "UInt32"},
|
||||||
{
|
{
|
||||||
Name: "SrcNetName",
|
Name: "SrcNetName",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
GenerateFrom: "dictGetOrDefault('networks', 'name', SrcAddr, '')",
|
ClickHouseGenerateFrom: "dictGetOrDefault('networks', 'name', SrcAddr, '')",
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetRole",
|
Name: "SrcNetRole",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
GenerateFrom: "dictGetOrDefault('networks', 'role', SrcAddr, '')",
|
ClickHouseGenerateFrom: "dictGetOrDefault('networks', 'role', SrcAddr, '')",
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetSite",
|
Name: "SrcNetSite",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
GenerateFrom: "dictGetOrDefault('networks', 'site', SrcAddr, '')",
|
ClickHouseGenerateFrom: "dictGetOrDefault('networks', 'site', SrcAddr, '')",
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetRegion",
|
Name: "SrcNetRegion",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
GenerateFrom: "dictGetOrDefault('networks', 'region', SrcAddr, '')",
|
ClickHouseGenerateFrom: "dictGetOrDefault('networks', 'region', SrcAddr, '')",
|
||||||
}, {
|
}, {
|
||||||
Name: "SrcNetTenant",
|
Name: "SrcNetTenant",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
GenerateFrom: "dictGetOrDefault('networks', 'tenant', SrcAddr, '')",
|
ClickHouseGenerateFrom: "dictGetOrDefault('networks', 'tenant', SrcAddr, '')",
|
||||||
},
|
},
|
||||||
{Name: "SrcCountry", Type: "FixedString(2)"},
|
{Name: "SrcCountry", ClickHouseType: "FixedString(2)"},
|
||||||
{
|
{
|
||||||
Name: "DstASPath",
|
Name: "DstASPath",
|
||||||
Type: "Array(UInt32)",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "Array(UInt32)",
|
||||||
}, {
|
}, {
|
||||||
Name: "Dst1stAS",
|
Name: "Dst1stAS",
|
||||||
Type: "UInt32",
|
ClickHouseType: "UInt32",
|
||||||
GenerateFrom: "c_DstASPath[1]",
|
ClickHouseGenerateFrom: "c_DstASPath[1]",
|
||||||
}, {
|
}, {
|
||||||
Name: "Dst2ndAS",
|
Name: "Dst2ndAS",
|
||||||
Type: "UInt32",
|
ClickHouseType: "UInt32",
|
||||||
GenerateFrom: "c_DstASPath[2]",
|
ClickHouseGenerateFrom: "c_DstASPath[2]",
|
||||||
}, {
|
}, {
|
||||||
Name: "Dst3rdAS",
|
Name: "Dst3rdAS",
|
||||||
Type: "UInt32",
|
ClickHouseType: "UInt32",
|
||||||
GenerateFrom: "c_DstASPath[3]",
|
ClickHouseGenerateFrom: "c_DstASPath[3]",
|
||||||
}, {
|
}, {
|
||||||
Name: "DstCommunities",
|
Name: "DstCommunities",
|
||||||
Type: "Array(UInt32)",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "Array(UInt32)",
|
||||||
}, {
|
}, {
|
||||||
Name: "DstLargeCommunities",
|
Name: "DstLargeCommunities",
|
||||||
Type: "Array(UInt128)",
|
MainOnly: true,
|
||||||
MainOnly: true,
|
ClickHouseType: "Array(UInt128)",
|
||||||
TransformFrom: []Column{
|
ClickHouseTransformFrom: []Column{
|
||||||
{Name: "DstLargeCommunities.ASN", Type: "Array(UInt32)"},
|
{Name: "DstLargeCommunities.ASN", ClickHouseType: "Array(UInt32)"},
|
||||||
{Name: "DstLargeCommunities.LocalData1", Type: "Array(UInt32)"},
|
{Name: "DstLargeCommunities.LocalData1", ClickHouseType: "Array(UInt32)"},
|
||||||
{Name: "DstLargeCommunities.LocalData2", Type: "Array(UInt32)"},
|
{Name: "DstLargeCommunities.LocalData2", ClickHouseType: "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`)",
|
ClickHouseTransformTo: "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,
|
ConsoleNotDimension: true,
|
||||||
},
|
},
|
||||||
{Name: "InIfName", Type: "LowCardinality(String)"},
|
{Name: "InIfName", ClickHouseType: "LowCardinality(String)"},
|
||||||
{Name: "InIfDescription", Type: "String", NotSortingKey: true},
|
{Name: "InIfDescription", ClickHouseType: "String", ClickHouseNotSortingKey: true},
|
||||||
{Name: "InIfSpeed", Type: "UInt32", NotSortingKey: true},
|
{Name: "InIfSpeed", ClickHouseType: "UInt32", ClickHouseNotSortingKey: true},
|
||||||
{Name: "InIfConnectivity", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "InIfConnectivity", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "InIfProvider", Type: "LowCardinality(String)", NotSortingKey: true},
|
{Name: "InIfProvider", ClickHouseType: "LowCardinality(String)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "InIfBoundary", Type: "Enum8('undefined' = 0, 'external' = 1, 'internal' = 2)", NotSortingKey: true},
|
{Name: "InIfBoundary", ClickHouseType: "Enum8('undefined' = 0, 'external' = 1, 'internal' = 2)", ClickHouseNotSortingKey: true},
|
||||||
{Name: "EType", Type: "UInt32"},
|
{Name: "EType", ClickHouseType: "UInt32"},
|
||||||
{Name: "Proto", Type: "UInt32"},
|
{Name: "Proto", ClickHouseType: "UInt32"},
|
||||||
{Name: "SrcPort", Type: "UInt32", MainOnly: true},
|
{Name: "SrcPort", ClickHouseType: "UInt32", MainOnly: true},
|
||||||
{Name: "Bytes", Type: "UInt64", NotSortingKey: true, NotSelectable: true},
|
{Name: "Bytes", ClickHouseType: "UInt64", ClickHouseNotSortingKey: true, ConsoleNotDimension: true},
|
||||||
{Name: "Packets", Type: "UInt64", NotSortingKey: true, NotSelectable: true},
|
{Name: "Packets", ClickHouseType: "UInt64", ClickHouseNotSortingKey: true, ConsoleNotDimension: true},
|
||||||
{
|
{
|
||||||
Name: "PacketSize",
|
Name: "PacketSize",
|
||||||
Type: "UInt64",
|
ClickHouseType: "UInt64",
|
||||||
Alias: "intDiv(Bytes, Packets)",
|
ClickHouseAlias: "intDiv(Bytes, Packets)",
|
||||||
NotSelectable: true,
|
ConsoleNotDimension: true,
|
||||||
}, {
|
}, {
|
||||||
Name: "PacketSizeBucket",
|
Name: "PacketSizeBucket",
|
||||||
Type: "LowCardinality(String)",
|
ClickHouseType: "LowCardinality(String)",
|
||||||
Alias: func() string {
|
ClickHouseAlias: func() string {
|
||||||
boundaries := []int{64, 128, 256, 512, 768, 1024, 1280, 1501,
|
boundaries := []int{64, 128, 256, 512, 768, 1024, 1280, 1501,
|
||||||
2048, 3072, 4096, 8192, 10240, 16384, 32768, 65536}
|
2048, 3072, 4096, 8192, 10240, 16384, 32768, 65536}
|
||||||
conditions := []string{}
|
conditions := []string{}
|
||||||
@@ -148,7 +148,7 @@ END`,
|
|||||||
return fmt.Sprintf("multiIf(%s)", strings.Join(conditions, ", "))
|
return fmt.Sprintf("multiIf(%s)", strings.Join(conditions, ", "))
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
{Name: "ForwardingStatus", Type: "UInt32"},
|
{Name: "ForwardingStatus", ClickHouseType: "UInt32"},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,18 +156,18 @@ func buildMapFromColumns(columns []Column) *orderedmap.OrderedMap[string, Column
|
|||||||
omap := orderedmap.NewOrderedMap[string, Column]()
|
omap := orderedmap.NewOrderedMap[string, Column]()
|
||||||
for _, column := range columns {
|
for _, column := range columns {
|
||||||
// Add non-main columns with an alias to NotSortingKey
|
// Add non-main columns with an alias to NotSortingKey
|
||||||
if !column.MainOnly && column.Alias != "" {
|
if !column.MainOnly && column.ClickHouseAlias != "" {
|
||||||
column.NotSortingKey = true
|
column.ClickHouseNotSortingKey = true
|
||||||
}
|
}
|
||||||
omap.Set(column.Name, column)
|
omap.Set(column.Name, column)
|
||||||
// Expand the schema Src → Dst and InIf → OutIf
|
// Expand the schema Src → Dst and InIf → OutIf
|
||||||
if strings.HasPrefix(column.Name, "Src") {
|
if strings.HasPrefix(column.Name, "Src") {
|
||||||
column.Name = fmt.Sprintf("Dst%s", column.Name[3:])
|
column.Name = fmt.Sprintf("Dst%s", column.Name[3:])
|
||||||
column.Alias = strings.ReplaceAll(column.Alias, "Src", "Dst")
|
column.ClickHouseAlias = strings.ReplaceAll(column.ClickHouseAlias, "Src", "Dst")
|
||||||
omap.Set(column.Name, column)
|
omap.Set(column.Name, column)
|
||||||
} else if strings.HasPrefix(column.Name, "InIf") {
|
} else if strings.HasPrefix(column.Name, "InIf") {
|
||||||
column.Name = fmt.Sprintf("OutIf%s", column.Name[4:])
|
column.Name = fmt.Sprintf("OutIf%s", column.Name[4:])
|
||||||
column.Alias = strings.ReplaceAll(column.Alias, "InIf", "OutIf")
|
column.ClickHouseAlias = strings.ReplaceAll(column.ClickHouseAlias, "InIf", "OutIf")
|
||||||
omap.Set(column.Name, column)
|
omap.Set(column.Name, column)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,11 +175,11 @@ func buildMapFromColumns(columns []Column) *orderedmap.OrderedMap[string, Column
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
for _, key := range Flows.PrimaryKeys {
|
for _, key := range Flows.ClickHousePrimaryKeys {
|
||||||
if column, ok := Flows.Columns.Get(key); !ok {
|
if column, ok := Flows.Columns.Get(key); !ok {
|
||||||
panic(fmt.Sprintf("primary key %q not a column", key))
|
panic(fmt.Sprintf("primary key %q not a column", key))
|
||||||
} else {
|
} else {
|
||||||
if column.NotSortingKey {
|
if column.ClickHouseNotSortingKey {
|
||||||
panic(fmt.Sprintf("primary key %q is marked as a non-sorting key", key))
|
panic(fmt.Sprintf("primary key %q is marked as a non-sorting key", key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Schema struct {
|
|||||||
|
|
||||||
// For ClickHouse. This is the set of primary keys (order is important and
|
// For ClickHouse. This is the set of primary keys (order is important and
|
||||||
// may not follow column order).
|
// may not follow column order).
|
||||||
PrimaryKeys []string
|
ClickHousePrimaryKeys []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column represents a column of data.
|
// Column represents a column of data.
|
||||||
@@ -29,14 +29,14 @@ type Column struct {
|
|||||||
// retrieved from the protobuf. `TransformFrom' and `TransformTo' work in
|
// retrieved from the protobuf. `TransformFrom' and `TransformTo' work in
|
||||||
// pairs. The first one is the set of column in the raw table while the
|
// pairs. The first one is the set of column in the raw table while the
|
||||||
// second one is how to transform it for the main table.
|
// second one is how to transform it for the main table.
|
||||||
Type string
|
ClickHouseType string
|
||||||
Codec string
|
ClickHouseCodec string
|
||||||
Alias string
|
ClickHouseAlias string
|
||||||
NotSortingKey bool
|
ClickHouseNotSortingKey bool
|
||||||
GenerateFrom string
|
ClickHouseGenerateFrom string
|
||||||
TransformFrom []Column
|
ClickHouseTransformFrom []Column
|
||||||
TransformTo string
|
ClickHouseTransformTo string
|
||||||
|
|
||||||
// For the console.
|
// For the console.
|
||||||
NotSelectable bool
|
ConsoleNotDimension bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,19 @@ func DefaultConfiguration() Configuration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Component) configHandlerFunc(gc *gin.Context) {
|
func (c *Component) configHandlerFunc(gc *gin.Context) {
|
||||||
|
dimensions := []string{}
|
||||||
|
for pair := schema.Flows.Columns.Front(); pair != nil; pair = pair.Next() {
|
||||||
|
column := pair.Value
|
||||||
|
if column.ConsoleNotDimension {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dimensions = append(dimensions, column.Name)
|
||||||
|
}
|
||||||
gc.JSON(http.StatusOK, gin.H{
|
gc.JSON(http.StatusOK, gin.H{
|
||||||
"version": c.config.Version,
|
"version": c.config.Version,
|
||||||
"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),
|
"dimensions": dimensions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ provided:
|
|||||||
`akvorado.yaml` configuration file.
|
`akvorado.yaml` configuration file.
|
||||||
- `asns` maps AS number to names (overriding the builtin ones)
|
- `asns` maps AS number to names (overriding the builtin ones)
|
||||||
- `orchestrator-url` defines the URL of the orchestrator to be used
|
- `orchestrator-url` defines the URL of the orchestrator to be used
|
||||||
by Clickhouse (autodetection when not specified)
|
by ClickHouse (autodetection when not specified)
|
||||||
|
|
||||||
The `resolutions` setting contains a list of resolutions. Each
|
The `resolutions` setting contains a list of resolutions. Each
|
||||||
resolution has two keys: `interval` and `ttl`. The first one is the
|
resolution has two keys: `interval` and `ttl`. The first one is the
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ func (c *Component) filterCompleteHandlerFunc(gc *gin.Context) {
|
|||||||
Detail: "ethernet type",
|
Detail: "ethernet type",
|
||||||
})
|
})
|
||||||
case "proto":
|
case "proto":
|
||||||
// Do not complete from Clickhouse, we want a subset of options
|
// Do not complete from ClickHouse, we want a subset of options
|
||||||
completions = append(completions,
|
completions = append(completions,
|
||||||
filterCompletion{"TCP", "protocol", true},
|
filterCompletion{"TCP", "protocol", true},
|
||||||
filterCompletion{"UDP", "protocol", true},
|
filterCompletion{"UDP", "protocol", true},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (qc queryColumn) String() string {
|
|||||||
}
|
}
|
||||||
func (qc *queryColumn) UnmarshalText(input []byte) error {
|
func (qc *queryColumn) UnmarshalText(input []byte) error {
|
||||||
name := string(input)
|
name := string(input)
|
||||||
if column, ok := schema.Flows.Columns.Get(name); ok && !column.NotSelectable {
|
if column, ok := schema.Flows.Columns.Get(name); ok && !column.ConsoleNotDimension {
|
||||||
*qc = queryColumn(name)
|
*qc = queryColumn(name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ type Configuration struct {
|
|||||||
// sources to be ready. 503 is returned when not.
|
// sources to be ready. 503 is returned when not.
|
||||||
NetworkSourcesTimeout time.Duration `validate:"min=0"`
|
NetworkSourcesTimeout time.Duration `validate:"min=0"`
|
||||||
// OrchestratorURL allows one to override URL to reach
|
// OrchestratorURL allows one to override URL to reach
|
||||||
// orchestrator from Clickhouse
|
// orchestrator from ClickHouse
|
||||||
OrchestratorURL string `validate:"isdefault|url"`
|
OrchestratorURL string `validate:"isdefault|url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,10 +200,10 @@ func (c *Component) createRawFlowsTable(ctx context.Context) error {
|
|||||||
gin.H{
|
gin.H{
|
||||||
"Database": c.config.Database,
|
"Database": c.config.Database,
|
||||||
"Table": tableName,
|
"Table": tableName,
|
||||||
"Schema": schema.Flows.CreateTable(
|
"Schema": schema.Flows.ClickHouseCreateTable(
|
||||||
schema.SkipGeneratedColumns,
|
schema.ClickHouseSkipGeneratedColumns,
|
||||||
schema.UseTransformFromType,
|
schema.ClickHouseUseTransformFromType,
|
||||||
schema.SkipAliasedColumns),
|
schema.ClickHouseSkipAliasedColumns),
|
||||||
"Engine": kafkaEngine,
|
"Engine": kafkaEngine,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -245,10 +245,10 @@ func (c *Component) createRawFlowsConsumerView(ctx context.Context) error {
|
|||||||
`{{ .With }} SELECT {{ .Columns }} FROM {{ .Database }}.{{ .Table }} WHERE length(_error) = 0`,
|
`{{ .With }} SELECT {{ .Columns }} FROM {{ .Database }}.{{ .Table }} WHERE length(_error) = 0`,
|
||||||
gin.H{
|
gin.H{
|
||||||
"With": "WITH arrayCompact(DstASPath) AS c_DstASPath",
|
"With": "WITH arrayCompact(DstASPath) AS c_DstASPath",
|
||||||
"Columns": strings.Join(schema.Flows.SelectColumns(
|
"Columns": strings.Join(schema.Flows.ClickHouseSelectColumns(
|
||||||
schema.SubstituteGenerates,
|
schema.ClickHouseSubstituteGenerates,
|
||||||
schema.SubstituteTransforms,
|
schema.ClickHouseSubstituteTransforms,
|
||||||
schema.SkipAliasedColumns), ", "),
|
schema.ClickHouseSkipAliasedColumns), ", "),
|
||||||
"Database": c.config.Database,
|
"Database": c.config.Database,
|
||||||
"Table": tableName,
|
"Table": tableName,
|
||||||
})
|
})
|
||||||
@@ -352,7 +352,7 @@ PARTITION BY toYYYYMMDDhhmmss(toStartOfInterval(TimeReceived, INTERVAL {{ .Parti
|
|||||||
ORDER BY (TimeReceived, ExporterAddress, InIfName, OutIfName)
|
ORDER BY (TimeReceived, ExporterAddress, InIfName, OutIfName)
|
||||||
TTL TimeReceived + toIntervalSecond({{ .TTL }})
|
TTL TimeReceived + toIntervalSecond({{ .TTL }})
|
||||||
`, gin.H{
|
`, gin.H{
|
||||||
"Schema": schema.Flows.CreateTable(),
|
"Schema": schema.Flows.ClickHouseCreateTable(),
|
||||||
"PartitionInterval": partitionInterval,
|
"PartitionInterval": partitionInterval,
|
||||||
"TTL": ttl,
|
"TTL": ttl,
|
||||||
})
|
})
|
||||||
@@ -366,10 +366,10 @@ ORDER BY ({{ .SortingKey }})
|
|||||||
TTL TimeReceived + toIntervalSecond({{ .TTL }})
|
TTL TimeReceived + toIntervalSecond({{ .TTL }})
|
||||||
`, gin.H{
|
`, gin.H{
|
||||||
"Table": tableName,
|
"Table": tableName,
|
||||||
"Schema": schema.Flows.CreateTable(schema.SkipMainOnlyColumns),
|
"Schema": schema.Flows.ClickHouseCreateTable(schema.ClickHouseSkipMainOnlyColumns),
|
||||||
"PartitionInterval": partitionInterval,
|
"PartitionInterval": partitionInterval,
|
||||||
"PrimaryKey": strings.Join(schema.Flows.PrimaryKeys, ", "),
|
"PrimaryKey": strings.Join(schema.Flows.ClickHousePrimaryKeys, ", "),
|
||||||
"SortingKey": strings.Join(schema.Flows.SortingKeys(), ", "),
|
"SortingKey": strings.Join(schema.Flows.ClickHouseSortingKeys(), ", "),
|
||||||
"TTL": ttl,
|
"TTL": ttl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -413,15 +413,15 @@ outer:
|
|||||||
for _, existingColumn := range existingColumns {
|
for _, existingColumn := range existingColumns {
|
||||||
if wantedColumn.Name == existingColumn.Name {
|
if wantedColumn.Name == existingColumn.Name {
|
||||||
// Do a few sanity checks
|
// Do a few sanity checks
|
||||||
if wantedColumn.Type != existingColumn.Type {
|
if wantedColumn.ClickHouseType != existingColumn.Type {
|
||||||
return fmt.Errorf("table %s, column %s has a non-matching type: %s vs %s",
|
return fmt.Errorf("table %s, column %s has a non-matching type: %s vs %s",
|
||||||
tableName, wantedColumn.Name, existingColumn.Type, wantedColumn.Type)
|
tableName, wantedColumn.Name, existingColumn.Type, wantedColumn.ClickHouseType)
|
||||||
}
|
}
|
||||||
if resolution.Interval > 0 && slices.Contains(schema.Flows.PrimaryKeys, wantedColumn.Name) && existingColumn.IsPrimaryKey == 0 {
|
if resolution.Interval > 0 && slices.Contains(schema.Flows.ClickHousePrimaryKeys, wantedColumn.Name) && existingColumn.IsPrimaryKey == 0 {
|
||||||
return fmt.Errorf("table %s, column %s should be a primary key, cannot change that",
|
return fmt.Errorf("table %s, column %s should be a primary key, cannot change that",
|
||||||
tableName, wantedColumn.Name)
|
tableName, wantedColumn.Name)
|
||||||
}
|
}
|
||||||
if resolution.Interval > 0 && !wantedColumn.NotSortingKey && existingColumn.IsSortingKey == 0 {
|
if resolution.Interval > 0 && !wantedColumn.ClickHouseNotSortingKey && existingColumn.IsSortingKey == 0 {
|
||||||
// That's something we can fix, but we need to drop it before recreating it
|
// That's something we can fix, but we need to drop it before recreating it
|
||||||
err := c.d.ClickHouse.Exec(ctx,
|
err := c.d.ClickHouse.Exec(ctx,
|
||||||
fmt.Sprintf("ALTER TABLE %s DROP COLUMN %s", tableName, existingColumn.Name))
|
fmt.Sprintf("ALTER TABLE %s DROP COLUMN %s", tableName, existingColumn.Name))
|
||||||
@@ -431,27 +431,27 @@ outer:
|
|||||||
}
|
}
|
||||||
// Schedule adding it back
|
// Schedule adding it back
|
||||||
modifications = append(modifications,
|
modifications = append(modifications,
|
||||||
fmt.Sprintf("ADD COLUMN %s AFTER %s", wantedColumn, previousColumn))
|
fmt.Sprintf("ADD COLUMN %s AFTER %s", wantedColumn.ClickHouseDefinition(), previousColumn))
|
||||||
}
|
}
|
||||||
previousColumn = wantedColumn.Name
|
previousColumn = wantedColumn.Name
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add the missing column. Only if not primary.
|
// Add the missing column. Only if not primary.
|
||||||
if resolution.Interval > 0 && slices.Contains(schema.Flows.PrimaryKeys, wantedColumn.Name) {
|
if resolution.Interval > 0 && slices.Contains(schema.Flows.ClickHousePrimaryKeys, wantedColumn.Name) {
|
||||||
return fmt.Errorf("table %s, column %s is missing but it is a primary key",
|
return fmt.Errorf("table %s, column %s is missing but it is a primary key",
|
||||||
tableName, wantedColumn.Name)
|
tableName, wantedColumn.Name)
|
||||||
}
|
}
|
||||||
c.r.Debug().Msgf("add missing column %s to %s", wantedColumn.Name, tableName)
|
c.r.Debug().Msgf("add missing column %s to %s", wantedColumn.Name, tableName)
|
||||||
modifications = append(modifications,
|
modifications = append(modifications,
|
||||||
fmt.Sprintf("ADD COLUMN %s AFTER %s", wantedColumn, previousColumn))
|
fmt.Sprintf("ADD COLUMN %s AFTER %s", wantedColumn.ClickHouseDefinition(), previousColumn))
|
||||||
previousColumn = wantedColumn.Name
|
previousColumn = wantedColumn.Name
|
||||||
}
|
}
|
||||||
if len(modifications) > 0 {
|
if len(modifications) > 0 {
|
||||||
// Also update ORDER BY
|
// Also update ORDER BY
|
||||||
if resolution.Interval > 0 {
|
if resolution.Interval > 0 {
|
||||||
modifications = append(modifications,
|
modifications = append(modifications,
|
||||||
fmt.Sprintf("MODIFY ORDER BY (%s)", strings.Join(schema.Flows.SortingKeys(), ", ")))
|
fmt.Sprintf("MODIFY ORDER BY (%s)", strings.Join(schema.Flows.ClickHouseSortingKeys(), ", ")))
|
||||||
}
|
}
|
||||||
c.r.Info().Msgf("apply %d modifications to %s", len(modifications), tableName)
|
c.r.Info().Msgf("apply %d modifications to %s", len(modifications), tableName)
|
||||||
if resolution.Interval > 0 {
|
if resolution.Interval > 0 {
|
||||||
@@ -501,10 +501,10 @@ SELECT
|
|||||||
FROM {{ .Database }}.flows`, gin.H{
|
FROM {{ .Database }}.flows`, gin.H{
|
||||||
"Database": c.config.Database,
|
"Database": c.config.Database,
|
||||||
"Seconds": uint64(resolution.Interval.Seconds()),
|
"Seconds": uint64(resolution.Interval.Seconds()),
|
||||||
"Columns": strings.Join(schema.Flows.SelectColumns(
|
"Columns": strings.Join(schema.Flows.ClickHouseSelectColumns(
|
||||||
schema.SkipTimeReceived,
|
schema.ClickHouseSkipTimeReceived,
|
||||||
schema.SkipMainOnlyColumns,
|
schema.ClickHouseSkipMainOnlyColumns,
|
||||||
schema.SkipAliasedColumns), ",\n "),
|
schema.ClickHouseSkipAliasedColumns), ",\n "),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot build select statement for consumer %s: %w", viewName, err)
|
return fmt.Errorf("cannot build select statement for consumer %s: %w", viewName, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user