diff --git a/docs/images/grafana.png b/docs/images/grafana.png new file mode 100644 index 00000000000..d5c953e7630 Binary files /dev/null and b/docs/images/grafana.png differ diff --git a/etc/monitoring/README.md b/etc/monitoring/README.md new file mode 100644 index 00000000000..89895646730 --- /dev/null +++ b/etc/monitoring/README.md @@ -0,0 +1,65 @@ +## Compose sample +### Prometheus & Grafana + +Project structure: +``` +. +├── compose.yaml +├── grafana +│   └── datasource.yml +├── prometheus +│   └── prometheus.yml +└── README.md +``` + +[_compose.yaml_](compose.yaml) +``` +services: + prometheus: + image: prom/prometheus + ... + ports: + - 9090:9090 + grafana: + image: grafana/grafana + ... + ports: + - 3000:3000 +``` +The compose file defines a stack with two services `prometheus` and `grafana`. +When deploying the stack, docker compose maps port the default ports for each service to the equivalent ports on the host in order to inspect easier the web interface of each service. +Make sure the ports 9090 and 3000 on the host are not already in use. + +## Deploy with docker compose + +``` +$ docker compose up -d +Creating network "prometheus-grafana_default" with the default driver +Creating volume "prometheus-grafana_prom_data" with default driver +... +Creating grafana ... done +Creating prometheus ... done +Attaching to prometheus, grafana + +``` + +## Expected result + +Listing containers must show two containers running and the port mapping as below: +``` +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +dbdec637814f prom/prometheus "/bin/prometheus --c…" 8 minutes ago Up 8 minutes 0.0.0.0:9090->9090/tcp prometheus +79f667cb7dc2 grafana/grafana "/run.sh" 8 minutes ago Up 8 minutes 0.0.0.0:3000->3000/tcp grafana +``` + +Navigate to `http://localhost:3000` in your web browser and use the login credentials (username=admin, password=grafana) specified in the compose file to access Grafana. It is already configured with prometheus as the default datasource. + +![page](/docs/images/grafana.png) + +Navigate to `http://localhost:9090` in your web browser to access directly the web interface of prometheus. + +Stop and remove the containers. Use `-v` to remove the volumes if looking to erase all data. +``` +$ docker compose down -v +``` diff --git a/etc/monitoring/compose.yaml b/etc/monitoring/compose.yaml new file mode 100644 index 00000000000..0ea5c32e1ab --- /dev/null +++ b/etc/monitoring/compose.yaml @@ -0,0 +1,30 @@ +services: + prometheus: + image: prom/prometheus + container_name: prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - 9090:9090 + restart: unless-stopped + volumes: + - ./prometheus:/etc/prometheus + - prom_data:/prometheus + extra_hosts: + - "host.docker.internal:host-gateway" + + grafana: + image: grafana/grafana + container_name: grafana + ports: + - 3000:3000 + restart: unless-stopped + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=grafana + volumes: + - ./grafana/datasource:/etc/grafana/provisioning/datasources + - ./grafana/dashboards:/etc/grafana/provisioning/dashboards + - ./grafana/dashboard_definitions:/var/lib/grafana/dashboards +volumes: + prom_data: diff --git a/etc/monitoring/grafana/dashboard_definitions/openroad.json b/etc/monitoring/grafana/dashboard_definitions/openroad.json new file mode 100644 index 00000000000..767a1ef835f --- /dev/null +++ b/etc/monitoring/grafana/dashboard_definitions/openroad.json @@ -0,0 +1,148 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "interval": "2", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "ord_hpwl", + "fullMetaSearch": false, + "includeNullMetadata": true, + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Half Perimeter Wire Length Global Placement", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "OpenROAD", + "uid": "bedke9uv3g5q8d", + "version": 6, + "weekStart": "" + } \ No newline at end of file diff --git a/etc/monitoring/grafana/dashboards/default.yml b/etc/monitoring/grafana/dashboards/default.yml new file mode 100644 index 00000000000..24b8d2c544f --- /dev/null +++ b/etc/monitoring/grafana/dashboards/default.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +providers: + - name: Default # A uniquely identifiable name for the provider + folder: Services # The folder where to place the dashboards + type: file + options: + path: + /var/lib/grafana/dashboards diff --git a/etc/monitoring/grafana/datasource/datasource.yml b/etc/monitoring/grafana/datasource/datasource.yml new file mode 100644 index 00000000000..d7b82868652 --- /dev/null +++ b/etc/monitoring/grafana/datasource/datasource.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: +- name: Prometheus + type: prometheus + url: http://prometheus:9090 + isDefault: true + access: proxy + editable: true diff --git a/etc/monitoring/prometheus/prometheus.yml b/etc/monitoring/prometheus/prometheus.yml new file mode 100644 index 00000000000..ff3b02c7129 --- /dev/null +++ b/etc/monitoring/prometheus/prometheus.yml @@ -0,0 +1,25 @@ +global: + scrape_interval: 15s + scrape_timeout: 10s + evaluation_interval: 15s + +alerting: + alertmanagers: + - static_configs: + - targets: [] # Keep this as is, or add your Alertmanager targets if you have any. + scheme: http # This is already correct + timeout: 10s # This is also fine + api_version: v2 # Changed from v1 to v2 + path_prefix: / # ADD THIS - Required for v2 + +scrape_configs: + - job_name: prometheus + honor_timestamps: true # This section is all good as is. + scrape_interval: 5s + scrape_timeout: 3s + metrics_path: /metrics + scheme: http + static_configs: + - targets: + - localhost:9090 + - host.docker.internal:8080 diff --git a/src/gpl/src/nesterovPlace.cpp b/src/gpl/src/nesterovPlace.cpp index abf042129d0..974ae9bf3b3 100644 --- a/src/gpl/src/nesterovPlace.cpp +++ b/src/gpl/src/nesterovPlace.cpp @@ -227,6 +227,15 @@ void NesterovPlace::init() totalBaseWireLengthCoeff += nb->getBaseWireLengthCoef(); } + std::shared_ptr registry = log_->getRegistry(); + auto& hpwl_gauge_family + = utl::BuildGauge() + .Name("ord_hpwl") + .Help("The half perimeter wire length of the block") + .Register(*registry); + auto& hpwl_gauge = hpwl_gauge_family.Add({}); + hpwl_gauge_ = &hpwl_gauge; + average_overflow_ = total_sum_overflow_ / nbVec_.size(); baseWireLengthCoef_ = totalBaseWireLengthCoeff / nbVec_.size(); updateWireLengthCoef(average_overflow_); @@ -753,6 +762,7 @@ void NesterovPlace::updateNextIter(const int iter) // Update divergence snapshot if (!npVars_.disableRevertIfDiverge) { int64_t hpwl = nbc_->getHpwl(); + hpwl_gauge_->Set(hpwl); if (hpwl < min_hpwl_ && average_overflow_unscaled_ <= 0.25) { min_hpwl_ = hpwl; diverge_snapshot_average_overflow_unscaled_ = average_overflow_unscaled_; diff --git a/src/gpl/src/nesterovPlace.h b/src/gpl/src/nesterovPlace.h index 76ac20980bd..814cce5a26e 100644 --- a/src/gpl/src/nesterovPlace.h +++ b/src/gpl/src/nesterovPlace.h @@ -40,6 +40,7 @@ #include "nesterovBase.h" #include "odb/dbBlockCallBackObj.h" #include "point.h" +#include "utl/prometheus/gauge.h" namespace utl { class Logger; @@ -135,6 +136,9 @@ class NesterovPlace float wireLengthCoefX_ = 0; float wireLengthCoefY_ = 0; + // observability metrics + utl::Gauge* hpwl_gauge_; + // half-parameter-wire-length int64_t prevHpwl_ = 0; diff --git a/src/utl/CMakeLists.txt b/src/utl/CMakeLists.txt index e4f4165a859..50af8fc3b84 100644 --- a/src/utl/CMakeLists.txt +++ b/src/utl/CMakeLists.txt @@ -73,6 +73,7 @@ add_library(utl_lib src/ScopedTemporaryFile.cpp src/Logger.cpp src/timer.cpp + src/prometheus/metrics_server.cpp ) target_include_directories(utl_lib diff --git a/src/utl/README.md b/src/utl/README.md index ffe7a090ec5..904f2cbc19e 100644 --- a/src/utl/README.md +++ b/src/utl/README.md @@ -9,6 +9,32 @@ The utility module contains the `man` command. - Parameters without square brackets `-param2 param2` are required. ``` +### Prometheus Metrics + +OpenROAD includes a metrics endpoint server that can track internal tool metrics over time. + +![page](/docs/images/grafana.png) + +To use this feature you need to do the following start the prometheus and grafana collectors + +[Detailed instructions](/etc/monitoring/README.md): +```shell +$ cd etc/monitoring +$ docker compose up -d +``` + +This will start a grafana endpoint ready to collect from the OpenROAD application you would +like to track. By default it's looking for an http server running on port 8080 on your localhost. + +To start the metrics endpoint in OpenROAD, run: +```tcl +utl::startPrometheusEndpoint 8080 +``` + +This is all configurable in the docker compose file, and you should be able to access grafana by going to +http://localhost:3000 username: admin, password: grafana. Go to the dashboard tab and click service, +then OpenROAD to see the pre-made dashboard. + ## Man installation The `man` command can be installed optionally as part of the OpenROAD diff --git a/src/utl/include/utl/Logger.h b/src/utl/include/utl/Logger.h index fcad815d7d7..9df4e4ed3c1 100644 --- a/src/utl/include/utl/Logger.h +++ b/src/utl/include/utl/Logger.h @@ -59,6 +59,9 @@ namespace utl { +class PrometheusMetricsServer; +class PrometheusRegistry; + // Keep this sorted #define FOREACH_TOOL(X) \ X(ANT) \ @@ -234,6 +237,11 @@ class Logger return (it != groups.end() && level <= it->second); } + void startPrometheusEndpoint(uint16_t port); + std::shared_ptr getRegistry(); + bool isPrometheusServerReadyToServe(); + uint16_t getPrometheusPort(); + void suppressMessage(ToolId tool, int id); void unsuppressMessage(ToolId tool, int id); @@ -350,6 +358,10 @@ class Logger std::unique_ptr string_redirect_; std::unique_ptr file_redirect_; + // Prometheus server metrics collection + std::shared_ptr prometheus_registry_; + std::unique_ptr prometheus_metrics_; + // This matrix is pre-allocated so it can be safely updated // from multiple threads without locks. using MessageCounter = std::array; @@ -386,9 +398,9 @@ struct test_ostream { public: template - static auto test(int) - -> decltype(std::declval() << std::declval(), - std::true_type()); + static auto test(int) -> decltype(std::declval() + << std::declval(), + std::true_type()); template static auto test(...) -> std::false_type; diff --git a/src/utl/include/utl/prometheus/atomic_floating.h b/src/utl/include/utl/prometheus/atomic_floating.h new file mode 100644 index 00000000000..501df048b1c --- /dev/null +++ b/src/utl/include/utl/prometheus/atomic_floating.h @@ -0,0 +1,77 @@ +// MIT License + +// Copyright (c) 2021 biaks (ianiskr@gmail.com) + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include + +namespace utl { + +template +inline std::atomic& atomic_add_for_floating_types( + std::atomic& value, + const FloatingType& add) +{ + FloatingType desired; + FloatingType expected = value.load(std::memory_order_relaxed); + do { + desired = expected + add; + } while (!value.compare_exchange_weak(expected, desired)); + return value; +} + +template ::value, int>::type> +inline std::atomic& operator++(std::atomic& value) +{ + return atomic_add_for_floating_types(value, 1.0); +} + +template ::value, int>::type> +inline std::atomic& operator+=(std::atomic& value, + const FloatingType& val) +{ + return atomic_add_for_floating_types(value, val); +} + +template ::value, int>::type> +inline std::atomic& operator--(std::atomic& value) +{ + return atomic_add_for_floating_types(value, -1.0); +} + +template ::value, int>::type> +inline std::atomic& operator-=(std::atomic& value, + const FloatingType& val) +{ + return atomic_add_for_floating_types(value, -val); +} + +} // namespace utl diff --git a/src/utl/include/utl/prometheus/benchmark.h b/src/utl/include/utl/prometheus/benchmark.h new file mode 100644 index 00000000000..465461a267a --- /dev/null +++ b/src/utl/include/utl/prometheus/benchmark.h @@ -0,0 +1,97 @@ +// MIT License + +// Copyright (c) 2021 biaks (ianiskr@gmail.com) + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include + +#include "client_metric.h" +#include "family.h" +#include "prometheus_metric.h" + +namespace utl { + +class Benchmark : public PrometheusMetric +{ +#ifndef NDEBUG + bool already_started = false; +#endif + + std::chrono::time_point start_; + std::chrono::time_point::duration elapsed + = std::chrono::time_point::duration:: + zero(); // elapsed time + + public: + using Value = double; + using Family = CustomFamily; + + static const PrometheusMetric::Type static_type + = PrometheusMetric::Type::Counter; + + Benchmark() : PrometheusMetric(PrometheusMetric::Type::Counter) {} + + void start() + { +#ifndef NDEBUG + if (already_started) { + throw std::runtime_error("try to start already started counter"); + } + already_started = true; +#endif + + start_ = std::chrono::high_resolution_clock::now(); + } + + void stop() + { +#ifndef NDEBUG + if (already_started == false) { + throw std::runtime_error("try to stop already stoped counter"); + } +#endif + + std::chrono::time_point stop; + stop = std::chrono::high_resolution_clock::now(); + elapsed += stop - start_; + +#ifndef NDEBUG + already_started = false; +#endif + } + + double Get() const + { + return std::chrono::duration_cast>(elapsed) + .count(); + } + + ClientMetric Collect() const override + { + ClientMetric metric; + metric.counter.value = Get(); + return metric; + } +}; + +} // namespace utl diff --git a/src/utl/include/utl/prometheus/builder.h b/src/utl/include/utl/prometheus/builder.h new file mode 100644 index 00000000000..0ffffb1b7fd --- /dev/null +++ b/src/utl/include/utl/prometheus/builder.h @@ -0,0 +1,61 @@ +// MIT License + +// Copyright (c) 2021 biaks (ianiskr@gmail.com) + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include + +#include "registry.h" + +namespace utl { + +template +class Builder +{ + Family::Labels labels_; + std::string name_; + std::string help_; + + public: + Builder& Labels(const std::map& labels) + { + labels_ = labels; + return *this; + } + Builder& Name(const std::string& name) + { + name_ = name; + return *this; + } + Builder& Help(const std::string& help) + { + help_ = help; + return *this; + } + CustomFamily& Register(PrometheusRegistry& registry) + { + return registry.Add>(name_, help_, labels_); + } +}; + +} // namespace utl \ No newline at end of file diff --git a/src/utl/include/utl/prometheus/ckms_quantiles.h b/src/utl/include/utl/prometheus/ckms_quantiles.h new file mode 100644 index 00000000000..4eb91e31177 --- /dev/null +++ b/src/utl/include/utl/prometheus/ckms_quantiles.h @@ -0,0 +1,224 @@ +// MIT License + +// Copyright (c) 2021 biaks (ianiskr@gmail.com) + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace utl { + +namespace detail { + +class CKMSQuantiles +{ + public: + struct Quantile + { + double quantile; + double error; + double u; + double v; + + Quantile(double quantile, double error) + : quantile(quantile), + error(error), + u(2.0 * error / (1.0 - quantile)), + v(2.0 * error / quantile) + { + } + }; + + private: + struct Item + { + double value; + int g; + int delta; + + Item(double value, int lower_delta, int delta) + : value(value), g(lower_delta), delta(delta) + { + } + }; + + public: + explicit CKMSQuantiles(const std::vector& quantiles) + : quantiles_(quantiles) + { + } + + void insert(double value) + { + buffer_[buffer_count_] = value; + ++buffer_count_; + + if (buffer_count_ == buffer_.size()) { + insertBatch(); + compress(); + } + } + + double get(double q) + { + insertBatch(); + compress(); + + if (sample_.empty()) { + return std::numeric_limits::quiet_NaN(); + } + + int rankMin = 0; + const auto desired = static_cast(q * static_cast(count_)); + const auto bound = desired + (allowableError(desired) / 2); + + auto it = sample_.begin(); + decltype(it) prev; + auto cur = it++; + + while (it != sample_.end()) { + prev = cur; + cur = it++; + + rankMin += prev->g; + + if (rankMin + cur->g + cur->delta > bound) { + return prev->value; + } + } + + return sample_.back().value; + } + + void reset() + { + count_ = 0; + sample_.clear(); + buffer_count_ = 0; + } + + private: + double allowableError(int rank) + { + auto size = sample_.size(); + double minError = static_cast(size + 1); + + for (const auto& q : quantiles_.get()) { + double error; + if (static_cast(rank) <= q.quantile * static_cast(size)) { + error = q.u * static_cast(size - rank); + } else { + error = q.v * rank; + } + if (error < minError) { + minError = error; + } + } + + return minError; + } + + bool insertBatch() + { + if (buffer_count_ == 0) { + return false; + } + + std::sort(buffer_.begin(), buffer_.begin() + buffer_count_); + + std::size_t start = 0; + if (sample_.empty()) { + sample_.emplace_back(buffer_[0], 1, 0); + ++start; + ++count_; + } + + std::size_t idx = 0; + std::size_t item = idx++; + + for (std::size_t i = start; i < buffer_count_; ++i) { + double v = buffer_[i]; + while (idx < sample_.size() && sample_[item].value < v) { + item = idx++; + } + + if (sample_[item].value > v) { + --idx; + } + + int delta; + if (idx - 1 == 0 || idx + 1 == sample_.size()) { + delta = 0; + } else { + delta = static_cast( + std::floor(allowableError(static_cast(idx + 1)))) + + 1; + } + + sample_.emplace(sample_.begin() + idx, v, 1, delta); + count_++; + item = idx++; + } + + buffer_count_ = 0; + return true; + } + + void compress() + { + if (sample_.size() < 2) { + return; + } + + std::size_t idx = 0; + std::size_t prev; + std::size_t next = idx++; + + while (idx < sample_.size()) { + prev = next; + next = idx++; + + if (sample_[prev].g + sample_[next].g + sample_[next].delta + <= allowableError(static_cast(idx - 1))) { + sample_[next].g += sample_[prev].g; + sample_.erase(sample_.begin() + prev); + } + } + } + + private: + const std::reference_wrapper> quantiles_; + + std::size_t count_{0}; + std::vector sample_; + std::array buffer_{}; + std::size_t buffer_count_{0}; +}; + +} // namespace detail + +} // namespace utl diff --git a/src/utl/include/utl/prometheus/client_metric.h b/src/utl/include/utl/prometheus/client_metric.h new file mode 100644 index 00000000000..f5eb1577113 --- /dev/null +++ b/src/utl/include/utl/prometheus/client_metric.h @@ -0,0 +1,126 @@ +// MIT License + +// Copyright (c) 2021 biaks (ianiskr@gmail.com) + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include + +namespace utl { + +struct ClientMetric +{ + // Label + + struct Label + { + std::string name; + std::string value; + + Label(std::string name_, std::string value_) + : name(std::move(name_)), value(std::move(value_)) + { + } + + bool operator<(const Label& rhs) const + { + return std::tie(name, value) < std::tie(rhs.name, rhs.value); + } + + bool operator==(const Label& rhs) const + { + return std::tie(name, value) == std::tie(rhs.name, rhs.value); + } + }; + + std::vector