Skip to content

Commit

Permalink
Merge pull request #42 from gmbeard/feature/histogram-metrics
Browse files Browse the repository at this point in the history
feat: Adds optional histogram metrics output
  • Loading branch information
gmbeard authored Aug 1, 2024
2 parents 455c6ea + fa59180 commit 2114474
Show file tree
Hide file tree
Showing 23 changed files with 450 additions and 405 deletions.
1 change: 1 addition & 0 deletions .versioning/changes/gRXSJ0T8eu.patch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds ability to output audio and video frame-time histogram metrics. This is enabled via the configure-time option `-DSHADOW_CAST_ENABLE_HISTOGRAMS=ON`
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ option(
)

option(
SHADOW_CAST_ENABLE_METRICS
"Enable metric collection for ${PROJECT_NAME}"
SHADOW_CAST_ENABLE_HISTOGRAMS
"Enable audio and video histograms for ${PROJECT_NAME}"
OFF
)

Expand Down
2 changes: 1 addition & 1 deletion doc/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
### 1. Test Parameters
My current go-to test for *Shadow Cast* is the game *Ghostrunner*. This game is very fast-paced with lots of visual movement so it seems to drive the video encoder pretty hard. Also, it has very low latency audio meaning the audio stream will be delivered in very small audio packets (~128 samples).

All metrics have been collected by compiling *Shadow Cast* with the `-DSHADOW_CAST_ENABLE_METRICS=ON` CMake configuration option. Running the application with this option enabled will emit a supplementary data file in the media output directory in *csv* format. Each metric row is a data point describing how long it took to process the frame. This data contains...
All metrics have been collected by compiling *Shadow Cast* with the (now obsolete) `-DSHADOW_CAST_ENABLE_METRICS=ON` CMake configuration option. Running the application with this option enabled will emit a supplementary data file in the media output directory in *csv* format. Each metric row is a data point describing how long it took to process the frame. This data contains...

