Kontrol runtime aplikasi perangkat lunak internal

Publikasi ini menyajikan implementasi perangkat lunak dari alat bawaan untuk mengumpulkan dan mengumpulkan informasi metrik pada runtime aplikasi yang ditulis dalam C / C ++ / C #.

Inti dari pendekatan yang dideskripsikan didasarkan pada dimasukkannya "titik kontrol" dalam kode program aplikasi untuk mengekstraksi data pada waktu eksekusi komponen struktural: metode, fungsi, dan blok {}. Informasi metrik yang diekstrak diakumulasikan dalam basis data internal, yang isinya di akhir aplikasi dikonversi menjadi bentuk laporan teks yang disimpan dalam file. Ketepatan menggunakan sarana kontrol bawaan waktu eksekusi adalah karena kebutuhan untuk mengidentifikasi area masalah kode, menganalisis penyebab degradasi sementara aplikasi: penuh atau sebagian, atau muncul pada set tertentu dari sumber data.

Contoh kode sumber C ++ / C # yang diberikan menunjukkan kemungkinan implementasi dari pendekatan yang dijelaskan.

Pendahuluan


Pengembangan aplikasi perangkat lunak pada setiap iterasi (misalnya, rilis rilis berikutnya) pengembangan evolusionernya meliputi langkah-langkah dasar berikut:

  • pengembangan dan pengujian fungsionalitas;
  • optimalisasi sumber daya RAM yang dikonsumsi;
  • stabilisasi metrik runtime.

Langkah-langkah ini membutuhkan sejumlah besar pengembang tidak hanya kreatif (seperti pengembangan dan implementasi algoritma yang efektif, membangun arsitektur perangkat lunak yang fleksibel, dll), tetapi juga pekerjaan rutin. Kategori yang terakhir mencakup kegiatan yang bertujuan untuk menstabilkan metrik waktu untuk eksekusi aplikasi. Dalam banyak kasus, ini adalah prosedur yang agak menyakitkan ketika pengembang dihadapkan pada degradasi, yang merupakan konsekuensi dari perluasan fungsionalitas produk perangkat lunak, pembangunan kembali arsitektur perangkat lunak dan munculnya utas baru dalam aplikasi. Pada saat yang sama, sumber-sumber degradasi memerlukan upaya tertentu untuk mendeteksinya, yang dicapai tidak hanya dengan tekun dan tanggung jawab pengembang yang tinggi (kondisi yang diperlukan), tetapi juga oleh komposisi alat yang digunakan untuk tujuan ini (kondisi yang memadai).

Salah satu pendekatan yang efektif untuk memecahkan masalah menganalisis metrik waktu aplikasi adalah penggunaan produk perangkat lunak khusus, misalnya gprof GNU. Analisis laporan yang dihasilkan oleh alat-alat tersebut memungkinkan Anda untuk mengidentifikasi "hambatan" (metode dan fungsi kelas), yang merupakan jumlah waktu yang dihabiskan untuk menjalankan aplikasi secara keseluruhan. Pada saat yang sama, validitas waktu yang dihabiskan untuk pelaksanaan metode dan prosedur tentu memenuhi syarat oleh pengembang.

Perlu juga dicatat bahwa produk perangkat lunak dari kelas ini, sebagai suatu peraturan, melakukan analisis metrik dari waktu eksekusi kode program pada level metode kelas dan fungsi, mengabaikan level yang lebih rendah (namun tetap signifikan dari sudut pandang analisis masalah): {...}, untuk, sementara, lakukan-sampai, jika - selain itu, coba-tangkap blok, yang di dalamnya pengeluaran pengeluaran waktu eksekusi tidak kurang signifikan terjadi.

Selanjutnya, konten utama dari salah satu solusi yang mungkin untuk penerapan alat kontrol runtime built-in yang ditujukan untuk mengekstraksi dan mengumpulkan informasi terperinci tentang metrik waktu dari blok perangkat lunak yang dikontrol dengan pembuatan laporan berikutnya untuk pengembang dipertimbangkan.

Metode untuk Mengambil Data Runtime


