From 4a036d07e9399cc74a1a85a945a66c8eeda4ce8a Mon Sep 17 00:00:00 2001 From: Didi Hoffmann Date: Mon, 25 Sep 2023 17:34:17 +0200 Subject: [PATCH] First round of refactoring of v0.2 --- README.md | 2 +- .../UserInterfaceState.xcuserstate | Bin 39652 -> 37535 bytes app/hog/hog/DetailView.swift | 161 +++++++++++------- berlin.green-coding.hog.plist | 2 +- install.sh | 3 +- migrations/20230909161250_first_db.py | 2 +- power_logger.py | 51 +++--- settings.ini | 2 +- 8 files changed, 126 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index c784ed4..5f744db 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ For security reasons, files in /Library/LaunchDaemons/ should have their permiss and should not be writable by others. ```bash -sed -i.bak "s|PATH_PLASE_CHANGE|$(pwd)|g" berlin.green-coding.hog.plist +sed -i.bak "s|PATH_PLEASE_CHANGE|$(pwd)|g" berlin.green-coding.hog.plist sudo cp berlin.green-coding.hog.plist /Library/LaunchDaemons/ sudo chown root:wheel /Library/LaunchDaemons/berlin.green-coding.hog.plist diff --git a/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/UserInterfaceState.xcuserstate b/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/UserInterfaceState.xcuserstate index 68be72339c002e8f67a50d498a88ae7ad3595122..2c9ebbca6824dc59b524fb4482b484effb9ba95f 100644 GIT binary patch delta 18313 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 diff --git a/app/hog/hog/DetailView.swift b/app/hog/hog/DetailView.swift index c46f11a..3d90753 100644 --- a/app/hog/hog/DetailView.swift +++ b/app/hog/hog/DetailView.swift @@ -58,14 +58,14 @@ private func isScriptRunningUsingPGrep(scriptName: String) -> Bool { private func isScriptRunningUsingDBCheck() -> Bool { var db: OpaquePointer? var running = false - + if sqlite3_open(db_path, &db) != SQLITE_OK { print("error opening database") return false } var queryStatement: OpaquePointer? - + let queryString = "SELECT COUNT (*) FROM power_measurements WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - 60000);" if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { while sqlite3_step(queryStatement) == SQLITE_ROW { @@ -81,7 +81,7 @@ private func isScriptRunningUsingDBCheck() -> Bool { sqlite3_finalize(queryStatement) sqlite3_close(db) - + return running } @@ -92,12 +92,12 @@ public func getNameByAppName(appName: String) -> String { return app.localizedName ?? "No Data" } } - + let components = appName.split(separator: ".") if let lastComponent = components.last { return String(lastComponent) } - + return "No Data" } @@ -114,14 +114,14 @@ public func getIconByAppName(appName: String) -> NSImage? { func getMachineId() -> String{ var db: OpaquePointer? var machineId = "" - + if sqlite3_open(db_path, &db) != SQLITE_OK { // Open database print("error opening database") return "" } var queryStatement: OpaquePointer? - let queryString = "SELECT machine_id FROM settings LIMIT 1" + let queryString = "SELECT machine_uuid FROM settings LIMIT 1" if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { while sqlite3_step(queryStatement) == SQLITE_ROW { @@ -136,7 +136,7 @@ func getMachineId() -> String{ sqlite3_finalize(queryStatement) sqlite3_close(db) - + return machineId } @@ -148,7 +148,7 @@ func checkDB() -> Bool { class SettingsManager: ObservableObject { var lookBackTime:Int = 0 - @Published var machine_id: String = "Loading ..." + @Published var machine_uuid: String = "Loading ..." @Published var powermetrics: Int = 0 @Published var api_url: String = "Loading ..." @Published var web_url: String = "Loading ..." @@ -177,10 +177,10 @@ class SettingsManager: ObservableObject { return } - let lastMeasurementQuery = "SELECT machine_id, powermetrics, api_url, web_url, upload_data FROM settings ORDER BY time DESC LIMIT 1;" + let lastMeasurementQuery = "SELECT machine_uuid, powermetrics, api_url, web_url, upload_data FROM settings ORDER BY time DESC LIMIT 1;" var queryStatement: OpaquePointer? - var new_machine_id = "Loading ..." + var new_machine_uuid = "Loading ..." var new_powermetrics: Int = 0 var new_api_url = "Loading ..." var new_web_url = "Loading ..." @@ -188,7 +188,7 @@ class SettingsManager: ObservableObject { if sqlite3_prepare_v2(db, lastMeasurementQuery, -1, &queryStatement, nil) == SQLITE_OK { if sqlite3_step(queryStatement) == SQLITE_ROW { - new_machine_id = String(cString: sqlite3_column_text(queryStatement, 0)) + new_machine_uuid = String(cString: sqlite3_column_text(queryStatement, 0)) new_powermetrics = Int(sqlite3_column_int(queryStatement, 1)) new_api_url = String(cString: sqlite3_column_text(queryStatement, 2)) new_web_url = String(cString: sqlite3_column_text(queryStatement, 3)) @@ -196,7 +196,7 @@ class SettingsManager: ObservableObject { } sqlite3_finalize(queryStatement) } - + let uploadCountQuery = "SELECT COUNT(*) FROM measurements WHERE uploaded = 0;" var new_upload_backlog: Int = 0 @@ -208,11 +208,11 @@ class SettingsManager: ObservableObject { } else { print("SELECT statement could not be prepared") } - + sqlite3_close(db) - + DispatchQueue.main.async { - self.machine_id = new_machine_id + self.machine_uuid = new_machine_uuid self.powermetrics = new_powermetrics self.api_url = new_api_url self.web_url = new_web_url @@ -233,7 +233,7 @@ class ValueManager: ObservableObject { @Published var topApp: String = "Loading..." @Published var isLoading: Bool = true - + enum ValueType { case float case string @@ -252,12 +252,12 @@ class ValueManager: ObservableObject { func loadDataFrom() { var db: OpaquePointer? - + if sqlite3_open(db_path, &db) != SQLITE_OK { // Open database print("error opening database") return } - + var newEnergy: CGFloat = 0 var energyQuery:String if self.lookBackTime == 0 { @@ -291,7 +291,7 @@ class ValueManager: ObservableObject { LIMIT 1; -- to get only the top name """ } - + if let result: String = queryDatabase(db: db, query:topQuery, type: .string) { newTopApp = String(result) } else { @@ -299,7 +299,7 @@ class ValueManager: ObservableObject { } let newScriptRunning = isScriptRunning(scriptName: "power_logger.py") - + DispatchQueue.main.async { self.energy = newEnergy self.providerRunning = newScriptRunning @@ -314,7 +314,7 @@ class ValueManager: ObservableObject { private func queryDatabase(db: OpaquePointer?, query: String, type: ValueType) -> T? { var queryStatement: OpaquePointer? - + if sqlite3_prepare_v2(db, query, -1, &queryStatement, nil) == SQLITE_OK { if sqlite3_step(queryStatement) == SQLITE_ROW { switch type { @@ -357,11 +357,11 @@ class TopProcessData: Identifiable, ObservableObject, RandomAccessCollection { var endIndex: Index { lines.endIndex } @Published var isLoading: Bool = true - + subscript(position: Index) -> Element { lines[position] } - + func sort(using sortOrder: [KeyPathComparator]) { // Implement sorting logic lines.sort { a, b in @@ -378,7 +378,7 @@ class TopProcessData: Identifiable, ObservableObject, RandomAccessCollection { return false } } - + public func refreshData(lookBackTime: Int = 0) -> Void{ self.isLoading = true self.lookBackTime = lookBackTime @@ -389,16 +389,16 @@ class TopProcessData: Identifiable, ObservableObject, RandomAccessCollection { private func loadDataFrom() { - + var db: OpaquePointer? - + if sqlite3_open(db_path, &db) != SQLITE_OK { print("error opening database") return } var queryStatement: OpaquePointer? - + let queryString: String if self.lookBackTime == 0 { queryString = """ @@ -428,7 +428,7 @@ class TopProcessData: Identifiable, ObservableObject, RandomAccessCollection { } let energy_impact = sqlite3_column_double(queryStatement, 1) let cputime_per = sqlite3_column_int(queryStatement, 2) - + newLines.append(TopProcess(name: name, energy_impact: energy_impact, cputime_per: cputime_per)) } DispatchQueue.main.async { @@ -470,11 +470,11 @@ class ChartData: ObservableObject, RandomAccessCollection { var endIndex: Index { points.endIndex } @Published var isLoading: Bool = false - + subscript(position: Index) -> Element { points[position] } - + public func refreshData(lookBackTime: Int = 0) -> Void{ self.isLoading = true self.lookBackTime = lookBackTime @@ -488,16 +488,16 @@ class ChartData: ObservableObject, RandomAccessCollection { } private func loadDataFrom() { - + var db: OpaquePointer? // SQLite database object - + if sqlite3_open(db_path, &db) != SQLITE_OK { // Open database print("error opening database") return } var queryStatement: OpaquePointer? - + let queryString: String if self.lookBackTime == 0 { queryString = "SELECT * FROM power_measurements;" @@ -515,7 +515,7 @@ class ChartData: ObservableObject, RandomAccessCollection { let time = Date(timeIntervalSince1970: id / 1000.0) let dataPoint = DataPoint(id: id, combined_energy: combined_energy, cpu_energy: cpu_energy, gpu_energy: gpu_energy, ane_energy: ane_energy, time: time) - + newPoints.append(dataPoint) } DispatchQueue.main.async { @@ -531,7 +531,7 @@ class ChartData: ObservableObject, RandomAccessCollection { struct PointsGraph: View { @ObservedObject var chartData: ChartData - + init(chartData: ChartData) { self.chartData = chartData } @@ -569,7 +569,7 @@ struct TopProcessTable: View { KeyPathComparator(\TopProcess.cputime_per, order: .forward), ] @Environment(\.colorScheme) var colorScheme - + var tableColour: Color { return colorScheme == .dark ? Color.white : Color.primary } @@ -594,9 +594,9 @@ struct TopProcessTable: View { .resizable() .frame(width: 15, height: 15) .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) - + }.width(20) - + TableColumn("Name", value: \TopProcess.name) TableColumn("Energy Impact", value: \TopProcess.energy_impact){ line in Text(String(format: "%.0f", line.energy_impact)) @@ -622,9 +622,23 @@ struct TopProcessTable: View { } } +struct TextInputView: View { + @Binding var text: String + @Binding var isPresented: Bool + + var body: some View { + VStack() { + TextField("Enter text here", text: $text) + Button("Done") { + isPresented = false + } + } + .padding() + } +} struct DataView: View { - + @State var chartData = ChartData() @State var lineData = TopProcessData() @ObservedObject var valueManager = ValueManager() @@ -635,24 +649,27 @@ struct DataView: View { var lookBackTime: Int - + @State private var text: String = "" + @State private var isTextInputViewPresented: Bool = false + + init(lookBackTime: Int = 0) { self.lookBackTime = lookBackTime self.chartData.refreshData(lookBackTime: self.lookBackTime) self.lineData.refreshData(lookBackTime: self.lookBackTime) self.valueManager.refreshData(lookBackTime: self.lookBackTime) } - + var body: some View { VStack{ - + HStack { VStack(alignment: .leading, spacing: 8) { Text("This is a very minimalistic overview of your energy usage.") } - + Spacer(minLength: 10) - + Button(action: { self.chartData.refreshData(lookBackTime: self.lookBackTime) self.lineData.refreshData(lookBackTime: self.lookBackTime) @@ -668,7 +685,7 @@ struct DataView: View { } VStack{ - + VStack(spacing: 0) { if valueManager.isLoading { Text("Loading") @@ -686,9 +703,21 @@ struct DataView: View { } } } + HStack{ + TextBadge(title: "", color: Color("menuTab"), image: "person.crop.circle.badge.clock", value: "No project set") + Button(action: { + isTextInputViewPresented = true + }) { + Image(systemName: "pencil.circle") + } + } + .sheet(isPresented: $isTextInputViewPresented) { + TextInputView(text: $text, isPresented: $isTextInputViewPresented) + } + } Button(action: { - if let url = URL(string: "\(settingsManager.web_url)\(settingsManager.machine_id)") { + if let url = URL(string: "\(settingsManager.web_url)\(settingsManager.machine_uuid)") { NSWorkspace.shared.open(url) } }) { @@ -701,9 +730,9 @@ struct DataView: View { PointsGraph(chartData: chartData) TopProcessTable(tpData: lineData) - + } - + }.padding() } } @@ -716,7 +745,7 @@ func ProcessBadge(title: String, color: Color, process: String)->some View { .font(.title2) .foregroundColor(color) .padding(10) - + Text(getNameByAppName(appName: process)) .font(.title2.bold()) @@ -747,7 +776,7 @@ func EnergyBadge(title: String, color: Color, image: String, value: CGFloat)->so .font(.title2) .foregroundColor(color) .padding(10) - + Text(String(format: "%@", formatEnergy(value))) .font(.title2.bold()) @@ -764,7 +793,7 @@ func TextBadge(title: String, color: Color, image: String, value: String)->some .font(.title2) .foregroundColor(color) .padding(10) - + Text(value) .font(.title2.bold()) @@ -790,7 +819,7 @@ struct CopyPasteTextField: NSViewRepresentable { } struct OneView: View { - + var body: some View { Text("1) Open Terminal").font(.headline) HStack{ @@ -806,7 +835,7 @@ struct OneView: View { Text("Terminal") } } - + Text("If the button does not work please look under the Utilities folder in your Apps and start the terminal.") }.padding() } @@ -817,10 +846,10 @@ struct TwoView: View { var body: some View { Text("2) Run this command").font(.headline) - + HStack(spacing: 20) { CopyPasteTextField(text: $text) - + Button("Copy Text") { let pasteboard = NSPasteboard.general pasteboard.clearContents() @@ -857,7 +886,7 @@ enum TabSelection: Hashable { class InstallViewModel: ObservableObject { @Published var renderToggle: Bool = false @Published var selectedTab: TabSelection = .last5Minutes - + func toggleRender() { renderToggle.toggle() } @@ -935,9 +964,9 @@ private struct SettingDetailView: View { } } struct SettingsView: View { - + @ObservedObject var settingsManager = SettingsManager() - + var body: some View { VStack(alignment: .leading) { Text("Settings") @@ -945,7 +974,7 @@ struct SettingsView: View { .bold() Text("These are the settings that are set by the power logger.\nPlease refer to https://github.com/green-coding-berlin/hog#settings") Divider().padding() - SettingDetailView(title: "Machine ID:", value: settingsManager.machine_id) + SettingDetailView(title: "Machine ID:", value: settingsManager.machine_uuid) SettingDetailView(title: "Powermetrics Intervall:", value: "\(settingsManager.powermetrics)") SettingDetailView(title: "Upload to URL:", value: settingsManager.api_url) SettingDetailView(title: "Web View URL:", value: settingsManager.web_url) @@ -961,10 +990,10 @@ struct SettingsView: View { struct DetailView: View { - + @ObservedObject var viewModel = InstallViewModel() @Environment(\.colorScheme) var colorScheme - + var body: some View { if checkDB() { TabView(selection: $viewModel.selectedTab) { @@ -973,19 +1002,19 @@ struct DetailView: View { Label("Last 5 Minutes", systemImage: "list.dash") } .tag(TabSelection.last5Minutes) - + DataView(lookBackTime: 86400000) .tabItem { Label("Last 24 Hours", systemImage: "square.and.pencil") } .tag(TabSelection.last24Hours) - + DataView() .tabItem { Label("All Time", systemImage: "square.and.pencil") } .tag(TabSelection.allTime) - + SettingsView() .tabItem { Label("Settings", systemImage: "square.and.pencil") @@ -997,7 +1026,7 @@ struct DetailView: View { } else { InstallView(viewModel: viewModel) } - + } } diff --git a/berlin.green-coding.hog.plist b/berlin.green-coding.hog.plist index 943af4a..79abf3c 100644 --- a/berlin.green-coding.hog.plist +++ b/berlin.green-coding.hog.plist @@ -7,7 +7,7 @@ ProgramArguments - PATH_PLASE_CHANGE/power_logger.py + PATH_PLEASE_CHANGE/power_logger.py RunAtLoad diff --git a/install.sh b/install.sh index e75d544..d1230e5 100644 --- a/install.sh +++ b/install.sh @@ -47,7 +47,8 @@ chmod +x /usr/local/bin/hog/power_logger.py mv /usr/local/bin/hog/berlin.green-coding.hog.plist /Library/LaunchDaemons/berlin.green-coding.hog.plist -sed -i "s|PATH_PLASE_CHANGE|/usr/local/bin/hog/|g" /Library/LaunchDaemons/berlin.green-coding.hog.plist +sed -i '' "s|PATH_PLEASE_CHANGE|/usr/local/bin/hog/|g" /Library/LaunchDaemons/berlin.green-coding.hog.plist + chown root:wheel /Library/LaunchDaemons/berlin.green-coding.hog.plist chmod 644 /Library/LaunchDaemons/berlin.green-coding.hog.plist diff --git a/migrations/20230909161250_first_db.py b/migrations/20230909161250_first_db.py index f41c4eb..059718c 100644 --- a/migrations/20230909161250_first_db.py +++ b/migrations/20230909161250_first_db.py @@ -28,7 +28,7 @@ def upgrade(connection): tbl_settings = '''CREATE TABLE IF NOT EXISTS settings (time INT, - machine_id TEXT, + machine_uuid TEXT, powermetrics INT, api_url STRING, web_url STRING, diff --git a/power_logger.py b/power_logger.py index bc94011..9f8e4c8 100755 --- a/power_logger.py +++ b/power_logger.py @@ -46,6 +46,7 @@ def sigint_handler(_, __): print('Received stop signal. Terminating all processes.') def siginfo_handler(_, __): + print(SETTINGS) print(stats) signal.signal(signal.SIGINT, sigint_handler) @@ -66,7 +67,7 @@ def siginfo_handler(_, __): 'powermetrics': 5000, 'upload_delta': 300, 'api_url': 'https://api.green-coding.berlin/v1/hog/add', - 'web_url': 'http://metrics.green-coding.berlin/hog-details.html?machine_id=', + 'web_url': 'http://metrics.green-coding.berlin/hog-details.html?machine_uuid=', 'upload_data': True, } @@ -86,18 +87,18 @@ def siginfo_handler(_, __): if config_path: config.read(config_path) SETTINGS = { - 'powermetrics': config['DEFAULT'].get('powermetrics', default_settings['powermetrics']), - 'upload_delta': config['DEFAULT'].get('upload_delta', default_settings['upload_delta']), + 'powermetrics': int(config['DEFAULT'].get('powermetrics', default_settings['powermetrics'])), + 'upload_delta': int(config['DEFAULT'].get('upload_delta', default_settings['upload_delta'])), 'api_url': config['DEFAULT'].get('api_url', default_settings['api_url']), 'web_url': config['DEFAULT'].get('web_url', default_settings['web_url']), - 'upload_data': config['DEFAULT'].getboolean('upload_data', default_settings['upload_data']), + 'upload_data': bool(config['DEFAULT'].getboolean('upload_data', default_settings['upload_data'])), } else: SETTINGS = default_settings -machine_id = None +machine_uuid = None conn = sqlite3.connect(DATABASE_FILE) c = conn.cursor() @@ -149,13 +150,15 @@ def process_lines(lines, debug): upload_data_to_endpoint() def upload_data_to_endpoint(): + retry_counter = 0 while True: - + retry_counter += 1 # We need to limit the amount of data here as otherwise the payload becomes to big c.execute('SELECT id, time, data FROM measurements WHERE uploaded = 0 LIMIT 10;') rows = c.fetchall() - if not rows: + if not rows or retry_counter > 3: + retry_counter = 0 break payload = [] @@ -171,7 +174,7 @@ def upload_data_to_endpoint(): 'time': time_val, 'data': data_val, 'settings': json.dumps(settings_upload), - 'machine_id': machine_id, + 'machine_uuid': machine_uuid, 'row_id': row_id }) @@ -188,16 +191,12 @@ def upload_data_to_endpoint(): conn.commit() else: print(f"Failed to upload data: {payload}\n HTTP status: {response.status}") - except urllib.error.HTTPError as e: - print(f"HTTP error occurred while uploading: {payload}\n {e.reason}") - except ConnectionRefusedError: - pass - except urllib.error.URLError: - pass - except http.client.RemoteDisconnected: - pass - except ConnectionResetError: - pass + except (urllib.error.HTTPError, + ConnectionRefusedError, + urllib.error.URLError, + http.client.RemoteDisconnected, + ConnectionResetError): + break def find_top_processes(data: list): @@ -281,13 +280,13 @@ def parse_powermetrics_output(output: str): conn.commit() def save_settings(): - global machine_id + global machine_uuid - c.execute('SELECT machine_id, powermetrics, api_url, web_url, upload_delta, upload_data FROM settings ORDER BY time DESC LIMIT 1;') + c.execute('SELECT machine_uuid, powermetrics, api_url, web_url, upload_delta, upload_data FROM settings ORDER BY time DESC LIMIT 1;') result = c.fetchone() if result: - machine_id, last_powermetrics, last_api_url, last_web_url, last_upload_delta, last_upload_data = result + machine_uuid, last_powermetrics, last_api_url, last_web_url, last_upload_delta, last_upload_data = result if (last_powermetrics == SETTINGS['powermetrics'] and last_api_url.strip() == SETTINGS['api_url'].strip() and @@ -296,13 +295,13 @@ def save_settings(): last_upload_data == SETTINGS['upload_data']): return else: - machine_id = str(uuid.uuid1()) + machine_uuid = str(uuid.uuid1()) c.execute('''INSERT INTO settings - (time, machine_id, powermetrics, api_url, web_url, upload_delta, upload_data) VALUES + (time, machine_uuid, powermetrics, api_url, web_url, upload_delta, upload_data) VALUES (?, ?, ?, ?, ?, ?, ?)''', ( int(time.time()), - machine_id, + machine_uuid, SETTINGS['powermetrics'], SETTINGS['api_url'].strip(), SETTINGS['web_url'].strip(), @@ -328,7 +327,7 @@ def save_settings(): 'powermetrics' : 1000, 'upload_delta': 5, 'api_url': 'http://api.green-coding.internal:9142/v1/hog/add', - 'web_url': 'http://metrics.green-coding.internal:9142/hog-details.html?machine_id=', + 'web_url': 'http://metrics.green-coding.internal:9142/hog-details.html?machine_uuid=', 'upload_data': True, } @@ -349,7 +348,7 @@ def save_settings(): if args.website: print('Please visit this url for detailed analytics:') - print(f"{SETTINGS['web_url']}{machine_id}") + print(f"{SETTINGS['web_url']}{machine_uuid}") sys.exit() run_powermetrics(args.debug, args.file) diff --git a/settings.ini b/settings.ini index 6829ba0..e4796e9 100644 --- a/settings.ini +++ b/settings.ini @@ -1,6 +1,6 @@ [DEFAULT] api_url = https://api.green-coding.berlin/v1/hog/add -web_url = http://metrics.green-coding.berlin/hog-details.html?machine_id= +web_url = http://metrics.green-coding.berlin/hog-details.html?machine_uuid= upload_delta = 300 powermetrics = 5000 upload_data = true \ No newline at end of file