Skip to content

Commit

Permalink
cleanup(libsinp): improve prometheus format conversion correctness
Browse files Browse the repository at this point in the history
Co-authored-by: Mickael Carl <[email protected]>
Signed-off-by: Melissa Kilby <[email protected]>
  • Loading branch information
incertum and mickael-carl committed Feb 8, 2024
1 parent 635bcc9 commit ab2df69
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 85 deletions.
3 changes: 2 additions & 1 deletion userspace/libscap/metrics_v2.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ typedef enum metrics_v2_value_unit{
METRIC_VALUE_UNIT_COUNT,
METRIC_VALUE_UNIT_PERC,
METRIC_VALUE_UNIT_MEMORY_BYTES,
METRIC_VALUE_UNIT_MEMORY_KILOBYTES,
METRIC_VALUE_UNIT_MEMORY_KIBIBYTES,
METRIC_VALUE_UNIT_MEMORY_MEGABYTES,
METRIC_VALUE_UNIT_DURATION_NS,
METRIC_VALUE_UNIT_DURATION_S,
}metrics_v2_value_unit;

typedef enum metrics_v2_metric_type{
Expand Down
93 changes: 45 additions & 48 deletions userspace/libsinsp/metrics_collector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,19 @@ static const char *const sinsp_stats_v2_resource_utilization_names[] = {

// For simplicity, needs to stay in sync w/ typedef enum metrics_v2_value_unit
// https://prometheus.io/docs/practices/naming/
static const char *const metrics_unit_name_mappings_prom[] = {
static const char *const metrics_unit_name_mappings_prometheus[] = {
[METRIC_VALUE_UNIT_COUNT] = "total",
[METRIC_VALUE_UNIT_PERC] = "percentage",
[METRIC_VALUE_UNIT_MEMORY_BYTES] = "bytes",
[METRIC_VALUE_UNIT_MEMORY_KILOBYTES] = "kilobytes",
[METRIC_VALUE_UNIT_MEMORY_KIBIBYTES] = "kibibytes",
[METRIC_VALUE_UNIT_MEMORY_MEGABYTES] = "megabytes",
[METRIC_VALUE_UNIT_DURATION_NS] = "duration_nanoseconds",
[METRIC_VALUE_UNIT_DURATION_S] = "duration_seconds",
};

// For simplicity, needs to stay in sync w/ typedef enum metrics_v2_metric_type
// https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
static const char *const metrics_metric_type_name_mappings_prom[] = {
static const char *const metrics_metric_type_name_mappings_prometheus[] = {
[METRIC_VALUE_MONOTONIC] = "counter",
[METRIC_VALUE_NON_MONOTONIC_CURRENT] = "gauge",
};
Expand Down Expand Up @@ -346,8 +347,8 @@ void metrics_collector::snapshot()
uint64_t memory_used_host{0}, open_fds_host{0};
double cpu_usage_perc{0.0}, cpu_usage_perc_total_host{0.0};
uint64_t container_memory_usage = get_container_memory_usage();
metrics_v2_value_unit rss_unit{METRIC_VALUE_UNIT_MEMORY_KILOBYTES}, vsz_unit{METRIC_VALUE_UNIT_MEMORY_KILOBYTES},
pss_unit{METRIC_VALUE_UNIT_MEMORY_KILOBYTES}, memory_used_host_unit{METRIC_VALUE_UNIT_MEMORY_KILOBYTES}, container_memory_usage_unit{METRIC_VALUE_UNIT_MEMORY_BYTES};
metrics_v2_value_unit rss_unit{METRIC_VALUE_UNIT_MEMORY_KIBIBYTES}, vsz_unit{METRIC_VALUE_UNIT_MEMORY_KIBIBYTES},
pss_unit{METRIC_VALUE_UNIT_MEMORY_KIBIBYTES}, memory_used_host_unit{METRIC_VALUE_UNIT_MEMORY_KIBIBYTES}, container_memory_usage_unit{METRIC_VALUE_UNIT_MEMORY_BYTES};
metrics_v2_value_type rss_type{METRIC_VALUE_TYPE_U32}, vsz_type{METRIC_VALUE_TYPE_U32},
pss_type{METRIC_VALUE_TYPE_U32}, memory_used_host_type{METRIC_VALUE_TYPE_U32}, container_memory_usage_type{METRIC_VALUE_TYPE_U64};
get_cpu_usage_and_total_procs(agent_info->start_time, cpu_usage_perc, cpu_usage_perc_total_host, procs_running_host);
Expand Down Expand Up @@ -458,91 +459,87 @@ void metrics_collector::snapshot()
}
}

std::string metrics_collector::convert_metric_to_prom_text(metrics_v2 metric, std::string_view prom_namespace, std::string_view prom_subsystem, std::map<std::string, std::string> const_labels)
std::string metrics_collector::convert_metric_to_prometheus_text(metrics_v2 metric, std::string_view prometheus_namespace, std::string_view prometheus_subsystem, std::map<std::string, std::string> const_labels)
{
// Create `prom_metric_name_fully_qualified`
std::string prom_metric_name_fully_qualified;
if (!prom_namespace.empty())
// Create `prometheus_metric_name_fully_qualified`
std::string prometheus_metric_name_fully_qualified;
if (!prometheus_namespace.empty())
{
prom_metric_name_fully_qualified += std::string(prom_namespace) + "_";
prometheus_metric_name_fully_qualified += std::string(prometheus_namespace) + "_";
}
if (!prom_subsystem.empty())
if (!prometheus_subsystem.empty())
{
prom_metric_name_fully_qualified += std::string(prom_subsystem) + "_";
prometheus_metric_name_fully_qualified += std::string(prometheus_subsystem) + "_";
}
prom_metric_name_fully_qualified += std::string(metric.name) + "_";
prom_metric_name_fully_qualified += std::string(metrics_unit_name_mappings_prom[metric.unit]);
prometheus_metric_name_fully_qualified += std::string(metric.name) + "_";
prometheus_metric_name_fully_qualified += std::string(metrics_unit_name_mappings_prometheus[metric.unit]);

// Create the complete 3-lines text-based Prometheus exposition format https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
std::string prom_text = "# HELP " + prom_metric_name_fully_qualified + " https://falco.org/docs/metrics/\n";
prom_text += "# TYPE " + prom_metric_name_fully_qualified + " " + std::string(metrics_metric_type_name_mappings_prom[metric.metric_type]) + "\n";
prom_text += prom_metric_name_fully_qualified;
prom_text += "{raw_name=\"" + std::string(metric.name) + "\"" ;
std::string prometheus_text = "# HELP " + prometheus_metric_name_fully_qualified + " https://falco.org/docs/metrics/\n";
prometheus_text += "# TYPE " + prometheus_metric_name_fully_qualified + " " + std::string(metrics_metric_type_name_mappings_prometheus[metric.metric_type]) + "\n";
prometheus_text += prometheus_metric_name_fully_qualified;
prometheus_text += "{raw_name=\"" + std::string(metric.name) + "\"" ;
for (const auto& [key, value] : const_labels)
{
prom_text += "," + key + "=\"" + value + "\"" ;
prometheus_text += "," + key + "=\"" + value + "\"" ;
}
prom_text += "} "; // white space at the end important!
prometheus_text += "} "; // white space at the end important!
switch (metric.type)
{
case METRIC_VALUE_TYPE_U32:
prom_text += std::to_string(metric.value.u32);
prometheus_text += std::to_string(metric.value.u32);
break;
case METRIC_VALUE_TYPE_S32:
prom_text += std::to_string(metric.value.s32);
prometheus_text += std::to_string(metric.value.s32);
break;
case METRIC_VALUE_TYPE_U64:
prom_text += std::to_string(metric.value.u64);
prometheus_text += std::to_string(metric.value.u64);
break;
case METRIC_VALUE_TYPE_S64:
prom_text += std::to_string(metric.value.s64);
prometheus_text += std::to_string(metric.value.s64);
break;
case METRIC_VALUE_TYPE_D:
prom_text += std::to_string(metric.value.d);
prometheus_text += std::to_string(metric.value.d);
break;
case METRIC_VALUE_TYPE_F:
prom_text += std::to_string(metric.value.f);
prometheus_text += std::to_string(metric.value.f);
break;
case METRIC_VALUE_TYPE_I:
prom_text += std::to_string(metric.value.i);
prometheus_text += std::to_string(metric.value.i);
break;
default:
break;
}

prom_text += " ";
prom_text += std::to_string(sinsp_utils::get_current_time_ns());
prom_text += "\n";
return prom_text;
prometheus_text += "\n";
return prometheus_text;
}

std::string metrics_collector::convert_metric_to_prom_text(std::string_view metric_name, std::string_view prom_namespace, std::string_view prom_subsystem, std::map<std::string, std::string> const_labels)
std::string metrics_collector::convert_metric_to_prometheus_text(std::string_view metric_name, std::string_view prometheus_namespace, std::string_view prometheus_subsystem, std::map<std::string, std::string> const_labels)
{
// Create `prom_metric_name_fully_qualified`
std::string prom_metric_name_fully_qualified;
if (!prom_namespace.empty())
// Create `prometheus_metric_name_fully_qualified`
std::string prometheus_metric_name_fully_qualified;
if (!prometheus_namespace.empty())
{
prom_metric_name_fully_qualified += std::string(prom_namespace) + "_";
prometheus_metric_name_fully_qualified += std::string(prometheus_namespace) + "_";
}
if (!prom_subsystem.empty())
if (!prometheus_subsystem.empty())
{
prom_metric_name_fully_qualified += std::string(prom_subsystem) + "_";
prometheus_metric_name_fully_qualified += std::string(prometheus_subsystem) + "_";
}
prom_metric_name_fully_qualified += std::string(metric_name) + "_info";
prometheus_metric_name_fully_qualified += std::string(metric_name) + "_info";

// Create the complete 3-lines text-based Prometheus exposition format https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
std::string prom_text = "# HELP " + prom_metric_name_fully_qualified + " https://falco.org/docs/metrics/\n";
prom_text += "# TYPE " + prom_metric_name_fully_qualified + " untyped\n";
prom_text += prom_metric_name_fully_qualified;
prom_text += "{raw_name=\"" + std::string(metric_name) + "\"" ;
std::string prometheus_text = "# HELP " + prometheus_metric_name_fully_qualified + " https://falco.org/docs/metrics/\n";
prometheus_text += "# TYPE " + prometheus_metric_name_fully_qualified + " gauge\n";
prometheus_text += prometheus_metric_name_fully_qualified;
prometheus_text += "{raw_name=\"" + std::string(metric_name) + "\"" ;
for (const auto& [key, value] : const_labels)
{
prom_text += "," + key + "=\"" + value + "\"" ;
prometheus_text += "," + key + "=\"" + value + "\"" ;
}
prom_text += "} 1 "; // white space at the end important!
prom_text += std::to_string(sinsp_utils::get_current_time_ns());
prom_text += "\n";
return prom_text;
prometheus_text += "} 1\n";
return prometheus_text;
}

const std::vector<metrics_v2>& metrics_collector::get_metrics() const
Expand Down
47 changes: 34 additions & 13 deletions userspace/libsinsp/metrics_collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class metrics_collector
const std::vector<metrics_v2>& get_metrics() const;

/*!
\brief Method to convert a metric to the text-based Prometheus exposition format.
\brief Method to convert a metrics_v2 metric to the text-based Prometheus exposition format.
*
* Reference: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
* Note: The design idea is to expose Prometheus metrics by piping text-based formats to new line-delimited fields
Expand All @@ -116,35 +116,56 @@ class metrics_collector
* The final fully qualified Prometheus metric name partially follows https://prometheus.io/docs/practices/naming/
* Prepend namespace and subsystem with "_" delimiter to create a fully qualified metric name according to
* https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Opts + append unit with "_" delimiter
* We do not follow the concept of base_units, but guarantee no units are mixed per unique `prom_metric_name_fully_qualified`
* We do not strictly follow and enforce the concept of base_units, but guarantee no units are mixed per unique
* `prometheus_metric_name_fully_qualified`
*
* We are monitoring updates wrt https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md
*
* Example:
*
* # HELP testns_falco_n_threads_total https://falco.org/docs/metrics/
* # TYPE testns_falco_n_threads_total gauge
* testns_falco_n_threads_total{raw_name="n_threads",example_key1="example1",example_key2="example2"} 12 1707281978248705000
* testns_falco_n_threads_total{raw_name="n_threads",example_key1="example1",example_key2="example2"} 12
* # HELP testns_falco_memory_rss_megabytes https://falco.org/docs/metrics/
* # TYPE testns_falco_memory_rss_megabytes gauge
* testns_falco_memory_rss_megabytes{raw_name="memory_rss",example_key1="example1",example_key2="example2"} 350.000000 1707281978248635000
* testns_falco_memory_rss_megabytes{raw_name="memory_rss",example_key1="example1",example_key2="example2"} 350.000000
*
* This method is a work in progress.
*
* @param metric metrics_v2 metric
* @param prometheus_namespace first component of `prometheus_metric_name_fully_qualified` (optional)
* @param prometheus_subsystem second component of `prometheus_metric_name_fully_qualified` (optional)
* @param const_labels map of additional labels (rarely used for a metrics_v2 metric)
* @return Complete new line delimited text-based Prometheus exposition format metric string
* w/ a `prometheus_metric_name_fully_qualified` - optional components prepended to and unit appended to.
* 3-lines including # HELP and # TYPE lines followed by the metric line, raw metric name always present as label.
*/
std::string convert_metric_to_prom_text(metrics_v2 metric, std::string_view prom_namespace = "", std::string_view prom_subsystem = "", std::map<std::string,std::string> const_labels = {});
std::string convert_metric_to_prometheus_text(metrics_v2 metric, std::string_view prometheus_namespace = "", std::string_view prometheus_subsystem = "", std::map<std::string,std::string> const_labels = {});

/*!
\brief Method to convert a software version like metric to the text-based Prometheus exposition format.
\brief Method to convert a software version like metric_name to the text-based Prometheus exposition format.
*
* Note: Instead of using const_labels, which is a rare use case according to https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels,
* Note: Instead of using const_labels, which is a rare use case according to
* https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels,
* exposing an overload to support metrics similar to https://www.robustperception.io/exposing-the-software-version-to-prometheus/.
* This approach is applicable to https://falco.org/docs/metrics/, such as Falco's "Base Fields" like falco.kernel_release and falco.version.
* This approach is applicable to https://falco.org/docs/metrics/, such as Falco's "Base Fields" like
* falco.kernel_release and falco.version.
*
* Example:
*
* # HELP testns_falco_kernel_release_info https://falco.org/docs/metrics/
* # TYPE testns_falco_kernel_release_info untyped
* testns_falco_kernel_release_info{raw_name="kernel_release",kernel_release="6.6.7-200.fc39.x86_64"} 1 1707286535681433000
* # TYPE testns_falco_kernel_release_info gauge
* testns_falco_kernel_release_info{raw_name="kernel_release",kernel_release="6.6.7-200.fc39.x86_64"} 1
*
* @param metric_name raw metric name
* @param prometheus_namespace first component of `prometheus_metric_name_fully_qualified` (optional)
* @param prometheus_subsystem second component of `prometheus_metric_name_fully_qualified` (optional)
* @param const_labels map of additional labels (typically used in software version like metrics)
* @return Complete new line delimited text-based Prometheus exposition format metric string
* w/ a `prometheus_metric_name_fully_qualified` - optional components prepended to and unit appended to.
* 3-lines including # HELP and # TYPE lines followed by the metric line, raw metric name always present as label.
*/
std::string convert_metric_to_prom_text(std::string_view metric_name, std::string_view prom_namespace = "", std::string_view prom_subsystem = "", std::map<std::string,std::string> const_labels = {});
std::string convert_metric_to_prometheus_text(std::string_view metric_name, std::string_view prometheus_namespace = "", std::string_view prometheus_subsystem = "", std::map<std::string,std::string> const_labels = {});

/*!
\brief Method to convert memory units; tied to metrics_v2 definitions
Expand All @@ -158,7 +179,7 @@ class metrics_collector
case METRIC_VALUE_UNIT_MEMORY_BYTES:
factor = 1;
break;
case METRIC_VALUE_UNIT_MEMORY_KILOBYTES:
case METRIC_VALUE_UNIT_MEMORY_KIBIBYTES:
factor = 1024.;
break;
case METRIC_VALUE_UNIT_MEMORY_MEGABYTES:
Expand All @@ -173,7 +194,7 @@ class metrics_collector
{
case METRIC_VALUE_UNIT_MEMORY_BYTES:
return bytes_val;
case METRIC_VALUE_UNIT_MEMORY_KILOBYTES:
case METRIC_VALUE_UNIT_MEMORY_KIBIBYTES:
return std::round((bytes_val / 1024.) * 10.) / 10.; // round to 1 decimal
case METRIC_VALUE_UNIT_MEMORY_MEGABYTES:
return std::round((bytes_val / 1024. / 1024.) * 10.) / 10.; // round to 1 decimal
Expand Down
Loading

0 comments on commit ab2df69

Please sign in to comment.