Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setting up prometheus #2

Merged
merged 30 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3bd0571
setting up prometheus
tanneberger May 16, 2024
b3e3299
Merge branch 'master' into prometheus
marenz2569 May 16, 2024
38b81fa
move header to include directory
marenz2569 May 16, 2024
3aa81b5
clang-format
marenz2569 May 16, 2024
3caa445
clang-format
marenz2569 May 16, 2024
6d10ce8
remove uncessary changes
marenz2569 May 16, 2024
11fad73
sort includes
marenz2569 May 16, 2024
331e251
sort includes
marenz2569 May 16, 2024
2b0800b
switch type of ports to uint16_t
marenz2569 May 16, 2024
7045162
fix compile warnings
marenz2569 May 16, 2024
d4841c6
save prometheus default host and port into a constant
marenz2569 May 16, 2024
e830009
use uint16_t for command line port input
marenz2569 May 16, 2024
8dd3086
add prometheus section to config parser
marenz2569 May 16, 2024
b7d679d
cleanup tetra-receiver main
marenz2569 May 16, 2024
63dc7dc
fix prometheus config parser
marenz2569 May 16, 2024
be58265
add name element to config
marenz2569 May 17, 2024
01241c8
add prometheus polling interval config option
marenz2569 May 17, 2024
6de4a14
implement first part of gnuradio processing for prometheus
marenz2569 May 17, 2024
f9abfaa
implement gnuradio block to populate the prometheus gauge
marenz2569 May 17, 2024
a7c1129
making code pretty
tanneberger May 18, 2024
e836a28
do averaging over one second for the signal stregth
marenz2569 May 18, 2024
caabe30
prometheus-gauge-populator: remove debug print
marenz2569 May 18, 2024
184e08c
remove unused variables
marenz2569 May 18, 2024
1934257
adding mock & tests
tanneberger May 19, 2024
7035ef7
borken tests
tanneberger May 19, 2024
a19e047
Revert "borken tests"
marenz2569 May 19, 2024
cc7f031
Revert "adding mock & tests"
marenz2569 May 19, 2024
ac9ef80
remove unused comment in test
marenz2569 May 19, 2024
24b1b9e
review comments
marenz2569 May 19, 2024
99c744b
delete default constructor of PrometheusGaugePopulator
marenz2569 May 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
12 changes: 12 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`, `Port` and `PollInterval` 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,11 @@ 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)
PollInterval = unsigned int (in seconds)

[DecimateA]
Frequency = unsigned int
SampleRate = unsigned int
Expand All @@ -72,3 +79,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.
116 changes: 86 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,38 @@ 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_;
/// the polling interval of prometheus
unsigned int polling_interval_;

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
/// \param polling_interval the interval at which data is sent
Prometheus(std::string host, const uint16_t port, const unsigned polling_interval)
: host_(std::move(host))
, port_(port)
, polling_interval_(polling_interval){};
};

class TopLevel {
Expand All @@ -147,10 +181,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 +198,36 @@ 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);
const unsigned int polling_interval = find<unsigned int>(v, "PollInterval");

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

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 +241,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 +275,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 +298,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 FUNNEL_PROMETHEUS_HPP
#define FUNNEL_PROMETHEUS_HPP

#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 // FUNNEL_PROMETHEUS_HPP
32 changes: 32 additions & 0 deletions include/prometheus_gauge_populator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef INCLUDE_PROMETHEUS_GAUGE_POPULATOR_H
#define INCLUDE_PROMETHEUS_GAUGE_POPULATOR_H

#include <gnuradio/block.h>
#include <gnuradio/blocks/api.h>

#include <prometheus/gauge.h>

namespace gr {
namespace prometheus {

/// This block takes a float as an input and writes in into a prometheus gauge
class prometheus_gauge_populator : virtual public block {
public:
using sptr = boost::shared_ptr<prometheus_gauge_populator>;
marenz2569 marked this conversation as resolved.
Show resolved Hide resolved

prometheus_gauge_populator(::prometheus::Gauge& gauge);

static sptr make(::prometheus::Gauge& gauge);
marenz2569 marked this conversation as resolved.
Show resolved Hide resolved

int general_work(int noutput_items, gr_vector_int& ninput_items, gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items) override;

private:
/// the prometheus gauge we are populating with this block
::prometheus::Gauge& gauge_;
marenz2569 marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace prometheus
} // namespace gr

#endif
Loading
Loading