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 
259 /******************************************************************************
260  * Includes
261  */
262 
263 #include <ctime>
264 #include <filesystem>
265 #include <fstream>
266 #include <iostream>
267 #include <map>
268 #include <mutex>
269 #include <thread>
270 
271 // C++20
272 #include <experimental/source_location>
273 
274 // Zakero
275 #include "Zakero_Base.h"
276 
277 
278 /******************************************************************************
279  * Macros
280  */
281 
282 // {{{ Macros
283 
284 #ifdef ZAKERO_PROFILER_ENABLE
285 
336 #define ZAKERO_PROFILER_INIT(output_, meta_data_...) \
337  zakero::Profiler::init(output_, ##meta_data_);
338 
384 #define ZAKERO_PROFILER_ACTIVATE \
385  zakero::Profiler::activate();
386 
394 #define ZAKERO_PROFILER_DEACTIVATE \
395  zakero::Profiler::deactivate();
396 
449 #define ZAKERO_PROFILER_COMPLETE(category_, name_, meta_data_...) \
450  zakero::Profiler::Complete ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
451  ( category_ \
452  , name_ \
453  , std::experimental::source_location::current() \
454  , ##meta_data_ \
455  ); \
456 
457 
508 #define ZAKERO_PROFILER_DURATION(category_, name_, meta_data_...) \
509  zakero::Profiler::Duration ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
510  ( category_ \
511  , name_ \
512  , std::experimental::source_location::current() \
513  , ##meta_data_ \
514  ); \
515 
536 #define ZAKERO_PROFILER_INSTANT(category_, name_, meta_data_...) \
537 { \
538  zakero::Profiler::Instant ZAKERO_CONCAT(zakero_profiler_, __LINE__) \
539  ( category_ \
540  , name_ \
541  , std::experimental::source_location::current() \
542  , ##meta_data_ \
543  ); \
544 } \
545 
546 #else
547 
548 #define ZAKERO_PROFILER_INIT(output_, meta_data_...)
549 #define ZAKERO_PROFILER_ACTIVATE
550 #define ZAKERO_PROFILER_DEACTIVATE
551 #define ZAKERO_PROFILER_COMPLETE(category_, name_, meta_data_...)
552 #define ZAKERO_PROFILER_DURATION(category_, name_, meta_data_...)
553 #define ZAKERO_PROFILER_INSTANT(category_, name_, meta_data_...)
554 
555 #endif
556 
557 // }}}
558 
559 namespace zakero
560 {
561  // {{{ Declaration
562 
563  class Profiler
564  {
565  public:
566  using MetaData = std::map<std::string, std::string>;
567 
568  struct Data
569  {
570  MetaData meta_data;
571  std::string category;
572  std::string name;
573  std::experimental::source_location location;
574  std::chrono::microseconds::rep duration;
575  std::chrono::microseconds::rep time_stamp;
576  std::thread::id thread_id;
577  pid_t process_id;
578  char phase;
579 
580  Data(const char, const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
581  };
582 
583  struct Complete
584  : public zakero::Profiler::Data
585  {
586  bool was_active;
587 
588  Complete(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
589  ~Complete() noexcept;
590  };
591 
592  struct Duration
593  : public zakero::Profiler::Data
594  {
595  bool was_active;
596 
597  Duration(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
598  ~Duration() noexcept;
599  };
600 
601  struct Instant
602  : public zakero::Profiler::Data
603  {
604  Instant(const std::string&, const std::string&, const std::experimental::source_location&, zakero::Profiler::MetaData = {}) noexcept;
605  };
606 
607  Profiler() noexcept;
608  ~Profiler() noexcept;
609 
610  static std::error_code init(std::ostream&, zakero::Profiler::MetaData = {}) noexcept;
611  static std::error_code init(const std::filesystem::path, zakero::Profiler::MetaData = {}) noexcept;
612 
613  static void activate() noexcept;
614  static void deactivate() noexcept;
615 
616  static void report(const zakero::Profiler::Data&) noexcept;
617 
618  private:
619  std::mutex mutex;
620  std::ostream* stream;
621  std::ofstream file_output;
622  bool is_active;
623  };
624 
625  // }}}
626 }
627 
628 // {{{ Implementation
629 
630 #ifdef ZAKERO_PROFILER_IMPLEMENTATION
631 
632 // {{{ Macros
633 
644 #define ZAKERO_PROFILER__ERROR_DATA \
645  X(Error_None , 0 , "No Error" ) \
646  X(Error_Stream_Already_Open , 1 , "The profiler is already using an output stream" ) \
647  X(Error_No_Filename , 2 , "No filename was provided" ) \
648  X(Error_Cant_Open_Stream , 3 , "Unable to open the output stream" ) \
649  X(Error_Bad_Stream , 4 , "The stream is not in a good state" ) \
650 
661 #define ZAKERO_PROFILER__ERROR(err_) std::error_code(err_, ProfilerErrorCategory)
662 
663 // {{{ Macros : Doxygen
664 
665 #ifdef ZAKERO__DOXYGEN_DEFINE_DOCS
666 
667 // Only used for generating Doxygen documentation
668 
680 #define ZAKERO_PROFILER_IMPLEMENTATION
681 
692 #define ZAKERO_PROFILER_ENABLE
693 
694 #endif // ZAKERO__DOXYGEN_DEFINE_DOCS
695 
696 // }}}
697 // }}}
698 
699 namespace zakero
700 {
701 // {{{ Anonymous Namespace
702 
703 namespace
704 {
714  class ProfilerErrorCategory_
715  : public std::error_category
716  {
717  public:
718  constexpr ProfilerErrorCategory_() noexcept
719  {
720  }
721 
722  const char* name() const noexcept override
723  {
724  return "zakero.Profiler";
725  }
726 
727  std::string message(int condition) const override
728  {
729  switch(condition)
730  {
731 #define X(name_, val_, mesg_) \
732  case val_: return mesg_;
733  ZAKERO_PROFILER__ERROR_DATA
734 #undef X
735  }
736 
737  return "Unknown error condition";
738  }
739  } ProfilerErrorCategory;
740 
741 #define X(name_, val_, mesg_) \
742  static constexpr int name_ = val_;
743  ZAKERO_PROFILER__ERROR_DATA
744 #undef X
745 
746  zakero::Profiler zakero_profiler;
747 
748  // std::format performance testing
749  //uint64_t count = 0;
750  //uint64_t total = 0;
751 }
752 
753 // }}}
754 // {{{ Documentation
755 
782 // }}}
783 
790 Profiler::Profiler() noexcept
791  : mutex()
792  , stream(nullptr)
793  , file_output()
794  , is_active(false)
795 {
796 }
797 
798 
804 Profiler::~Profiler() noexcept
805 {
806  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
807 
808  if(stream != nullptr)
809  {
810  (*stream) << "]}" << std::endl;
811 
812  if(file_output.is_open())
813  {
814  file_output.close();
815  }
816  }
817 
818  // std::format performance testing
819  //std::cout << "Avg: " << (total / count) << "\n";
820 }
821 
822 
833 std::error_code zakero::Profiler::init(const std::filesystem::path path
834  , zakero::Profiler::MetaData meta_data
835  ) noexcept
836 {
837  if(zakero_profiler.stream != nullptr)
838  {
839  return ZAKERO_PROFILER__ERROR(Error_Stream_Already_Open);
840  }
841 
842  if(path.has_filename() == false)
843  {
844  return ZAKERO_PROFILER__ERROR(Error_No_Filename);
845  }
846 
847  zakero_profiler.file_output.open(path);
848 
849  if(zakero_profiler.file_output.is_open() == false)
850  {
851  return ZAKERO_PROFILER__ERROR(Error_Cant_Open_Stream);
852  }
853 
854  std::error_code error;
855 
856  error = init(zakero_profiler.file_output, meta_data);
857 
858  return error;
859 }
860 
861 
871 std::error_code zakero::Profiler::init(std::ostream& output_stream
872  , zakero::Profiler::MetaData meta_data
873  ) noexcept
874 {
875  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
876 
877  if(zakero_profiler.stream != nullptr)
878  {
879  return ZAKERO_PROFILER__ERROR(Error_Stream_Already_Open);
880  }
881 
882  if(output_stream.good() == false)
883  {
884  return ZAKERO_PROFILER__ERROR(Error_Bad_Stream);
885  }
886 
887  zakero_profiler.stream = &output_stream;
888  activate();
889 
890  if(meta_data.contains("date") == false)
891  {
892  std::time_t time = std::chrono::system_clock::to_time_t(
893  std::chrono::system_clock::now()
894  );
895 
896  char buf[128];
897  std::strftime(buf, sizeof(buf), "%F %T", std::localtime(&time));
898 
899  meta_data["date"] = buf;
900  }
901 
902  if(meta_data.contains("traceEvents"))
903  {
904  meta_data.erase("traceEvents");
905  }
906 
907  meta_data["displayTimeUnit"] = "ms";
908 
909  (*zakero_profiler.stream) << "{";
910 
911  for(const auto& iter : meta_data)
912  {
913  (*zakero_profiler.stream) << "\"" << iter.first << "\":\"" << iter.second << "\",";
914  }
915 
916  (*zakero_profiler.stream) << "\"traceEvents\":[{}\n";
917 
918  return ZAKERO_PROFILER__ERROR(Error_None);
919 }
920 
921 
928 void zakero::Profiler::activate() noexcept
929 {
930  zakero_profiler.is_active = true;
931 }
932 
933 
943 void zakero::Profiler::deactivate() noexcept
944 {
945  zakero_profiler.is_active = false;
946 }
947 
948 
954 void Profiler::report(const zakero::Profiler::Data& data
955  ) noexcept
956 {
957  std::lock_guard<std::mutex> lock(zakero_profiler.mutex);
958 
959  // std::format performance testing
960  //const auto t1 = std::chrono::steady_clock::now();
961 
962  (*zakero_profiler.stream)
963  << ",{\"ph\":\"" << data.phase << "\""
964  << ",\"ts\":" << data.time_stamp
965  << ",\"dur\":" << data.duration
966  << ",\"pid\":" << data.process_id
967  << ",\"tid\":" << data.thread_id
968  << ",\"cat\":\"" << data.category << "\""
969  << ",\"name\":\"" << data.name << "\""
970  << ",\"args\":"
971  << "{\"file_name\":\"" << data.location.file_name() << "\""
972  << ",\"function_name\":\"" << data.location.function_name() << "\""
973  ;
974  for(const auto& iter : data.meta_data)
975  {
976  (*zakero_profiler.stream)
977  << ",\"" << iter.first << "\":\"" << iter.second << "\""
978  ;
979  }
980 
981  (*zakero_profiler.stream) << "}}\n";
982 
983  /* C++20 std::format ready
984  (*zakero_profiler.stream) << std::format(
985  ",{{\"ph\":\"{}\""
986  ",\"ts\":{}"
987  ",\"cat\":\"{}\""
988  ",\"name\":\"{}\""
989  ",\"pid\":{}"
990  ",\"tid\":{}"
991  ",\"args\": {{\"file_name\":\"{}\",\"function_name\":\"{}\"}}"
992  "}}\n"
993  , data.phase
994  , nano
995  , data.category
996  , data.name
997  , data.process_id
998  , data.thread_id
999  , data.location.file_name()
1000  , data.location.function_name()
1001  );
1002 
1003  // std::format performance testing
1004  const auto t2 = std::chrono::steady_clock::now();
1005  const auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
1006  std::cout << (uint64_t)delta << "\n";
1007  total += delta;
1008  count++;
1009  */
1010 }
1011 
1012 
1028 Profiler::Data::Data(const char phase
1029  , const std::string& category
1030  , const std::string& name
1031  , const std::experimental::source_location& location
1032  , zakero::Profiler::MetaData meta_data
1033  ) noexcept
1034  : meta_data(meta_data)
1035  , category(category)
1036  , name(name)
1037  , location(location)
1038  , duration(0)
1039  , time_stamp(ZAKERO_STEADY_TIME_NOW(microseconds))
1040  , thread_id(std::this_thread::get_id())
1041  , process_id(ZAKERO_PID)
1042  , phase(phase)
1043 {
1044 }
1045 
1046 
1065 Profiler::Complete::Complete(const std::string& category
1066  , const std::string& name
1067  , const std::experimental::source_location& location
1068  , zakero::Profiler::MetaData meta_data
1069  ) noexcept
1070  : zakero::Profiler::Data('X', category, name, location, meta_data)
1071  , was_active(zakero_profiler.is_active)
1072 {
1073 }
1074 
1075 
1081 Profiler::Complete::~Complete() noexcept
1082 {
1083  if(was_active || zakero_profiler.is_active)
1084  {
1085  duration = ZAKERO_STEADY_TIME_NOW(microseconds) - time_stamp;
1086  zakero::Profiler::report(*this);
1087  }
1088 }
1089 
1090 
1109 Profiler::Duration::Duration(const std::string& category
1110  , const std::string& name
1111  , const std::experimental::source_location& location
1112  , zakero::Profiler::MetaData meta_data
1113  ) noexcept
1114  : zakero::Profiler::Data('B', category, name, location, meta_data)
1115  , was_active(zakero_profiler.is_active)
1116 {
1117 }
1118 
1119 
1125 Profiler::Duration::~Duration() noexcept
1126 {
1127  if(was_active || zakero_profiler.is_active)
1128  {
1129  zakero::Profiler::report(*this);
1130 
1131  phase = 'E';
1132  time_stamp = ZAKERO_STEADY_TIME_NOW(microseconds);
1133  zakero::Profiler::report(*this);
1134  }
1135 }
1136 
1137 
1156 Profiler::Instant::Instant(const std::string& category
1157  , const std::string& name
1158  , const std::experimental::source_location& location
1159  , zakero::Profiler::MetaData meta_data
1160  ) noexcept
1161  : zakero::Profiler::Data('i', category, name, location, meta_data)
1162 {
1163  if(zakero_profiler.is_active)
1164  {
1165  zakero::Profiler::report(*this);
1166  }
1167 }
1168 
1169 } // zakero
1170 
1171 #endif // ZAKERO_PROFILER_IMPLEMENTATION
1172 
1173 // }}}
1174 
1175 #endif // zakero_Profiler_h
Zakero Base.
#define ZAKERO_STEADY_TIME_NOW(unit_)
Get the current time.
Definition: Zakero_Base.h:231
#define ZAKERO_PID
Get the Process Id.
Definition: Zakero_Base.h:212