Fungsionalitas aplikasi perangkat lunak apa pun dapat diartikan sebagai mesin abstrak dengan seperangkat status unik {St} dan transisi {Tr} di antaranya.

Dalam kerangka pendekatan ini, setiap aliran eksekusi dalam aplikasi harus diinterpretasikan sebagai urutan urutan negara dan transisi di antara mereka. Dalam hal ini, estimasi biaya waktu eksekusi dilakukan dengan menjumlahkan metrik waktu pada seluruh rangkaian negara yang disahkan dengan mengabaikan biaya transisi dari satu kondisi ke kondisi lainnya - sebagai nilai yang dapat diabaikan.

Ekstraksi dan akumulasi data pada waktu eksekusi aplikasi pada titik kontrol yang ditentukan adalah tugas utama yang diselesaikan oleh alat kontrol bawaan yang dijelaskan di bawah ini.

Untuk setiap breakpoint dideklarasikan dalam kode sumber dengan menempatkan
PROFILE_ENTRY C ++ makro, jumlah pass-nya selama eksekusi aplikasi dicatat, serta metrik waktu - total waktu aplikasi berada dalam status dari saat checkpoint melewati ke level berikutnya dari hirarki program (termasuk blok, metode kelas, fungsi, dll.) seperti yang diilustrasikan dalam diagram di bawah ini.

Kontrol titik kontrol (pendaftaran awal dan perhitungan metrik waktu mereka) dilakukan oleh objek 'timeManager' , yang dibuat dalam satu instance. Setiap peristiwa melewati pos pemeriksaan ditetapkan oleh objek 'timeManager', dan selama pass pertama terdaftar olehnya sebagai diamati sebagai 'registerEntry' .

Pada saat setiap bagian dari titik kontrol, timerObject dibuat, menetapkan waktu pembuatannya. Waktu eksekusi ditetapkan pada pos pemeriksaan ketika aplikasi keluar dari level hirarki perangkat lunak saat ini. Pada saat ini, timerObject dari objek secara otomatis dihancurkan, yang disertai dengan perhitungan "seumur hidupnya" T. Sebagai hasilnya, manajer waktu meningkatkan jumlah kali melewati pos pemeriksaan dan waktu yang dihabiskan di dalamnya oleh T. Untuk semua titik kontrol yang ditetapkan, timeManager mengumpulkan data dengan rilis laporan berikutnya saat aplikasi berakhir.



Di bawah ini adalah kode sumber C ++ yang mengimplementasikan alat bawaan untuk mengendalikan waktu eksekusi aplikasi.

