2021-03-16 20:32:25 +00:00
|
|
|
#include <sstream>
|
|
|
|
#include <iomanip>
|
2022-08-22 19:28:57 +00:00
|
|
|
#include <array>
|
2021-07-16 00:28:46 +00:00
|
|
|
#include <spdlog/spdlog.h>
|
2020-06-19 14:28:55 +00:00
|
|
|
#include "logging.h"
|
|
|
|
#include "overlay.h"
|
2020-08-15 13:14:55 +00:00
|
|
|
#include "config.h"
|
2021-03-16 20:32:25 +00:00
|
|
|
#include "file_utils.h"
|
2021-09-18 15:21:20 +00:00
|
|
|
#include "string_utils.h"
|
2023-01-05 06:42:54 +00:00
|
|
|
#include "version.h"
|
2020-06-19 14:28:55 +00:00
|
|
|
|
2022-04-07 08:28:48 +00:00
|
|
|
using namespace std;
|
|
|
|
|
2021-02-10 08:16:51 +00:00
|
|
|
string os, cpu, gpu, ram, kernel, driver, cpusched;
|
2020-06-19 14:28:55 +00:00
|
|
|
bool sysInfoFetched = false;
|
|
|
|
double fps;
|
2023-01-23 14:36:00 +00:00
|
|
|
float frametime;
|
2020-06-19 15:32:04 +00:00
|
|
|
logData currentLogData = {};
|
2020-06-21 16:52:38 +00:00
|
|
|
std::unique_ptr<Logger> logger;
|
2023-01-23 14:16:05 +00:00
|
|
|
ofstream output_file;
|
|
|
|
std::thread log_thread;
|
2020-06-19 14:28:55 +00:00
|
|
|
|
|
|
|
string exec(string command) {
|
2022-02-24 21:26:23 +00:00
|
|
|
#ifndef _WIN32
|
2022-08-07 12:40:25 +00:00
|
|
|
command = "unset LD_PRELOAD; " + command;
|
2022-02-24 21:26:23 +00:00
|
|
|
#endif
|
2021-12-09 20:16:12 +00:00
|
|
|
std::array<char, 128> buffer;
|
|
|
|
std::string result;
|
|
|
|
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(command.c_str(), "r"), pclose);
|
|
|
|
if (!pipe) {
|
2020-06-19 14:28:55 +00:00
|
|
|
return "popen failed!";
|
2021-12-09 20:16:12 +00:00
|
|
|
}
|
|
|
|
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
|
|
|
result += buffer.data();
|
|
|
|
}
|
|
|
|
return result;
|
2020-06-19 14:28:55 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static void upload_file(std::string logFile){
|
2020-06-19 14:28:55 +00:00
|
|
|
std::string command = "curl --include --request POST https://flightlessmango.com/logs -F 'log[game_id]=26506' -F 'log[user_id]=176' -F 'attachment=true' -A 'mangohud' ";
|
2020-06-24 20:03:49 +00:00
|
|
|
command += " -F 'log[uploads][]=@" + logFile + "'";
|
2020-06-12 15:44:42 +00:00
|
|
|
|
2020-06-19 14:28:55 +00:00
|
|
|
command += " | grep Location | cut -c11-";
|
|
|
|
std::string url = exec(command);
|
2023-06-22 09:58:44 +00:00
|
|
|
std::cout << "upload url: " << url;
|
2020-06-19 14:28:55 +00:00
|
|
|
exec("xdg-open " + url);
|
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static void upload_files(const std::vector<std::string>& logFiles){
|
2020-06-19 14:28:55 +00:00
|
|
|
std::string command = "curl --include --request POST https://flightlessmango.com/logs -F 'log[game_id]=26506' -F 'log[user_id]=176' -F 'attachment=true' -A 'mangohud' ";
|
|
|
|
for (auto& file : logFiles)
|
|
|
|
command += " -F 'log[uploads][]=@" + file + "'";
|
2020-06-12 15:44:42 +00:00
|
|
|
|
2020-06-19 14:28:55 +00:00
|
|
|
command += " | grep Location | cut -c11-";
|
|
|
|
std::string url = exec(command);
|
2023-06-22 09:58:44 +00:00
|
|
|
std::cout << "upload url: " << url;
|
2020-06-19 14:28:55 +00:00
|
|
|
exec("xdg-open " + url);
|
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static bool compareByFps(const logData &a, const logData &b)
|
2022-01-06 06:10:09 +00:00
|
|
|
{
|
|
|
|
return a.fps < b.fps;
|
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static void writeSummary(string filename){
|
2022-01-06 06:10:09 +00:00
|
|
|
auto& logArray = logger->get_log_data();
|
|
|
|
filename = filename.substr(0, filename.size() - 4);
|
|
|
|
filename += "_summary.csv";
|
2022-06-18 15:20:32 +00:00
|
|
|
SPDLOG_INFO("{}", filename);
|
|
|
|
SPDLOG_DEBUG("Writing summary log file [{}]", filename);
|
2022-01-06 06:10:09 +00:00
|
|
|
std::ofstream out(filename, ios::out | ios::app);
|
|
|
|
if (out){
|
2022-01-21 10:20:50 +00:00
|
|
|
out << "0.1% Min FPS," << "1% Min FPS," << "97% Percentile FPS," << "Average FPS," << "GPU Load," << "CPU Load" << "\n";
|
2022-01-06 06:10:09 +00:00
|
|
|
std::vector<logData> sorted = logArray;
|
|
|
|
std::sort(sorted.begin(), sorted.end(), compareByFps);
|
2022-01-19 15:03:14 +00:00
|
|
|
float total = 0.0f;
|
|
|
|
float total_cpu = 0.0f;
|
|
|
|
float total_gpu = 0.0f;
|
|
|
|
float result;
|
2022-01-06 06:10:09 +00:00
|
|
|
float percents[2] = {0.001, 0.01};
|
|
|
|
for (auto percent : percents){
|
|
|
|
total = 0;
|
|
|
|
size_t idx = ceil(sorted.size() * percent);
|
|
|
|
for (size_t i = 0; i < idx; i++){
|
|
|
|
total = total + sorted[i].fps;
|
|
|
|
}
|
|
|
|
result = total / idx;
|
|
|
|
out << fixed << setprecision(1) << result << ",";
|
|
|
|
}
|
|
|
|
// 97th percentile
|
2022-01-19 00:51:50 +00:00
|
|
|
result = sorted.empty() ? 0.0f : sorted[floor(0.97 * (sorted.size() - 1))].fps;
|
2022-01-06 06:10:09 +00:00
|
|
|
out << fixed << setprecision(1) << result << ",";
|
|
|
|
// avg
|
|
|
|
total = 0;
|
|
|
|
for (auto input : sorted){
|
|
|
|
total = total + input.fps;
|
|
|
|
total_cpu = total_cpu + input.cpu_load;
|
|
|
|
total_gpu = total_gpu + input.gpu_load;
|
|
|
|
}
|
|
|
|
result = total / sorted.size();
|
|
|
|
out << fixed << setprecision(1) << result << ",";
|
|
|
|
// GPU
|
|
|
|
result = total_gpu / sorted.size();
|
2022-01-21 10:20:50 +00:00
|
|
|
out << result << ",";
|
2022-01-06 06:10:09 +00:00
|
|
|
// CPU
|
|
|
|
result = total_cpu / sorted.size();
|
2022-01-21 10:20:50 +00:00
|
|
|
out << result;
|
2022-01-06 06:10:09 +00:00
|
|
|
} else {
|
2022-06-18 15:20:32 +00:00
|
|
|
SPDLOG_ERROR("Failed to write log file");
|
2022-01-06 06:10:09 +00:00
|
|
|
}
|
2022-07-28 00:48:35 +00:00
|
|
|
out.close();
|
2022-01-06 06:10:09 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static void writeFileHeaders(ofstream& out){
|
2023-01-23 14:16:05 +00:00
|
|
|
if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_log_versioning]){
|
2022-07-27 23:19:16 +00:00
|
|
|
printf("log versioning");
|
|
|
|
out << "v1" << endl;
|
|
|
|
out << MANGOHUD_VERSION << endl;
|
|
|
|
out << "---------------------SYSTEM INFO---------------------" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
out << "os," << "cpu," << "gpu," << "ram," << "kernel," << "driver," << "cpuscheduler" << endl;
|
|
|
|
out << os << "," << cpu << "," << gpu << "," << ram << "," << kernel << "," << driver << "," << cpusched << endl;
|
|
|
|
|
|
|
|
if (HUDElements.params->enabled[OVERLAY_PARAM_ENABLED_log_versioning])
|
|
|
|
out << "--------------------FRAME METRICS--------------------" << endl;
|
|
|
|
|
|
|
|
out << "fps," << "frametime," << "cpu_load," << "gpu_load," << "cpu_temp," << "gpu_temp," << "gpu_core_clock," << "gpu_mem_clock," << "gpu_vram_used," << "gpu_power," << "ram_used," << "elapsed" << endl;
|
2023-01-23 14:16:05 +00:00
|
|
|
}
|
2022-07-27 23:19:16 +00:00
|
|
|
|
2023-01-23 14:16:05 +00:00
|
|
|
void Logger::writeToFile(){
|
|
|
|
if (!output_file){
|
|
|
|
output_file.open(m_log_files.back(), ios::out | ios::app);
|
|
|
|
writeFileHeaders(output_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto& logArray = logger->get_log_data();
|
|
|
|
if (output_file && !logArray.empty()){
|
|
|
|
output_file << logArray.back().fps << ",";
|
2023-01-23 14:36:00 +00:00
|
|
|
output_file << logArray.back().frametime << ",";
|
2023-01-23 14:16:05 +00:00
|
|
|
output_file << logArray.back().cpu_load << ",";
|
|
|
|
output_file << logArray.back().gpu_load << ",";
|
|
|
|
output_file << logArray.back().cpu_temp << ",";
|
|
|
|
output_file << logArray.back().gpu_temp << ",";
|
|
|
|
output_file << logArray.back().gpu_core_clock << ",";
|
|
|
|
output_file << logArray.back().gpu_mem_clock << ",";
|
|
|
|
output_file << logArray.back().gpu_vram_used << ",";
|
|
|
|
output_file << logArray.back().gpu_power << ",";
|
|
|
|
output_file << logArray.back().ram_used << ",";
|
|
|
|
output_file << std::chrono::duration_cast<std::chrono::nanoseconds>(logArray.back().previous).count() << "\n";
|
2023-01-24 06:36:53 +00:00
|
|
|
output_file.flush();
|
2021-02-01 08:46:33 +00:00
|
|
|
} else {
|
2023-01-23 14:16:05 +00:00
|
|
|
printf("MANGOHUD: Failed to write log file\n");
|
2021-02-01 08:46:33 +00:00
|
|
|
}
|
2020-06-19 14:28:55 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 10:43:59 +00:00
|
|
|
static string get_log_suffix(){
|
2020-06-19 14:28:55 +00:00
|
|
|
time_t now_log = time(0);
|
|
|
|
tm *log_time = localtime(&now_log);
|
|
|
|
std::ostringstream buffer;
|
2020-06-12 15:44:42 +00:00
|
|
|
buffer << std::put_time(log_time, "%Y-%m-%d_%H-%M-%S") << ".csv";
|
|
|
|
string log_name = buffer.str();
|
|
|
|
return log_name;
|
2020-06-19 14:28:55 +00:00
|
|
|
}
|
|
|
|
|
2022-03-13 11:17:38 +00:00
|
|
|
Logger::Logger(const overlay_params* in_params)
|
2022-07-26 06:38:12 +00:00
|
|
|
: output_folder(in_params->output_folder),
|
|
|
|
log_interval(in_params->log_interval),
|
|
|
|
log_duration(in_params->log_duration),
|
2022-03-06 13:46:00 +00:00
|
|
|
m_logging_on(false),
|
|
|
|
m_values_valid(false)
|
2020-06-21 16:52:38 +00:00
|
|
|
{
|
2023-01-23 14:16:05 +00:00
|
|
|
if(output_folder.empty()) output_folder = std::getenv("HOME");
|
2022-01-18 22:49:30 +00:00
|
|
|
m_log_end = Clock::now() - 15s;
|
2021-07-21 16:48:45 +00:00
|
|
|
SPDLOG_DEBUG("Logger constructed!");
|
2020-06-21 16:52:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::start_logging() {
|
2020-06-24 20:03:49 +00:00
|
|
|
if(m_logging_on) return;
|
|
|
|
m_values_valid = false;
|
|
|
|
m_logging_on = true;
|
|
|
|
m_log_start = Clock::now();
|
2023-01-23 14:16:05 +00:00
|
|
|
|
|
|
|
std::string program = get_wine_exe_name();
|
|
|
|
printf("%s\n", output_folder.c_str());
|
|
|
|
if (program.empty())
|
|
|
|
program = get_program_name();
|
|
|
|
|
|
|
|
m_log_files.emplace_back(output_folder + "/" + program + "_" + get_log_suffix());
|
|
|
|
|
2022-07-26 06:38:12 +00:00
|
|
|
if(log_interval != 0){
|
2023-01-23 14:16:05 +00:00
|
|
|
std::thread log_thread(&Logger::logging, this);
|
|
|
|
log_thread.detach();
|
2020-06-21 16:52:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::stop_logging() {
|
2020-12-04 03:49:02 +00:00
|
|
|
if(!m_logging_on) return;
|
2020-06-24 20:03:49 +00:00
|
|
|
m_logging_on = false;
|
|
|
|
m_log_end = Clock::now();
|
2023-01-23 14:16:05 +00:00
|
|
|
if (log_thread.joinable()) log_thread.join();
|
2020-06-21 16:52:38 +00:00
|
|
|
|
2022-01-06 11:21:42 +00:00
|
|
|
calculate_benchmark_data();
|
2023-01-23 14:16:05 +00:00
|
|
|
output_file.close();
|
|
|
|
writeSummary(m_log_files.back());
|
2022-03-06 11:36:32 +00:00
|
|
|
clear_log_data();
|
2023-03-12 13:44:27 +00:00
|
|
|
#ifdef __linux__
|
2022-08-01 00:07:59 +00:00
|
|
|
control_client_check(HUDElements.params->control, global_control_client, gpu.c_str());
|
2022-07-28 00:48:35 +00:00
|
|
|
const char * cmd = "LoggingFinished";
|
|
|
|
control_send(global_control_client, cmd, strlen(cmd), 0, 0);
|
2023-03-12 13:44:27 +00:00
|
|
|
#endif
|
2022-03-06 11:36:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::logging(){
|
|
|
|
wait_until_data_valid();
|
|
|
|
while (is_active()){
|
|
|
|
try_log();
|
2022-07-26 06:38:12 +00:00
|
|
|
this_thread::sleep_for(std::chrono::milliseconds(log_interval));
|
2022-03-06 11:36:32 +00:00
|
|
|
}
|
2023-01-23 14:16:05 +00:00
|
|
|
clear_log_data();
|
2020-06-21 16:52:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::try_log() {
|
2020-12-04 03:49:02 +00:00
|
|
|
if(!is_active()) return;
|
|
|
|
if(!m_values_valid) return;
|
2020-06-21 16:52:38 +00:00
|
|
|
auto now = Clock::now();
|
2020-06-24 20:03:49 +00:00
|
|
|
auto elapsedLog = now - m_log_start;
|
2020-06-21 16:52:38 +00:00
|
|
|
|
|
|
|
currentLogData.previous = elapsedLog;
|
2023-01-23 14:16:05 +00:00
|
|
|
currentLogData.fps = fps;
|
2020-09-02 05:50:26 +00:00
|
|
|
currentLogData.frametime = frametime;
|
2020-06-24 20:03:49 +00:00
|
|
|
m_log_array.push_back(currentLogData);
|
2023-01-23 14:16:05 +00:00
|
|
|
writeToFile();
|
2020-06-21 16:52:38 +00:00
|
|
|
|
2022-07-26 06:38:12 +00:00
|
|
|
if(log_duration && (elapsedLog >= std::chrono::seconds(log_duration))){
|
2020-06-21 16:52:38 +00:00
|
|
|
stop_logging();
|
2020-06-19 14:28:55 +00:00
|
|
|
}
|
2020-06-21 16:52:38 +00:00
|
|
|
}
|
2020-06-19 14:28:55 +00:00
|
|
|
|
2020-06-21 16:52:38 +00:00
|
|
|
void Logger::wait_until_data_valid() {
|
2020-06-24 20:03:49 +00:00
|
|
|
std::unique_lock<std::mutex> lck(m_values_valid_mtx);
|
|
|
|
while(! m_values_valid) m_values_valid_cv.wait(lck);
|
2020-06-12 15:44:42 +00:00
|
|
|
}
|
2020-06-21 16:52:38 +00:00
|
|
|
|
|
|
|
void Logger::notify_data_valid() {
|
2020-06-24 20:03:49 +00:00
|
|
|
std::unique_lock<std::mutex> lck(m_values_valid_mtx);
|
|
|
|
m_values_valid = true;
|
|
|
|
m_values_valid_cv.notify_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::upload_last_log() {
|
|
|
|
if(m_log_files.empty()) return;
|
|
|
|
std::thread(upload_file, m_log_files.back()).detach();
|
2020-08-01 23:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Logger::upload_last_logs() {
|
|
|
|
if(m_log_files.empty()) return;
|
|
|
|
std::thread(upload_files, m_log_files).detach();
|
2020-11-27 13:17:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void autostart_log(int sleep) {
|
2023-01-18 17:49:09 +00:00
|
|
|
// os_time_sleep() causes freezes with zink + autologging :frog_donut:
|
|
|
|
this_thread::sleep_for(chrono::seconds(sleep));
|
2020-11-27 13:17:43 +00:00
|
|
|
logger->start_logging();
|
2020-12-04 06:15:17 +00:00
|
|
|
}
|
2022-01-06 11:21:42 +00:00
|
|
|
|
|
|
|
void Logger::calculate_benchmark_data(){
|
2022-05-09 11:05:54 +00:00
|
|
|
vector<float> sorted {};
|
|
|
|
for (auto& point : m_log_array)
|
|
|
|
sorted.push_back(point.fps);
|
|
|
|
|
2022-01-06 13:53:40 +00:00
|
|
|
std::sort(sorted.begin(), sorted.end());
|
|
|
|
benchmark.percentile_data.clear();
|
2022-01-06 11:21:42 +00:00
|
|
|
|
2022-01-06 13:53:40 +00:00
|
|
|
benchmark.total = 0.f;
|
|
|
|
for (auto fps_ : sorted){
|
|
|
|
benchmark.total = benchmark.total + fps_;
|
|
|
|
}
|
2022-01-06 11:21:42 +00:00
|
|
|
|
2022-01-06 13:53:40 +00:00
|
|
|
size_t max_label_size = 0;
|
2022-01-06 11:21:42 +00:00
|
|
|
|
2022-01-06 13:53:40 +00:00
|
|
|
float result;
|
2022-05-16 02:38:07 +00:00
|
|
|
for (std::string percentile : HUDElements.params->benchmark_percentiles) {
|
2022-01-06 11:21:42 +00:00
|
|
|
// special case handling for a mean-based average
|
|
|
|
if (percentile == "AVG") {
|
2022-01-06 13:53:40 +00:00
|
|
|
result = benchmark.total / sorted.size();
|
2022-01-06 11:21:42 +00:00
|
|
|
} else {
|
2022-01-06 13:53:40 +00:00
|
|
|
// the percentiles are already validated when they're parsed from the config.
|
|
|
|
float fraction = parse_float(percentile) / 100;
|
2022-01-06 11:21:42 +00:00
|
|
|
|
2022-01-19 00:51:50 +00:00
|
|
|
result = sorted.empty() ? 0.0f : sorted[(fraction * sorted.size()) - 1];
|
2022-01-06 13:53:40 +00:00
|
|
|
percentile += "%";
|
2022-01-06 11:21:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (percentile.length() > max_label_size)
|
2022-01-06 13:53:40 +00:00
|
|
|
max_label_size = percentile.length();
|
2022-01-06 11:21:42 +00:00
|
|
|
|
|
|
|
benchmark.percentile_data.push_back({percentile, result});
|
2022-01-06 13:53:40 +00:00
|
|
|
}
|
|
|
|
string label;
|
|
|
|
float mins[2] = {0.01f, 0.001f}, total;
|
|
|
|
for (auto percent : mins){
|
|
|
|
total = 0;
|
|
|
|
size_t idx = ceil(sorted.size() * percent);
|
|
|
|
for (size_t i = 0; i < idx; i++){
|
|
|
|
total = total + sorted[i];
|
|
|
|
}
|
|
|
|
result = total / idx;
|
|
|
|
|
|
|
|
if (percent == 0.001f)
|
|
|
|
label = "0.1%";
|
|
|
|
if (percent == 0.01f)
|
|
|
|
label = "1%";
|
2022-03-06 13:56:14 +00:00
|
|
|
|
2022-01-06 13:53:40 +00:00
|
|
|
if (label.length() > max_label_size)
|
|
|
|
max_label_size = label.length();
|
|
|
|
|
|
|
|
benchmark.percentile_data.push_back({label, result});
|
|
|
|
}
|
2022-01-06 11:21:42 +00:00
|
|
|
|
|
|
|
for (auto& entry : benchmark.percentile_data) {
|
|
|
|
entry.first.append(max_label_size - entry.first.length(), ' ');
|
|
|
|
}
|
|
|
|
}
|