From 1bcf5e2d918eed4eaef6b96f305733df65ebfeec Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Tue, 11 Jun 2024 00:59:07 -0400 Subject: [PATCH] Track more information in histograms/distributions about inputs and estimated quantiles. Closes https://github.com/oxidecomputer/omicron/issues/4913. This is part 1 of tracking more information within a histogram in order to compute min, max, means, std_dev, and estimates for p50, p90, p99 and how to store/retrieve the associated info from Clickhouse. --- Cargo.lock | 7 + Cargo.toml | 1 + openapi/nexus.json | 692 ++++++++++++++++++- oximeter/db/schema/replicated/5/up.sql | 340 +++++++++ oximeter/db/schema/replicated/db-init.sql | 360 +++++++++- oximeter/db/schema/single-node/5/up.sql | 170 +++++ oximeter/db/schema/single-node/db-init.sql | 180 ++++- oximeter/db/src/bin/oxdb/oxql.rs | 103 ++- oximeter/db/src/bin/oxdb/sql.rs | 39 +- oximeter/db/src/client/mod.rs | 16 +- oximeter/db/src/client/oxql.rs | 9 +- oximeter/db/src/model.rs | 279 ++++++-- oximeter/db/src/oxql/ast/table_ops/filter.rs | 32 + oximeter/db/src/oxql/point.rs | 238 ++++++- oximeter/db/src/oxql/query/mod.rs | 35 + oximeter/db/src/sql/mod.rs | 26 +- oximeter/instruments/src/http.rs | 6 +- oximeter/oximeter/Cargo.toml | 1 + oximeter/oximeter/src/histogram.rs | 502 +++++++++++--- oximeter/oximeter/src/lib.rs | 3 + oximeter/oximeter/src/quantile.rs | 405 +++++++++++ oximeter/oximeter/src/test_util.rs | 25 +- 22 files changed, 3184 insertions(+), 285 deletions(-) create mode 100644 oximeter/db/schema/replicated/5/up.sql create mode 100644 oximeter/db/schema/single-node/5/up.sql create mode 100644 oximeter/oximeter/src/quantile.rs diff --git a/Cargo.lock b/Cargo.lock index af3738e0a02..4163cf030f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2435,6 +2435,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "flume" version = "0.11.0" @@ -6221,6 +6227,7 @@ dependencies = [ "approx", "bytes", "chrono", + "float-ord", "num", "omicron-common", "omicron-workspace-hack", diff --git a/Cargo.toml b/Cargo.toml index d461e0585ad..23222ae287f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -273,6 +273,7 @@ expectorate = "1.1.0" fatfs = "0.3.6" filetime = "0.2.23" flate2 = "1.0.30" +float-ord = "0.3.2" flume = "0.11.0" foreign-types = "0.3.2" fs-err = "2.11.0" diff --git a/openapi/nexus.json b/openapi/nexus.json index 01ec9aeb569..f5ef42e1e0d 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -12299,7 +12299,7 @@ ] }, "Distributiondouble": { - "description": "A distribution is a sequence of bins and counts in those bins.", + "description": "A distribution is a sequence of bins and counts in those bins, and some statistical information tracked to compute the mean, standard deviation, and quantile estimates.", "type": "object", "properties": { "bins": { @@ -12316,15 +12316,47 @@ "format": "uint64", "minimum": 0 } + }, + "max": { + "type": "number", + "format": "double" + }, + "min": { + "type": "number", + "format": "double" + }, + "p50": { + "$ref": "#/components/schemas/Quantile" + }, + "p90": { + "$ref": "#/components/schemas/Quantile" + }, + "p99": { + "$ref": "#/components/schemas/Quantile" + }, + "sum_of_samples": { + "type": "number", + "format": "double" + }, + "sum_of_squares": { + "type": "number", + "format": "double" } }, "required": [ "bins", - "counts" + "counts", + "max", + "min", + "p50", + "p90", + "p99", + "sum_of_samples", + "sum_of_squares" ] }, "Distributionint64": { - "description": "A distribution is a sequence of bins and counts in those bins.", + "description": "A distribution is a sequence of bins and counts in those bins, and some statistical information tracked to compute the mean, standard deviation, and quantile estimates.", "type": "object", "properties": { "bins": { @@ -12341,11 +12373,43 @@ "format": "uint64", "minimum": 0 } + }, + "max": { + "type": "integer", + "format": "int64" + }, + "min": { + "type": "integer", + "format": "int64" + }, + "p50": { + "$ref": "#/components/schemas/Quantile" + }, + "p90": { + "$ref": "#/components/schemas/Quantile" + }, + "p99": { + "$ref": "#/components/schemas/Quantile" + }, + "sum_of_samples": { + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "type": "integer", + "format": "int64" } }, "required": [ "bins", - "counts" + "counts", + "max", + "min", + "p50", + "p90", + "p99", + "sum_of_samples", + "sum_of_squares" ] }, "EphemeralIpCreate": { @@ -13102,25 +13166,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Bindouble" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "number", + "format": "double" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "number", + "format": "double" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "number", + "format": "double" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "number", + "format": "double" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramfloat": { @@ -13128,25 +13246,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binfloat" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "number", + "format": "float" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "number", + "format": "float" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "number", + "format": "double" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "number", + "format": "double" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramint16": { @@ -13154,25 +13326,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binint16" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "int16" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "int16" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramint32": { @@ -13180,25 +13406,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binint32" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "int32" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "int32" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramint64": { @@ -13206,25 +13486,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binint64" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramint8": { @@ -13232,25 +13566,79 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binint8" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "int8" + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "int8" + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramuint16": { @@ -13258,25 +13646,81 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binuint16" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramuint32": { @@ -13284,25 +13728,81 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binuint32" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramuint64": { @@ -13310,25 +13810,81 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binuint64" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Histogramuint8": { @@ -13336,25 +13892,81 @@ "type": "object", "properties": { "bins": { + "description": "The bins of the histogram.", "type": "array", "items": { "$ref": "#/components/schemas/Binuint8" } }, + "max": { + "description": "The maximum value of all samples in the histogram.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "min": { + "description": "The minimum value of all samples in the histogram.", + "type": "integer", + "format": "uint8", + "minimum": 0 + }, "n_samples": { + "description": "The total number of samples in the histogram.", "type": "integer", "format": "uint64", "minimum": 0 }, + "p50": { + "description": "p50 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p90": { + "description": "p95 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, + "p99": { + "description": "p99 Quantile", + "allOf": [ + { + "$ref": "#/components/schemas/Quantile" + } + ] + }, "start_time": { + "description": "The start time of the histogram.", "type": "string", "format": "date-time" + }, + "sum_of_samples": { + "description": "The sum of all samples in the histogram.", + "type": "integer", + "format": "int64" + }, + "sum_of_squares": { + "description": "The sum of the squares of all samples in the histogram.", + "type": "integer", + "format": "int64" } }, "required": [ "bins", + "max", + "min", "n_samples", - "start_time" + "p50", + "p90", + "p99", + "start_time", + "sum_of_samples", + "sum_of_squares" ] }, "Hostname": { @@ -15746,6 +16358,58 @@ } } }, + "Quantile": { + "description": "Structure for estimating the p-quantile of a population.\n\nThis is based on the P² algorithm for estimating quantiles using constant space.\n\nThe algorithm consists of maintaining five markers: the minimum, the p/2-, p-, and (1 + p)/2 quantiles, and the maximum.\n\nRead for more.", + "type": "object", + "properties": { + "desired_marker_increments": { + "description": "The increment for the desired marker positions.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "minItems": 5, + "maxItems": 5 + }, + "desired_marker_positions": { + "description": "The desired marker positions.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "minItems": 5, + "maxItems": 5 + }, + "marker_heights": { + "description": "The heights of the markers.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "minItems": 5, + "maxItems": 5 + }, + "marker_positions": { + "description": "The positions of the markers.", + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "minItems": 5, + "maxItems": 5 + } + }, + "required": [ + "desired_marker_increments", + "desired_marker_positions", + "marker_heights", + "marker_positions" + ] + }, "Rack": { "description": "View of an Rack", "type": "object", diff --git a/oximeter/db/schema/replicated/5/up.sql b/oximeter/db/schema/replicated/5/up.sql new file mode 100644 index 00000000000..8cbe4a18d45 --- /dev/null +++ b/oximeter/db/schema/replicated/5/up.sql @@ -0,0 +1,340 @@ +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN min Int8; +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN max Int8; +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN min Int8; +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN max Int8; +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN min Int16; +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN max Int16; +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN min Int16; +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN max Int16; +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + + +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN min Int32; +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN max Int32; +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN min Int32; +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN max Int32; +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN min Int64; +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN max Int64; +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN min Int64; +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN max Int64; +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN min UInt8; +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN max UInt8; +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN min UInt8; +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN max UInt8; +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN min UInt16; +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN max UInt16; +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN min UInt16; +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN max UInt16; +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN min UInt32; +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN max UInt32; +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN min UInt32; +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN max UInt32; +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN min UNINT64; +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN max UNINT64; +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN min UNINT64; +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN max UNINT64; +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN min Float32; +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN max Float32; +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN min Float32; +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN max Float32; +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN min Float64; +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN max Float64; +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64_local ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN min Float64; +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN max Float64; +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ON CLUSTER oximeter_cluster ADD COLUMN p99_desired_marker_increments Array(Float64); diff --git a/oximeter/db/schema/replicated/db-init.sql b/oximeter/db/schema/replicated/db-init.sql index 27df02b709c..ca2da18016a 100644 --- a/oximeter/db/schema/replicated/db-init.sql +++ b/oximeter/db/schema/replicated/db-init.sql @@ -374,7 +374,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami8_local ON CLUSTER ox start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int8), - counts Array(UInt64) + counts Array(UInt64), + min Int8, + max Int8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogrami8_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -387,7 +403,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami8 ON CLUSTER oximeter start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int8), - counts Array(UInt64) + counts Array(UInt64), + min Int8, + max Int8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogrami8_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -398,7 +430,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu8_local ON CLUSTER ox start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt8), - counts Array(UInt64) + counts Array(UInt64), + min UInt8, + max UInt8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramu8_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -411,7 +459,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu8 ON CLUSTER oximeter start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt8), - counts Array(UInt64) + counts Array(UInt64), + min UInt8, + max UInt8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramu8_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -422,7 +486,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami16_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int16), - counts Array(UInt64) + counts Array(UInt64), + min Int16, + max Int16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogrami16_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -435,7 +515,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami16 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int16), - counts Array(UInt64) + counts Array(UInt64), + min Int16, + max Int16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogrami16_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -446,7 +542,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu16_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt16), - counts Array(UInt64) + counts Array(UInt64), + min UInt16, + max UInt16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramu16_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -459,7 +571,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu16 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt16), - counts Array(UInt64) + counts Array(UInt64), + min UInt16, + max UInt16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramu16_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -470,7 +598,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami32_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int32), - counts Array(UInt64) + counts Array(UInt64), + min Int32, + max Int32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogrami32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -483,7 +627,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami32 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int32), - counts Array(UInt64) + counts Array(UInt64), + min Int32, + max Int32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogrami32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -494,7 +654,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu32_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt32), - counts Array(UInt64) + counts Array(UInt64), + min UInt32, + max UInt32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramu32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -507,7 +683,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu32 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt32), - counts Array(UInt64) + counts Array(UInt64), + min UInt32, + max UInt32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramu32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -518,7 +710,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami64_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int64), - counts Array(UInt64) + counts Array(UInt64), + min Int64, + max Int64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogrami64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -531,7 +739,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami64 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int64), - counts Array(UInt64) + counts Array(UInt64), + min Int64, + max Int64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogrami64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -542,7 +766,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu64_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt64), - counts Array(UInt64) + counts Array(UInt64), + min UInt64, + max UInt64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramu64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -555,7 +795,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu64 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt64), - counts Array(UInt64) + counts Array(UInt64), + min UInt64, + max UInt64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramu64_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -566,7 +822,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf32_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float32), - counts Array(UInt64) + counts Array(UInt64), + min Float32, + max Float32, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramf32_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -579,7 +851,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf32 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float32), - counts Array(UInt64) + counts Array(UInt64), + min Float32, + max Float32, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramf32_local', xxHash64(splitByChar(':', timeseries_name)[1])); @@ -590,7 +878,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf64_local ON CLUSTER o start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float64), - counts Array(UInt64) + counts Array(UInt64), + min Float64, + max Float64, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/measurements_histogramf64_local', '{replica}') ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -603,7 +907,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf64 ON CLUSTER oximete start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float64), - counts Array(UInt64) + counts Array(UInt64), + min Float64, + max Float64, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = Distributed('oximeter_cluster', 'oximeter', 'measurements_histogramf64_local', xxHash64(splitByChar(':', timeseries_name)[1])); diff --git a/oximeter/db/schema/single-node/5/up.sql b/oximeter/db/schema/single-node/5/up.sql new file mode 100644 index 00000000000..9bef98e8707 --- /dev/null +++ b/oximeter/db/schema/single-node/5/up.sql @@ -0,0 +1,170 @@ +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN min Int8; +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN max Int8; +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami8 ADD COLUMN p99_desired_marker_increments Array(Float64); + + +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN min Int16; +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN max Int16; +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami16 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN min Int32; +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN max Int32; +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami32 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN min Int64; +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN max Int64; +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogrami64 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN min UInt8; +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN max UInt8; +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu8 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN min UInt16; +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN max UInt16; +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu16 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN min UInt32; +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN max UInt32; +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu32 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN min UInt64; +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN max UInt64; +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN sum_of_samples Int64; +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN sum_of_squares Int64; +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramu64 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN min Float32; +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN max Float32; +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf32 ADD COLUMN p99_desired_marker_increments Array(Float64); + +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN min Float64; +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN max Float64; +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN sum_of_samples Float64; +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN sum_of_squares Float64; +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p50_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p50_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p50_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p50_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p90_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p90_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p90_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p90_desired_marker_increments Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p99_marker_heights Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p99_marker_positions Array(Int64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p99_desired_marker_positions Array(Float64); +ALTER TABLE oximeter.measurements_histogramf64 ADD COLUMN p99_desired_marker_increments Array(Float64); diff --git a/oximeter/db/schema/single-node/db-init.sql b/oximeter/db/schema/single-node/db-init.sql index 510c1071c82..78772981ca7 100644 --- a/oximeter/db/schema/single-node/db-init.sql +++ b/oximeter/db/schema/single-node/db-init.sql @@ -235,7 +235,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami8 * to figure out another way to represent missing samples here. */ bins Array(Int8), - counts Array(UInt64) + counts Array(UInt64), + min Int8, + max Int8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -248,7 +264,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu8 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt8), - counts Array(UInt64) + counts Array(UInt64), + min UInt8, + max UInt8, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -261,7 +293,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami16 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int16), - counts Array(UInt64) + counts Array(UInt64), + min Int16, + max Int16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -274,7 +322,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu16 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt16), - counts Array(UInt64) + counts Array(UInt64), + min UInt16, + max UInt16, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -287,7 +351,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami32 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int32), - counts Array(UInt64) + counts Array(UInt64), + min Int32, + max Int32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -300,7 +380,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu32 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt32), - counts Array(UInt64) + counts Array(UInt64), + min UInt32, + max UInt32, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -313,7 +409,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogrami64 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Int64), - counts Array(UInt64) + counts Array(UInt64), + min Int64, + max Int64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -326,7 +438,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramu64 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(UInt64), - counts Array(UInt64) + counts Array(UInt64), + min UInt64, + max UInt64, + sum_of_samples Int64, + sum_of_squares Int64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -339,7 +467,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf32 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float32), - counts Array(UInt64) + counts Array(UInt64), + min Float32, + max Float32, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) @@ -352,7 +496,23 @@ CREATE TABLE IF NOT EXISTS oximeter.measurements_histogramf64 start_time DateTime64(9, 'UTC'), timestamp DateTime64(9, 'UTC'), bins Array(Float64), - counts Array(UInt64) + counts Array(UInt64), + min Float64, + max Float64, + sum_of_samples Float64, + sum_of_squares Float64, + p50_marker_heights Array(Float64), + p50_marker_positions Array(Int64), + p50_desired_marker_positions Array(Float64), + p50_desired_marker_increments Array(Float64), + p90_marker_heights Array(Float64), + p90_marker_positions Array(Int64), + p90_desired_marker_positions Array(Float64), + p90_desired_marker_increments Array(Float64), + p99_marker_heights Array(Float64), + p99_marker_positions Array(Int64), + p99_desired_marker_positions Array(Float64), + p99_desired_marker_increments Array(Float64) ) ENGINE = MergeTree() ORDER BY (timeseries_name, timeseries_key, start_time, timestamp) diff --git a/oximeter/db/src/bin/oxdb/oxql.rs b/oximeter/db/src/bin/oxdb/oxql.rs index 54e40afa15f..cc7982bdbce 100644 --- a/oximeter/db/src/bin/oxdb/oxql.rs +++ b/oximeter/db/src/bin/oxdb/oxql.rs @@ -11,6 +11,7 @@ use clap::Args; use crossterm::style::Stylize; use dropshot::EmptyScanParams; use dropshot::WhichPage; +use oximeter::TimeseriesSchema; use oximeter_db::oxql::query::special_idents; use oximeter_db::oxql::Table; use oximeter_db::Client; @@ -145,7 +146,72 @@ async fn list_timeseries(client: &Client) -> anyhow::Result<()> { } } -// Describe a single timeseries. +/// Prepare the columns for a timeseries or virtual table. +pub(crate) fn prepare_columns( + schema: &TimeseriesSchema, +) -> (Vec, Vec) { + let mut cols = Vec::with_capacity(schema.field_schema.len() + 2); + let mut types = cols.clone(); + + for field in schema.field_schema.iter() { + cols.push(field.name.clone()); + types.push(field.field_type.to_string()); + } + + cols.push(special_idents::TIMESTAMP.into()); + types.push(special_idents::DATETIME64.into()); + + if schema.datum_type.is_histogram() { + cols.push(special_idents::START_TIME.into()); + types.push(special_idents::DATETIME64.into()); + + cols.push(special_idents::BINS.into()); + types.push( + special_idents::array_type_name_from_histogram_type( + schema.datum_type, + ) + .unwrap(), + ); + + cols.push(special_idents::COUNTS.into()); + types.push(special_idents::ARRAYU64.into()); + + cols.push(special_idents::MIN.into()); + types.push(special_idents::FLOAT64.into()); + + cols.push(special_idents::MAX.into()); + types.push(special_idents::FLOAT64.into()); + + cols.push(special_idents::SUM_OF_SAMPLES.into()); + types.push(special_idents::UINT64.into()); + + cols.push(special_idents::SUM_OF_SQUARES.into()); + types.push(special_idents::UINT64.into()); + + for quantile in ["P50", "P90", "P99"].iter() { + cols.push(format!("{}_MARKER_HEIGHTS", quantile)); + types.push(special_idents::ARRAYFLOAT64.into()); + cols.push(format!("{}_MARKER_POSITIONS", quantile)); + types.push(special_idents::ARRAYINT64.into()); + cols.push(format!("{}_DESIRED_MARKER_POSITIONS", quantile)); + types.push(special_idents::ARRAYFLOAT64.into()); + cols.push(format!("{}_DESIRED_MARKER_INCREMENTS", quantile)); + types.push(special_idents::ARRAYFLOAT64.into()); + } + } else if schema.datum_type.is_cumulative() { + cols.push(special_idents::START_TIME.into()); + types.push(special_idents::DATETIME64.into()); + cols.push(special_idents::DATUM.into()); + types.push(schema.datum_type.to_string()); + } else { + cols.push(special_idents::DATUM.into()); + types.push(schema.datum_type.to_string()); + } + + (cols, types) +} + +/// Describe a single timeseries. async fn describe_timeseries( client: &Client, timeseries: &str, @@ -158,40 +224,7 @@ async fn describe_timeseries( ), Ok(name) => { if let Some(schema) = client.schema_for_timeseries(&name).await? { - let mut cols = - Vec::with_capacity(schema.field_schema.len() + 2); - let mut types = cols.clone(); - for field in schema.field_schema.iter() { - cols.push(field.name.clone()); - types.push(field.field_type.to_string()); - } - cols.push(special_idents::TIMESTAMP.into()); - types.push(special_idents::DATETIME64.into()); - - if schema.datum_type.is_histogram() { - cols.push(special_idents::START_TIME.into()); - types.push(special_idents::DATETIME64.into()); - - cols.push(special_idents::BINS.into()); - types.push( - special_idents::array_type_name_from_histogram_type( - schema.datum_type, - ) - .unwrap(), - ); - - cols.push(special_idents::COUNTS.into()); - types.push(special_idents::ARRAYU64.into()); - } else if schema.datum_type.is_cumulative() { - cols.push(special_idents::START_TIME.into()); - types.push(special_idents::DATETIME64.into()); - cols.push(special_idents::DATUM.into()); - types.push(schema.datum_type.to_string()); - } else { - cols.push(special_idents::DATUM.into()); - types.push(schema.datum_type.to_string()); - } - + let (cols, types) = prepare_columns(&schema); let mut builder = tabled::builder::Builder::default(); builder.push_record(cols); // first record is the header builder.push_record(types); diff --git a/oximeter/db/src/bin/oxdb/sql.rs b/oximeter/db/src/bin/oxdb/sql.rs index d50a60f4d77..44780592fcb 100644 --- a/oximeter/db/src/bin/oxdb/sql.rs +++ b/oximeter/db/src/bin/oxdb/sql.rs @@ -6,6 +6,7 @@ // Copyright 2024 Oxide Computer Company +use super::oxql; use crate::make_client; use clap::Args; use dropshot::EmptyScanParams; @@ -63,43 +64,7 @@ async fn describe_virtual_table( Err(_) => println!("Invalid timeseries name: {table}"), Ok(name) => { if let Some(schema) = client.schema_for_timeseries(&name).await? { - let mut cols = - Vec::with_capacity(schema.field_schema.len() + 2); - let mut types = cols.clone(); - for field in schema.field_schema.iter() { - cols.push(field.name.clone()); - types.push(field.field_type.to_string()); - } - cols.push("timestamp".into()); - types.push("DateTime64".into()); - - if schema.datum_type.is_histogram() { - cols.push("start_time".into()); - types.push("DateTime64".into()); - - cols.push("bins".into()); - types.push(format!( - "Array[{}]", - schema - .datum_type - .to_string() - .strip_prefix("Histogram") - .unwrap() - .to_lowercase(), - )); - - cols.push("counts".into()); - types.push("Array[u64]".into()); - } else if schema.datum_type.is_cumulative() { - cols.push("start_time".into()); - types.push("DateTime64".into()); - cols.push("datum".into()); - types.push(schema.datum_type.to_string()); - } else { - cols.push("datum".into()); - types.push(schema.datum_type.to_string()); - } - + let (cols, types) = oxql::prepare_columns(&schema); let mut builder = tabled::builder::Builder::default(); builder.push_record(cols); // first record is the header builder.push_record(types); diff --git a/oximeter/db/src/client/mod.rs b/oximeter/db/src/client/mod.rs index 9a2b7b1bd30..19af05ccc74 100644 --- a/oximeter/db/src/client/mod.rs +++ b/oximeter/db/src/client/mod.rs @@ -866,7 +866,6 @@ impl Client { let id = usdt::UniqueId::new(); probes::sql__query__start!(|| (&id, &sql)); let start = Instant::now(); - // Submit the SQL request itself. let response = self .client @@ -1072,7 +1071,6 @@ mod tests { // explored or decided on these. // // TODO-robustness TODO-correctness: Figure out the ClickHouse options we need. - #[tokio::test] async fn test_single_node() { let logctx = test_setup_log("test_single_node"); @@ -3101,7 +3099,7 @@ mod tests { let insert_sql = format!( "INSERT INTO {measurement_table} FORMAT JSONEachRow {inserted_row}", ); - println!("Inserted row: {}", inserted_row); + println!("Row to insert: {}", inserted_row); client .execute(insert_sql) .await @@ -3114,11 +3112,17 @@ mod tests { measurement.timestamp().format(crate::DATABASE_TIMESTAMP_FORMAT), crate::DATABASE_SELECT_FORMAT, ); + + println!("Selecting row: {}", select_sql); + let body = client .execute_with_body(select_sql) .await .expect("Failed to select measurement row") .1; + + println!("Body: {}", body); + let (_, actual_row) = crate::model::parse_measurement_from_row( &body, measurement.datum_type(), @@ -3671,12 +3675,12 @@ mod tests { // Write out the upgrading SQL files. // - // Note that all of these statements are going in the version 2 schema + // Note that all of these statements are going in the version 6 schema // directory. let (schema_dir, version_dirs) = create_test_upgrade_schema_directory(replicated, &[NEXT_VERSION]) .await; - const NEXT_VERSION: u64 = 2; + const NEXT_VERSION: u64 = OXIMETER_VERSION + 1; let first_sql = format!("ALTER TABLE {test_name}.tbl ADD COLUMN `col1` UInt16;"); let second_sql = @@ -3895,7 +3899,7 @@ mod tests { // Write out the upgrading SQL files. // // Note that each statement goes into a different version. - const VERSIONS: [u64; 3] = [1, 2, 3]; + const VERSIONS: [u64; 4] = [1, 2, 3, 4]; let (schema_dir, version_dirs) = create_test_upgrade_schema_directory(replicated, &VERSIONS).await; let first_sql = String::new(); diff --git a/oximeter/db/src/client/oxql.rs b/oximeter/db/src/client/oxql.rs index d1ce1315815..24844bd3c84 100644 --- a/oximeter/db/src/client/oxql.rs +++ b/oximeter/db/src/client/oxql.rs @@ -825,7 +825,14 @@ impl Client { datum_type: oximeter::DatumType, ) -> String { let value_columns = if datum_type.is_histogram() { - "timeseries_key, start_time, timestamp, bins, counts" + r#"timeseries_key, start_time, timestamp, bins, counts, min, max, + sum_of_samples, sum_of_squares, + p50_marker_heights, p50_marker_positions, + p50_desired_marker_positions, p50_desired_marker_increments, + p90_marker_heights, p90_marker_positions, + p90_desired_marker_positions, p90_desired_marker_increments + p99_marker_heights, p99_marker_positions, + p99_desired_marker_positions, p99_desired_marker_increments"# } else if datum_type.is_cumulative() { "timeseries_key, start_time, timestamp, datum" } else { diff --git a/oximeter/db/src/model.rs b/oximeter/db/src/model.rs index 106c347ef6f..7531cd287f0 100644 --- a/oximeter/db/src/model.rs +++ b/oximeter/db/src/model.rs @@ -4,7 +4,7 @@ //! Models for timeseries data in ClickHouse -// Copyright 2023 Oxide Computer Company +// Copyright 2024 Oxide Computer Company use crate::DbFieldSource; use crate::FieldSchema; @@ -16,6 +16,7 @@ use crate::TimeseriesSchema; use bytes::Bytes; use chrono::DateTime; use chrono::Utc; +use num::traits::Zero; use oximeter::histogram::Histogram; use oximeter::traits; use oximeter::types::Cumulative; @@ -27,6 +28,7 @@ use oximeter::types::FieldValue; use oximeter::types::Measurement; use oximeter::types::MissingDatum; use oximeter::types::Sample; +use oximeter::Quantile; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; @@ -43,7 +45,7 @@ use uuid::Uuid; /// - [`crate::Client::initialize_db_with_version`] /// - [`crate::Client::ensure_schema`] /// - The `clickhouse-schema-updater` binary in this crate -pub const OXIMETER_VERSION: u64 = 4; +pub const OXIMETER_VERSION: u64 = 5; // Wrapper type to represent a boolean in the database. // @@ -446,15 +448,87 @@ declare_cumulative_measurement_row! { CumulativeU64MeasurementRow, u64, "cumulat declare_cumulative_measurement_row! { CumulativeF32MeasurementRow, f32, "cumulativef32" } declare_cumulative_measurement_row! { CumulativeF64MeasurementRow, f64, "cumulativef64" } +/// A representation of all quantiles for a histogram. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +struct AllQuantiles { + p50_marker_heights: [f64; 5], + p50_marker_positions: [i64; 5], + p50_desired_marker_positions: [f64; 5], + p50_desired_marker_increments: [f64; 5], + + p90_marker_heights: [f64; 5], + p90_marker_positions: [i64; 5], + p90_desired_marker_positions: [f64; 5], + p90_desired_marker_increments: [f64; 5], + + p99_marker_heights: [f64; 5], + p99_marker_positions: [i64; 5], + p99_desired_marker_positions: [f64; 5], + p99_desired_marker_increments: [f64; 5], +} + +impl AllQuantiles { + /// Create a flat `AllQuantiles` struct from the given quantiles. + fn flatten(p50: Quantile, p90: Quantile, p99: Quantile) -> Self { + Self { + p50_marker_heights: p50.marker_hghts, + p50_marker_positions: p50.marker_pos, + p50_desired_marker_positions: p50.desired_marker_pos, + p50_desired_marker_increments: p50.desired_marker_incrs, + p90_marker_heights: p90.marker_hghts, + p90_marker_positions: p90.marker_pos, + p90_desired_marker_positions: p90.desired_marker_pos, + p90_desired_marker_increments: p90.desired_marker_incrs, + p99_marker_heights: p99.marker_hghts, + p99_marker_positions: p99.marker_pos, + p99_desired_marker_positions: p99.desired_marker_pos, + p99_desired_marker_increments: p99.desired_marker_incrs, + } + } + + /// Split the quantiles into separate `Quantile` structs in order of P. + fn split(&self) -> (Quantile, Quantile, Quantile) { + ( + Quantile { + marker_hghts: self.p50_marker_heights, + marker_pos: self.p50_marker_positions, + desired_marker_pos: self.p50_desired_marker_positions, + desired_marker_incrs: self.p50_desired_marker_increments, + }, + Quantile { + marker_hghts: self.p90_marker_heights, + marker_pos: self.p90_marker_positions, + desired_marker_pos: self.p90_desired_marker_positions, + desired_marker_incrs: self.p90_desired_marker_increments, + }, + Quantile { + marker_hghts: self.p99_marker_heights, + marker_pos: self.p99_marker_positions, + desired_marker_pos: self.p99_desired_marker_positions, + desired_marker_incrs: self.p99_desired_marker_increments, + }, + ) + } +} + // Representation of a histogram in ClickHouse. // -// The tables storing measurements of a histogram metric use a pair of arrays to represent them, -// for the bins and counts, respectively. This handles conversion between the type used to -// represent histograms in Rust, [`Histogram`], and this in-database representation. +// The tables storing measurements of a histogram metric use a set of arrays to +// represent them. This handles conversion between the type used to represent +// histograms in Rust, [`Histogram`], and this in-database representation. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -struct DbHistogram { +struct DbHistogram +where + T: traits::HistogramSupport, +{ pub bins: Vec, pub counts: Vec, + pub min: T, + pub max: T, + pub sum_of_samples: T::Width, + pub sum_of_squares: T::Width, + #[serde(flatten)] + pub quantiles: AllQuantiles, } // We use an empty histogram to indicate a missing sample. @@ -467,9 +541,24 @@ struct DbHistogram { // // That means we can currently use an empty array from the database as a // sentinel for a missing sample. -impl DbHistogram { +impl DbHistogram +where + T: traits::HistogramSupport, +{ fn null() -> Self { - Self { bins: vec![], counts: vec![] } + let p50 = Quantile::p50(); + let p90 = Quantile::p90(); + let p99 = Quantile::p99(); + + Self { + bins: vec![], + counts: vec![], + min: T::zero(), + max: T::zero(), + sum_of_samples: T::Width::zero(), + sum_of_squares: T::Width::zero(), + quantiles: AllQuantiles::flatten(p50, p90, p99), + } } } @@ -478,8 +567,20 @@ where T: traits::HistogramSupport, { fn from(hist: &Histogram) -> Self { - let (bins, counts) = hist.to_arrays(); - Self { bins, counts } + let (bins, counts) = hist.bins_and_counts(); + Self { + bins, + counts, + min: hist.min(), + max: hist.max(), + sum_of_samples: hist.sum_of_samples(), + sum_of_squares: hist.sum_of_squares(), + quantiles: AllQuantiles::flatten( + hist.p50q().clone(), + hist.p90q().clone(), + hist.p99q().clone(), + ), + } } } @@ -1255,7 +1356,10 @@ struct DbTimeseriesScalarCumulativeSample { // A histogram timestamped sample from a timeseries, as extracted from a query to the database. #[derive(Debug, Clone, Deserialize)] -struct DbTimeseriesHistogramSample { +struct DbTimeseriesHistogramSample +where + T: traits::HistogramSupport, +{ timeseries_key: TimeseriesKey, #[serde(with = "serde_timestamp")] start_time: DateTime, @@ -1263,6 +1367,12 @@ struct DbTimeseriesHistogramSample { timestamp: DateTime, bins: Vec, counts: Vec, + min: T, + max: T, + sum_of_samples: T::Width, + sum_of_squares: T::Width, + #[serde(flatten)] + quantiles: AllQuantiles, } impl From> for Measurement @@ -1314,14 +1424,30 @@ where .unwrap(), ) } else { - Datum::from( - Histogram::from_arrays( - sample.start_time, - sample.bins, - sample.counts, - ) - .unwrap(), + if sample.bins.len() != sample.counts.len() { + panic!( + "Array size mismatch: bins: {}, counts: {}", + sample.bins.len(), + sample.counts.len() + ); + } + + let (p50, p90, p99) = sample.quantiles.split(); + let hist = Histogram::with( + sample.start_time, + sample.bins, + sample.counts, + sample.min, + sample.max, + sample.sum_of_samples, + sample.sum_of_squares, + p50, + p90, + p99, ) + .unwrap(); + + Datum::from(hist) }; Measurement::new(sample.timestamp, datum) } @@ -1475,12 +1601,16 @@ where (sample.timeseries_key, sample.into()) } -fn parse_timeseries_histogram_measurement( - line: &str, +fn parse_timeseries_histogram_measurement<'a, T>( + line: &'a str, ) -> (TimeseriesKey, Measurement) where - T: Into + traits::HistogramSupport + FromDbHistogram, + T: Into + + traits::HistogramSupport + + FromDbHistogram + + Deserialize<'a>, Datum: From>, + ::Width: Deserialize<'a>, { let sample = serde_json::from_str::>(line).unwrap(); @@ -1741,6 +1871,7 @@ pub(crate) fn parse_field_select_row( mod tests { use super::*; use chrono::Timelike; + use oximeter::histogram::Record; use oximeter::test_util; use oximeter::Datum; @@ -1826,9 +1957,18 @@ mod tests { hist.sample(1).unwrap(); hist.sample(10).unwrap(); let dbhist = DbHistogram::from(&hist); - let (bins, counts) = hist.to_arrays(); + let (bins, counts) = hist.bins_and_counts(); assert_eq!(dbhist.bins, bins); assert_eq!(dbhist.counts, counts); + assert_eq!(dbhist.min, hist.min()); + assert_eq!(dbhist.max, hist.max()); + assert_eq!(dbhist.sum_of_samples, hist.sum_of_samples()); + assert_eq!(dbhist.sum_of_squares, hist.sum_of_squares()); + + let (p50, p90, p99) = dbhist.quantiles.split(); + assert_eq!(&p50, hist.p50q()); + assert_eq!(&p90, hist.p90q()); + assert_eq!(&p99, hist.p99q()); } #[test] @@ -1877,10 +2017,20 @@ mod tests { assert_eq!(table_name, "oximeter.measurements_histogramf64"); let unpacked: HistogramF64MeasurementRow = serde_json::from_str(&row).unwrap(); - let unpacked_hist = Histogram::from_arrays( + let (unpacked_p50, unpacked_p90, unpacked_p99) = + unpacked.datum.quantiles.split(); + + let unpacked_hist = Histogram::with( unpacked.start_time, unpacked.datum.bins, unpacked.datum.counts, + unpacked.datum.min, + unpacked.datum.max, + unpacked.datum.sum_of_samples, + unpacked.datum.sum_of_squares, + unpacked_p50, + unpacked_p90, + unpacked_p99, ) .unwrap(); let measurement = &sample.measurement; @@ -1986,7 +2136,30 @@ mod tests { .with_nanosecond(123_456_789) .unwrap(); - let line = r#"{"timeseries_key": 12, "start_time": "2021-01-01 00:00:00.123456789", "timestamp": "2021-01-01 01:00:00.123456789", "bins": [0, 1], "counts": [1, 1] }"#; + let line = r#" + { + "timeseries_key": 12, + "start_time": "2021-01-01 00:00:00.123456789", + "timestamp": "2021-01-01 01:00:00.123456789", + "bins": [0, 1], + "counts": [1, 1], + "min": 0, + "max": 1, + "sum_of_samples": 2, + "sum_of_squares": 2, + "p50_marker_heights": [0.0, 0.0, 0.0, 0.0, 1.0], + "p50_marker_positions": [1, 2, 3, 4, 0], + "p50_desired_marker_positions": [1.0, 3.0, 5.0, 5.0, 5.0], + "p50_desired_marker_increments": [0.0, 0.5, 1.0, 1.0, 1.0], + "p90_marker_heights": [0.0, 0.0, 0.0, 0.0, 1.0], + "p90_marker_positions": [1, 2, 3, 4, 0], + "p90_desired_marker_positions": [1.0, 3.0, 5.0, 5.0, 5.0], + "p90_desired_marker_increments": [0.0, 0.5, 1.0, 1.0, 1.0], + "p99_marker_heights": [0.0, 0.0, 0.0, 0.0, 1.0], + "p99_marker_positions": [1, 2, 3, 4, 0], + "p99_desired_marker_positions": [1.0, 3.0, 5.0, 5.0, 5.0], + "p99_desired_marker_increments": [0.0, 0.5, 1.0, 1.0, 1.0] + }"#; let (key, measurement) = parse_measurement_from_row(line, DatumType::HistogramI64); assert_eq!(key, 12); @@ -1997,6 +2170,38 @@ mod tests { }; assert_eq!(hist.n_bins(), 3); assert_eq!(hist.n_samples(), 2); + assert_eq!(hist.min(), 0); + assert_eq!(hist.max(), 1); + assert_eq!(hist.sum_of_samples(), 2); + assert_eq!(hist.sum_of_squares(), 2); + assert_eq!( + hist.p50q(), + &Quantile { + marker_hghts: [0.0, 0.0, 0.0, 0.0, 1.0], + marker_pos: [1, 2, 3, 4, 0], + desired_marker_pos: [1.0, 3.0, 5.0, 5.0, 5.0], + desired_marker_incrs: [0.0, 0.5, 1.0, 1.0, 1.0], + } + ); + assert_eq!( + hist.p90q(), + &Quantile { + marker_hghts: [0.0, 0.0, 0.0, 0.0, 1.0], + marker_pos: [1, 2, 3, 4, 0], + desired_marker_pos: [1.0, 3.0, 5.0, 5.0, 5.0], + desired_marker_incrs: [0.0, 0.5, 1.0, 1.0, 1.0], + } + ); + + assert_eq!( + hist.p99q(), + &Quantile { + marker_hghts: [0.0, 0.0, 0.0, 0.0, 1.0], + marker_pos: [1, 2, 3, 4, 0], + desired_marker_pos: [1.0, 3.0, 5.0, 5.0, 5.0], + desired_marker_incrs: [0.0, 0.5, 1.0, 1.0, 1.0], + } + ); } #[test] @@ -2007,32 +2212,6 @@ mod tests { assert_eq!(measurement.datum(), &Datum::from("/some/path")); } - #[test] - fn test_histogram_to_arrays() { - let mut hist = Histogram::new(&[0, 10, 20]).unwrap(); - hist.sample(1).unwrap(); - hist.sample(11).unwrap(); - - let (bins, counts) = hist.to_arrays(); - assert_eq!( - bins.len(), - counts.len(), - "Bins and counts should have the same size" - ); - assert_eq!( - bins.len(), - hist.n_bins(), - "Paired-array bins should be of the same length as the histogram" - ); - assert_eq!(counts, &[0, 1, 1, 0], "Paired-array counts are incorrect"); - - let rebuilt = - Histogram::from_arrays(hist.start_time(), bins, counts).unwrap(); - assert_eq!( - hist, rebuilt, - "Histogram reconstructed from paired arrays is not correct" - ); - } #[test] fn test_parse_bytes_measurement() { let s = r#"{"timeseries_key": 101, "timestamp": "2023-11-21 18:25:21.963714255", "datum": "\u0001\u0002\u0003"}"#; diff --git a/oximeter/db/src/oxql/ast/table_ops/filter.rs b/oximeter/db/src/oxql/ast/table_ops/filter.rs index e5963fe69c5..115f10a226e 100644 --- a/oximeter/db/src/oxql/ast/table_ops/filter.rs +++ b/oximeter/db/src/oxql/ast/table_ops/filter.rs @@ -514,6 +514,22 @@ fn implicit_field_names( ) => { out.insert(special_idents::BINS); out.insert(special_idents::COUNTS); + out.insert(special_idents::MIN); + out.insert(special_idents::MAX); + out.insert(special_idents::SUM_OF_SAMPLES); + out.insert(special_idents::SUM_OF_SQUARES); + out.insert(special_idents::P50_MARKER_HEIGHTS); + out.insert(special_idents::P50_MARKER_POSITIONS); + out.insert(special_idents::P50_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P50_DESIRED_MARKER_INCREMENTS); + out.insert(special_idents::P90_MARKER_HEIGHTS); + out.insert(special_idents::P90_MARKER_POSITIONS); + out.insert(special_idents::P90_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P90_DESIRED_MARKER_INCREMENTS); + out.insert(special_idents::P99_MARKER_HEIGHTS); + out.insert(special_idents::P99_MARKER_POSITIONS); + out.insert(special_idents::P99_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P99_DESIRED_MARKER_INCREMENTS); } // Scalars, either delta or cumulatives. ( @@ -530,6 +546,22 @@ fn implicit_field_names( ) => { out.insert(special_idents::BINS); out.insert(special_idents::COUNTS); + out.insert(special_idents::MIN); + out.insert(special_idents::MAX); + out.insert(special_idents::SUM_OF_SAMPLES); + out.insert(special_idents::SUM_OF_SQUARES); + out.insert(special_idents::P50_MARKER_HEIGHTS); + out.insert(special_idents::P50_MARKER_POSITIONS); + out.insert(special_idents::P50_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P50_DESIRED_MARKER_INCREMENTS); + out.insert(special_idents::P90_MARKER_HEIGHTS); + out.insert(special_idents::P90_MARKER_POSITIONS); + out.insert(special_idents::P90_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P90_DESIRED_MARKER_INCREMENTS); + out.insert(special_idents::P99_MARKER_HEIGHTS); + out.insert(special_idents::P99_MARKER_POSITIONS); + out.insert(special_idents::P99_DESIRED_MARKER_POSITIONS); + out.insert(special_idents::P99_DESIRED_MARKER_INCREMENTS); out.insert(special_idents::START_TIME); } // Impossible combinations diff --git a/oximeter/db/src/oxql/point.rs b/oximeter/db/src/oxql/point.rs index e12214aaf04..dfafd2ad314 100644 --- a/oximeter/db/src/oxql/point.rs +++ b/oximeter/db/src/oxql/point.rs @@ -11,12 +11,15 @@ use anyhow::Context; use chrono::DateTime; use chrono::Utc; use num::ToPrimitive; +use oximeter::traits::HistogramSupport; use oximeter::DatumType; use oximeter::Measurement; +use oximeter::Quantile; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use std::fmt; +use std::ops::Sub; /// The type of each individual data point's value in a timeseries. #[derive( @@ -1428,7 +1431,7 @@ impl ValueArray { CumulativeDatum::DoubleDistribution(last), oximeter::Datum::HistogramF32(new), ) => { - let new = Distribution::from(new); + let new = Distribution::::from(new); self.as_double_distribution_mut()? .push(Some(new.checked_sub(&last)?)); } @@ -1436,7 +1439,7 @@ impl ValueArray { CumulativeDatum::DoubleDistribution(last), oximeter::Datum::HistogramF64(new), ) => { - let new = Distribution::from(new); + let new = Distribution::::from(new); self.as_double_distribution_mut()? .push(Some(new.checked_sub(&last)?)); } @@ -1517,15 +1520,27 @@ pub trait DistributionSupport: impl DistributionSupport for i64 {} impl DistributionSupport for f64 {} -/// A distribution is a sequence of bins and counts in those bins. +/// A distribution is a sequence of bins and counts in those bins, and some +/// statistical information tracked to compute the mean, standard deviation, and +/// quantile estimates. #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[schemars(rename = "Distribution{T}")] pub struct Distribution { bins: Vec, counts: Vec, + min: T, + max: T, + sum_of_samples: T, + sum_of_squares: T, + p50: Quantile, + p90: Quantile, + p99: Quantile, } -impl fmt::Display for Distribution { +impl fmt::Display for Distribution +where + T: DistributionSupport + HistogramSupport + Sub, +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let elems = self .bins @@ -1534,11 +1549,26 @@ impl fmt::Display for Distribution { .map(|(bin, count)| format!("{bin}: {count}")) .collect::>() .join(", "); - write!(f, "{}", elems) + + write!( + f, + "{}, min: {}, max: {}, mean: {}, std_dev: {}, p50: {}, p90: {}, p99: {}", + elems, + self.min, + self.max, + self.mean().unwrap_or_default(), + self.std_dev().unwrap_or_default(), + self.p50.estimate().unwrap_or_default(), + self.p90.estimate().unwrap_or_default(), + self.p99.estimate().unwrap_or_default() + ) } } -impl Distribution { +impl Distribution +where + T: DistributionSupport + HistogramSupport + Sub, +{ // Subtract two distributions, checking that they have the same bins. fn checked_sub( &self, @@ -1548,14 +1578,49 @@ impl Distribution { self.bins == rhs.bins, "Cannot subtract distributions with different bins", ); - let counts = self + let counts: Vec<_> = self .counts .iter() - .zip(rhs.counts.iter().copied()) - .map(|(x, y)| x.checked_sub(y)) + .zip(rhs.counts.iter()) + .map(|(x, y)| x.checked_sub(*y)) .collect::>() .context("Underflow subtracting distributions values")?; - Ok(Self { bins: self.bins.clone(), counts }) + + // Recalculate min and max based on the new counts (and bins). + let min = counts + .iter() + .position(|&x| x > 0) + .map(|idx| self.bins[idx]) + .unwrap_or(self.bins[0]); // Default to the first bin + + let max = counts + .iter() + .rposition(|&x| x > 0) + .map(|idx| self.bins[idx]) + .unwrap_or(self.bins[self.bins.len() - 1]); // Default to the last bin + + // Subtract sum_of_samples and sum_of_squares directly. + // We allow underflow here. + let sum_of_samples = self.sum_of_samples - rhs.sum_of_samples; + let sum_of_squares = self.sum_of_squares - rhs.sum_of_squares; + + // Subtract quantiles directly. + // We allow underflow here. + let p50 = self.p50.sub(&rhs.p50); + let p90 = self.p50.sub(&rhs.p90); + let p99 = self.p50.sub(&rhs.p99); + + Ok(Self { + bins: self.bins.clone(), + counts, + min: T::from(min).unwrap(), + max: T::from(max).unwrap(), + sum_of_samples, + sum_of_squares, + p50, + p90, + p99, + }) } /// Return the slice of bins. @@ -1568,6 +1633,60 @@ impl Distribution { &self.counts } + /// Return the minimum value in the distribution. + pub fn min(&self) -> T { + self.min + } + + /// Return the maximum value in the distribution. + pub fn max(&self) -> T { + self.max + } + + /// Return the mean of the distribution. + pub fn mean(&self) -> Option { + let n_samples = self.counts.len(); + if n_samples > 0 { + self.sum_of_samples.to_f64().map(|sum| sum / (n_samples as f64)) + } else { + None + } + } + + /// Return the sample mean of the distribution. + pub fn std_dev(&self) -> Option { + let n_samples = self.counts.len(); + if n_samples > 1 { + self.mean().and_then(|mean| { + let sum_of_squares = self.sum_of_squares.to_f64()?; + let sum_of_samples = self.sum_of_samples.to_f64()?; + + let variance = (sum_of_squares - (sum_of_samples * mean)) + / (n_samples as f64); + Some(variance.sqrt()) + }) + } else { + None + } + } + + /// Return the sample standard deviation of the distribution. + pub fn sample_std_dev(&self) -> Option { + let n_samples = self.counts.len(); + if n_samples > 1 { + self.mean().and_then(|mean| { + let sum_of_squares = self.sum_of_squares.to_f64()?; + let sum_of_samples = self.sum_of_samples.to_f64()?; + + let variance = (sum_of_squares - (sum_of_samples * mean)) + / (n_samples as f64 - 1.0); + Some(variance.sqrt()) + }) + } else { + None + } + } + /// Return an iterator over each bin and count. pub fn iter(&self) -> impl ExactSizeIterator + '_ { self.bins.iter().zip(self.counts.iter()) @@ -1578,8 +1697,18 @@ macro_rules! i64_dist_from { ($t:ty) => { impl From<&oximeter::histogram::Histogram<$t>> for Distribution { fn from(hist: &oximeter::histogram::Histogram<$t>) -> Self { - let (bins, counts) = hist.to_arrays(); - Self { bins: bins.into_iter().map(i64::from).collect(), counts } + let (bins, counts) = hist.bins_and_counts(); + Self { + bins: bins.into_iter().map(i64::from).collect(), + counts, + min: hist.min() as i64, + max: hist.max() as i64, + sum_of_samples: hist.sum_of_samples(), + sum_of_squares: hist.sum_of_squares(), + p50: hist.p50q().clone(), + p90: hist.p90q().clone(), + p99: hist.p99q().clone(), + } } } @@ -1604,13 +1733,23 @@ impl TryFrom<&oximeter::histogram::Histogram> for Distribution { fn try_from( hist: &oximeter::histogram::Histogram, ) -> Result { - let (bins, counts) = hist.to_arrays(); + let (bins, counts) = hist.bins_and_counts(); let bins = bins .into_iter() .map(i64::try_from) .collect::>() .context("Overflow converting u64 to i64")?; - Ok(Self { bins, counts }) + Ok(Self { + bins, + counts, + min: hist.min() as i64, + max: hist.max() as i64, + sum_of_samples: hist.sum_of_samples(), + sum_of_squares: hist.sum_of_squares(), + p50: hist.p50q().clone(), + p90: hist.p90q().clone(), + p99: hist.p99q().clone(), + }) } } @@ -1627,8 +1766,18 @@ macro_rules! f64_dist_from { ($t:ty) => { impl From<&oximeter::histogram::Histogram<$t>> for Distribution { fn from(hist: &oximeter::histogram::Histogram<$t>) -> Self { - let (bins, counts) = hist.to_arrays(); - Self { bins: bins.into_iter().map(f64::from).collect(), counts } + let (bins, counts) = hist.bins_and_counts(); + Self { + bins: bins.into_iter().map(f64::from).collect(), + counts, + min: hist.min() as f64, + max: hist.max() as f64, + sum_of_samples: hist.sum_of_samples() as f64, + sum_of_squares: hist.sum_of_squares() as f64, + p50: hist.p50q().clone(), + p90: hist.p90q().clone(), + p99: hist.p99q().clone(), + } } } @@ -1645,12 +1794,12 @@ f64_dist_from!(f64); #[cfg(test)] mod tests { - use crate::oxql::point::{DataType, ValueArray}; - use super::{Distribution, MetricType, Points, Values}; + use crate::oxql::point::{DataType, ValueArray}; use chrono::{DateTime, Utc}; - use oximeter::types::Cumulative; - use oximeter::Measurement; + use oximeter::{ + histogram::Record, types::Cumulative, Measurement, Quantile, + }; use std::time::Duration; #[test] @@ -1747,6 +1896,36 @@ mod tests { ); } + #[test] + fn test_sub_between_histogram_distributions() { + let now = Utc::now(); + let current1 = now + Duration::from_secs(1); + let mut hist1 = + oximeter::histogram::Histogram::new(&[0i64, 10, 20]).unwrap(); + hist1.sample(1).unwrap(); + hist1.set_start_time(current1); + let current2 = now + Duration::from_secs(2); + let mut hist2 = + oximeter::histogram::Histogram::new(&[0i64, 10, 20]).unwrap(); + hist2.sample(5).unwrap(); + hist2.sample(10).unwrap(); + hist2.set_start_time(current2); + let dist1 = Distribution::from(&hist1); + let dist2 = Distribution::from(&hist2); + + let diff = dist2.checked_sub(&dist1).unwrap(); + assert_eq!(diff.bins(), &[i64::MIN, 0, 10, 20]); + assert_eq!(diff.counts(), &[0, 0, 1, 0]); + assert_eq!(diff.min(), 10); + assert_eq!(diff.max(), 10); + assert_eq!(diff.mean().unwrap(), 3.5); + assert_eq!(diff.std_dev().unwrap(), 4.330127018922194); + assert_eq!(diff.sample_std_dev().unwrap(), 5.0); + assert_eq!(diff.p50.estimate().unwrap(), 4.0); + assert_eq!(diff.p90.estimate().unwrap(), 4.0); + assert_eq!(diff.p99.estimate().unwrap(), 4.0); + } + fn timestamps(n: usize) -> Vec> { let now = Utc::now(); let mut out = Vec::with_capacity(n); @@ -1972,7 +2151,17 @@ mod tests { timestamps: timestamps(1), values: vec![Values { values: ValueArray::IntegerDistribution(vec![Some( - Distribution { bins: vec![0, 1, 2], counts: vec![0; 3] }, + Distribution { + bins: vec![0, 1, 2], + counts: vec![0; 3], + min: 0, + max: 2, + sum_of_samples: 0, + sum_of_squares: 0, + p50: Quantile::p50(), + p90: Quantile::p90(), + p99: Quantile::p99(), + }, )]), metric_type: MetricType::Gauge, }], @@ -2012,6 +2201,13 @@ mod tests { Distribution { bins: vec![0.0, 1.0, 2.0], counts: vec![0; 3], + min: 0.0, + max: 2.0, + sum_of_samples: 0.0, + sum_of_squares: 0.0, + p50: Quantile::p50(), + p90: Quantile::p90(), + p99: Quantile::p99(), }, )]), metric_type: MetricType::Gauge, diff --git a/oximeter/db/src/oxql/query/mod.rs b/oximeter/db/src/oxql/query/mod.rs index 1c4383d68d1..f68ae5b5987 100644 --- a/oximeter/db/src/oxql/query/mod.rs +++ b/oximeter/db/src/oxql/query/mod.rs @@ -29,13 +29,48 @@ use std::time::Duration; pub mod special_idents { use oximeter::DatumType; + macro_rules! gen_marker { + ($p:expr, $field:expr) => { + concat!("p", $p, "_", $field) + }; + } + pub const TIMESTAMP: &str = "timestamp"; pub const START_TIME: &str = "start_time"; pub const DATUM: &str = "datum"; pub const BINS: &str = "bins"; pub const COUNTS: &str = "counts"; + pub const MIN: &str = "min"; + pub const MAX: &str = "max"; + pub const SUM_OF_SAMPLES: &str = "sum_of_samples"; + pub const SUM_OF_SQUARES: &str = "sum_of_squares"; + pub const P50_MARKER_HEIGHTS: &str = gen_marker!("50", "marker_heights"); + pub const P50_MARKER_POSITIONS: &str = + gen_marker!("50", "marker_positions"); + pub const P50_DESIRED_MARKER_POSITIONS: &str = + gen_marker!("50", "desired_marker_positions"); + pub const P50_DESIRED_MARKER_INCREMENTS: &str = + gen_marker!("50", "desired_marker_increments"); + pub const P90_MARKER_HEIGHTS: &str = gen_marker!("90", "marker_heights"); + pub const P90_MARKER_POSITIONS: &str = + gen_marker!("90", "marker_positions"); + pub const P90_DESIRED_MARKER_POSITIONS: &str = + gen_marker!("90", "desired_marker_positions"); + pub const P90_DESIRED_MARKER_INCREMENTS: &str = + gen_marker!("90", "desired_marker_increments"); + pub const P99_MARKER_HEIGHTS: &str = gen_marker!("99", "marker_heights"); + pub const P99_MARKER_POSITIONS: &str = + gen_marker!("99", "marker_positions"); + pub const P99_DESIRED_MARKER_POSITIONS: &str = + gen_marker!("99", "desired_marker_positions"); + pub const P99_DESIRED_MARKER_INCREMENTS: &str = + gen_marker!("99", "desired_marker_increments"); pub const DATETIME64: &str = "DateTime64"; pub const ARRAYU64: &str = "Array[u64]"; + pub const ARRAYFLOAT64: &str = "Array[f64]"; + pub const ARRAYINT64: &str = "Array[i64]"; + pub const FLOAT64: &str = "f64"; + pub const UINT64: &str = "u64"; pub fn array_type_name_from_histogram_type( type_: DatumType, diff --git a/oximeter/db/src/sql/mod.rs b/oximeter/db/src/sql/mod.rs index f3082dcaa5f..da4086fa554 100644 --- a/oximeter/db/src/sql/mod.rs +++ b/oximeter/db/src/sql/mod.rs @@ -610,12 +610,34 @@ impl RestrictedQuery { // Return the required measurement columns for a specific datum type. // // Scalar measurements have only a timestamp and datum. Cumulative counters - // have those plus a start_time. And histograms have those plus the bins. + // have those plus a start_time. And histograms have those plus the bins, + // counts, min, max, sum of samples, sum of squares, and quantiile arrays. fn datum_type_to_columns( datum_type: &DatumType, ) -> &'static [&'static str] { if datum_type.is_histogram() { - &["start_time", "timestamp", "bins", "counts"] + &[ + "start_time", + "timestamp", + "bins", + "counts", + "min", + "max", + "sum_of_samples", + "sum_of_squares", + "p50_marker_heights", + "p50_marker_positions", + "p50_desired_marker_positions", + "p50_desired_marker_increments", + "p90_marker_heights", + "p90_marker_positions", + "p90_desired_marker_positions", + "p90_desired_marker_increments", + "p99_marker_heights", + "p99_marker_positions", + "p99_desired_marker_positions", + "p99_desired_marker_increments", + ] } else if datum_type.is_cumulative() { &["start_time", "timestamp", "datum"] } else { diff --git a/oximeter/instruments/src/http.rs b/oximeter/instruments/src/http.rs index dcbaf65c069..4bc6cf86771 100644 --- a/oximeter/instruments/src/http.rs +++ b/oximeter/instruments/src/http.rs @@ -12,8 +12,10 @@ use dropshot::{ use futures::Future; use http::StatusCode; use http::Uri; -use oximeter::histogram::Histogram; -use oximeter::{Metric, MetricsError, Producer, Sample, Target}; +use oximeter::{ + histogram::Histogram, histogram::Record, Metric, MetricsError, Producer, + Sample, Target, +}; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; diff --git a/oximeter/oximeter/Cargo.toml b/oximeter/oximeter/Cargo.toml index 2445e0483a4..b9d7e8a9c29 100644 --- a/oximeter/oximeter/Cargo.toml +++ b/oximeter/oximeter/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] bytes = { workspace = true, features = [ "serde" ] } chrono.workspace = true +float-ord.workspace = true num.workspace = true omicron-common.workspace = true oximeter-macro-impl.workspace = true diff --git a/oximeter/oximeter/src/histogram.rs b/oximeter/oximeter/src/histogram.rs index 82b99161532..ce371be5119 100644 --- a/oximeter/oximeter/src/histogram.rs +++ b/oximeter/oximeter/src/histogram.rs @@ -4,23 +4,28 @@ //! Types for managing metrics that are histograms. -// Copyright 2023 Oxide Computer Company +// Copyright 2024 Oxide Computer Company +use super::Quantile; +use super::QuantileError; use chrono::DateTime; use chrono::Utc; use num::traits::Bounded; use num::traits::FromPrimitive; use num::traits::Num; use num::traits::ToPrimitive; +use num::traits::Zero; +use num::CheckedAdd; +use num::CheckedMul; use num::Float; use num::Integer; use num::NumCast; use schemars::JsonSchema; -use serde::de::DeserializeOwned; use serde::Deserialize; use serde::Serialize; use std::cmp::Ordering; use std::num::NonZeroUsize; +use std::ops::AddAssign; use std::ops::Bound; use std::ops::Range; use std::ops::RangeBounds; @@ -37,24 +42,34 @@ pub trait HistogramSupport: + Bounded + JsonSchema + Serialize - + DeserializeOwned + Clone + Num + + Zero + FromPrimitive + ToPrimitive + + AddAssign + NumCast + 'static { type Power; + type Width: HistogramAdditiveWidth; /// Return true if `self` is a finite number, not NAN or infinite. fn is_finite(&self) -> bool; } +/// Used for designating the subset of types that can be used as the width for +/// summing up values in a histogram. +pub trait HistogramAdditiveWidth: HistogramSupport {} + +impl HistogramAdditiveWidth for i64 {} +impl HistogramAdditiveWidth for f64 {} + macro_rules! impl_int_histogram_support { ($($type:ty),+) => { $( impl HistogramSupport for $type { type Power = u16; + type Width = i64; fn is_finite(&self) -> bool { true } @@ -70,6 +85,7 @@ macro_rules! impl_float_histogram_support { $( impl HistogramSupport for $type { type Power = i16; + type Width = f64; fn is_finite(&self) -> bool { <$type>::is_finite(*self) } @@ -104,8 +120,13 @@ pub enum HistogramError { #[error("Bin and count arrays must have the same size, found {n_bins} and {n_counts}")] ArraySizeMismatch { n_bins: usize, n_counts: usize }, + /// Error returned when a quantization error occurs. #[error("Quantization error")] Quantization(#[from] QuantizationError), + + /// Error returned when a quantile error occurs. + #[error("Quantile error")] + Quantile(#[from] QuantileError), } /// Errors occurring during quantizated bin generation. @@ -333,12 +354,137 @@ pub struct Bin { // `Histogram::with_log_linear_bins()` are exactly the ones expected. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[schemars(rename = "Histogram{T}")] -pub struct Histogram { +pub struct Histogram +where + T: HistogramSupport, +{ + /// The start time of the histogram. start_time: DateTime, + /// The bins of the histogram. bins: Vec>, + /// The total number of samples in the histogram. n_samples: u64, + /// The minimum value of all samples in the histogram. + min: T, + /// The maximum value of all samples in the histogram. + max: T, + /// The sum of all samples in the histogram. + sum_of_samples: T::Width, + /// The sum of the squares of all samples in the histogram. + sum_of_squares: T::Width, + /// p50 Quantile + p50: Quantile, + /// p95 Quantile + p90: Quantile, + /// p99 Quantile + p99: Quantile, +} + +/// A trait for recording samples into a histogram. +pub trait Record { + /// Add a new sample into the histogram. + /// + /// This bumps the internal counter at the bin containing `value`. An `Err` is returned if the + /// sample is not within the distribution's support (non-finite). + fn sample(&mut self, value: T) -> Result<(), HistogramError>; +} + +macro_rules! impl_int_sample { + ($($type:ty),+) => { + $( + impl Record<$type> for Histogram<$type> where $type: HistogramSupport + Integer + CheckedAdd + CheckedMul { + fn sample(&mut self, value: $type) -> Result<(), HistogramError> { + ensure_finite(value)?; + + if self.n_samples == 0 { + self.min = <$type>::max_value(); + self.max = <$type>::min_value(); + } + + let index = self + .bins + .binary_search_by(|bin| bin.range.cmp(&value).reverse()) + .unwrap(); // The `ensure_finite` call above catches values that don't end up in a bin + self.bins[index].count += 1; + self.n_samples += 1; + + if value < self.min { + self.min = value; + } + if value > self.max { + self.max = value; + } + + // Ensure that the sum of samples and sum of squares don't overflow + let on_unwrap = || { + if value < <$type>::zero() { + i64::MIN + } else { + i64::MAX + } + }; + + self.sum_of_samples = self.sum_of_samples.checked_add(value as i64).unwrap_or_else(on_unwrap); + self.sum_of_squares = self + .sum_of_squares + .checked_add((value as i64).checked_mul(value as i64).unwrap_or_else(on_unwrap)) + .unwrap_or_else(on_unwrap); + + self.p50.append(value)?; + self.p90.append(value)?; + self.p99.append(value)?; + + Ok(()) + } + } + )+ + } } +impl_int_sample! { i8, u8, i16, u16, i32, u32, i64, u64 } + +macro_rules! impl_float_sample { + ($($type:ty),+) => { + $( + impl Record<$type> for Histogram<$type> where $type: HistogramSupport + Float { + fn sample(&mut self, value: $type) -> Result<(), HistogramError> { + ensure_finite(value)?; + + if self.n_samples == 0 { + self.min = <$type as num::Bounded>::max_value(); + self.max = <$type as num::Bounded>::min_value(); + } + + let index = self + .bins + .binary_search_by(|bin| bin.range.cmp(&value).reverse()) + .unwrap(); // The `ensure_finite` call above catches values that don't end up in a bin + self.bins[index].count += 1; + self.n_samples += 1; + + if value < self.min { + self.min = value; + } + if value > self.max { + self.max = value; + } + + self.sum_of_samples += value as f64; + self.sum_of_squares += (value as f64) * value as f64; + + self.p50.append(value)?; + self.p90.append(value)?; + self.p99.append(value)?; + + Ok(()) + } + } + )+ + } +} + +impl_float_sample! { f32, f64 } + impl Histogram where T: HistogramSupport, @@ -435,7 +581,18 @@ where if let Bound::Excluded(end) = bins_.last().unwrap().range.end_bound() { ensure_finite(*end)?; } - Ok(Self { start_time: Utc::now(), bins: bins_, n_samples: 0 }) + Ok(Self { + start_time: Utc::now(), + bins: bins_, + n_samples: 0, + min: T::zero(), + max: T::zero(), + sum_of_samples: T::Width::zero(), + sum_of_squares: T::Width::zero(), + p50: Quantile::p50(), + p90: Quantile::p90(), + p99: Quantile::p99(), + }) } /// Construct a new histogram from left bin edges. @@ -480,22 +637,61 @@ where if current < ::max_value() { bins.push(Bin { range: BinRange::from(current), count: 0 }); } - Ok(Self { start_time: Utc::now(), bins, n_samples: 0 }) + Ok(Self { + start_time: Utc::now(), + bins, + n_samples: 0, + min: T::zero(), + max: T::zero(), + sum_of_samples: T::Width::zero(), + sum_of_squares: T::Width::zero(), + p50: Quantile::p50(), + p90: Quantile::p90(), + p99: Quantile::p99(), + }) } - /// Add a new sample into the histogram. - /// - /// This bumps the internal counter at the bin containing `value`. An `Err` is returned if the - /// sample is not within the distribution's support (non-finite). - pub fn sample(&mut self, value: T) -> Result<(), HistogramError> { - ensure_finite(value)?; - let index = self - .bins - .binary_search_by(|bin| bin.range.cmp(&value).reverse()) - .unwrap(); // The `ensure_finite` call above catches values that don't end up in a bin - self.bins[index].count += 1; - self.n_samples += 1; - Ok(()) + /// Construct a new histogram with the given struct information, including + /// bins and and counts. + #[allow(clippy::too_many_arguments)] + pub fn with( + start_time: DateTime, + bins: Vec, + counts: Vec, + min: T, + max: T, + sum_of_samples: T::Width, + sum_of_squares: T::Width, + p50: Quantile, + p90: Quantile, + p99: Quantile, + ) -> Result { + if bins.len() != counts.len() { + return Err(HistogramError::ArraySizeMismatch { + n_bins: bins.len(), + n_counts: counts.len(), + }); + } + + let mut hist = Self::new(&bins)?; + let mut n_samples = 0; + for (bin, count) in hist.bins.iter_mut().zip(counts.into_iter()) { + bin.count = count; + n_samples += count; + } + + Ok(Self { + start_time, + bins: hist.bins, + n_samples, + min, + max, + sum_of_samples, + sum_of_squares, + p50, + p90, + p99, + }) } /// Return the total number of samples contained in the histogram. @@ -508,32 +704,18 @@ where self.bins.len() } - /// Iterate over the bins of the histogram. - pub fn iter(&self) -> impl Iterator> { - self.bins.iter() - } - - /// Get the bin at the given index. - pub fn get(&self, index: usize) -> Option<&Bin> { - self.bins.get(index) - } - - /// Generate paired arrays with the left bin edges and the counts, for each bin. - /// - /// The returned edges are always left-inclusive, by construction of the histogram. - pub fn to_arrays(&self) -> (Vec, Vec) { + /// Return the bins of the histogram. + pub fn bins_and_counts(&self) -> (Vec, Vec) { let mut bins = Vec::with_capacity(self.n_bins()); let mut counts = Vec::with_capacity(self.n_bins()); - - // The first bin may either be BinRange::To or BinRange::Range. for bin in self.bins.iter() { match bin.range { BinRange::Range { start, .. } => { bins.push(start); - }, - BinRange::RangeFrom{start} => { + } + BinRange::RangeFrom { start} => { bins.push(start); - }, + } _ => unreachable!("No bins in a constructed histogram should be of type RangeTo"), } counts.push(bin.count); @@ -541,33 +723,117 @@ where (bins, counts) } - /// Construct a histogram from a start time and paired arrays with the left bin-edge and counts. - pub fn from_arrays( - start_time: DateTime, - bins: Vec, - counts: Vec, - ) -> Result { - if bins.len() != counts.len() { - return Err(HistogramError::ArraySizeMismatch { - n_bins: bins.len(), - n_counts: counts.len(), - }); + /// Return the minimum value of inputs to the histogram. + pub fn min(&self) -> T { + self.min + } + + /// Return the maximum value of all inputs to the histogram. + pub fn max(&self) -> T { + self.max + } + + /// Return the mean of all inputs/samples in the histogram. + pub fn mean(&self) -> Option { + if self.n_samples() > 0 { + self.sum_of_samples + .to_f64() + .map(|sum| sum / (self.n_samples() as f64)) + } else { + None } - let mut hist = Self::new(&bins)?; - hist.start_time = start_time; - let mut n_samples = 0; - for (bin, count) in hist.bins.iter_mut().zip(counts.into_iter()) { - bin.count = count; - n_samples += count; + } + + /// Return the sum of all inputs to the histogram. + pub fn sum_of_samples(&self) -> T::Width { + self.sum_of_samples + } + + /// Return the sum of the squares of all inputs to the histogram. + pub fn sum_of_squares(&self) -> T::Width { + self.sum_of_squares + } + + /// Return the standard deviation for inputs to the histogram. + pub fn std_dev(&self) -> Option { + if self.n_samples() > 1 { + self.mean().and_then(|mean| { + let sum_of_squares = self.sum_of_squares.to_f64()?; + let sum_of_samples = self.sum_of_samples.to_f64()?; + + let variance = (sum_of_squares - (sum_of_samples * mean)) + / (self.n_samples() as f64); + Some(variance.sqrt()) + }) + } else { + None + } + } + + /// Return the sample standard deviation for inputs to the histogram. + pub fn sample_std_dev(&self) -> Option { + if self.n_samples() > 1 { + self.mean().and_then(|mean| { + let sum_of_squares = self.sum_of_squares.to_f64()?; + let sum_of_samples = self.sum_of_samples.to_f64()?; + + let variance = (sum_of_squares - (sum_of_samples * mean)) + / (self.n_samples() as f64 - 1.0); + Some(variance.sqrt()) + }) + } else { + None } - hist.n_samples = n_samples; - Ok(hist) + } + + /// Iterate over the bins of the histogram. + pub fn iter(&self) -> impl Iterator> { + self.bins.iter() + } + + /// Get the bin at the given index. + pub fn get(&self, index: usize) -> Option<&Bin> { + self.bins.get(index) } /// Return the start time for this histogram pub fn start_time(&self) -> DateTime { self.start_time } + + pub fn set_start_time(&mut self, start_time: DateTime) { + self.start_time = start_time; + } + + /// Return the p50 quantile for the histogram. + pub fn p50q(&self) -> &Quantile { + &self.p50 + } + + /// Return the p90 quantile for the histogram. + pub fn p90q(&self) -> &Quantile { + &self.p90 + } + + /// Return the p99 quantile for the histogram. + pub fn p99q(&self) -> &Quantile { + &self.p99 + } + + /// Return the p50 estimate for the histogram. + pub fn p50(&self) -> Result { + self.p50.estimate() + } + + /// Return the p90 estimate for the histogram. + pub fn p90(&self) -> Result { + self.p90.estimate() + } + + /// Return the p99 estimate for the histogram. + pub fn p99(&self) -> Result { + self.p99.estimate() + } } impl Histogram @@ -938,20 +1204,64 @@ mod tests { "Histogram should have 1 more bin than bin edges specified" ); assert_eq!(hist.n_samples(), 0, "Histogram should init with 0 samples"); - - let samples = [-10i64, 0, 1, 10, 50]; + let max_sample = 100; + let min_sample = -10i64; + let samples = [min_sample, 0, 1, 10, max_sample]; let expected_counts = [1u64, 2, 1, 1]; for (i, sample) in samples.iter().enumerate() { hist.sample(*sample).unwrap(); let count = i as u64 + 1; + let current_sum = samples[..=i].iter().sum::() as f64; + let current_mean = current_sum / count as f64; + let current_std_dev = (samples[..=i] + .iter() + .map(|x| (*x as f64 - current_mean).powi(2)) + .sum::() + / count as f64) + .sqrt(); assert_eq!( hist.n_samples(), count, "Histogram should have {} sample(s)", count ); + + if count > 0 { + assert_eq!( + hist.mean().unwrap(), + current_mean, + "Histogram should have a mean of {}", + current_mean + ); + } else { + assert!(hist.mean().is_none()); + } + + if count > 1 { + assert_eq!( + hist.std_dev().unwrap(), + current_std_dev, + "Histogram should have a sample standard deviation of {}", + current_std_dev + ); + } else { + assert!(hist.std_dev().is_none()); + } } + assert_eq!( + hist.min(), + min_sample, + "Histogram should have a minimum value of {}", + min_sample + ); + assert_eq!( + hist.max(), + max_sample, + "Histogram should have a maximum value of {}", + max_sample + ); + for (bin, &expected_count) in hist.iter().zip(expected_counts.iter()) { assert_eq!( bin.count, expected_count, @@ -959,6 +1269,15 @@ mod tests { bin.range, expected_count, bin.count ); } + + let p50 = hist.p50().unwrap(); + assert_eq!(p50, 1.0, "P50 should be 1.0, but found {}", p50); + + let p90 = hist.p90().unwrap(); + assert_eq!(p90, 1.0, "P90 should be 1.0, but found {}", p90); + + let p99 = hist.p99().unwrap(); + assert_eq!(p99, 1.0, "P99 should be 1.0, but found {}", p99); } #[test] @@ -972,6 +1291,44 @@ mod tests { assert_eq!(data[2].range, BinRange::from(10)); } + #[test] + fn test_histogram_construct_with() { + let mut hist = Histogram::new(&[0, 10, 20]).unwrap(); + hist.sample(1).unwrap(); + hist.sample(11).unwrap(); + + let (bins, counts) = hist.bins_and_counts(); + assert_eq!( + bins.len(), + counts.len(), + "Bins and counts should have the same size" + ); + assert_eq!( + bins.len(), + hist.n_bins(), + "Paired-array bins should be of the same length as the histogram" + ); + assert_eq!(counts, &[0, 1, 1, 0], "Paired-array counts are incorrect"); + + let rebuilt = Histogram::with( + hist.start_time(), + bins, + counts, + hist.min(), + hist.max(), + hist.sum_of_samples(), + hist.sum_of_squares(), + hist.p50.clone(), + hist.p90.clone(), + hist.p99.clone(), + ) + .unwrap(); + assert_eq!( + hist, rebuilt, + "Histogram reconstructed from paired arrays is not correct" + ); + } + #[test] fn test_histogram_with_overlapping_bins() { let bins = &[(..1_u64).into(), (0..10).into()]; @@ -1082,33 +1439,6 @@ mod tests { ); } - #[test] - fn test_histogram_to_arrays() { - let mut hist = Histogram::new(&[0, 10, 20]).unwrap(); - hist.sample(1).unwrap(); - hist.sample(11).unwrap(); - - let (bins, counts) = hist.to_arrays(); - assert_eq!( - bins.len(), - counts.len(), - "Bins and counts should have the same size" - ); - assert_eq!( - bins.len(), - hist.n_bins(), - "Paired-array bins should be of the same length as the histogram" - ); - assert_eq!(counts, &[0, 1, 1, 0], "Paired-array counts are incorrect"); - - let rebuilt = - Histogram::from_arrays(hist.start_time(), bins, counts).unwrap(); - assert_eq!( - hist, rebuilt, - "Histogram reconstructed from paired arrays is not correct" - ); - } - #[test] fn test_span_decades() { let hist = Histogram::::span_decades(0, 3).unwrap(); diff --git a/oximeter/oximeter/src/lib.rs b/oximeter/oximeter/src/lib.rs index 1855762abe3..cd5c5adf8c1 100644 --- a/oximeter/oximeter/src/lib.rs +++ b/oximeter/oximeter/src/lib.rs @@ -108,11 +108,14 @@ pub use oximeter_macro_impl::*; extern crate self as oximeter; pub mod histogram; +pub mod quantile; pub mod schema; pub mod test_util; pub mod traits; pub mod types; +pub use quantile::Quantile; +pub use quantile::QuantileError; pub use schema::FieldSchema; pub use schema::TimeseriesName; pub use schema::TimeseriesSchema; diff --git a/oximeter/oximeter/src/quantile.rs b/oximeter/oximeter/src/quantile.rs new file mode 100644 index 00000000000..40dc974e604 --- /dev/null +++ b/oximeter/oximeter/src/quantile.rs @@ -0,0 +1,405 @@ +use crate::traits::HistogramSupport; +use num::traits::Float; +use num::traits::ToPrimitive; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use std::cmp::min; +use thiserror::Error; + +/// Errors related to constructing a `Quantile` instance or estimating the p-quantile. +#[derive(Debug, Clone, Error, JsonSchema, Serialize, Deserialize)] +#[serde(tag = "type", content = "content", rename_all = "snake_case")] +pub enum QuantileError { + /// The p value must be in the range [0, 1]. + #[error("The p value must be in the range [0, 1].")] + InvalidPValue, + /// Quantile estimation is not possible without samples. + #[error("Quantile estimation is not possible without samples.")] + EmptyQuantile, + /// A non-finite was encountered, either as a bin edge or a sample. + #[error("Samples must be finite values, found: {0:?}")] + NonFiniteValue(String), +} + +/// Structure for estimating the p-quantile of a population. +/// +/// This is based on the P² algorithm for estimating quantiles using +/// constant space. +/// +/// The algorithm consists of maintaining five markers: the +/// minimum, the p/2-, p-, and (1 + p)/2 quantiles, and the maximum. +/// +/// Read for more. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Quantile { + /// The heights of the markers. + #[serde(rename = "marker_heights")] + pub marker_hghts: [f64; 5], + /// The positions of the markers. + #[serde(rename = "marker_positions")] + pub marker_pos: [i64; 5], + /// The desired marker positions. + #[serde(rename = "desired_marker_positions")] + pub desired_marker_pos: [f64; 5], + /// The increment for the desired marker positions. + #[serde(rename = "desired_marker_increments")] + pub desired_marker_incrs: [f64; 5], +} + +impl Quantile { + /// Create a new `Quantile` instance. + pub fn new(p: u64) -> Result { + let p = p as f64 / 100.; + if p < 0. || p > 1. { + return Err(QuantileError::InvalidPValue); + } + + Ok(Self { + marker_hghts: [0.; 5], + marker_pos: [1, 2, 3, 4, 0], + desired_marker_pos: [1., 1. + 2. * p, 1. + 4. * p, 3. + 2. * p, 5.], + desired_marker_incrs: [0., p / 2., p, (1. + p) / 2., 1.], + }) + } + + /// Create a new `Quantile` instance from the given marker heights and + /// positions. + pub fn from( + marker_hghts: [f64; 5], + marker_pos: [i64; 5], + desired_marker_pos: [f64; 5], + desired_marker_incrs: [f64; 5], + ) -> Self { + Self { + marker_hghts, + marker_pos, + desired_marker_pos, + desired_marker_incrs, + } + } + + /// Construct a `Quantile` instance for the 50th/median percentile. + pub fn p50() -> Self { + Self::new(50).unwrap() + } + + /// Construct a `Quantile` instance for the 90th percentile. + pub fn p90() -> Self { + Self::new(90).unwrap() + } + + /// Construct a `Quantile` instance for the 95th percentile. + pub fn p95() -> Self { + Self::new(95).unwrap() + } + + /// Construct a `Quantile` instance for the 99th percentile. + pub fn p99() -> Self { + Self::new(99).unwrap() + } + + /// Get the p value as a float. + fn _p(&self) -> f64 { + self.desired_marker_incrs[2] + } + + /// Get the p value as an integer. + pub fn p(&self) -> u64 { + (self.desired_marker_incrs[2] * 100.0) as u64 + } + + /// Estimate the p-quantile of the population. + /// + /// Returns an error if the sample is empty. + pub fn estimate(&self) -> Result { + // Return NaN if the sample is empty. + if self.is_empty() { + return Err(QuantileError::EmptyQuantile); + } + + // Return the middle marker height if the sample size is at least 5. + if self.len() >= 5 { + return Ok(self.marker_hghts[2]); + } + + let mut heights: [f64; 4] = [ + self.marker_hghts[0], + self.marker_hghts[1], + self.marker_hghts[2], + self.marker_hghts[3], + ]; + + let len = self.len() as usize; + float_ord::sort(&mut heights[..len]); + + let desired_index = (len as f64) * self._p() - 1.; + let mut index = desired_index.ceil(); + if desired_index == index && index >= 0. { + let index = index.round_ties_even() as usize; + if index < len - 1 { + // `marker_hghts[index]` and `marker_hghts[index + 1]` are + // equally valid estimates, by convention we take their average. + return Ok(0.5 * self.marker_hghts[index] + + 0.5 * self.marker_hghts[index + 1]); + } + } + index = index.max(0.); + let mut index = index.round_ties_even() as usize; + index = min(index, len - 1); + Ok(self.marker_hghts[index]) + } + + /// Return the sample size. + pub fn len(&self) -> u64 { + self.marker_pos[4] as u64 + } + + /// Determine whether the sample is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Append a value/observation to the population and adjust the heights. + pub fn append(&mut self, value: T) -> Result<(), QuantileError> + where + T: HistogramSupport, + { + if !value.is_finite() { + return Err(QuantileError::NonFiniteValue(format!("{:?}", value))); + } + + // we've already checked that the value is finite. + let value = value.to_f64().unwrap(); + + // n[4] is the sample size. + if self.marker_pos[4] < 5 { + self.marker_hghts[self.marker_pos[4] as usize] = value; + self.marker_pos[4] += 1; + if self.marker_pos[4] == 5 { + float_ord::sort(&mut self.marker_hghts); + } + return Ok(()); + } + + // Find cell k. + let mut k: usize; + if value < self.marker_hghts[0] { + self.marker_hghts[0] = value; + k = 0; + } else { + k = 4; + for i in 1..5 { + if value < self.marker_hghts[i] { + k = i; + break; + } + } + if self.marker_hghts[4] < value { + self.marker_hghts[4] = value; + } + }; + + // Increment all positions greater than k. + for i in k..5 { + self.marker_pos[i] += 1; + } + for i in 0..5 { + self.desired_marker_pos[i] += self.desired_marker_incrs[i]; + } + + // Adjust height of markers. + for i in 1..4 { + // unwrap is safe because we know the exact marker positions as literals. + let d = self.desired_marker_pos[i] + - self.marker_pos[i].to_f64().unwrap(); + if d >= 1. && self.marker_pos[i + 1] - self.marker_pos[i] > 1 + || d <= -1. && self.marker_pos[i - 1] - self.marker_pos[i] < -1 + { + let d = Float::signum(d); + let q_new = self.parabolic(i, d); + if self.marker_hghts[i - 1] < q_new + && q_new < self.marker_hghts[i + 1] + { + self.marker_hghts[i] = q_new; + } else { + self.marker_hghts[i] = self.linear(i, d); + } + let delta = d.round_ties_even() as i64; + debug_assert_eq!(delta.abs(), 1); + self.marker_pos[i] += delta; + } + } + + Ok(()) + } + + /// Subtract another `Quantile` instance from this one. + pub fn sub(&self, other: &Quantile) -> Quantile { + let marker_hghts = [ + self.marker_hghts[0] - other.marker_hghts[0], + self.marker_hghts[1] - other.marker_hghts[1], + self.marker_hghts[2] - other.marker_hghts[2], + self.marker_hghts[3] - other.marker_hghts[3], + self.marker_hghts[4] - other.marker_hghts[4], + ]; + + let marker_pos = [ + self.marker_pos[0] - other.marker_pos[0], + self.marker_pos[1] - other.marker_pos[1], + self.marker_pos[2] - other.marker_pos[2], + self.marker_pos[3] - other.marker_pos[3], + self.marker_pos[4] - other.marker_pos[4], + ]; + + let desired_marker_pos = [ + self.desired_marker_pos[0] - other.desired_marker_pos[0], + self.desired_marker_pos[1] - other.desired_marker_pos[1], + self.desired_marker_pos[2] - other.desired_marker_pos[2], + self.desired_marker_pos[3] - other.desired_marker_pos[3], + self.desired_marker_pos[4] - other.desired_marker_pos[4], + ]; + + let desired_marker_incrs = [ + self.desired_marker_incrs[0] - other.desired_marker_incrs[0], + self.desired_marker_incrs[1] - other.desired_marker_incrs[1], + self.desired_marker_incrs[2] - other.desired_marker_incrs[2], + self.desired_marker_incrs[3] - other.desired_marker_incrs[3], + self.desired_marker_incrs[4] - other.desired_marker_incrs[4], + ]; + + Quantile { + marker_hghts, + marker_pos, + desired_marker_pos, + desired_marker_incrs, + } + } + + /// Parabolic prediction for marker height. + fn parabolic(&self, i: usize, d: f64) -> f64 { + //let s = d.round_ties_even() as i64; + let term1 = + d / (self.marker_pos[i + 1] - self.marker_pos[i - 1]) as f64; + let term2 = ((self.marker_pos[i] - self.marker_pos[i - 1]) as f64 + d) + * (self.marker_hghts[i + 1] - self.marker_hghts[i]) + / (self.marker_pos[i + 1] - self.marker_pos[i]) as f64; + let term3 = ((self.marker_pos[i + 1] - self.marker_pos[i]) as f64 - d) + * (self.marker_hghts[i] - self.marker_hghts[i - 1]) + / (self.marker_pos[i] - self.marker_pos[i - 1]) as f64; + + self.marker_hghts[i] + term1 * (term2 + term3) + } + + /// Linear prediction for marker height. + fn linear(&self, i: usize, d: f64) -> f64 { + let index = if d < 0. { i - 1 } else { i + 1 }; + self.marker_hghts[i] + + d * (self.marker_hghts[index] - self.marker_hghts[i]) + / (self.marker_pos[index] - self.marker_pos[i]) as f64 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use oximeter::test_util::assert_almost_eq; + + #[test] + fn test_float_observations() { + let observations = [ + 0.02, 0.5, 0.74, 3.39, 0.83, 22.37, 10.15, 15.43, 38.62, 15.92, + 34.60, 10.28, 1.47, 0.40, 0.05, 11.39, 0.27, 0.42, 0.09, 11.37, + ]; + let mut q = Quantile::p50(); + for &o in observations.iter() { + q.append(o).unwrap(); + } + assert_eq!(q.marker_pos, [1, 6, 10, 16, 20]); + assert_eq!(q.desired_marker_pos, [1., 5.75, 10.50, 15.25, 20.0]); + assert_eq!(q.len(), 20); + assert_eq!(q.p(), 50); + assert_almost_eq!(q.estimate().unwrap(), 4.2462394088036435, 2e-15); + } + + #[test] + fn test_integer_observations() { + let observations = 1..=100; + let mut q = Quantile::new(30).unwrap(); + for o in observations { + q.append(o).unwrap(); + } + + assert_eq!(q.marker_pos, [1, 15, 30, 65, 100]); + assert_eq!( + q.desired_marker_pos, + [ + 1.0, + 15.850000000000026, + 30.70000000000005, + 65.34999999999992, + 100.0 + ] + ); + assert_eq!(q.len(), 100); + assert_eq!(q.p(), 30); + assert_eq!(q.estimate().unwrap(), 30.0); + } + + #[test] + fn test_p50() { + let observations = 1..=100; + let mut q = Quantile::p50(); + for o in observations { + q.append(o).unwrap(); + } + assert_eq!(q.p(), 50); + assert_eq!(q.estimate().unwrap(), 50.0); + } + + #[test] + fn test_p90() { + let observations = 1..=100; + let mut q = Quantile::p90(); + for o in observations { + q.append(o).unwrap(); + } + assert_eq!(q.p(), 90); + assert_eq!(q.estimate().unwrap(), 90.0); + } + + #[test] + fn test_p95() { + let observations = 1..=100; + let mut q = Quantile::p95(); + for o in observations { + q.append(o).unwrap(); + } + assert_eq!(q.p(), 95); + assert_eq!(q.estimate().unwrap(), 95.0); + } + + #[test] + fn test_p99() { + let observations = 1..=100; + let mut q = Quantile::p99(); + for o in observations { + q.append(o).unwrap(); + } + assert_eq!(q.p(), 99); + assert_eq!(q.estimate().unwrap(), 97.0); + } + + #[test] + fn test_empty_sample() { + let q = Quantile::p50(); + assert!(q.is_empty()); + assert!(q.estimate().is_err()); + } + + #[test] + fn test_invalid_p_value() { + assert!(Quantile::new(101).is_err()); + assert!(Quantile::new(u64::MAX).is_err()); + } +} diff --git a/oximeter/oximeter/src/test_util.rs b/oximeter/oximeter/src/test_util.rs index a9778d03bc2..76734edc021 100644 --- a/oximeter/oximeter/src/test_util.rs +++ b/oximeter/oximeter/src/test_util.rs @@ -6,7 +6,7 @@ // Copyright 2021 Oxide Computer Company use crate::histogram; -use crate::histogram::Histogram; +use crate::histogram::{Histogram, Record}; use crate::types::{Cumulative, Sample}; use uuid::Uuid; @@ -113,6 +113,29 @@ pub fn generate_test_samples( samples } +/// Assert that two numbers are almost equal to each other. +/// +/// On panic, this macro will print the values of the expressions with their +/// debug representations. +#[macro_export] +macro_rules! assert_almost_eq { + ($a:expr, $b:expr, $prec:expr) => { + let diff = ($a - $b).abs(); + assert!( + diff <= $prec, + "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ + (left: `{}`, right: `{}`)", + diff, + $prec, + $a, + $b + ); + }; +} + +/// Assert that two numbers are almost equal to each other. +pub use assert_almost_eq; + #[cfg(test)] mod tests { use super::*;