- Metric category, either video or audio
- Frame no. (video) / Current sample no. (audio)
Expand Down
3 changes: 2 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ add_library(shadow-cast-obj
platform/opengl.cpp
platform/wayland.cpp

metrics/metrics.cpp

services/audio_service.cpp
services/color_converter.cpp
services/context.cpp
services/drm_video_service.cpp
services/encoder.cpp
services/encoder_service.cpp
services/metrics_service.cpp
services/readiness.cpp
services/service.cpp
services/service_registry.cpp
Expand Down
20 changes: 1 addition & 19 deletions src/config.hpp.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef SHADOW_CAST_CONFIG_HPP_INCLUDED
#define SHADOW_CAST_CONFIG_HPP_INCLUDED

#cmakedefine SHADOW_CAST_ENABLE_METRICS
#cmakedefine SHADOW_CAST_ENABLE_HISTOGRAMS

namespace sc
{
Expand All @@ -18,22 +18,4 @@ enum struct ByteOrder
// clang-format on
} // namespace sc

#ifdef SHADOW_CAST_ENABLE_METRICS
#define SC_METRICS_MEMBER_DECLARE(type, name) type name
#define SC_METRICS_MEMBER_USE(param, name) \
, name \
{ \
param \
}
#define SC_METRICS_PARAM_DECLARE(type) , type
#define SC_METRICS_PARAM_DEFINE(type, name) , type name
#define SC_METRICS_PARAM_USE(val) , val
#else
#define SC_METRICS_MEMBER_DECLARE(type, name)
#define SC_METRICS_MEMBER_USE(param, name)
#define SC_METRICS_PARAM_DECLARE(type)
#define SC_METRICS_PARAM_DEFINE(type, name)
#define SC_METRICS_PARAM_USE(val)
#endif

#endif // SHADOW_CAST_CONFIG_HPP_INCLUDED
90 changes: 29 additions & 61 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "utils/frame_time.hpp"

#include <X11/Xlib.h>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
Expand All @@ -13,7 +14,12 @@
#include <signal.h>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
#ifdef SHADOW_CAST_ENABLE_HISTOGRAMS
#include "metrics/formatting.hpp"
#include "metrics/metrics.hpp"
#endif

#if FF_API_BUFFER_SIZE_T
using BufferSize = int;
Expand Down Expand Up @@ -75,7 +81,7 @@ struct DestroyCaptureSessionGuard

auto run_loop(sc::Context& main,
sc::Context& media,
sc::Context& audio SC_METRICS_PARAM_DEFINE(sc::Context&, metrics),
sc::Context& audio,
sc::BorrowedPtr<AVCodecContext> video_codec,
sc::BorrowedPtr<AVStream> video_stream,
sc::BorrowedPtr<AVCodecContext> audio_codec,
Expand All @@ -86,30 +92,13 @@ auto run_loop(sc::Context& main,
sc::Encoder encoder { media };

auto const stop = [&] {
#ifdef SHADOW_CAST_ENABLE_METRICS
metrics.request_stop();
#endif
main.request_stop();
audio.request_stop();
};

main.services().add<sc::SignalService>(sc::SignalService {});
add_signal_handler(main, SIGINT, [&](std::uint32_t) { stop(); });

#ifdef SHADOW_CAST_ENABLE_METRICS
auto metrics_thread = std::thread([&] {
SC_SCOPE_GUARD([&] { stop(); });
try {
metrics.run();
}
catch (...) {
std::lock_guard lock { exception_mutex };
if (!ex)
ex = std::current_exception();
}
});
#endif

auto media_thread = std::thread([&] {
SC_SCOPE_GUARD([&] { stop(); });
try {
Expand Down Expand Up @@ -148,9 +137,6 @@ auto run_loop(sc::Context& main,

std::cerr << "Finalizing output. Please wait...\n";

#ifdef SHADOW_CAST_ENABLE_METRICS
metrics_thread.join();
#endif
audio_thread.join();

try {
Expand Down Expand Up @@ -302,12 +288,6 @@ auto run_wayland(sc::Parameters const& params, sc::wayland::DisplayPtr display)
sc::av_error_to_string(ret) };
}

#ifdef SHADOW_CAST_ENABLE_METRICS
sc::Context metrics_ctx { params.frame_time };
metrics_ctx.services().add_from_factory<sc::MetricsService>([&] {
return std::make_unique<sc::MetricsService>(params.output_file);
});
#endif
sc::Context ctx { params.frame_time };
sc::Context audio_ctx { params.frame_time };
sc::Context media_ctx { params.frame_time };
Expand All @@ -316,9 +296,6 @@ auto run_wayland(sc::Parameters const& params, sc::wayland::DisplayPtr display)
add_signal_handler(ctx, SIGINT, [&](std::uint32_t) {
ctx.request_stop();
audio_ctx.request_stop();
#ifdef SHADOW_CAST_ENABLE_METRICS
metrics_ctx.request_stop();
#endif
});

audio_ctx.services().add_from_factory<sc::AudioService>([&] {
Expand All @@ -327,24 +304,16 @@ auto run_wayland(sc::Parameters const& params, sc::wayland::DisplayPtr display)
params.sample_rate,
audio_encoder_context->frame_size
? audio_encoder_context->frame_size
: 2048 SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
: 2048);
});

ctx.services().add_from_factory<sc::DRMVideoService>([&] {
return std::make_unique<sc::DRMVideoService>(
nvcudalib,
cuda_ctx.get(),
egl,
*wayland,
wayland_egl SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
nvcudalib, cuda_ctx.get(), egl, *wayland, wayland_egl);
});

media_ctx.services().add_from_factory<sc::EncoderService>([&] {
return std::make_unique<sc::EncoderService>(
format_context.get() SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
return std::make_unique<sc::EncoderService>(format_context.get());
});

sc::Encoder media_writer { media_ctx };
Expand All @@ -366,7 +335,7 @@ auto run_wayland(sc::Parameters const& params, sc::wayland::DisplayPtr display)

run_loop(ctx,
media_ctx,
audio_ctx SC_METRICS_PARAM_USE(metrics_ctx),
audio_ctx,
video_encoder_context.get(),
video_stream.get(),
audio_encoder_context.get(),
Expand Down Expand Up @@ -526,20 +495,11 @@ auto run(sc::Parameters const& params) -> void
sc::Context ctx { params.frame_time };
sc::Context audio_ctx { params.frame_time };
sc::Context media_ctx { params.frame_time };
#ifdef SHADOW_CAST_ENABLE_METRICS
sc::Context metrics_ctx { params.frame_time };
metrics_ctx.services().add_from_factory<sc::MetricsService>([&] {
return std::make_unique<sc::MetricsService>(params.output_file);
});
#endif

ctx.services().add<sc::SignalService>(sc::SignalService {});
add_signal_handler(ctx, SIGINT, [&](std::uint32_t) {
ctx.request_stop();
audio_ctx.request_stop();
#ifdef SHADOW_CAST_ENABLE_METRICS
metrics_ctx.request_stop();
#endif
});

audio_ctx.services().add_from_factory<sc::AudioService>([&] {
Expand All @@ -548,22 +508,16 @@ auto run(sc::Parameters const& params) -> void
params.sample_rate,
audio_encoder_context->frame_size
? audio_encoder_context->frame_size
: 2048 SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
: 2048);
});

ctx.services().add_from_factory<sc::VideoService>([&] {
return std::make_unique<sc::VideoService>(
nvfbc,
cuda_ctx.get(),
nvfbc_instance.get() SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
nvfbc, cuda_ctx.get(), nvfbc_instance.get());
});

media_ctx.services().add_from_factory<sc::EncoderService>([&] {
return std::make_unique<sc::EncoderService>(
format_context.get() SC_METRICS_PARAM_USE(
metrics_ctx.services().use_if<sc::MetricsService>()));
return std::make_unique<sc::EncoderService>(format_context.get());
});

sc::Encoder media_writer { media_ctx };
Expand All @@ -585,7 +539,7 @@ auto run(sc::Parameters const& params) -> void

run_loop(ctx,
media_ctx,
audio_ctx SC_METRICS_PARAM_USE(metrics_ctx),
audio_ctx,
video_encoder_context.get(),
video_stream.get(),
audio_encoder_context.get(),
Expand Down Expand Up @@ -633,5 +587,19 @@ auto main(int argc, char const** argv) -> int
return 1;
}

#ifdef SHADOW_CAST_ENABLE_HISTOGRAMS
sc::metrics::format_histogram(
std::cout,
sc::metrics::get_histogram(sc::metrics::audio_metrics),
"Frame time (ns)",
"Audio Frame Times");
std::cout << '\n';
sc::metrics::format_histogram(
std::cout,
sc::metrics::get_histogram(sc::metrics::video_metrics),
"Frame time (ns)",
"Video Frame Times");
#endif

return 0;
}
72 changes: 72 additions & 0 deletions src/metrics/formatting.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef SHADOW_CAST_METRICS_FORMATTING_HPP_INCLUDED
#define SHADOW_CAST_METRICS_FORMATTING_HPP_INCLUDED

#include "metrics/histogram.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <iterator>
#include <ostream>
#include <string_view>

namespace sc::metrics
{

template <typename CharT, typename Traits, typename T, std::size_t N, T I>
auto format_histogram_rows(std::basic_ostream<CharT, Traits>& os,
Histogram<T, N, I> const& data,
std::size_t column_size) -> void
{
std::for_each(
std::begin(data), std::end(data), [&](auto const& data_point) {
os << "| ";
os.width(column_size);
os << std::get<0>(data_point);
os << " | ";

os.width(column_size);
os << std::get<1>(data_point) << " |\n";
});
}

template <typename CharT, typename Traits, typename T, std::size_t N, T I>
auto format_histogram(std::basic_ostream<CharT, Traits>& os,
Histogram<T, N, I> const& data,
std::string_view series_name,
std::string_view title = "") -> void
{
if (title.size()) {
os << title << '\n';
}

constexpr std::size_t kColumnMargin = 1;
auto const column_width = std::max(series_name.size(), 10ul);

os << "| ";
os.width(column_width);
os << series_name << " | ";
os.width(column_width);

os << "Frequency"
<< " |\n";

os.put('|').fill('-');

os.width(column_width + kColumnMargin * 2);
os << '-' << '|';
os.width(column_width + kColumnMargin * 2);
os << '-' << '|';
os.put('\n').fill(' ');

format_histogram_rows(os, data, column_width);
}

template <typename CharT, typename Traits, typename T, std::size_t N, T I>
auto format_histogram(std::basic_ostream<CharT, Traits>& os,
Histogram<T, N, I> const& data) -> void
{
format_histogram(os, data, "Buckets");
}
} // namespace sc::metrics

#endif // SHADOW_CAST_METRICS_FORMATTING_HPP_INCLUDED
Loading

0 comments on commit 2114474

Please sign in to comment.