//     #include <vector> #include <map> #include <algorithm> #include <stdio.h> #include <time.h> typedef unsigned long LONGEST_INT; // ,    //     //     //  ( )   // ()   //    #define PROFILER_ENABLED // CREATE_PROFILER  timeManager , //     // 'main()' #ifdef PROFILER_ENABLED #define CREATE_PROFILER timeManager tManager; #else #define CREATE_PROFILER //   CREATE_PROFILER. #endif //INITIALIZE_PROFILER    timeManager  //       //   'main()' #ifdef PROFILER_ENABLED #define INITIALIZE_PROFILER bool timeManager::object = false;\ std::vector<registerEntry> timeManager::entries; #else #define INITIALIZE_PROFILER //   INITIALIZE_PROFILER. #endif //DELAY(_SECONDS)   '_SECONDS' . //    ,  //     //  #ifdef PROFILER_ENABLED #define DELAY(_SECONDS) {clock_t clk_wait=clock()+((double)_ARG)*CLOCKS_PER_SEC;\ while(clock()<clk_wait) {}} #else #define DELAY(_SECONDS) //    DELAY. #endif //     , //     UNIX  WINDOWS //      #ifdef PROFILER_ENABLED #define MERGE2(x,y) x##y #define MERGE1(_X,_Y) MERGE2(_X,_Y) #if WIN32 #define UNIQUENAME prefix,postfix) MERGE1(prefix,postfix) #else #define UNIQUENAME(prefix,postfix) MERGE2(prefix,postfix) #endif #define GEN_SRC(_ARG1,_ARG2) static int UNIQUENAME(ind,_ARG2)=-1;\ if(UNIQUENAME(ind,_ARG2)<0)\ UNIQUENAME(ind,_ARG2)=timeManager::add_entry(_ARG1);\ timeManager::incr_counter(UNIQUENAME(ind,_ARG2));\ timerObject UNIQUENAME(tm,_ARG2)(UNIQUENAME(ind,_ARG2)); //PROFILE_ENTRY      #ifdef PROFILER_ENABLED #if WIN32 #define PROFILE_ENTRY(_TITLE) GEN_SRC(_TITLE,__COUNTER__) #else #define PROFILE_ENTRY(_TITLE) GEN_SRC(_TITLE,__LINE__) #endif #else #define PROFILE_ENTRY(_TITLE) //    PROFILE_ENTRY. #endif //        //    //    ,   timeManager struct registerEntry { //     (  ) std::string entry_name; //     //     LONGEST_INT covers_counter; //      //     (ticks) LONGEST_INT elapsed_time; // registerEntry(const char * title):entry_name(title), covers_counter(0), elapsed_time(0) {} }; //     class timerObject { //   ,     int index; //    clock_t start_time; public: //       timerObject(int ind):index(ind),start_time(clock()) {} //    β€œ ”  //       //   ~timerObject(void) { timeManager::incr_timer(index,(LONGEST_INT)(clock()-start_time)); } }; //     class timeManager { private: //     static std::vector<registerEntry> entries; // ,     //    static bool object; public: //     //  ,    //     static int add_entry(const char * title) { entries.push_back(registerEntry(title)); return (((int)entries.size())-1); } //       //      static void incr_counter(int profile_entry_id) { entries[profile_entry_id].covers_counter++; } //  'value'     //      static void incr_timer(int profile_entry_id, LONGEST_INT value) { entries[profile_entry_id].elapsed_time += val; } //       //   static void report(void); //  timeManager(void) { if(!object) object = true; else { printf("\n<<>>:    'timeManager' .\n"); throw; } } //        //   virtual ~timeManager(void) {report();} }; //      bool cmp_entries(registerEntry & first, registerEntry & second) { if(first.entry_name.compare(second.entry_name)>0) return false; return true; } //      //    void timeManager::report(void) { const std::string bar(72,'*'); //        const char * REPORT_FILE = "C:\\tmProfile.txt"; FILE * fp = fopen(REPORT_FILE,"w"); if(!fp) { printf("\n<<>>:       (%s)",REPORT_FILE); return; } fprintf(fp,"\n#%s",bar.c_str()); fprintf(fp,"\n#\n#      "); fprintf(fp,"\n#\n#%s",bar.c_str()); fprintf(fp,"\n#\n# %-35s %-15s %-20s", " ",""," ()"); fprintf(fp,"\n# %-35s %-15s %-20s", "------------------","-------------","---------------\n#"); //         std::sort(entries.begin(),entries.end(),cmp_entries); for(unsigned jj = 0; jj< entries.size(); jj++) { fprintf(fp,"\n# %-35s %-16d", entries[jj].entry_name.c_str(), entries[jj].covers_counter); if(entries[jj].covers_counter == 0) fprintf(fp,"%-20d",0); else fprintf(fp,"%-20.0f", static_cast<double>(entries[jj].elapsed_time)/ static_cast<double>(CLOCKS_PER_SEC)); } if(entries.size() == 0) fprintf(fp,"\n# No covered profile entries found\n"); fprintf(fp,"\n#\n#%s\n",bar.c_str()); fclose(fp); } 

Struktur aplikasi demo diilustrasikan di bawah ini, mengilustrasikan penggunaan alat kontrol runtime built-in sebagai contoh, serta tabel hasil yang diperoleh (untuk detail, lihat Lampiran 1. Kode sumber aplikasi demo ).





Bagian Adendum 2. Kode sumber alat kontrol bawaan untuk waktu eksekusi aplikasi C # menyajikan implementasi yang serupa dari kontrol bawaan di C #.

