Alat praktis untuk mengukur kode C #


Dalam produk yang serba cepat, besar, dan kompleks, menemukan hambatan dalam hal kinerja bukan lagi tugas yang sepele. Tentu saja, ada alat untuk menyelesaikan masalah seperti itu, misalnya, BenchmarkDotNet . Tidak diragukan lagi, ini adalah alat yang berguna dan perlu, tetapi tidak selalu nyaman. Ini dapat dideteksi, misalnya, ketika perlu untuk tidak mengukur metode publik sepenuhnya dengan menetapkan atribut yang sesuai, tetapi beberapa potongan kode di dalam metode pribadi. Di sini, saya tidak akan mengkritik alat yang ditunjuk, tetapi hanya ingin menunjukkan perlunya cara yang sedikit berbeda dalam mengumpulkan metrik untuk fragmen kode. Ada metode yang sangat kompleks dan membingungkan, dengan percabangan, dengan loop, menangani situasi luar biasa, dan titik masalah mungkin ada di suatu tempat di dalam. Untuk menemukannya, Anda perlu banyak menutupi kode dengan meter, sementara ada keinginan yang jelas untuk tidak menyentuh atau mengerjakan ulang logika yang ada.

Anda dapat menggunakan kelas StopWatch untuk mengukur waktu eksekusi bagian kode.

Sesuatu seperti ini:

var sw = new Stopwatch(); sw.Start(); //   sw.Stop(); Console.WriteLine(sw.Elapsed); //   

