# Mixer objects
AUDIO_MIXER_OBJS = audio_mixer.o alsa_input.o alsa_pool.o ebu_r128_proc.o stereocompressor.o resampling_queue.o flags.o correlation_measurer.o filter.o input_mapping.o state.pb.o
-OBJS += chroma_subsampler.o v210_converter.o mixer.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o httpd.o flags.o image_input.o alsa_output.o disk_space_estimator.o print_latency.o timecode_renderer.o tweaked_inputs.o $(AUDIO_MIXER_OBJS)
+OBJS += chroma_subsampler.o v210_converter.o mixer.o metrics.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o httpd.o flags.o image_input.o alsa_output.o disk_space_estimator.o print_latency.o timecode_renderer.o tweaked_inputs.o $(AUDIO_MIXER_OBJS)
# Streaming and encoding objects
OBJS += quicksync_encoder.o x264_encoder.o x264_dynamic.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o ffmpeg_raii.o ffmpeg_util.o
#include "defs.h"
#include "metacube2.h"
+#include "metrics.h"
struct MHD_Connection;
struct MHD_Response;
}
*con_cls = stream;
- // Does not strictly have to be equal to MUX_BUFFER_SIZE.
- MHD_Response *response = MHD_create_response_from_callback(
- (size_t)-1, MUX_BUFFER_SIZE, &HTTPD::Stream::reader_callback_thunk, stream, &HTTPD::free_stream);
+ MHD_Response *response;
- // TODO: Content-type?
- if (framing == HTTPD::Stream::FRAMING_METACUBE) {
- MHD_add_response_header(response, "Content-encoding", "metacube");
+ if (strcmp(url, "/metrics") == 0) {
+ string contents = global_metrics.serialize();
+ response = MHD_create_response_from_buffer(
+ contents.size(), &contents[0], MHD_RESPMEM_MUST_COPY);
+ MHD_add_response_header(response, "Content-type", "text/plain");
+ } else {
+ // Does not strictly have to be equal to MUX_BUFFER_SIZE.
+ response = MHD_create_response_from_callback(
+ (size_t)-1, MUX_BUFFER_SIZE, &HTTPD::Stream::reader_callback_thunk, stream, &HTTPD::free_stream);
+ // TODO: Content-type?
+ if (framing == HTTPD::Stream::FRAMING_METACUBE) {
+ MHD_add_response_header(response, "Content-encoding", "metacube");
+ }
}
+
int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response); // Only decreases the refcount; actual free is after the request is done.
--- /dev/null
+#include "metrics.h"
+
+#include <locale>
+#include <sstream>
+
+using namespace std;
+
+Metrics global_metrics;
+
+void Metrics::register_int_metric(const string &name, atomic<int64_t> *location)
+{
+ lock_guard<mutex> lock(mu);
+ int_metrics.emplace(name, location);
+}
+
+void Metrics::register_double_metric(const string &name, atomic<double> *location)
+{
+ lock_guard<mutex> lock(mu);
+ double_metrics.emplace(name, location);
+}
+
+string Metrics::serialize() const
+{
+ stringstream ss;
+ ss.imbue(locale("C"));
+ ss.precision(20);
+ ss << scientific;
+
+ lock_guard<mutex> lock(mu);
+ for (const auto &key_and_value : int_metrics) {
+ ss << key_and_value.first.c_str() << " " << key_and_value.second->load() << "\n";
+ }
+ for (const auto &key_and_value : double_metrics) {
+ ss << key_and_value.first.c_str() << " " << key_and_value.second->load() << "\n";
+ }
+
+ return ss.str();
+}
--- /dev/null
+#ifndef _METRICS_H
+#define _METRICS_H 1
+
+// A simple global class to keep track of metrics export in Prometheus format.
+// It would be better to use a more full-featured Prometheus client library for this,
+// but it would introduce a dependency that is not commonly packaged in distributions,
+// which makes it quite unwieldy. Thus, we'll package our own for the time being.
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+class Metrics {
+public:
+ void register_int_metric(const std::string &name, std::atomic<int64_t> *location);
+ void register_double_metric(const std::string &name, std::atomic<double> *location);
+ std::string serialize() const;
+
+private:
+ mutable std::mutex mu;
+ std::unordered_map<std::string, std::atomic<int64_t> *> int_metrics;
+ std::unordered_map<std::string, std::atomic<double> *> double_metrics;
+};
+
+extern Metrics global_metrics;
+
+#endif // !defined(_METRICS_H)
#include "ffmpeg_capture.h"
#include "flags.h"
#include "input_mapping.h"
+#include "metrics.h"
#include "pbo_frame_allocator.h"
#include "ref_counted_gl_sync.h"
#include "resampling_queue.h"
desired_output_card_index = global_flags.output_card;
set_output_card_internal(global_flags.output_card);
}
+
+ global_metrics.register_int_metric("nageru_num_frames", &metrics_num_frames);
+ global_metrics.register_int_metric("nageru_dropped_frames", &metrics_dropped_frames);
+ global_metrics.register_double_metric("nageru_uptime", &metrics_uptime);
}
Mixer::~Mixer()
now = steady_clock::now();
double elapsed = duration<double>(now - start).count();
+
+ metrics_num_frames = frame_num;
+ metrics_dropped_frames = stats_dropped_frames;
+ metrics_uptime = elapsed;
+
if (frame_num % 100 == 0) {
printf("%d frames (%d dropped) in %.3f seconds = %.1f fps (%.1f ms/frame)",
frame_num, stats_dropped_frames, elapsed, frame_num / elapsed,
std::vector<uint32_t> mode_scanlist[MAX_VIDEO_CARDS];
unsigned mode_scanlist_index[MAX_VIDEO_CARDS]{ 0 };
std::chrono::steady_clock::time_point last_mode_scan_change[MAX_VIDEO_CARDS];
+
+ // Metrics.
+ std::atomic<int64_t> metrics_num_frames;
+ std::atomic<int64_t> metrics_dropped_frames;
+ std::atomic<double> metrics_uptime;
};
extern Mixer *global_mixer;