Penulis menggunakan pasangan TimeWatcher.StartWatch () dan TimeWatcher.StopWatch () ketika membuat profil waktu pelaksanaan metode dan prosedur yang melelahkan (dari sudut pandang komputasi) sebagai bagian dari produk perangkat lunak Delta Design yang dikembangkan oleh EREMEX - sistem desain berbantuan komputer untuk peralatan elektronik.

Di bawah ini adalah contoh laporan singkat tentang metrik waktu dari salah satu fungsi produk yang disebutkan.


Kesimpulan singkat


Alat yang dijelaskan dapat digunakan untuk mengumpulkan data tentang waktu eksekusi aplikasi di berbagai bagian kode programnya, khususnya, mereka memungkinkan:

  • mengumpulkan dan mengumpulkan data tentang metrik waktu utas eksekusi dalam aplikasi;
  • melakukan perkiraan waktu pelaksanaan kode program yang akurat untuk konstruksi bahasa dasar;
  • mengelola volume data yang diekstraksi dengan menghidupkan dan mematikan alat kontrol bawaan pada bagian yang sesuai dari aliran eksekusi aplikasi;
  • mengembangkan dan menerapkan tes regresi yang memantau stabilitas (dan mendeteksi degradasi) dari metrik waktu aplikasi.

Sebagai kesimpulan, harus dicatat bahwa di luar ruang lingkup publikasi ini ada pertanyaan tentang penggunaan alat kontrol bawaan yang dijelaskan dalam konteks aplikasi multithreading dan tidak ada analisis keakuratan data yang diperoleh dengan metrik waktu disajikan dalam bentuk apa pun. Yang terakhir ini disebabkan oleh kenyataan bahwa dalam praktiknya, ketika mengidentifikasi penyebab degradasi sementara suatu aplikasi, data pada distribusi relatif dari biaya waktu eksekusi antara komponen perangkat lunak dari aplikasi tersebut terutama relevan . Dalam hal ini, pertanyaan tentang keakuratan data yang diperoleh memudar ke latar belakang.

