Skip to content

Commit

Permalink
setting up prometheus (#2)
Browse files Browse the repository at this point in the history
* setting up prometheus

* move header to include directory

* clang-format

* clang-format

* remove uncessary changes

* sort includes

* sort includes

* switch type of ports to uint16_t

* fix compile warnings

* save prometheus default host and port into a constant

* use uint16_t for command line port input

* add prometheus section to config parser

* cleanup tetra-receiver main

* fix prometheus config parser

* add name element to config

* add prometheus polling interval config option

* implement first part of gnuradio processing for prometheus

* implement gnuradio block to populate the prometheus gauge

* making code pretty

* do averaging over one second for the signal stregth

* prometheus-gauge-populator: remove debug print

* remove unused variables

* adding mock & tests

* borken tests

* Revert "borken tests"

This reverts commit 7035ef7.

* Revert "adding mock & tests"

This reverts commit 1934257.

* remove unused comment in test

* review comments

* delete default constructor of PrometheusGaugePopulator

---------

Co-authored-by: Markus Schmidl <[email protected]>
  • Loading branch information
tanneberger and marenz2569 authored May 19, 2024
1 parent 7fa467c commit 5678b39
Show file tree
Hide file tree
Showing 13 changed files with 384 additions and 71 deletions.
18 changes: 17 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ find_package(Boost REQUIRED)
find_package(Volk REQUIRED)
find_package(gnuradio-osmosdr REQUIRED)
find_package(cxxopts REQUIRED)
find_package(prometheus-cpp CONFIG REQUIRED)

include_directories(${GNURADIO_ALL_INCLUDE_DIRS})

Expand All @@ -32,12 +33,27 @@ target_include_directories(lib-tetra-receiver PUBLIC include)
# Build the tool
#
add_executable(tetra-receiver
src/prometheus.cpp
src/prometheus_gauge_populator.cpp
src/tetra-receiver.cpp
)

target_compile_options(tetra-receiver PUBLIC -std=c++17 -Wall)

target_link_libraries(tetra-receiver lib-tetra-receiver log4cpp volk gnuradio-osmosdr gnuradio-digital gnuradio-analog gnuradio-filter gnuradio-blocks gnuradio-fft gnuradio-runtime gnuradio-pmt)
target_link_libraries(tetra-receiver
lib-tetra-receiver
log4cpp
volk
gnuradio-osmosdr
gnuradio-digital
gnuradio-analog
gnuradio-filter
gnuradio-blocks
gnuradio-fft
gnuradio-runtime
gnuradio-pmt
prometheus-cpp::pull
)

#
# Testing
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Therefore this application supports multiple stages of decimation.
The config has mandatory global arguments `CenterFrequency`, `DeviceString` and `SampleRate` for the SDR.
The optional argumens `RFGain`, `IFGain` and `BBGain` are for setting the gains of the SDR, by default these are zero.

Specify an optional table with the name `Prometheus` and the values `Host` and `Port` to send metrics about the currently received signal strength to a prometheus server.

If a table specifies `Frequency`, `Host` and `Port`, the signal is directly decoded from the SDR.
If it is specified in a subtable, it is decoded from the decimated signal described by the associtated table.

Expand All @@ -53,6 +55,10 @@ RFGain = unsigned int (default 0)
IFGain = unsigned int (default 0)
BBGain = unsigned int (default 0)
[Prometheus]
Host = "string" (default 127.0.0.1)
Port = unsigned int (default 9010)
[DecimateA]
Frequency = unsigned int
SampleRate = unsigned int
Expand All @@ -72,3 +78,8 @@ Frequency = unsigned int
Host = "string"
Port = unsigned int
```

## Prometheus
The power of each stream can be exported when setting the `Prometheus` config table.

The magnitude of the stream is filtered to match the frequency of the polling interval.
30 changes: 24 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 81 additions & 30 deletions include/config.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
#ifndef CONFIG_H
#define CONFIG_H

#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>

#include <optional>
#include <toml.hpp>
#include <variant>

#ifndef INCLUDE_CONFIG_H
#define INCLUDE_CONFIG_H

namespace config {

Expand All @@ -21,14 +20,20 @@ namespace config {

/// The default host where to send the TETRA data. Gnuradio breaks if this is a host and not an IP
[[maybe_unused]] const std::string kDefaultHost = "127.0.0.1";
[[maybe_unused]] constexpr unsigned int kDefaultPort = 42000;
[[maybe_unused]] constexpr uint16_t kDefaultPort = 42000;

// The default host to which we send the signal strength data for prometheus
const std::string kDefaultPrometheusHost = "127.0.0.1";
constexpr uint16_t kDefaultPrometheusPort = 9010;

template <typename T> class Range {
private:
T min_ = 0;
T max_ = 0;

public:
Range() = delete;

/// A class takes two values of type T and stores the minimum value in min_,
/// the maximum in max_ respectively.
Range(const T lhs, const T rhs) noexcept {
Expand Down Expand Up @@ -59,6 +64,8 @@ template <typename T> class SpectrumSlice {
/// the sampling frequency for this slice of EM spectrum
T sample_rate_;

SpectrumSlice() = delete;

/// Get different properties for a slice of EM spectrum
/// \param frequency the center frequency of the slice
/// \param sample_rate the sample rate of this spectrum
Expand All @@ -67,20 +74,17 @@ template <typename T> class SpectrumSlice {
, frequency_range_(center_frequency - sample_rate / 2, center_frequency + sample_rate / 2)
, sample_rate_(sample_rate){};

friend auto operator==(const SpectrumSlice<T>&, const SpectrumSlice<T>&) -> bool;
friend auto operator!=(const SpectrumSlice<T>&, const SpectrumSlice<T>&) -> bool;
};

template <typename T> auto operator==(const SpectrumSlice<T>& lhs, const SpectrumSlice<T>& rhs) -> bool {
return lhs.center_frequency_ == rhs.center_frequency_ && lhs.sample_rate_ == rhs.sample_rate_;
};
friend auto operator==(const SpectrumSlice<T>& lhs, const SpectrumSlice<T>& rhs) -> bool {
return lhs.center_frequency_ == rhs.center_frequency_ && lhs.sample_rate_ == rhs.sample_rate_;
};

template <typename T> auto operator!=(const SpectrumSlice<T>& lhs, const SpectrumSlice<T>& rhs) -> bool {
return !operator==<T>(lhs, rhs);
friend auto operator!=(const SpectrumSlice<T>& lhs, const SpectrumSlice<T>& rhs) -> bool { return !(lhs == rhs); };
};

class Stream {
public:
/// the name of the table in the config
const std::string name_;
/// the slice of spectrum_ that is input to this block
const SpectrumSlice<unsigned int> input_spectrum_;
/// the slice of spectrum_ of the TETRA Stream
Expand All @@ -94,20 +98,25 @@ class Stream {
/// Optional field
/// The port to which the samples of the Stream should be sent. This defaults
/// to 42000.
const unsigned int port_ = 0;
const uint16_t port_ = 0;

Stream() = delete;

/// Describe the on which frequency a TETRA Stream should be extracted and
/// where data should be sent to.
/// \param name the name of the table in the config
/// \param input_spectrum the slice of spectrum that is input to this block
/// \param spectrum the slice of spectrum of the TETRA Stream
/// \param host the to send the data to
/// \param port the port to send the data to
Stream(const SpectrumSlice<unsigned int>& input_spectrum, const SpectrumSlice<unsigned int>& spectrum,
std::string host, unsigned int port);
Stream(const std::string& name, const SpectrumSlice<unsigned int>& input_spectrum,
const SpectrumSlice<unsigned int>& spectrum, std::string host, uint16_t port);
};

class Decimate {
public:
/// the name of the table in the config
const std::string name_;
/// the slice of spectrum that is input to this block
const SpectrumSlice<unsigned int> input_spectrum_;
/// the slice of spectrum after decimation_
Expand All @@ -120,13 +129,34 @@ class Decimate {
/// connected to.
std::vector<Stream> streams_;

Decimate() = delete;

/// Describe the decimation of the SDR Stream by the frequency where we want
/// to extract a signal with a width of sample_rate
/// \param name the name of the table in the config
/// \param input_spectrum the slice of spectrum that is input to this block
/// \param spectrum the slice of spectrum after decimation
/// \param streams the vector of streams the decimated signal should be sent
/// to
Decimate(const SpectrumSlice<unsigned int>& input_spectrum, const SpectrumSlice<unsigned int>& spectrum);
Decimate(const std::string& name, const SpectrumSlice<unsigned int>& input_spectrum,
const SpectrumSlice<unsigned int>& spectrum);
};

class Prometheus {
public:
/// the host to which prometheus is sending data to
const std::string host_;
/// the port to which prometheus is sending data to
const uint16_t port_;

Prometheus() = delete;

/// The prometheus exporter to we want to send the metrics
/// \param host the host to which prometheus is sending data
/// \param port the port to which prometheus is sending data
Prometheus(std::string host, const uint16_t port)
: host_(std::move(host))
, port_(port){};
};

class TopLevel {
Expand All @@ -147,10 +177,14 @@ class TopLevel {
/// The vector of decimators which should first Decimate a signal of the SDR
/// and then sent it to the vector of streams inside them.
const std::vector<Decimate> decimators_{};
/// Optional config element for the prometheus exporter
const std::unique_ptr<Prometheus> prometheus_;

TopLevel() = delete;

TopLevel(const SpectrumSlice<unsigned int>& spectrum, std::string device_string, unsigned int rf_gain,
unsigned int if_gain, unsigned int bb_gain, const std::vector<Stream>& streams,
const std::vector<Decimate>& decimators);
const std::vector<Decimate>& decimators, std::unique_ptr<Prometheus>&& prometheus);
};

using decimate_or_stream = std::variant<Decimate, Stream>;
Expand All @@ -160,26 +194,35 @@ using decimate_or_stream = std::variant<Decimate, Stream>;
namespace toml {

static config::decimate_or_stream get_decimate_or_stream(const config::SpectrumSlice<unsigned int>& input_spectrum,
const value& v) {
const std::string& name, const value& v) {
std::optional<unsigned int> sample_rate;

const unsigned int frequency = find<unsigned int>(v, "Frequency");
if (v.contains("SampleRate"))
sample_rate = find<unsigned int>(v, "SampleRate");

const std::string host = find_or(v, "Host", config::kDefaultHost);
const unsigned int port = find_or(v, "Port", config::kDefaultPort);
const uint16_t port = find_or(v, "Port", config::kDefaultPort);

// If we have a sample rate specified this is a Decimate, otherwhise this is
// a Stream.
if (sample_rate.has_value()) {
return config::Decimate(input_spectrum, config::SpectrumSlice<unsigned int>(frequency, *sample_rate));
return config::Decimate(name, input_spectrum, config::SpectrumSlice<unsigned int>(frequency, *sample_rate));
} else {
return config::Stream(input_spectrum, config::SpectrumSlice<unsigned int>(frequency, config::kTetraSampleRate),
host, port);
return config::Stream(name, input_spectrum,
config::SpectrumSlice<unsigned int>(frequency, config::kTetraSampleRate), host, port);
}
}

template <> struct from<std::unique_ptr<config::Prometheus>> {
static auto from_toml(const value& v) -> std::unique_ptr<config::Prometheus> {
const std::string prometheus_host = find_or(v, "Host", config::kDefaultPrometheusHost);
const uint16_t prometheus_port = find_or(v, "Port", config::kDefaultPrometheusPort);

return std::make_unique<config::Prometheus>(prometheus_host, prometheus_port);
}
};

template <> struct from<config::TopLevel> {
static auto from_toml(const value& v) -> config::TopLevel {
const unsigned int center_frequency = find<unsigned int>(v, "CenterFrequency");
Expand All @@ -193,16 +236,24 @@ template <> struct from<config::TopLevel> {

std::vector<config::Stream> streams;
std::vector<config::Decimate> decimators;
std::unique_ptr<config::Prometheus> prometheus;

// Iterate over all elements in the root table
for (const auto& root_kv : v.as_table()) {
const auto& name = root_kv.first;
const auto& table = root_kv.second;

// Find table entries. These can be decimators or streams.
if (!table.is_table())
continue;

const auto element = get_decimate_or_stream(sdr_spectrum, table);
// If the table is labled "Prometheus" extract the prometheus arguments
if (name == "Prometheus") {
prometheus = get<std::unique_ptr<config::Prometheus>>(table);
continue;
}

const auto element = get_decimate_or_stream(sdr_spectrum, name, table);

// Save the Stream
if (std::holds_alternative<config::Stream>(element)) {
Expand All @@ -219,12 +270,13 @@ template <> struct from<config::TopLevel> {
// Find all subtables, that must be Stream entries and add them to the
// decimator
for (const auto& stream_pair : table.as_table()) {
auto& stream_name = stream_pair.first;
auto& stream_table = stream_pair.second;

if (!stream_table.is_table())
continue;

const auto stream_element = get_decimate_or_stream(decimate_element.spectrum_, stream_table);
const auto stream_element = get_decimate_or_stream(decimate_element.spectrum_, stream_name, stream_table);

if (!std::holds_alternative<config::Stream>(stream_element)) {
throw std::invalid_argument("Did not find a Stream block under the Decimate block");
Expand All @@ -241,12 +293,11 @@ template <> struct from<config::TopLevel> {
throw std::invalid_argument("Did not handle a derived type of decimate_or_stream");
}

return config::TopLevel(sdr_spectrum, device_string, rf_gain, if_gain, bb_gain, streams, decimators);
return config::TopLevel(sdr_spectrum, device_string, rf_gain, if_gain, bb_gain, streams, decimators,
std::move(prometheus));
}
};

}; // namespace toml

#endif

#endif
22 changes: 22 additions & 0 deletions include/prometheus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef PROMETHEUS_H
#define PROMETHEUS_H

#include <memory>

#include <prometheus/counter.h>
#include <prometheus/exposer.h>
#include <prometheus/registry.h>

class PrometheusExporter {
private:
std::shared_ptr<prometheus::Registry> registry_;
std::unique_ptr<prometheus::Exposer> exposer_;

public:
PrometheusExporter(const std::string& host) noexcept;
~PrometheusExporter() noexcept = default;

auto signal_strength() noexcept -> prometheus::Family<prometheus::Gauge>&;
};

#endif // PROMETHEUS_H
Loading

0 comments on commit 5678b39

Please sign in to comment.