Agar tidak menempatkan StopWatch menurut kode setiap kali, Anda dapat menyatukan prosedur pengukuran melalui fungsi tingkat tinggi, misalnya, seperti ini:

 static void LambdaMeter(string label, Action act) { var sw = new Stopwatch(); sw.Start(); act(); sw.Stop(); Console.WriteLine($"{label} : {sw.Elapsed}"); //   } 

Dan gunakan cara ini:

 LambdaMeter("foo", () => { // TODO something }); 

Anda dapat mengubah panggilan keluar melalui ekstensi:

 public static class TimeInspector { public static void Meter(this Action act, string label) { var sw = new Stopwatch(); sw.Start(); act(); sw.Stop(); Console.WriteLine($"{label} : {sw.Elapsed}"); //   } } 

Dan gunakan seperti ini:

 new Action(() => { // TODO something }).Meter("foo"); 

Sepertinya masalah tampaknya sudah diatasi, tetapi teknik ini masih sangat terbatas. Faktanya adalah bahwa akan ada output yang dikembalikan dalam kode yang diukur, sehingga Anda tidak dapat membungkusnya dalam Action. Bahkan menggunakan Func tidak akan menyelesaikan masalah. Di bawah ini saya ingin menggambarkan teknik yang saya gunakan lebih dari sekali, dengan perubahan terkecil dalam kode akan memecahkan masalah mengukur bagian kode sewenang-wenang. Kami akan membuat logger sederhana dan ringkas untuk mengukur kecepatan eksekusi, jika perlu, juga cukup sederhana untuk memodifikasinya untuk kebutuhan lain, misalnya, untuk mencari kebocoran memori. Kode di bawah ini tidak mengklaim sebagai lengkap dan fungsional, melainkan demonstrasi prinsip dengan dasar di mana Anda dapat membangun alat yang sangat berguna dan mudah. Namun, kode ini berfungsi dan dapat digunakan untuk tujuan mengumpulkan metrik sebagaimana adanya atau diadaptasi untuk tugas yang lebih spesifik. Saya juga ingin mencatat bahwa dalam kode di bawah ini informasi tambahan dikumpulkan, seperti ID proses atau nama host. Parameter ini berguna bagi saya dalam situasi tertentu dan hanya dibiarkan sebagai contoh. Siapa pun yang memutuskan untuk menyesuaikan kode dengan tugas-tugas mereka mungkin perlu mengumpulkan parameter yang sama sekali berbeda, sebagian karena alasan ini kode yang ditentukan tidak mempublikasikan di GitHub publik sebagai produk jadi.

C # memiliki pernyataan penggunaan yang terkenal dan bermanfaat, ini berlaku untuk jenis yang mendukung antarmuka IDisposable. Intinya adalah, ketika Anda keluar dari blok operator, panggil metode Buang, di mana, sebagai aturan, sumber daya dilepaskan. Namun, mekanisme ini dapat digunakan untuk berbagai keperluan. Dalam kasus kami, ini akan menjadi pengukuran waktu eksekusi blok, diikuti dengan menyimpan hasil untuk analisis. Idenya adalah bahwa instance kelas dibuat di blok using, pada saat pembuatan, berbagai jenis informasi berguna dikumpulkan dan StopWatch dimulai. Ketika Buang dijalankan ketika keluar dari blok, hasilnya tetap dan objek dikirim untuk menyimpan log.

Pernyataan penggunaan adalah gula sintaksis, ketika dikompilasi, itu berubah menjadi blok coba-akhirnya.

Misalnya, kode:

 using(var obj = new SomeDisposableClass()) { // TODO something } 

Dikonversi ke desain formulir:

 IDisposable obj = new SomeDisposableClass(); try { // TODO something } finally { if (obj != null) { obj.Dispose(); } } 

Dengan demikian, pada setiap keluar dari blok coba, metode Buang akan dijamin dan segera dipanggil, sementara logika kode yang digunakan tidak akan berubah dengan cara apa pun. Yang kami ubah hanyalah menambahkan Panggilan. Dipandu oleh ide ini, kami akan membuat pengukur runtime yang paling sederhana.

 public class Meter :IDisposable { private Stopwatch _sw; private string _label; public static Meter Job(string lable) { return new Meter(lable); } Meter(string label) { _label = label; _sw = Stopwatch.StartNew(); } public void Dispose() { _sw.Stop(); Console.WriteLine($"{_label} : {_sw.Elapsed}"); //   } } 

Jadi, apa artinya ini bagi kita?

 //       return static int Case0() { using (Meter.Job("case-0")) { return 1; } return 2; } //     yield return static IEnumerable<string> Case1() { yield return "one"; using (Meter.Job("case-1")) { yield return "two"; yield return "three"; } yield return "four"; } //     break static void Case2() { while (true) { using (Meter.Job("case-2")) { // todo something break; } } } //    switch    static int Case3(int @case) { using (Meter.Job("case-3")) { switch (@case) { case 1: using (Meter.Job($"case-3-{@case}")) { // todo something break; } case 2: using (Meter.Job($"case-3-{@case}")) { // todo something return @case; } } return @case; } } //       static void Case4() { try { using (Meter.Job($"case-4")) { // todo something throw new Exception(); } } catch (Exception e) { Console.WriteLine(e); } } 

Apa yang hilang dalam teknik yang dijelaskan? Topik tentang cara mengamankan hasil tidak sepenuhnya diungkapkan, jelas bahwa tidak ada banyak akal dalam merekam hasil di konsol. Dalam kebanyakan kasus, sangat mungkin untuk menggunakan alat populer NLog, Log4Net, Serilog dengan roti visualisasi dan analitik minimal. Dalam kasus yang sangat parah, analitik yang lebih canggih mungkin diperlukan untuk mencari tahu apa yang terjadi.

Anda dapat menyimpan hasilnya dalam file teks sederhana, tetapi untuk tugas ini saya pikir ini bukan solusi yang sangat nyaman, karena mungkin ada banyak potongan kode, dan mereka mungkin dapat disarangkan, di samping itu, beberapa utas paralel. Oleh karena itu, semata-mata untuk kenyamanan menavigasi log, kami menggunakan database SQLite.

Kami mulai dengan mendefinisikan kelas yang bertanggung jawab untuk mengukur waktu dan menangkap informasi tambahan yang berguna untuk analisis selanjutnya.

 public class LogItem : IDisposable { public string Id; // Log item unique id public string Label; // User text labl public string Lid; // Log Id public string Method; // Method name public int Tid; // Thread Id public DateTime CallTime; // Start time public TimeSpan Elapsed; // Metering public long ElapsedMs; // Metering miliseconds public long ElapsedTicks; // Metering ticks private Stopwatch _sw; private Logger _cl; public LogItem(Logger cl, string method, string label) { Id = Guid.NewGuid().ToString("N"); _cl = cl; Lid = _cl.LId; Label = label; Method = method; Tid = Thread.CurrentThread.ManagedThreadId; CallTime = DateTime.Now; _sw = new Stopwatch(); _sw.Start(); } public void Dispose() { _sw.Stop(); Elapsed = _sw.Elapsed; ElapsedMs = _sw.ElapsedMilliseconds; ElapsedTicks = _sw.ElapsedTicks; _cl.WriteLog(this); } } 

Tautan ke objek logger dan informasi tambahan diteruskan ke konstruktor kelas LogItem, nama lengkap metode di mana pengukuran dilakukan dan label dengan pesan sewenang-wenang. Untuk memahami log dari eksekusi kode asinkron, bidang Tid (Thread id) akan membantu, dimana Anda dapat mengelompokkan data dari satu utas. Juga, saat membuat objek LogItem, waktu mulai pengukuran sudah diperbaiki dan StopWatch dimulai. Dalam metode Buang, berhenti StopWatch dan panggil metode logger WriteLog, melewati objek penembak saat ini dan memperbaiki semua informasi LogItem yang diperlukan untuk itu.

Logger pada dasarnya memiliki dua tugas utama: untuk memulai objek LogItem dan mencatat hasilnya setelah pengukuran selesai. Karena saya masih perlu menangkap hasilnya, tidak hanya dalam SQLite, tetapi juga dalam file teks dari berbagai format atau database selain SQLite, logika interaksi ditempatkan di kelas yang terpisah. Di kelas Logger, hanya bagian umum yang tersisa.

 public class Logger { public string LId; // Log id public string Login; // User login public int Pid; // Process Id public string App; // Application name public string Host; // Host name public string Ip; // Ip address private ConcurrentQueue<dynamic> queue; // Queue log items to save public Dictionary<Type, Action<dynamic>> LogDispatcher; // Different log objects handlers dispatcher public bool IsEnabled = true; public Logger(Dictionary<Type, Action<dynamic>> logDispatcher) { queue = new ConcurrentQueue<dynamic>(); LogDispatcher = logDispatcher; LId = Guid.NewGuid().ToString("N"); Login = WindowsIdentity.GetCurrent().Name; Host = Dns.GetHostName(); Ip = Dns.GetHostEntry(Host).AddressList.FirstOrDefault(f => f.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)?.ToString(); var proc = Process.GetCurrentProcess(); Pid = proc.Id; App = proc.ProcessName; queue.Enqueue(this); // Save the starting of a new measurement new Thread(() => { // Dequeue and save loop dynamic logItem; while (true) { if (queue.TryDequeue(out logItem)) { var logT = logItem.GetType(); if (LogDispatcher.ContainsKey(logT)) { LogDispatcher[logT](logItem); } } else { Thread.Sleep(500); } }}) { IsBackground = true}.Start(); } public LogItem Watch(int level, string label = null) { return new LogItem(this, GetMethodFullName(level), label); } private static string GetMethodFullName(int level) { var t = new StackTrace().GetFrames()?[level].GetMethod(); if (t == null) return ""; var path = t.ReflectedType != null ? t.ReflectedType.FullName : "_"; return $"{path}.{t.Name}"; } public void WriteLog(LogItem li) { if (IsEnabled) { queue.Enqueue(li); } } } 

Jadi, kelas Logger adalah perantara, tugasnya adalah untuk mengumpulkan informasi mengenai lingkungan di mana penebangan dimulai. Dalam contoh di atas, ini adalah login pengguna, nama mesin, ID alamat jaringan dan nama proses. Semua informasi ini berguna ketika kita ingin mengukur waktu eksekusi bagian kode pada mesin pengguna, mis. Intinya untuk identifikasi dalam hal deteksi anomali. Saya pikir pembaca akan setuju bahwa penting bahwa logger selama pengukuran memiliki dampak minimal pada waktu eksekusi kode yang diukur. Itulah sebabnya menyimpan hasil dilakukan dengan menambahkannya ke antrian, yang proses latar belakangnya berputar, menyimpan hasilnya ke database, file atau mentransfernya melalui jaringan. Pendekatan ini memiliki kelemahan serius, jika ada terlalu banyak peristiwa logging, antrian akan mengisi lebih cepat daripada berputar hingga mengambil semua memori yang tersedia untuk aplikasi. Tetapi untuk memancing situasi ini, Anda masih perlu mencoba. Pengiriman prosedur penangan untuk menyimpan kelas yang berbeda dilakukan melalui kamus di mana kuncinya adalah tipe, nilainya adalah prosedur untuk menyimpan objek jenis ini.

Untuk kelas logging, yang akan diberikan di bawah ini, perlu untuk memenuhi ketergantungan dengan menginstal paket nuget System.Data> SQLite.

  public class SqliteLogger { private readonly HLTimeCall.Logger _iternalLogger; protected string _connectionString; static object dbLocker = new object(); public SqliteLogger() { InitDb(); var dispatcher = new Dictionary<Type, Action<dynamic>>(); dispatcher[typeof(HLTimeCall.Logger)] = o => LogMainRecord(o); dispatcher[typeof(HLTimeCall.LogItem)] = o => LogCall(o); _iternalLogger = new HLTimeCall.Logger(dispatcher); } private void InitDb() { const string databaseFile = "TimeCallLogs.db"; _connectionString = $"Data Source = {Path.GetFullPath(databaseFile)};"; var dbFileName = Path.GetFullPath(databaseFile); if (!File.Exists(dbFileName)) { SQLiteConnection.CreateFile(dbFileName); Bootstrap(); } } struct DbNames { public const string TMainRecord = "T_MAINRECORD"; public const string TCalllog = "T_CALLLOG"; public const string FLid = "LID"; public const string FLogin = "LOGIN"; public const string FPid = "PID"; public const string FApp = "APP"; public const string FHost = "HOST"; public const string FIp = "IP"; public const string FId = "ID"; public const string FLabel = "LABEL"; public const string FMethod = "METHOD"; public const string FTid = "TID"; public const string FCallTime = "CALL_TIME"; public const string FElapsed = "ELAPSED"; public const string FElapsedMs = "ELAPSED_MS"; public const string FElapsedTicks = "ELAPSED_TICKS"; } public void Bootstrap() { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.CommandText = $"CREATE TABLE {DbNames.TMainRecord} ({DbNames.FLid} VARCHAR(45) PRIMARY KEY UNIQUE NOT NULL, {DbNames.FLogin} VARCHAR(45), {DbNames.FPid} INTEGER, {DbNames.FApp} VARCHAR(45), {DbNames.FHost} VARCHAR(45), {DbNames.FIp} VARCHAR(45))"; cmd.ExecuteNonQuery(); cmd.CommandText = $"CREATE TABLE {DbNames.TCalllog} ({DbNames.FId} VARCHAR(45) PRIMARY KEY UNIQUE NOT NULL, {DbNames.FLabel} VARCHAR(45), {DbNames.FLid} VARCHAR(45) NOT NULL, {DbNames.FMethod} VARCHAR(150), {DbNames.FTid} VARCHAR(45), {DbNames.FCallTime} DATETIME, {DbNames.FElapsed} TIME, {DbNames.FElapsedMs} INTEGER, {DbNames.FElapsedTicks} INTEGER)"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } private DbConnection Connect() { var conn = new SQLiteConnection(_connectionString); conn.Open(); return conn; } public HLTimeCall.LogItem Watch(string label = null) { return _iternalLogger.Watch(3, label); } static object CreateParameter(string key, object value) { return new SQLiteParameter(key, value ?? DBNull.Value); } private void LogMainRecord(HLTimeCall.Logger logItem) { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.Parameters.Add(CreateParameter(DbNames.FLid, logItem.LId)); cmd.Parameters.Add(CreateParameter(DbNames.FLogin, logItem.Login)); cmd.Parameters.Add(CreateParameter(DbNames.FPid, logItem.Pid)); cmd.Parameters.Add(CreateParameter(DbNames.FApp, logItem.App)); cmd.Parameters.Add(CreateParameter(DbNames.FHost, logItem.Host)); cmd.Parameters.Add(CreateParameter(DbNames.FIp, logItem.Ip)); cmd.CommandText = $"INSERT INTO {DbNames.TMainRecord}({DbNames.FLid},{DbNames.FLogin},{DbNames.FPid},{DbNames.FApp},{DbNames.FHost},{DbNames.FIp})VALUES(:{DbNames.FLid},:{DbNames.FLogin},:{DbNames.FPid},:{DbNames.FApp},:{DbNames.FHost},:{DbNames.FIp})"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } private void LogCall(HLTimeCall.LogItem logItem) { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.Parameters.Add(CreateParameter(DbNames.FId, logItem.Id)); cmd.Parameters.Add(CreateParameter(DbNames.FLabel, logItem.Label)); cmd.Parameters.Add(CreateParameter(DbNames.FLid, logItem.Lid)); cmd.Parameters.Add(CreateParameter(DbNames.FMethod, logItem.Method)); cmd.Parameters.Add(CreateParameter(DbNames.FTid, logItem.Tid)); cmd.Parameters.Add(CreateParameter(DbNames.FCallTime, logItem.CallTime)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsed, logItem.Elapsed)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsedMs, logItem.ElapsedMs)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsedTicks, logItem.ElapsedTicks)); cmd.CommandText = $"INSERT INTO {DbNames.TCalllog}({DbNames.FId},{DbNames.FLabel},{DbNames.FLid},{DbNames.FMethod},{DbNames.FTid},{DbNames.FCallTime},{DbNames.FElapsed},{DbNames.FElapsedMs},{DbNames.FElapsedTicks})VALUES(:{DbNames.FId},:{DbNames.FLabel},:{DbNames.FLid},:{DbNames.FMethod},:{DbNames.FTid},:{DbNames.FCallTime},:{DbNames.FElapsed},:{DbNames.FElapsedMs},:{DbNames.FElapsedTicks})"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } } {DbNames.FId}, {DbNames.FLabel}, {DbNames.FLid}, {DbNames.FMethod}, {DbNames.FTid}, {DbNames.FCallTime}, {DbNames.FElapsed  public class SqliteLogger { private readonly HLTimeCall.Logger _iternalLogger; protected string _connectionString; static object dbLocker = new object(); public SqliteLogger() { InitDb(); var dispatcher = new Dictionary<Type, Action<dynamic>>(); dispatcher[typeof(HLTimeCall.Logger)] = o => LogMainRecord(o); dispatcher[typeof(HLTimeCall.LogItem)] = o => LogCall(o); _iternalLogger = new HLTimeCall.Logger(dispatcher); } private void InitDb() { const string databaseFile = "TimeCallLogs.db"; _connectionString = $"Data Source = {Path.GetFullPath(databaseFile)};"; var dbFileName = Path.GetFullPath(databaseFile); if (!File.Exists(dbFileName)) { SQLiteConnection.CreateFile(dbFileName); Bootstrap(); } } struct DbNames { public const string TMainRecord = "T_MAINRECORD"; public const string TCalllog = "T_CALLLOG"; public const string FLid = "LID"; public const string FLogin = "LOGIN"; public const string FPid = "PID"; public const string FApp = "APP"; public const string FHost = "HOST"; public const string FIp = "IP"; public const string FId = "ID"; public const string FLabel = "LABEL"; public const string FMethod = "METHOD"; public const string FTid = "TID"; public const string FCallTime = "CALL_TIME"; public const string FElapsed = "ELAPSED"; public const string FElapsedMs = "ELAPSED_MS"; public const string FElapsedTicks = "ELAPSED_TICKS"; } public void Bootstrap() { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.CommandText = $"CREATE TABLE {DbNames.TMainRecord} ({DbNames.FLid} VARCHAR(45) PRIMARY KEY UNIQUE NOT NULL, {DbNames.FLogin} VARCHAR(45), {DbNames.FPid} INTEGER, {DbNames.FApp} VARCHAR(45), {DbNames.FHost} VARCHAR(45), {DbNames.FIp} VARCHAR(45))"; cmd.ExecuteNonQuery(); cmd.CommandText = $"CREATE TABLE {DbNames.TCalllog} ({DbNames.FId} VARCHAR(45) PRIMARY KEY UNIQUE NOT NULL, {DbNames.FLabel} VARCHAR(45), {DbNames.FLid} VARCHAR(45) NOT NULL, {DbNames.FMethod} VARCHAR(150), {DbNames.FTid} VARCHAR(45), {DbNames.FCallTime} DATETIME, {DbNames.FElapsed} TIME, {DbNames.FElapsedMs} INTEGER, {DbNames.FElapsedTicks} INTEGER)"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } private DbConnection Connect() { var conn = new SQLiteConnection(_connectionString); conn.Open(); return conn; } public HLTimeCall.LogItem Watch(string label = null) { return _iternalLogger.Watch(3, label); } static object CreateParameter(string key, object value) { return new SQLiteParameter(key, value ?? DBNull.Value); } private void LogMainRecord(HLTimeCall.Logger logItem) { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.Parameters.Add(CreateParameter(DbNames.FLid, logItem.LId)); cmd.Parameters.Add(CreateParameter(DbNames.FLogin, logItem.Login)); cmd.Parameters.Add(CreateParameter(DbNames.FPid, logItem.Pid)); cmd.Parameters.Add(CreateParameter(DbNames.FApp, logItem.App)); cmd.Parameters.Add(CreateParameter(DbNames.FHost, logItem.Host)); cmd.Parameters.Add(CreateParameter(DbNames.FIp, logItem.Ip)); cmd.CommandText = $"INSERT INTO {DbNames.TMainRecord}({DbNames.FLid},{DbNames.FLogin},{DbNames.FPid},{DbNames.FApp},{DbNames.FHost},{DbNames.FIp})VALUES(:{DbNames.FLid},:{DbNames.FLogin},:{DbNames.FPid},:{DbNames.FApp},:{DbNames.FHost},:{DbNames.FIp})"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } private void LogCall(HLTimeCall.LogItem logItem) { lock (dbLocker) { using (var conn = Connect()) { using (var cmd = conn.CreateCommand()) { cmd.Transaction = conn.BeginTransaction(); cmd.CommandType = System.Data.CommandType.Text; cmd.Parameters.Add(CreateParameter(DbNames.FId, logItem.Id)); cmd.Parameters.Add(CreateParameter(DbNames.FLabel, logItem.Label)); cmd.Parameters.Add(CreateParameter(DbNames.FLid, logItem.Lid)); cmd.Parameters.Add(CreateParameter(DbNames.FMethod, logItem.Method)); cmd.Parameters.Add(CreateParameter(DbNames.FTid, logItem.Tid)); cmd.Parameters.Add(CreateParameter(DbNames.FCallTime, logItem.CallTime)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsed, logItem.Elapsed)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsedMs, logItem.ElapsedMs)); cmd.Parameters.Add(CreateParameter(DbNames.FElapsedTicks, logItem.ElapsedTicks)); cmd.CommandText = $"INSERT INTO {DbNames.TCalllog}({DbNames.FId},{DbNames.FLabel},{DbNames.FLid},{DbNames.FMethod},{DbNames.FTid},{DbNames.FCallTime},{DbNames.FElapsed},{DbNames.FElapsedMs},{DbNames.FElapsedTicks})VALUES(:{DbNames.FId},:{DbNames.FLabel},:{DbNames.FLid},:{DbNames.FMethod},:{DbNames.FTid},:{DbNames.FCallTime},:{DbNames.FElapsed},:{DbNames.FElapsedMs},:{DbNames.FElapsedTicks})"; cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); } } } } } 

Seperti yang telah disebutkan, kelas SqliteLogger bertanggung jawab untuk menyimpan hasil pengukuran dalam database. Sebelum Anda mulai menggunakan basis data, Anda perlu membuatnya, lebih banyak baris kode dikhususkan untuk ini. Di sini kelas akan mencatat fakta pembuatan logger (dipahami bahwa ini terjadi secara bersamaan dengan peluncuran aplikasi), hasilnya akan disimpan dalam tabel T_MAINRECORD. Kami juga akan menyimpan hasil pengukuran di tabel T_CALLLOG. Metode LogMainRecord dan LogCall bertanggung jawab untuk menyimpan dua peristiwa ini, referensi untuk mereka disimpan dalam kamus operator. Untuk membuat objek LogItem dengan benar, Anda perlu menggunakan fungsi Watch di blok inisialisasi pernyataan using.

Misalnya, seperti ini:

 var cMeter = new SqliteLogger(); using (cMeter.Watch("   ")) { // TODO } 

Sekarang, untuk menggunakan alat ini, Anda perlu mengatur penggunaan balok di tempat yang dipertanyakan dan membiarkan aplikasi berfungsi. Setelah itu, tetap menganalisis informasi yang dikumpulkan, yang saya harap akan cukup untuk mengidentifikasi tempat masalah. Saya mengusulkan untuk memikirkan tentang metrik apa yang dapat dikumpulkan dengan menggunakan teknik ini?

Terima kasih atas perhatian, semoga sukses dan kode cepat :-)

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


All Articles