diff --git a/IOTracesCORE/Program.cs b/IOTracesCORE/Program.cs index 2dfd5a2..161f0d3 100644 --- a/IOTracesCORE/Program.cs +++ b/IOTracesCORE/Program.cs @@ -1,4 +1,5 @@ using IOTracesCORE.cloudstorage; +using IOTracesCORE.utils; using Microsoft.Win32; using System; using System.Diagnostics; @@ -44,6 +45,8 @@ static void Main(string[] args) var iconStream = assembly.GetManifestResourceStream("IOTracesCORE.Opera_Glasses_icon-icons.com_54155.ico"); var icon = iconStream != null ? new Icon(iconStream) : SystemIcons.Application; + ConfigClasses.LoadTracemetaConfiguration(); + string currentVersion = VersionManager.Instance.GetCurrentVersion(); trayIcon = new NotifyIcon { @@ -57,10 +60,14 @@ static void Main(string[] args) contextMenu.Items.Add("-"); contextMenu.Items.Add("Show Status", null, (s, e) => { + TimeSpan Total_current_session = WriterManager.active_session; + TimeSpan Total_trace_duration = WriterManager.trace_duration; MessageBox.Show( - $"Version: {currentVersion}\n" + - $"Logs Created: {WriterManager.amount_compressed_file}\n" + - $"Logs Uploaded: {ObjectStorageHandler.UploadedFiles}", + $"Computer ID: {PathHasher.deviceId}\n" + + $"Logs Created / Uploaded: {WriterManager.amount_compressed_file} / {ObjectStorageHandler.UploadedFiles}\n" + + $"File events collected: {DisplayHelper.ToPowerOfTen(WriterManager.file_event_counter)}\n\n" + + $"Active session elapsed (HH:MM:SS): {Total_current_session.TotalHours:00}:{Total_current_session.Minutes:00}:{Total_current_session.Seconds:00}\n" + + $"Trace Duration: {Total_trace_duration.TotalDays:00} Days {Total_trace_duration.Hours:00} Hours", "Status", MessageBoxButtons.OK, MessageBoxIcon.Information); diff --git a/IOTracesCORE/TracerConfigForm.cs b/IOTracesCORE/TracerConfigForm.cs index cdd1ca8..59c49f3 100644 --- a/IOTracesCORE/TracerConfigForm.cs +++ b/IOTracesCORE/TracerConfigForm.cs @@ -5,6 +5,7 @@ using System.Security.Principal; using System.Text; using System.Text.Json; +using static IOTracesCORE.utils.ConfigClasses; namespace IOTracesCORE { @@ -33,7 +34,7 @@ public TracerConfigForm(CancellationToken token) LoadSavedConfiguration(); - if (!File.Exists(ConfigPath)) + if (!File.Exists(AppConfigPath)) { chkEnableUpload.Checked = true; ChkEnableUpload_CheckedChanged(this, EventArgs.Empty); @@ -316,17 +317,6 @@ private void RunTracer(string outputPath, bool anonymous, bool upload, ObjectSto }); } - private class PersistedConfig - { - public string OutputPath { get; set; } = ""; - public bool Anonymous { get; set; } - public bool UploadEnabled { get; set; } - } - - private string ConfigPath => - Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "IOTracer", "config.json.enc"); - private void SaveConfiguration() { try @@ -335,14 +325,14 @@ private void SaveConfiguration() { OutputPath = txtOutputPath.Text, Anonymous = chkAnonymous.Checked, - UploadEnabled = chkEnableUpload.Checked + UploadEnabled = chkEnableUpload.Checked, }; string json = JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true }); byte[] encrypted = ProtectedData.Protect( Encoding.UTF8.GetBytes(json), null, DataProtectionScope.CurrentUser); - Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath)!); - File.WriteAllBytes(ConfigPath, encrypted); + Directory.CreateDirectory(Path.GetDirectoryName(AppConfigPath)!); + File.WriteAllBytes(AppConfigPath, encrypted); } catch (Exception ex) { Debug.WriteLine("Save config failed: " + ex.Message); } } @@ -351,9 +341,10 @@ private void LoadSavedConfiguration() { try { - if (!File.Exists(ConfigPath)) return; + Debug.Write($"Receiving config from: {AppConfigPath}"); + if (!File.Exists(AppConfigPath)) return; - byte[] encrypted = File.ReadAllBytes(ConfigPath); + byte[] encrypted = File.ReadAllBytes(AppConfigPath); byte[] decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser); string json = Encoding.UTF8.GetString(decrypted); var cfg = JsonSerializer.Deserialize(json); diff --git a/IOTracesCORE/WriterManager.cs b/IOTracesCORE/WriterManager.cs index 236a063..0c92cc6 100644 --- a/IOTracesCORE/WriterManager.cs +++ b/IOTracesCORE/WriterManager.cs @@ -34,6 +34,10 @@ class WriterManager private bool is_anonymous; private bool is_upload_automatically; public static int amount_compressed_file = 0; + public static int disk_event_counter = 0; + public static int file_event_counter = 0; + public static TimeSpan active_session = TimeSpan.FromSeconds(0); + public static TimeSpan trace_duration = TimeSpan.FromSeconds(0); public WriterManager(string dirpath, bool is_anonymous, bool upload,ObjectStorageHandler obj) { @@ -58,6 +62,8 @@ public WriterManager(string dirpath, bool is_anonymous, bool upload,ObjectStorag process_snap_filepath = GenerateFilePath("process"); fs_snap_filepath = GenerateFilePath("filesystem_snapshot"); this.is_anonymous = is_anonymous; + + StartEventRateDetector(); } public void InitiateDirectory() @@ -95,6 +101,32 @@ public void InitiateDirectory() Console.WriteLine("File output: {0}", this.dir_path); } + private void StartEventRateDetector() + { + Debug.WriteLine("Starting event rate detector thread..."); + Thread eventRateThread = new(EventRateDetector) + { + IsBackground = true + }; + eventRateThread.Start(); + } + + private void EventRateDetector() + { + while (true) + { + int initial_count = disk_event_counter; + Thread.Sleep(1000); + int final_count = disk_event_counter; + int events_in_interval = final_count - initial_count; + disk_event_counter = 0; + //Debug.WriteLine($"Rate: {events_in_interval}"); + if (events_in_interval > 100) { + active_session += TimeSpan.FromSeconds(1); + } + } + } + private static string EscapeCsvField(string field) { if (string.IsNullOrEmpty(field)) @@ -145,6 +177,8 @@ public void Write(FilesystemTrace data) { return; } + file_event_counter += 1; + //event_counter += 1; fs_sb.Append(data.FormatAsCsv(is_anonymous)); if (IsTimeToFlush(fs_sb)) { @@ -165,7 +199,7 @@ public void Write(DiskTrace data) { return; } - + disk_event_counter += 1; ds_sb.Append(data.FormatAsCsv()); if (IsTimeToFlush(ds_sb)) @@ -187,7 +221,7 @@ public void Write(NetworkTrace data) { return; } - + //event_counter += 1; nw_sb.Append(data.FormatAsCsv()); if (IsTimeToFlush(nw_sb)) @@ -279,6 +313,8 @@ public void DirectWrite(string file_out_path ,string input) writer.Write(input); } + amount_compressed_file++; + if (is_upload_automatically) { obj_storage.QueueFile(out_path); @@ -361,10 +397,6 @@ public void CompressRun() Directory.Delete(dir_path, true); } - public void FlushSnapper() - { - FlushWrite(fs_snap_sb, fs_snap_filepath, "filesystem_snapshot"); - } public async Task CompressAllAsync() { @@ -380,6 +412,7 @@ public async Task CompressAllAsync() WriteStatus(); await obj_storage.ClearQueue(); + ConfigClasses.SaveTracemetaConfiguration(active_session + trace_duration, file_event_counter); CompressRun(); } diff --git a/IOTracesCORE/cloudstorage/ObjectStorageHandler.cs b/IOTracesCORE/cloudstorage/ObjectStorageHandler.cs index b95affc..61076db 100644 --- a/IOTracesCORE/cloudstorage/ObjectStorageHandler.cs +++ b/IOTracesCORE/cloudstorage/ObjectStorageHandler.cs @@ -17,6 +17,8 @@ internal class ObjectStorageHandler private R2Client r2Client; private ConcurrentQueue uploadQueue = new(); public static int UploadedFiles = 0; + private TimeSpan LastActiveHours = TimeSpan.FromSeconds(0); + public static int LastFileEvent; public ObjectStorageHandler() @@ -26,9 +28,16 @@ public ObjectStorageHandler() public async Task UploadFile(string filepath) { + //double deltaSeconds = WriterManager.active_session.TotalSeconds - LastActiveHours.TotalSeconds; + //int deltaFileEvent = WriterManager.file_event_counter - LastFileEvent; + //Debug.WriteLine($"Uploading file with delta seconds: {deltaSeconds}, delta file events: {deltaFileEvent}"); + FileInfo fi = new FileInfo(filepath); await r2Client.PutObject(fi); File.Delete(filepath); + + LastFileEvent = WriterManager.file_event_counter; + LastActiveHours = WriterManager.active_session; } public void QueueFile(string filepath) @@ -54,7 +63,7 @@ public async Task ClearQueue() catch (Exception ex) { Debug.WriteLine($"Error uploading {filepath}: {ex}"); - QueueFile(filepath); + //QueueFile(filepath); } } } diff --git a/IOTracesCORE/cloudstorage/R2Client.cs b/IOTracesCORE/cloudstorage/R2Client.cs index a4effbc..b7f8ee4 100644 --- a/IOTracesCORE/cloudstorage/R2Client.cs +++ b/IOTracesCORE/cloudstorage/R2Client.cs @@ -25,6 +25,8 @@ public async Task PutObject(FileInfo file) { try { + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + var currentVersion = VersionManager.Instance.GetCurrentVersion(); if ( string.IsNullOrWhiteSpace(currentVersion) || @@ -39,7 +41,21 @@ public async Task PutObject(FileInfo file) string dir_name = file.DirectoryName ?? "unknown_dir"; string trace_type = Path.GetFileName(file.DirectoryName) ?? "unknown_type"; - var response = await http.GetAsync($"{EndpointUrl}/windows_trace/{PathHasher.deviceId}/{CurrentDate}/{trace_type}/{file.Name}"); + + var endpoint = $"{EndpointUrl}/windows_trace/{PathHasher.deviceId}/{CurrentDate}/{trace_type}/{file.Name}"; + + var request = new HttpRequestMessage( + HttpMethod.Get, + endpoint + ); + + Debug.WriteLine(endpoint); + + //request.Headers.Add("X-Active-Delta-Seconds", deltaSeconds.ToString()); + //request.Headers.Add("X-File-Events-Delta-Collected", deltaFileEvent.ToString()); + //request.Headers.Add("X-Computer-Id", PathHasher.deviceId); + + var response = await http.SendAsync(request); response.EnsureSuccessStatusCode(); var presignedUrl = await response.Content.ReadAsStringAsync(); @@ -53,11 +69,10 @@ public async Task PutObject(FileInfo file) }; - var uploadResponse = await http.SendAsync(uploadRequest); + var uploadResponse = await http.SendAsync(uploadRequest, cts.Token); uploadResponse.EnsureSuccessStatusCode(); Debug.WriteLine($"{file.FullName} successfully uploaded"); - } catch (Exception) { diff --git a/IOTracesCORE/utils/ConfigClasses.cs b/IOTracesCORE/utils/ConfigClasses.cs new file mode 100644 index 0000000..4265666 --- /dev/null +++ b/IOTracesCORE/utils/ConfigClasses.cs @@ -0,0 +1,83 @@ +using IOTracesCORE.cloudstorage; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace IOTracesCORE.utils +{ + internal class ConfigClasses + { + public class PersistedConfig + { + public string OutputPath { get; set; } = ""; + public bool Anonymous { get; set; } + public bool UploadEnabled { get; set; } + } + + public class TraceMetadata + { + public long TraceDuration { get; set; } + public long FileEventsCount { get; set; } + } + + public static string AppConfigPath => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "IOTracer", "config.json.enc"); + + public static string ConfigTraceMetadataPath => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "IOTracer", "trace_metadata.json.enc"); + + public static void SaveTracemetaConfiguration(TimeSpan trace_active, long file_events) + { + try + { + Debug.WriteLine($"Saving a session of {trace_active.TotalSeconds} seconds"); + var cfg = new TraceMetadata + { + TraceDuration = (long)trace_active.TotalSeconds, + FileEventsCount = file_events + }; + + string json = JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true }); + byte[] encrypted = ProtectedData.Protect( + Encoding.UTF8.GetBytes(json), null, DataProtectionScope.CurrentUser); + Directory.CreateDirectory(Path.GetDirectoryName(ConfigTraceMetadataPath)!); + File.WriteAllBytes(ConfigTraceMetadataPath, encrypted); + } + catch (Exception ex) { Debug.WriteLine("Save config failed: " + ex.Message); } + } + + public static void LoadTracemetaConfiguration() + { + try + { + Debug.Write($"Receiving config from: {ConfigTraceMetadataPath}"); + if (!File.Exists(ConfigTraceMetadataPath)) return; + + byte[] encrypted = File.ReadAllBytes(ConfigTraceMetadataPath); + byte[] decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser); + string json = Encoding.UTF8.GetString(decrypted); + var cfg = JsonSerializer.Deserialize(json); + + if (cfg == null) return; + + WriterManager.trace_duration = TimeSpan.FromSeconds(cfg.TraceDuration); + WriterManager.file_event_counter += (int)cfg.FileEventsCount; + ObjectStorageHandler.LastFileEvent = (int)cfg.FileEventsCount; + Debug.WriteLine($"Loaded a session of {cfg.TraceDuration} seconds"); + Debug.WriteLine($"Loaded {cfg.FileEventsCount} file events"); + } + catch (Exception ex) + { + Debug.WriteLine("Load config failed: " + ex.Message); + + } + } + } +} diff --git a/IOTracesCORE/utils/DisplayHelper.cs b/IOTracesCORE/utils/DisplayHelper.cs new file mode 100644 index 0000000..4ba9a40 --- /dev/null +++ b/IOTracesCORE/utils/DisplayHelper.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IOTracesCORE.utils +{ + internal class DisplayHelper + { + public static string ToPowerOfTen(long count) + { + if (count < 1_000_000) + return count.ToString("N0"); + + int exponent = (int)Math.Floor(Math.Log10(count)); + double mantissa = count / Math.Pow(10, exponent); + + return $"{mantissa:F2} × 10{ToSuperscript(exponent)}"; + } + + public static string ToSuperscript(int number) + { + string str = number.ToString(); + string result = ""; + foreach (char c in str) + { + result += c switch + { + '0' => '⁰', + '1' => '¹', + '2' => '²', + '3' => '³', + '4' => '⁴', + '5' => '⁵', + '6' => '⁶', + '7' => '⁷', + '8' => '⁸', + '9' => '⁹', + '-' => '⁻', + _ => c + }; + } + return result; + } + } +}