Zakero's C++ Header Libraries
A collection of reusable C++ libraries
Zakero_Profiler.h
Go to the documentation of this file.
1 /******************************************************************************
2  * Copyright 2020 Andrew Moore
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7  */
8 
9 #ifndef zakero_Profiler_h
10 #define zakero_Profiler_h
11 
251 /******************************************************************************
252  * Includes
253  */
254 
255 #include <ctime>
256 #include <filesystem>
257 #include <fstream>
258 #include <iostream>
259 #include <map>
260 #include <mutex>
261 #include <thread>
262 
263 // C++20
264 #include <experimental/source_location>
265 
266 // Zakero
267 #include "Zakero_Base.h"
268 
269 
270 /******************************************************************************
271  * Macros
272  */
273 
274 // {{{ Macros
275 
276 #ifdef ZAKERO_PROFILER_ENABLE
277 
328 #define ZAKERO_PROFILER_INIT(output_, meta_data_...) \
329  zakero::Profiler::init(output_, ##meta_data_);
330 
376 #define ZAKERO_PROFILER_ACTIVATE \
377  zakero::Profiler::activate();
378 
386 #define ZAKERO_PROFILER_DEACTIVATE \
387  zakero::Profiler::deactivate();
388 
441 #define ZAKERO_PROFILER_COMPLETE(category_, name_, meta_data_...) \
442  zakero::Profiler::Complete ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
443  ( category_ \
444  , name_ \
445  , std::experimental::source_location::current() \
446  , ##meta_data_ \
447  ); \
448 
449 
500 #define ZAKERO_PROFILER_DURATION(category_, name_, meta_data_...) \
501  zakero::Profiler::Duration ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
502  ( category_ \
503  , name_ \
504  , std::experimental::source_location::current() \
505  , ##meta_data_ \
506  ); \
507 
528 #define ZAKERO_PROFILER_INSTANT(category_, name_, meta_data_...) \
529 { \
530  zakero::Profiler::Instant ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
531  ( category_ \
532  , name_ \
533  , std::experimental::source_location::current() \
534  , ##meta_data_ \
535  ); \
536 } \
537 
538 #else
539 
540 #define ZAKERO_PROFILER_INIT(output_, meta_data_...)
541 #define ZAKERO_PROFILER_ACTIVATE
542 #define ZAKERO_PROFILER_DEACTIVATE
543 #define ZAKERO_PROFILER_COMPLETE(category_, name_, meta_data_...)
544 #define ZAKERO_PROFILER_DURATION(category_, name_, meta_data_...)
545 #define ZAKERO_PROFILER_INSTANT(category_, name_, meta_data_...)
546 
547 #endif
548 
549 // }}}
550 
551 namespace zakero
552 {
553  // {{{ Declaration
554 
555  class Profiler
556  {
557  public:
558  using MetaData = std::map<std::string, std::string>;
559 
560  struct Data
561  {
562  MetaData meta_data;
563  std::string category;
564  std::string name;
565  std::experimental::source_location location;
566  std::chrono::microseconds::rep duration;
567  std::chrono::microseconds::rep time_stamp;
568  std::thread::id thread_id;
569  pid_t process_id;
570  char phase;
571 
572  Data(const char, const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
573  };
574 
575  struct Complete
576  : public zakero::Profiler::Data
577  {
578  bool was_active;
579 
580  Complete(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
581  ~Complete() noexcept;
582  };
583 
584  struct Duration
585  : public zakero::Profiler::Data
586  {
587  bool was_active;
588 
589  Duration(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
590  ~Duration() noexcept;
591  };
592 
593  struct Instant
594  : public zakero::Profiler::Data
595  {
596  Instant(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
597  };
598 
599  Profiler() noexcept;
600  ~Profiler() noexcept;
601 
602  static std::error_code init(std::ostream&, zakero::Profiler::MetaData = {}) noexcept;
603  static std::error_code init(const std::filesystem::path, zakero::Profiler::MetaData = {}) noexcept;
604 
605  static void activate() noexcept;
606  static void deactivate() noexcept;
607 
608  static void report(const zakero::Profiler::Data&) noexcept;
609 
610  private:
611  std::mutex mutex;
612  std::ostream* stream;
613  std::ofstream file_output;
614  bool is_active;
615  };
616 
617  // }}}
618 }
619 
620 // {{{ Implementation
621 
622 #ifdef ZAKERO_PROFILER_IMPLEMENTATION
623 
624 // {{{ Macros
625 
636 #define ZAKERO_PROFILER__ERROR_DATA \
637  X(Error_None , 0 , "No Error" ) \
638  X(Error_Stream_Already_Open , 1 , "The profiler is already using an output stream" ) \
639  X(Error_No_Filename , 2 , "No filename was provided" ) \
640  X(Error_Cant_Open_Stream , 3 , "Unable to open the output stream" ) \
641  X(Error_Bad_Stream , 4 , "The stream is not in a good state" ) \
642 
653 #define ZAKERO_PROFILER__ERROR(err_) std::error_code(err_, ProfilerErrorCategory)
654 
655 // {{{ Macros : Doxygen
656 
657 #ifdef ZAKERO__DOXYGEN_DEFINE_DOCS
658 
659 // Only used for generating Doxygen documentation
660 
672 #define ZAKERO_PROFILER_IMPLEMENTATION
673 
684 #define ZAKERO_PROFILER_ENABLE
685 
686 #endif // ZAKERO__DOXYGEN_DEFINE_DOCS
687 
688 // }}}
689 // }}}
690 
691 namespace zakero
692 {
693 // {{{ Anonymous Namespace
694 
695 namespace
696 {
706  class ProfilerErrorCategory_
707  : public std::error_category
708  {
709  public:
710  constexpr ProfilerErrorCategory_() noexcept
711  {
712  }
713 
714  const char* name() const noexcept override
715  {
716  return "zakero.Profiler";
717  }
718 
719  std::string message(int condition) const override
720  {
721  switch(condition)
722  {
723 #define X(name_, val_, mesg_) \
724  case val_: return mesg_;
725  ZAKERO_PROFILER__ERROR_DATA
726 #undef X
727  }
728 
729  return "Unknown error condition";
730  }
731  } ProfilerErrorCategory;
732 
733 #define X(name_, val_, mesg_) \
734  static constexpr int name_ = val_;
735  ZAKERO_PROFILER__ERROR_DATA
736 #undef X
737 
738  zakero::Profiler zakero_profiler;
739 
740  // std::format performance testing
741  //uint64_t count = 0;
742  //uint64_t total = 0;
743 }
744 
745 // }}}
746 // {{{ Documentation
747 
774 // }}}
775 
782 Profiler::Profiler() noexcept
783  : mutex()
784  , stream(nullptr)
785  , file_output()
786  , is_active(false)
787 {
788 }
789 
790 
796 Profiler::~Profiler() noexcept
797 {
798  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
799 
800  if(stream != nullptr)
801  {
802  (*stream) << "]}" << std::endl;
803 
804  if(file_output.is_open())
805  {
806  file_output.close();
807  }
808  }
809 
810  // std::format performance testing
811  //std::cout << "Avg: " << (total / count) << "\n";
812 }
813 
814 
825 std::error_code zakero::Profiler::init(const std::filesystem::path path
826  , zakero::Profiler::MetaData meta_data
827  ) noexcept
828 {
829  if(zakero_profiler.stream != nullptr)
830  {
831  return ZAKERO_PROFILER__ERROR(Error_Stream_Already_Open);
832  }
833 
834  if(path.has_filename() == false)
835  {
836  return ZAKERO_PROFILER__ERROR(Error_No_Filename);
837  }
838 
839  zakero_profiler.file_output.open(path);
840 
841  if(zakero_profiler.file_output.is_open() == false)
842  {
843  return ZAKERO_PROFILER__ERROR(Error_Cant_Open_Stream);
844  }
845 
846  std::error_code error;
847 
848  error = init(zakero_profiler.file_output, meta_data);
849 
850  return error;
851 }
852 
853 
863 std::error_code zakero::Profiler::init(std::ostream& output_stream
864  , zakero::Profiler::MetaData meta_data
865  ) noexcept
866 {
867  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
868 
869  if(zakero_profiler.stream != nullptr)
870  {
871  return ZAKERO_PROFILER__ERROR(Error_Stream_Already_Open);
872  }
873 
874  if(output_stream.good() == false)
875  {
876  return ZAKERO_PROFILER__ERROR(Error_Bad_Stream);
877  }
878 
879  zakero_profiler.stream = &output_stream;
880  activate();
881 
882  if(meta_data.contains("date") == false)
883  {
884  std::time_t time = std::chrono::system_clock::to_time_t(
885  std::chrono::system_clock::now()
886  );
887 
888  char buf[128];
889  std::strftime(buf, sizeof(buf), "%F %T", std::localtime(&time));
890 
891  meta_data["date"] = buf;
892  }
893 
894  if(meta_data.contains("traceEvents"))
895  {
896  meta_data.erase("traceEvents");
897  }
898 
899  meta_data["displayTimeUnit"] = "ms";
900 
901  (*zakero_profiler.stream) << "{";
902 
903  for(const auto& iter : meta_data)
904  {
905  (*zakero_profiler.stream) << "\"" << iter.first << "\":\"" << iter.second << "\",";
906  }
907 
908  (*zakero_profiler.stream) << "\"traceEvents\":[{}\n";
909 
910  return ZAKERO_PROFILER__ERROR(Error_None);
911 }
912 
913 
920 void zakero::Profiler::activate() noexcept
921 {
922  zakero_profiler.is_active = true;
923 }
924 
925 
935 void zakero::Profiler::deactivate() noexcept
936 {
937  zakero_profiler.is_active = false;
938 }
939 
940 
946 void Profiler::report(const zakero::Profiler::Data& data
947  ) noexcept
948 {
949  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
950 
951  // std::format performance testing
952  //const auto t1 = std::chrono::steady_clock::now();
953 
954  (*zakero_profiler.stream)
955  << ",{\"ph\":\"" << data.phase << "\""
956  << ",\"ts\":" << data.time_stamp
957  << ",\"dur\":" << data.duration
958  << ",\"pid\":" << data.process_id
959  << ",\"tid\":" << data.thread_id
960  << ",\"cat\":\"" << data.category << "\""
961  << ",\"name\":\"" << data.name << "\""
962  << ",\"args\":"
963  << "{\"file_name\":\"" << data.location.file_name() << "\""
964  << ",\"function_name\":\"" << data.location.function_name() << "\""
965  ;
966  for(const auto& iter : data.meta_data)
967  {
968  (*zakero_profiler.stream)
969  << ",\"" << iter.first << "\":\"" << iter.second << "\""
970  ;
971  }
972 
973  (*zakero_profiler.stream) << "}}\n";
974 
975  /* C++20 std::format ready
976  (*zakero_profiler.stream) << std::format(
977  ",{{\"ph\":\"{}\""
978  ",\"ts\":{}"
979  ",\"cat\":\"{}\""
980  ",\"name\":\"{}\""
981  ",\"pid\":{}"
982  ",\"tid\":{}"
983  ",\"args\": {{\"file_name\":\"{}\",\"function_name\":\"{}\"}}"
984  "}}\n"
985  , data.phase
986  , nano
987  , data.category
988  , data.name
989  , data.process_id
990  , data.thread_id
991  , data.location.file_name()
992  , data.location.function_name()
993  );
994 
995  // std::format performance testing
996  const auto t2 = std::chrono::steady_clock::now();
997  const auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
998  std::cout << (uint64_t)delta << "\n";
999  total += delta;
1000  count++;
1001  */
1002 }
1003 
1004 
1020 Profiler::Data::Data(const char phase
1021  , const std::string& category
1022  , const std::string& name
1023  , const std::experimental::source_location& location
1024  , zakero::Profiler::MetaData meta_data
1025  ) noexcept
1026  : meta_data(meta_data)
1027  , category(category)
1028  , name(name)
1029  , location(location)
1030  , duration(0)
1031  , time_stamp(ZAKERO_STEADY_TIME_NOW(microseconds))
1032  , thread_id(std::this_thread::get_id())
1033  , process_id(ZAKERO_PID)
1034  , phase(phase)
1035 {
1036 }
1037 
1038 
1057 Profiler::Complete::Complete(const std::string& category
1058  , const std::string& name
1059  , const std::experimental::source_location& location
1060  , zakero::Profiler::MetaData meta_data
1061  ) noexcept
1062  : zakero::Profiler::Data('X', category, name, location, meta_data)
1063  , was_active(zakero_profiler.is_active)
1064 {
1065 }
1066 
1067 
1073 Profiler::Complete::~Complete() noexcept
1074 {
1075  if(was_active || zakero_profiler.is_active)
1076  {
1077  duration = ZAKERO_STEADY_TIME_NOW(microseconds) - time_stamp;
1078  zakero::Profiler::report(*this);
1079  }
1080 }
1081 
1082 
1101 Profiler::Duration::Duration(const std::string& category
1102  , const std::string& name
1103  , const std::experimental::source_location& location
1104  , zakero::Profiler::MetaData meta_data
1105  ) noexcept
1106  : zakero::Profiler::Data('B', category, name, location, meta_data)
1107  , was_active(zakero_profiler.is_active)
1108 {
1109 }
1110 
1111 
1117 Profiler::Duration::~Duration() noexcept
1118 {
1119  if(was_active || zakero_profiler.is_active)
1120  {
1121  zakero::Profiler::report(*this);
1122 
1123  phase = 'E';
1124  time_stamp = ZAKERO_STEADY_TIME_NOW(microseconds);
1125  zakero::Profiler::report(*this);
1126  }
1127 }
1128 
1129 
1148 Profiler::Instant::Instant(const std::string& category
1149  , const std::string& name
1150  , const std::experimental::source_location& location
1151  , zakero::Profiler::MetaData meta_data
1152  ) noexcept
1153  : zakero::Profiler::Data('i', category, name, location, meta_data)
1154 {
1155  if(zakero_profiler.is_active)
1156  {
1157  zakero::Profiler::report(*this);
1158  }
1159 }
1160 
1161 } // zakero
1162 
1163 #endif // ZAKERO_PROFILER_IMPLEMENTATION
1164 
1165 // }}}
1166 
1167 #endif // zakero_Profiler_h
Zakero Base.
#define ZAKERO_STEADY_TIME_NOW(unit_)
Get the current time.
Definition: Zakero_Base.h:205
#define ZAKERO_PID
Get the Process Id.
Definition: Zakero_Base.h:187