Lampiran 1. Kode sumber untuk aplikasi demo


 INITIALIZE_PROFILER int main(int argc, char * argv[]) { //create profile manager CREATE_PROFILER PROFILE_ENTRY("1 Main context") f11(); for(unsigned jj = 0;jj<4;jj++) f12(); f13 (); f14 (); f15 (); f16 (); f17(); return 0; } void f11(void)///////////////////////////////////////// { PROFILE_ENTRY ("2 f11()........................") for (unsigned jj = 0; jj<5; jj++) { PROFILE_ENTRY ("2 f11()::for(...){...} iterat-ing") DELAY(1) } //profile entry for repeating int nn(3); while(nn > 0) { PROFILE_ENTRY("2 f11()::while(...){...} iterat-ing") DELAY(1) nn--; } } void f12(void)///////////////////////////////////////// { PROFILE_ENTRY("3 f12()........................") goto ending; { PROFILE_ENTRY("3 f12()::ignored code part") DELAY(1) } ending: PROFILE_ENTRY("3 f12()::ending code part") DELAY(2) } void f13(void) ///////////////////////////////////////// { PROFILE_ENTRY("4 f13()........................") srand((unsigned) time(NULL)/2); for(unsigned jj = 0; jj < 200; jj++) { if(rand()%2 == 0) { PROFILE_ENTRY("4 f13()::even branch") DELAY(0.01) } else { PROFILE_ENTRY("4 f13()::od branch") DELAY(0.02) } } } void f14(void)///////////////////////////////////////// { static int depth = 10; { PROFILE_ENTRY("5 f14() recursion") depth--; DELAY(0.5) if(depth == 0) return; } f14(); } void f15(void)///////////////////////////////////////// { PROFILE_ENTRY("7 f15()........................") for(unsigned jj = 0; jj < 10; jj++) { demo_class obj; obj.method1(); obj.method2(); obj.method3(); } } void f16(void)///////////////////////////////////////// { PROFILE_ENTRY("8 f16()........................") try { for(int jj = 10; jj >= 0; jj--) { PROFILE_ENTRY("81 f16() try clause") DELAY(1) int rr = 200/jj; } } catch(...) { PROFILE_ENTRY("81 f16() catch clause") DELAY(2) return; } } void f17(void)///////////////////////////////////////// { PROFILE_ENTRY("9 f17()........................") f21(); f22(); f23(); f24(); f25(); } void f22(void)///////////////////////////////////////// { PROFILE_ENTRY("91 f22()........................") DELAY(1) f221(); f222(); f223(); } void f23(void) {PROFILE_ENTRY("91 f23()") DELAY(1) } void f24(void) {PROFILE_ENTRY("91 f24()") DELAY(1) } void f25(void) {PROFILE_ENTRY("91 f25()") DELAY(1) } void f221(void) {PROFILE_ENTRY("91 f221()") DELAY(3) } void f222(void) {PROFILE_ENTRY("91 f222()") DELAY(4) } void f223(void) {PROFILE_ENTRY("91 f223()") DELAY(5) } 

Tambahan 2. Kode sumber aplikasi kontrol C # runtime bawaan


 /// <summary> ///             /// </summary> public class TimeWatcher { /// <summary> ///            /// </summary> internal class TimeEntry { //     public Stopwatch timeWatch; //       public long elapsedTime; //  public TimeEntry() { timeWatch = new Stopwatch(); elapsedTime = 0; } } //       //    private static bool enableTimeWatcher = false; //            private static Dictionary<string, TimeEntry> entryDictionary = new Dictionary<string, TimeEntry>(); //         public static void StartWatch(string postfix = "") { if (!enableTimeWatcher) return; string entryName = GetCurrentMethod(); if (postfix != "") { entryName += postfix; } //    ,      //         if (!entryDictionary.ContainsKey(entryName)) { entryDictionary.Add(entryName, new TimeEntry()); entryDictionary[entryName].timeWatch.Start(); } else { if (entryDictionary[entryName].timeWatch.IsRunning) { throw new System.InvalidOperationException(":    '" + entryName + "'  ."); } else entryDictionary[entryName].timeWatch.Restart(); } } //        public static void StopWatch(string postfix = "") { if (!enableTimeWatcher) return; string entryName = GetCurrentMethod(); if (postfix != "") { entryName += postfix; } //    ,      if (!entryDictionary.ContainsKey(entryName)) { throw new System.InvalidOperationException(":     -    '" + entryName + "'."); } if (!entryDictionary[entryName].timeWatch.IsRunning) { throw new System.InvalidOperationException ":        '" + entryName + "'."); } entryDictionary[entryName].timeWatch.Stop(); entryDictionary[entryName].elapsedTime += entryDictionary[entryName].timeWatch.ElapsedMilliseconds; } //        //     public static void TimeWatchReport() { const string bar = "============================================="; if (!enableTimeWatcher) return; Console.WriteLine(""); Console.WriteLine(bar); Console.WriteLine("     (): "); Console.WriteLine(""); int maxLen = 0; foreach (var timeEntry in entryDictionary) { if(timeEntry.Key.Length > maxLen) maxLen = timeEntry.Key.Length; } maxLen++; string strFormat = "{0," + maxLen + "} ... {1,-10}"; foreach (var timeEntry in entryDictionary) { Console.WriteLine(strFormat, timeEntry.Key, timeEntry.Value.elapsedTime); } Console.WriteLine(bar); Console.WriteLine(""); entryDictionary.Clear(); enableTimeWatcher = false; } //          //      /tmw    //   public static void InitTimeWatch() { if (Environment.GetCommandLineArgs().Any(v => v == "/tmw")) { if (entryDictionary.Count > 0) { TimeWatchReport(); } entryDictionary.Clear(); enableTimeWatcher = true; } } //        private static string GetCurrentMethod() { StackTrace st = new StackTrace(); StackFrame sf = st.GetFrame(2); return sf.GetMethod().Name; } } 

Source: https://habr.com/ru/post/id468403/


All Articles