Zakero's C++ Header Libraries
A collection of reusable C++ libraries
Zakero_Profiler.h
Go to the documentation of this file.
1 /* *******************************************************************
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5  */
6 
218 #ifndef zakero_Profiler_h
219 #define zakero_Profiler_h
220 
221 #include <chrono>
222 #include <ctime>
223 #include <experimental/source_location>
224 #include <fstream>
225 #include <iostream>
226 #include <map>
227 #include <thread>
228 
229 // {{{ Macros
230 
231 #if defined(ZAKERO_PROFILER_ENABLE)
232 
265 #define ZAKERO_PROFILER_INIT(output_) \
266  zakero::Profiler::init(output_, {});
267 
310 #define ZAKERO_PROFILER_INIT_METADATA(output_, meta_data_) \
311  zakero::Profiler::init(output_, meta_data_);
312 
359 #define ZAKERO_PROFILER_ACTIVATE \
360  zakero::Profiler::activate();
361 
369 #define ZAKERO_PROFILER_DEACTIVATE \
370  zakero::Profiler::deactivate();
371 
383 #define ZAKERO_CONCAT_(thing_1_, thing_2_) thing_1_ ## thing_2_
384 
405 #define ZAKERO_CONCAT(thing_1_, thing_2_) ZAKERO_CONCAT_(thing_1_, thing_2_)
406 
421 #define ZAKERO_PROFILER_UNIQUE(name_) ZAKERO_CONCAT(name_, __LINE__)
422 
463 #define ZAKERO_PROFILER_DURATION(category_, name_) \
464  zakero::Profiler::Duration ZAKERO_PROFILER_UNIQUE(zakero_profiler_duration_) \
465  ( category_ \
466  , name_ \
467  , std::experimental::source_location::current() \
468  ); \
469 
470 
482 #define ZAKERO_PROFILER_INSTANT(category_, name_) \
483  { \
484  zakero::Profiler::Instant ZAKERO_PROFILER_UNIQUE(zakero_profiler_instant_) \
485  ( category_ \
486  , name_ \
487  , std::experimental::source_location::current() \
488  ); \
489  } \
490 
491 #else
492 
493 #define ZAKERO_PROFILER_INIT(output_)
494 #define ZAKERO_PROFILER_INIT_METADATA(output_, meta_data_)
495 #define ZAKERO_PROFILER_ACTIVATE
496 #define ZAKERO_PROFILER_DEACTIVATE
497 #define ZAKERO_PROFILER_DURATION(category_, name_)
498 #define ZAKERO_PROFILER_INSTANT(category_, name_)
499 
500 #endif
501 
502 #define TIME_NOW \
503 std::chrono::duration_cast<std::chrono::microseconds>( \
504  std::chrono::steady_clock::now().time_since_epoch() \
505  ).count() \
506 
507 // {{{ Defines : Doxygen
508 
509 #if defined(ZAKERO__DOXYGEN_DEFINE_DOCS)
510 
511 // Only used for generating Doxygen documentation
512 
523 #define ZAKERO_PROFILER_ENABLE
524 
536 #define ZAKERO_PROFILER_IMPLEMENTATION
537 
538 #endif
539 
540 // }}}
541 // }}}
542 // {{{ Declaration
543 
544 namespace zakero
545 {
546  class Profiler
547  {
548  public:
549  using MetaData = std::map<std::string, std::string>;
550 
551  struct Data
552  {
553  std::string category;
554  std::string name;
555  std::experimental::source_location location;
556  std::chrono::microseconds::rep time_stamp;
557  std::thread::id thread_id;
558  pid_t process_id;
559  char phase;
560 
561  Data(const char, const std::string&, const std::string&, const std::experimental::source_location&) noexcept;
562  };
563 
564  struct Duration
565  : public zakero::Profiler::Data
566  {
567  bool was_active;
568 
569  Duration(const std::string&, const std::string&, const std::experimental::source_location&) noexcept;
570  ~Duration() noexcept;
571  };
572 
573  struct Instant
574  : public zakero::Profiler::Data
575  {
576  Instant(const std::string&, const std::string&, const std::experimental::source_location&) noexcept;
577  };
578 
579  Profiler() noexcept;
580  ~Profiler() noexcept;
581 
582  static void init(std::ostream&, zakero::Profiler::MetaData) noexcept;
583  static void init(std::string, zakero::Profiler::MetaData) noexcept;
584 
585  static void activate() noexcept;
586  static void deactivate() noexcept;
587 
588  static void report(const zakero::Profiler::Data&) noexcept;
589 
590  private:
591  std::ostream* stream;
592  std::ofstream file_output;
593  bool is_active;
594  };
595 }
596 
597 // }}}
598 // {{{ Implementation
599 
600 #if defined (ZAKERO_PROFILER_IMPLEMENTATION)
601 
602 #ifdef __linux__
603 #include <sys/types.h>
604 #include <unistd.h>
605 #define ZAKERO_PROFILER_PID getpid()
606 #else
607 #define ZAKERO_PROFILER_PID -1
608 #endif
609 
610 namespace
611 {
612  zakero::Profiler zakero_profiler;
613 
614  // std::format performance testing
615  //uint64_t count = 0;
616  //uint64_t total = 0;
617 }
618 
619 
620 namespace zakero
621 {
622 
655 Profiler::Profiler() noexcept
656  : stream(nullptr)
657  , file_output()
658  , is_active(false)
659 {
660 }
661 
662 
668 Profiler::~Profiler() noexcept
669 {
670  (*stream) << "]}" << std::endl;
671 
672  if(file_output.is_open())
673  {
674  file_output.close();
675  }
676 
677  // std::format performance testing
678  //std::cout << "Avg: " << (total / count) << "\n";
679 }
680 
681 
693 void zakero::Profiler::init(std::string file_name
694  , zakero::Profiler::MetaData meta_data
695  ) noexcept
696 {
697  if(zakero_profiler.stream != nullptr)
698  {
699  return;
700  }
701 
702  zakero_profiler.file_output.open(file_name);
703 
704  init(zakero_profiler.file_output, meta_data);
705 }
706 
707 
718 void zakero::Profiler::init(std::ostream& output_stream
719  , zakero::Profiler::MetaData meta_data
720  ) noexcept
721 {
722  if(zakero_profiler.stream != nullptr)
723  {
724  return;
725  }
726 
727  zakero_profiler.stream = &output_stream;
728  activate();
729 
730  if(meta_data.contains("date") == false)
731  {
732  std::time_t time = std::chrono::system_clock::to_time_t(
733  std::chrono::system_clock::now()
734  );
735 
736  char buf[128];
737  std::strftime(buf, sizeof(buf), "%F %T", std::localtime(&time));
738 
739  meta_data["date"] = buf;
740  }
741 
742  if(meta_data.contains("traceEvents"))
743  {
744  meta_data.erase("traceEvents");
745  }
746 
747  meta_data["displayTimeUnit"] = "ms";
748 
749  (*zakero_profiler.stream) << "{";
750 
751  for(const auto& iter : meta_data)
752  {
753  (*zakero_profiler.stream) << "\"" << iter.first << "\":\"" << iter.second << "\",";
754  }
755 
756  (*zakero_profiler.stream) << "\"traceEvents\":[{}\n";
757 }
758 
759 
766 void zakero::Profiler::activate() noexcept
767 {
768  zakero_profiler.is_active = true;
769 }
770 
771 
781 void zakero::Profiler::deactivate() noexcept
782 {
783  zakero_profiler.is_active = false;
784 }
785 
786 
792 void Profiler::report(const zakero::Profiler::Data& data
793  ) noexcept
794 {
795  // std::format performance testing
796  //const auto t1 = std::chrono::steady_clock::now();
797 
798  (*zakero_profiler.stream)
799  << ",{\"ph\":\"" << data.phase << "\""
800  << ",\"ts\":" << data.time_stamp
801  << ",\"pid\":" << data.process_id
802  << ",\"tid\":" << data.thread_id
803  << ",\"cat\":\"" << data.category << "\""
804  << ",\"name\":\"" << data.name << "\""
805  << ",\"args\":"
806  << "{\"file_name\":\"" << data.location.file_name() << "\""
807  << ",\"function_name\":\"" << data.location.function_name() << "\""
808  << "}"
809  << "}\n"
810  ;
811  /* C++20 std::format ready
812  (*zakero_profiler.stream) << std::format(
813  ",{{\"ph\":\"{}\""
814  ",\"ts\":{}"
815  ",\"cat\":\"{}\""
816  ",\"name\":\"{}\""
817  ",\"pid\":{}"
818  ",\"tid\":{}"
819  ",\"args\": {{\"file_name\":\"{}\",\"function_name\":\"{}\"}}"
820  "}}\n"
821  , data.phase
822  , nano
823  , data.category
824  , data.name
825  , data.process_id
826  , data.thread_id
827  , data.location.file_name()
828  , data.location.function_name()
829  );
830 
831  // std::format performance testing
832  const auto t2 = std::chrono::steady_clock::now();
833  const auto delta = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
834  std::cout << (uint64_t)delta << "\n";
835  total += delta;
836  count++;
837  */
838 }
839 
840 
856 Profiler::Data::Data(const char phase
857  , const std::string& category
858  , const std::string& name
859  , const std::experimental::source_location& location
860  ) noexcept
861  : category(category)
862  , name(name)
863  , location(location)
864  , time_stamp(TIME_NOW)
865  , thread_id(std::this_thread::get_id())
866  , process_id(ZAKERO_PROFILER_PID)
867  , phase(phase)
868 {
869 }
870 
871 
890 Profiler::Duration::Duration(const std::string& category
891  , const std::string& name
892  , const std::experimental::source_location& location
893  ) noexcept
894  : zakero::Profiler::Data('B', category, name, location)
895  , was_active(zakero_profiler.is_active)
896 {
897 }
898 
899 
905 Profiler::Duration::~Duration() noexcept
906 {
907  if(was_active || zakero_profiler.is_active)
908  {
909  zakero::Profiler::report(*this);
910 
911  phase = 'E';
912  time_stamp = TIME_NOW;
913  zakero::Profiler::report(*this);
914  }
915 }
916 
917 
936 Profiler::Instant::Instant(const std::string& category
937  , const std::string& name
938  , const std::experimental::source_location& location
939  ) noexcept
940  : zakero::Profiler::Data('I', category, name, location)
941 {
942  if(zakero_profiler.is_active)
943  {
944  zakero::Profiler::report(*this);
945  }
946 }
947 
948 } // zakero
949 
950 #endif // ZAKERO_PROFILER_IMPLEMENTATION
951 
952 // }}}
953 
954 #endif // zakero_Profiler_h