diff --git a/README.md b/README.md index 27300ff..483f564 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,63 @@ # hog -A tool suit that collects data of programs that use a lot of energy +The hog is a tool that periodically collects energy statistics of your mac and makes them available to you. + +There are two main aims: + +1) Identify which apps are using a lot of energy on your machine. +2) Collecting the data from as many machines as possible to identify wasteful apps. + +The hog consists of 2 apps. + +## Power logger + +The background process `power_logger.py` which saves the power statists to the database. We use the mac internal +`powermetrics` tool to collect the data. Because the powermetrics tool needs to be run as root so does the power_logger +script. The tool accepts one argument `-d` to run the tool in debug mode. It can also be sent the SIGINFO command to +give some statistics. You can either call it by hand and send it to the background with `&` or define it an agent. +For development purposes we recommend to always first run the program in the foreground and see if everything works fine +and then use the launch agent. + +### Launch agent (still needs work) + +Please modify the `hog.green-coding.berlin.plist` file to reference the right path. + +Place the .plist file in the `/Library/LaunchAgents/` (`sudo mv hog.green-coding.berlin.plist /Library/LaunchAgents/ `) +directory. For security reasons, files in /Library/LaunchDaemons/ should have their permissions set to be owned by root:wheel +and should not be writable by others. + +```bash +sudo chown root:wheel /Library/LaunchDaemons/hog.green-coding.berlin.plist +sudo chmod 644 /Library/LaunchDaemons/hog.green-coding.berlin.plist + +``` + +After placing the .plist file in the right directory, you need to tell launchd to load the new configuration: + +```bash +sudo launchctl load /Library/LaunchAgents/hog.green-coding.berlin.plist +``` + +You can check if your service is loaded with: + +```bash +sudo launchctl list | grep hog.green-coding.berlin.plist +``` + +If you want to unload or stop the service: + +```bash + +sudo launchctl unload /Library/LaunchAgents/hog.green-coding.berlin.plist +``` + +## The desktop App + +The hog desktop app gives you analytics of the data that was recorded. + +## Database + +All data is saved in an sqlite database that is located under: + +```bash +~/Library/Application Support/gcb_hog/db.db +``` diff --git a/app/hog/hog.xcodeproj/project.pbxproj b/app/hog/hog.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d8de42e --- /dev/null +++ b/app/hog/hog.xcodeproj/project.pbxproj @@ -0,0 +1,601 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 0A69EDE42AA0820E00F4A364 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A69EDE32AA0820E00F4A364 /* DetailView.swift */; }; + 0AEC07772A40D4C2003C82E7 /* hogApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC07762A40D4C2003C82E7 /* hogApp.swift */; }; + 0AEC07792A40D4C2003C82E7 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC07782A40D4C2003C82E7 /* ContentView.swift */; }; + 0AEC077B2A40D4C3003C82E7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AEC077A2A40D4C3003C82E7 /* Assets.xcassets */; }; + 0AEC077E2A40D4C3003C82E7 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AEC077D2A40D4C3003C82E7 /* Preview Assets.xcassets */; }; + 0AEC07892A40D4C3003C82E7 /* hogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC07882A40D4C3003C82E7 /* hogTests.swift */; }; + 0AEC07932A40D4C3003C82E7 /* hogUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC07922A40D4C3003C82E7 /* hogUITests.swift */; }; + 0AEC07952A40D4C3003C82E7 /* hogUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEC07942A40D4C3003C82E7 /* hogUITestsLaunchTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0AEC07852A40D4C3003C82E7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0AEC076B2A40D4C2003C82E7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0AEC07722A40D4C2003C82E7; + remoteInfo = hog; + }; + 0AEC078F2A40D4C3003C82E7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0AEC076B2A40D4C2003C82E7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0AEC07722A40D4C2003C82E7; + remoteInfo = hog; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0A69EDE32AA0820E00F4A364 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = ""; }; + 0AAD6F392AA734A100416201 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 0AEC07732A40D4C2003C82E7 /* hog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = hog.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0AEC07762A40D4C2003C82E7 /* hogApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hogApp.swift; sourceTree = ""; }; + 0AEC07782A40D4C2003C82E7 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 0AEC077A2A40D4C3003C82E7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0AEC077D2A40D4C3003C82E7 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 0AEC077F2A40D4C3003C82E7 /* hog.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = hog.entitlements; sourceTree = ""; }; + 0AEC07842A40D4C3003C82E7 /* hogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = hogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 0AEC07882A40D4C3003C82E7 /* hogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hogTests.swift; sourceTree = ""; }; + 0AEC078E2A40D4C3003C82E7 /* hogUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = hogUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 0AEC07922A40D4C3003C82E7 /* hogUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hogUITests.swift; sourceTree = ""; }; + 0AEC07942A40D4C3003C82E7 /* hogUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = hogUITestsLaunchTests.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0AEC07702A40D4C2003C82E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC07812A40D4C3003C82E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC078B2A40D4C3003C82E7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0AEC076A2A40D4C2003C82E7 = { + isa = PBXGroup; + children = ( + 0AEC07752A40D4C2003C82E7 /* hog */, + 0AEC07872A40D4C3003C82E7 /* hogTests */, + 0AEC07912A40D4C3003C82E7 /* hogUITests */, + 0AEC07742A40D4C2003C82E7 /* Products */, + ); + sourceTree = ""; + }; + 0AEC07742A40D4C2003C82E7 /* Products */ = { + isa = PBXGroup; + children = ( + 0AEC07732A40D4C2003C82E7 /* hog.app */, + 0AEC07842A40D4C3003C82E7 /* hogTests.xctest */, + 0AEC078E2A40D4C3003C82E7 /* hogUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 0AEC07752A40D4C2003C82E7 /* hog */ = { + isa = PBXGroup; + children = ( + 0AAD6F392AA734A100416201 /* Info.plist */, + 0AEC07762A40D4C2003C82E7 /* hogApp.swift */, + 0AEC07782A40D4C2003C82E7 /* ContentView.swift */, + 0A69EDE32AA0820E00F4A364 /* DetailView.swift */, + 0AEC077A2A40D4C3003C82E7 /* Assets.xcassets */, + 0AEC077F2A40D4C3003C82E7 /* hog.entitlements */, + 0AEC077C2A40D4C3003C82E7 /* Preview Content */, + ); + path = hog; + sourceTree = ""; + }; + 0AEC077C2A40D4C3003C82E7 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 0AEC077D2A40D4C3003C82E7 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 0AEC07872A40D4C3003C82E7 /* hogTests */ = { + isa = PBXGroup; + children = ( + 0AEC07882A40D4C3003C82E7 /* hogTests.swift */, + ); + path = hogTests; + sourceTree = ""; + }; + 0AEC07912A40D4C3003C82E7 /* hogUITests */ = { + isa = PBXGroup; + children = ( + 0AEC07922A40D4C3003C82E7 /* hogUITests.swift */, + 0AEC07942A40D4C3003C82E7 /* hogUITestsLaunchTests.swift */, + ); + path = hogUITests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0AEC07722A40D4C2003C82E7 /* hog */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0AEC07982A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hog" */; + buildPhases = ( + 0AEC076F2A40D4C2003C82E7 /* Sources */, + 0AEC07702A40D4C2003C82E7 /* Frameworks */, + 0AEC07712A40D4C2003C82E7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = hog; + productName = hog; + productReference = 0AEC07732A40D4C2003C82E7 /* hog.app */; + productType = "com.apple.product-type.application"; + }; + 0AEC07832A40D4C3003C82E7 /* hogTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0AEC079B2A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hogTests" */; + buildPhases = ( + 0AEC07802A40D4C3003C82E7 /* Sources */, + 0AEC07812A40D4C3003C82E7 /* Frameworks */, + 0AEC07822A40D4C3003C82E7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 0AEC07862A40D4C3003C82E7 /* PBXTargetDependency */, + ); + name = hogTests; + productName = hogTests; + productReference = 0AEC07842A40D4C3003C82E7 /* hogTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 0AEC078D2A40D4C3003C82E7 /* hogUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0AEC079E2A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hogUITests" */; + buildPhases = ( + 0AEC078A2A40D4C3003C82E7 /* Sources */, + 0AEC078B2A40D4C3003C82E7 /* Frameworks */, + 0AEC078C2A40D4C3003C82E7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 0AEC07902A40D4C3003C82E7 /* PBXTargetDependency */, + ); + name = hogUITests; + productName = hogUITests; + productReference = 0AEC078E2A40D4C3003C82E7 /* hogUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0AEC076B2A40D4C2003C82E7 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1430; + LastUpgradeCheck = 1430; + TargetAttributes = { + 0AEC07722A40D4C2003C82E7 = { + CreatedOnToolsVersion = 14.3.1; + }; + 0AEC07832A40D4C3003C82E7 = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 0AEC07722A40D4C2003C82E7; + }; + 0AEC078D2A40D4C3003C82E7 = { + CreatedOnToolsVersion = 14.3.1; + TestTargetID = 0AEC07722A40D4C2003C82E7; + }; + }; + }; + buildConfigurationList = 0AEC076E2A40D4C2003C82E7 /* Build configuration list for PBXProject "hog" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0AEC076A2A40D4C2003C82E7; + productRefGroup = 0AEC07742A40D4C2003C82E7 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0AEC07722A40D4C2003C82E7 /* hog */, + 0AEC07832A40D4C3003C82E7 /* hogTests */, + 0AEC078D2A40D4C3003C82E7 /* hogUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0AEC07712A40D4C2003C82E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0AEC077E2A40D4C3003C82E7 /* Preview Assets.xcassets in Resources */, + 0AEC077B2A40D4C3003C82E7 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC07822A40D4C3003C82E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC078C2A40D4C3003C82E7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0AEC076F2A40D4C2003C82E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0AEC07792A40D4C2003C82E7 /* ContentView.swift in Sources */, + 0A69EDE42AA0820E00F4A364 /* DetailView.swift in Sources */, + 0AEC07772A40D4C2003C82E7 /* hogApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC07802A40D4C3003C82E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0AEC07892A40D4C3003C82E7 /* hogTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0AEC078A2A40D4C3003C82E7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0AEC07932A40D4C3003C82E7 /* hogUITests.swift in Sources */, + 0AEC07952A40D4C3003C82E7 /* hogUITestsLaunchTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0AEC07862A40D4C3003C82E7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0AEC07722A40D4C2003C82E7 /* hog */; + targetProxy = 0AEC07852A40D4C3003C82E7 /* PBXContainerItemProxy */; + }; + 0AEC07902A40D4C3003C82E7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0AEC07722A40D4C2003C82E7 /* hog */; + targetProxy = 0AEC078F2A40D4C3003C82E7 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 0AEC07962A40D4C3003C82E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LSUIElemen = ""; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 0AEC07972A40D4C3003C82E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LSUIElemen = ""; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 0AEC07992A40D4C3003C82E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = hog/hog.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"hog/Preview Content\""; + DEVELOPMENT_TEAM = SBWA476E6F; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = hog/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Hog; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "berlin.green-coding.hog"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 0AEC079A2A40D4C3003C82E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = hog/hog.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"hog/Preview Content\""; + DEVELOPMENT_TEAM = SBWA476E6F; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = hog/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Hog; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_LSUIElement = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "berlin.green-coding.hog"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 0AEC079C2A40D4C3003C82E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = SBWA476E6F; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.green-coding.berlin.hogTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hog.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hog"; + }; + name = Debug; + }; + 0AEC079D2A40D4C3003C82E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = SBWA476E6F; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.3; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.green-coding.berlin.hogTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/hog.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/hog"; + }; + name = Release; + }; + 0AEC079F2A40D4C3003C82E7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = SBWA476E6F; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.green-coding.berlin.hogUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = hog; + }; + name = Debug; + }; + 0AEC07A02A40D4C3003C82E7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = SBWA476E6F; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.green-coding.berlin.hogUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = hog; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0AEC076E2A40D4C2003C82E7 /* Build configuration list for PBXProject "hog" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0AEC07962A40D4C3003C82E7 /* Debug */, + 0AEC07972A40D4C3003C82E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0AEC07982A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hog" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0AEC07992A40D4C3003C82E7 /* Debug */, + 0AEC079A2A40D4C3003C82E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0AEC079B2A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hogTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0AEC079C2A40D4C3003C82E7 /* Debug */, + 0AEC079D2A40D4C3003C82E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0AEC079E2A40D4C3003C82E7 /* Build configuration list for PBXNativeTarget "hogUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0AEC079F2A40D4C3003C82E7 /* Debug */, + 0AEC07A02A40D4C3003C82E7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0AEC076B2A40D4C2003C82E7 /* Project object */; +} diff --git a/app/hog/hog.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/hog/hog.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/app/hog/hog.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/hog/hog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/hog/hog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/hog/hog.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/IDEFindNavigatorScopes.plist b/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + 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 new file mode 100644 index 0000000..77d290a Binary files /dev/null and b/app/hog/hog.xcodeproj/project.xcworkspace/xcuserdata/didi.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/app/hog/hog.xcodeproj/xcshareddata/xcschemes/hog.xcscheme b/app/hog/hog.xcodeproj/xcshareddata/xcschemes/hog.xcscheme new file mode 100644 index 0000000..1228815 --- /dev/null +++ b/app/hog/hog.xcodeproj/xcshareddata/xcschemes/hog.xcscheme @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..b09a7be --- /dev/null +++ b/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcschemes/xcschememanagement.plist b/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..a9b6020 --- /dev/null +++ b/app/hog/hog.xcodeproj/xcuserdata/didi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,32 @@ + + + + + SchemeUserState + + hog.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + 0AEC07722A40D4C2003C82E7 + + primary + + + 0AEC07832A40D4C3003C82E7 + + primary + + + 0AEC078D2A40D4C3003C82E7 + + primary + + + + + diff --git a/app/hog/hog/Assets.xcassets/AccentColor.colorset/Contents.json b/app/hog/hog/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/1024-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/1024-mac.png new file mode 100644 index 0000000..a81e834 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/1024-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/128-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/128-mac.png new file mode 100644 index 0000000..f561a58 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/128-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/16-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/16-mac.png new file mode 100644 index 0000000..b441c8d Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/16-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac 1.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac 1.png new file mode 100644 index 0000000..b74610f Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac 1.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac.png new file mode 100644 index 0000000..b74610f Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/256-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac 1.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac 1.png new file mode 100644 index 0000000..8f73d85 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac 1.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac.png new file mode 100644 index 0000000..8f73d85 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/32-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac 1.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac 1.png new file mode 100644 index 0000000..a9ec981 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac 1.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac.png new file mode 100644 index 0000000..a9ec981 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/512-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/64-mac.png b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/64-mac.png new file mode 100644 index 0000000..c5db1ea Binary files /dev/null and b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/64-mac.png differ diff --git a/app/hog/hog/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2c976ad --- /dev/null +++ b/app/hog/hog/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "16-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32-mac 1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128-mac.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256-mac 1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512-mac 1.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024-mac.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/BG.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/BG.colorset/Contents.json new file mode 100644 index 0000000..8e77182 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/BG.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.973", + "red" : "0.937" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/Contents.json b/app/hog/hog/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/chartColor1.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/chartColor1.colorset/Contents.json new file mode 100644 index 0000000..9564ac4 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/chartColor1.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.447", + "green" : "0.447", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/chartColor2.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/chartColor2.colorset/Contents.json new file mode 100644 index 0000000..15ac841 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/chartColor2.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.588", + "green" : "0.871", + "red" : "0.635" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/menuTab.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/menuTab.colorset/Contents.json new file mode 100644 index 0000000..3772160 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/menuTab.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.843", + "green" : "0.796", + "red" : "0.067" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/red.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/red.colorset/Contents.json new file mode 100644 index 0000000..55d1a12 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/red.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.0", + "green" : "0.0", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Colors/roundColor.colorset/Contents.json b/app/hog/hog/Assets.xcassets/Colors/roundColor.colorset/Contents.json new file mode 100644 index 0000000..117a72d --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Colors/roundColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.569", + "green" : "0.259", + "red" : "0.961" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Contents.json b/app/hog/hog/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/Contents.json b/app/hog/hog/Assets.xcassets/Icons/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/chart.imageset/Contents.json b/app/hog/hog/Assets.xcassets/Icons/chart.imageset/Contents.json new file mode 100644 index 0000000..1ca076c --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/chart.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "chart.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/chart.imageset/chart.png b/app/hog/hog/Assets.xcassets/Icons/chart.imageset/chart.png new file mode 100644 index 0000000..ac3991a Binary files /dev/null and b/app/hog/hog/Assets.xcassets/Icons/chart.imageset/chart.png differ diff --git a/app/hog/hog/Assets.xcassets/Icons/chat.imageset/Contents.json b/app/hog/hog/Assets.xcassets/Icons/chat.imageset/Contents.json new file mode 100644 index 0000000..e3fa3b5 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/chat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "chat.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/chat.imageset/chat.png b/app/hog/hog/Assets.xcassets/Icons/chat.imageset/chat.png new file mode 100644 index 0000000..799f15f Binary files /dev/null and b/app/hog/hog/Assets.xcassets/Icons/chat.imageset/chat.png differ diff --git a/app/hog/hog/Assets.xcassets/Icons/home.imageset/Contents.json b/app/hog/hog/Assets.xcassets/Icons/home.imageset/Contents.json new file mode 100644 index 0000000..7f2c0a7 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/home.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "home.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/home.imageset/home.png b/app/hog/hog/Assets.xcassets/Icons/home.imageset/home.png new file mode 100644 index 0000000..99de329 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/Icons/home.imageset/home.png differ diff --git a/app/hog/hog/Assets.xcassets/Icons/option.imageset/Contents.json b/app/hog/hog/Assets.xcassets/Icons/option.imageset/Contents.json new file mode 100644 index 0000000..f08c57f --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/option.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "option.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/option.imageset/option.png b/app/hog/hog/Assets.xcassets/Icons/option.imageset/option.png new file mode 100644 index 0000000..fee3a90 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/Icons/option.imageset/option.png differ diff --git a/app/hog/hog/Assets.xcassets/Icons/setting.imageset/Contents.json b/app/hog/hog/Assets.xcassets/Icons/setting.imageset/Contents.json new file mode 100644 index 0000000..55b5095 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/Icons/setting.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "setting.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/Assets.xcassets/Icons/setting.imageset/setting.png b/app/hog/hog/Assets.xcassets/Icons/setting.imageset/setting.png new file mode 100644 index 0000000..65df6e8 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/Icons/setting.imageset/setting.png differ diff --git a/app/hog/hog/Assets.xcassets/logo.imageset/64.png b/app/hog/hog/Assets.xcassets/logo.imageset/64.png new file mode 100644 index 0000000..266b2b3 Binary files /dev/null and b/app/hog/hog/Assets.xcassets/logo.imageset/64.png differ diff --git a/app/hog/hog/Assets.xcassets/logo.imageset/Contents.json b/app/hog/hog/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000..d01f313 --- /dev/null +++ b/app/hog/hog/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "64.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/ContentView.swift b/app/hog/hog/ContentView.swift new file mode 100644 index 0000000..ad5d007 --- /dev/null +++ b/app/hog/hog/ContentView.swift @@ -0,0 +1,339 @@ +//// +//// ContentView.swift +//// hog +//// +//// Created by Didi Hoffmann on 19.06.23. +//// +// +//import SwiftUI +//import Charts +//import SQLite3 +//import Cocoa +//import AppKit +// +//public func isScriptRunning(scriptName: String) -> Bool { +// let process = Process() +// let outputPipe = Pipe() +// +// process.launchPath = "/usr/bin/env" +// process.arguments = ["pgrep", "-f", scriptName] +// process.standardOutput = outputPipe +// +// do { +// try process.run() +// process.waitUntilExit() +// +// let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() +// if let output = String(data: outputData, encoding: .utf8), !output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { +// return true +// } +// } catch { +// print("An error occurred: \(error)") +// } +// +// return false +//} +// +//public func getNameByAppName(appName: String) -> String { +// let runningApps = NSWorkspace.shared.runningApplications +// for app in runningApps { +// if app.bundleIdentifier == appName { +// return app.localizedName ?? "No Data" +// } +// } +// +// let components = appName.split(separator: ".") +// if let lastComponent = components.last { +// return String(lastComponent) +// } +// +// return "No Data" +//} +// +//public func getIconByAppName(appName: String) -> NSImage? { +// let runningApps = NSWorkspace.shared.runningApplications +// for app in runningApps { +// if app.bundleIdentifier == appName { +// return app.icon +// } +// } +// return NSImage(systemSymbolName: "terminal", accessibilityDescription: nil) +//} +// +// +//class ValueManager: ObservableObject { +// @Published var last5Min: CGFloat = 0 +// @Published var last24Hours: CGFloat = 0 +// @Published var totalEnergy: CGFloat = 0 +// @Published var providerRunning: Bool = false +// @Published var top5MinApp: String = "Loading..." +// @Published var top24HourApp: String = "Loading..." +// +// enum ValueType { +// case float +// case string +// } +// +// +// func fetchValues() { +// DispatchQueue.global(qos: .userInitiated).async { +// self.fetchValuesAsync() +// } +// } +// +// func fetchValuesAsync() { +// var db: OpaquePointer? +// +// let fileManager = FileManager.default +// let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") +// +// if let dir = appSupportDir { +// let fileURL = dir.appendingPathComponent("db.db") +// +// if sqlite3_open(fileURL.path, &db) != SQLITE_OK { // Open database +// print("error opening database") +// return +// } +// } else { +// print("Directory not found") +// return +// } +// +// var newLast5Min: CGFloat = 0 +// var newLast24Hours: CGFloat = 0 +// var newTotalEnergy: CGFloat = 0 +// var newTop5MinApp: String = "Loading" +// var newTop24HourApp: String = "Loading" +// +// let last5MinQuery = "SELECT COALESCE(sum(combined_energy), 0) FROM power_measurements WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - 300000);" +// if let result: CGFloat = queryDatabase(db: db, query:last5MinQuery, type: .float) { +// newLast5Min = result +// } +// +// +// let last24HoursQuery = "SELECT COALESCE(sum(combined_energy), 0) FROM power_measurements WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - 86400000);" +// if let result: CGFloat = queryDatabase(db: db, query:last24HoursQuery, type: .float) { +// newLast24Hours = result +// } +// +// let totalEnergyQuery = "SELECT COALESCE(sum(combined_energy), 0) FROM power_measurements;" +// if let result: CGFloat = queryDatabase(db: db, query:totalEnergyQuery, type: .float) { +// newTotalEnergy = result +// } +// +// +// let top5MinAppQuery = """ +// SELECT name +// FROM top_processes +// WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - 300000) +// GROUP BY name +// ORDER BY SUM(energy_impact) DESC +// LIMIT 1; -- to get only the top name +// """ +// +// if let result: String = queryDatabase(db: db, query:top5MinAppQuery, type: .string) { +// newTop5MinApp = String(result) +// } else { +// newTop5MinApp = "No data" +// } +// +// let top24HourAppQuery = """ +// SELECT name +// FROM top_processes +// WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - 86400000) +// GROUP BY name +// ORDER BY SUM(energy_impact) DESC +// LIMIT 1; -- to get only the top name +// """ +// +// if let result: String = queryDatabase(db: db, query:top24HourAppQuery, type: .string) { +// newTop24HourApp = String(result) +// } else { +// newTop5MinApp = "No data" +// } +// +// DispatchQueue.main.async { +// self.last5Min = newLast5Min +// self.totalEnergy = newTotalEnergy +// self.last24Hours = newLast24Hours +// self.providerRunning = isScriptRunning(scriptName: "power_logger_all.py") +// self.top5MinApp = newTop5MinApp +// self.top24HourApp = newTop24HourApp +// } +// +// sqlite3_close(db) +// +// } +// +// +// +// +// 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 { +// case .float: +// let value = CGFloat(sqlite3_column_double(queryStatement, 0)) +// sqlite3_finalize(queryStatement) +// return value as? T +// case .string: +// if let cString = sqlite3_column_text(queryStatement, 0) { +// let value = String(cString: cString) +// sqlite3_finalize(queryStatement) +// return value as? T +// } +// } +// } +// } +// sqlite3_finalize(queryStatement) +// return nil +// } +//} +// +// +// +// +//struct Home: View { +// +// @ObservedObject var valueManager = ValueManager() +// @Environment(\.openWindow) private var openWindow +// +// +// +// var body: some View { +// VStack(spacing: 18) { +// // MARK: TITLE +// HStack { +// VStack(alignment: .leading, spacing: 8) { +// Text("Quick Overview") +// .font(.title.bold()) +// } +// +// Spacer(minLength: 10) +// Button(action: { +// valueManager.fetchValues() +// }) { +// Image(systemName: "goforward") +// } +// Button(action: { +// exit(0) +// }) { +// Image(systemName: "x.circle") +// } +// +// +// } +// VStack() { +// +// HStack(spacing: 0) { +// ProcessBadge(title: "Highest energy\n 5 min", color: Color("chartColor2"), process: valueManager.top5MinApp) +// +// ProcessBadge(title: "Highest energy\n 24h", color: Color("chartColor2"), process: valueManager.top24HourApp) +// +// +// if valueManager.providerRunning { +// TextBadge(title: "Running", color: Color("chartColor2"), image: "checkmark.seal", value: "Provider App") +// } else { +// TextBadge(title: "Not Running", color: Color("red"), image: "exclamationmark.octagon", value: "Provider App") +// } +// +// } +// HStack(spacing: 0) { +// EnergyBadge(title: "Last 5 Minutes", color: Color("chartColor2"), image: "clock.badge.checkmark", value: valueManager.last5Min, unit: "mJ") +// EnergyBadge(title: "Last 24 hours", color: Color("chartColor2"), image: "clock.badge.checkmark", value: valueManager.last24Hours, unit: "mJ") +// EnergyBadge(title: "Total System Energy", color: Color("chartColor2"), image: "bolt.circle", value: valueManager.totalEnergy, unit: "mJ") +// } +// +// } +// .padding() +// .cornerRadius(18) +// +// Button("View more details") { +// openWindow(id: "details") +// } +// +// +// } +// .onAppear { +// valueManager.fetchValues() +// }.padding() +// } +// +// @ViewBuilder +// func ProcessBadge(title: String, color: Color, process: String)->some View { +// HStack { +// Image(nsImage: getIconByAppName(appName: process) ?? NSImage()) +// .font(.title2) +// .foregroundColor(color) +// .padding(10) +// +// VStack(alignment: .leading, spacing: 8) { +// Text(title) +// .font(.caption2.bold()) +// .foregroundColor(.gray) +// +// Text(getNameByAppName(appName: process)) +// .font(.title2.bold()) +// +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// } +// +// @ViewBuilder +// func EnergyBadge(title: String, color: Color, image: String, value: CGFloat, unit: String)->some View { +// HStack { +// Image(systemName: image) +// .font(.title2) +// .foregroundColor(color) +// .padding(10) +// +// VStack(alignment: .leading, spacing: 8) { +// Text(String(format: "%.1f %@", value / 1000, unit)) +// .font(.title2.bold()) +// +// Text(title) +// .font(.caption2.bold()) +// .foregroundColor(.gray) +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// } +// @ViewBuilder +// func TextBadge(title: String, color: Color, image: String, value: String)->some View { +// HStack { +// Image(systemName: image) +// .font(.title2) +// .foregroundColor(color) +// .padding(10) +// +// VStack(alignment: .leading, spacing: 8) { +// Text(value) +// .font(.title2.bold()) +// +// Text(title) +// .font(.caption2.bold()) +// .foregroundColor(.gray) +// } +// } +// .frame(maxWidth: .infinity, alignment: .leading) +// } +//} +// +// +// +//struct ContentView: View { +// var body: some View{ +// Home() +// } +//} +// +// +// +//struct ContentView_Previews: PreviewProvider { +// static var previews: some View { +// ContentView() +// } +//} diff --git a/app/hog/hog/DetailView.swift b/app/hog/hog/DetailView.swift new file mode 100644 index 0000000..a5c9ee4 --- /dev/null +++ b/app/hog/hog/DetailView.swift @@ -0,0 +1,678 @@ +// +// DetailView.swift +// hog +// +// Created by Didi Hoffmann on 31.08.23. +// + +import SwiftUI +import SQLite3 +import Charts +import AppKit + +public func isScriptRunning(scriptName: String) -> Bool { + let process = Process() + let outputPipe = Pipe() + + process.launchPath = "/usr/bin/env" + process.arguments = ["pgrep", "-f", scriptName] + process.standardOutput = outputPipe + + do { + try process.run() + process.waitUntilExit() + + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + if let output = String(data: outputData, encoding: .utf8), !output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return true + } + } catch { + print("An error occurred: \(error)") + } + + return false +} + +public func getNameByAppName(appName: String) -> String { + let runningApps = NSWorkspace.shared.runningApplications + for app in runningApps { + if app.bundleIdentifier == appName { + return app.localizedName ?? "No Data" + } + } + + let components = appName.split(separator: ".") + if let lastComponent = components.last { + return String(lastComponent) + } + + return "No Data" +} + +public func getIconByAppName(appName: String) -> NSImage? { + let runningApps = NSWorkspace.shared.runningApplications + for app in runningApps { + if app.bundleIdentifier == appName { + return app.icon + } + } + return NSImage(systemSymbolName: "terminal", accessibilityDescription: nil) +} + +func getMachineId() -> String{ + var db: OpaquePointer? + var machineId = "" + let fileManager = FileManager.default + let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") + + if let dir = appSupportDir { + let fileURL = dir.appendingPathComponent("db.db") + + if sqlite3_open(fileURL.path, &db) != SQLITE_OK { // Open database + print("error opening database") + return "" + } + } else { + print("Directory not found") + return "" + } + + var queryStatement: OpaquePointer? + let queryString = "SELECT machine_id FROM settings LIMIT 1" + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + while sqlite3_step(queryStatement) == SQLITE_ROW { + let queryResultCol1 = sqlite3_column_text(queryStatement, 0) + machineId = String(cString: queryResultCol1!) + print("Machine ID: \(machineId)") + } + } else { + let errorMessage = String(cString: sqlite3_errmsg(db)) + print("Query could not be prepared! \(errorMessage)") + } + + sqlite3_finalize(queryStatement) + sqlite3_close(db) + + return machineId +} + +func checkDB() -> Bool { + let fileManager = FileManager.default + guard let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") else { + print("Directory not found") + return false + } + + let fileURL = appSupportDir.appendingPathComponent("db.db") + + return fileManager.fileExists(atPath: fileURL.path) +} + + +class ValueManager: ObservableObject { + var lookBackTime:Int = 0 + + @Published var energy: CGFloat = 0 + @Published var providerRunning: Bool = false + @Published var topApp: String = "Loading..." + @Published var isLoading: Bool = false + + enum ValueType { + case float + case string + } + + public func refreshData(lookBackTime: Int = 0) -> Void{ + self.isLoading = true + self.lookBackTime = lookBackTime + + DispatchQueue.global(qos: .userInitiated).async { + self.loadDataFrom() + DispatchQueue.main.async { + self.isLoading = false + } + } + } + + func loadDataFrom() { + var db: OpaquePointer? + + let fileManager = FileManager.default + let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") + + if let dir = appSupportDir { + let fileURL = dir.appendingPathComponent("db.db") + + if sqlite3_open(fileURL.path, &db) != SQLITE_OK { // Open database + print("error opening database") + return + } + } else { + print("Directory not found") + return + } + + var newEnergy: CGFloat = 0 + var energyQuery:String + if self.lookBackTime == 0 { + energyQuery = "SELECT COALESCE(sum(combined_energy), 0) FROM power_measurements;" + }else{ + energyQuery = "SELECT COALESCE(sum(combined_energy), 0) FROM power_measurements WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - \(self.lookBackTime));" + } + if let result: CGFloat = queryDatabase(db: db, query:energyQuery, type: .float) { + newEnergy = result + } + + + var newTopApp: String = "Loading" + var topQuery:String + + if self.lookBackTime == 0 { + topQuery = """ + SELECT name + FROM top_processes + GROUP BY name + ORDER BY SUM(energy_impact) DESC + LIMIT 1; -- to get only the top name + """ + }else{ + topQuery = """ + SELECT name + FROM top_processes + WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - \(self.lookBackTime)) + GROUP BY name + ORDER BY SUM(energy_impact) DESC + LIMIT 1; -- to get only the top name + """ + } + + if let result: String = queryDatabase(db: db, query:topQuery, type: .string) { + newTopApp = String(result) + } else { + newTopApp = "No data" + } + + + + DispatchQueue.main.async { + self.energy = newEnergy + self.providerRunning = isScriptRunning(scriptName: "power_logger_all.py") + self.topApp = newTopApp + } + + sqlite3_close(db) + + } + + + 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 { + case .float: + let value = CGFloat(sqlite3_column_double(queryStatement, 0)) + sqlite3_finalize(queryStatement) + return value as? T + case .string: + if let cString = sqlite3_column_text(queryStatement, 0) { + let value = String(cString: cString) + sqlite3_finalize(queryStatement) + return value as? T + } + } + } + } + sqlite3_finalize(queryStatement) + return nil + } +} +struct TopProcess: Codable, Identifiable { + let id: UUID = UUID() // Add this line if you want a unique identifier + let name: String + let energy_impact: Double + let cputime_ns: Int64 + + enum CodingKeys: String, CodingKey { + case name, energy_impact, cputime_ns + } +} + +class TopProcessData: ObservableObject, RandomAccessCollection { + var lookBackTime:Int = 0 + typealias Element = TopProcess + typealias Index = Array.Index + + @Published var lines: [TopProcess] = [] + + var startIndex: Index { lines.startIndex } + var endIndex: Index { lines.endIndex } + + @Published var isLoading: Bool = false + + subscript(position: Index) -> Element { + lines[position] + } + + public func refreshData(lookBackTime: Int = 0) -> Void{ + self.isLoading = true + self.lookBackTime = lookBackTime + DispatchQueue.global(qos: .userInitiated).async { + self.loadDataFrom() + DispatchQueue.main.async { + self.isLoading = false + } + } + } + + + private func loadDataFrom() { + + var db: OpaquePointer? // SQLite database object + + + let fileManager = FileManager.default + let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") + + if let dir = appSupportDir { + let fileURL = dir.appendingPathComponent("db.db") + + if sqlite3_open(fileURL.path, &db) != SQLITE_OK { // Open database + print("error opening database") + return + } + } else { + print("Directory not found") + return + } + + var queryStatement: OpaquePointer? + + let queryString: String + if self.lookBackTime == 0 { + queryString = """ + SELECT name, SUM(energy_impact), SUM(cputime_ns) + FROM top_processes + GROUP BY name + ORDER BY SUM(energy_impact) DESC + LIMIT 50; + + """ + } else { + queryString = """ + SELECT name, SUM(energy_impact), SUM(cputime_ns) + FROM top_processes + WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - \(self.lookBackTime)) + GROUP BY name + ORDER BY SUM(energy_impact) DESC + LIMIT 50; + """ + } + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + var newLines: [TopProcess] = [] + while sqlite3_step(queryStatement) == SQLITE_ROW { + var name: String = "" + if let namePointer = sqlite3_column_text(queryStatement, 0) { + name = String(cString: namePointer) + } + let energy_impact = sqlite3_column_double(queryStatement, 1) + let cputime_ns = sqlite3_column_int64(queryStatement, 2) + + newLines.append(TopProcess(name: name, energy_impact: energy_impact, cputime_ns: cputime_ns)) + } + DispatchQueue.main.async { + self.lines = newLines + } + } + + sqlite3_finalize(queryStatement) + sqlite3_close(db) + } +} + + + + +struct DataPoint: Codable, Identifiable { + let id: Double + let combined_energy: Double + let cpu_energy: Double + let gpu_energy: Double + let ane_energy: Double + var time: Date? + + enum CodingKeys: String, CodingKey { + case id, combined_energy, cpu_energy, gpu_energy, ane_energy + } +} + +class ChartData: ObservableObject, RandomAccessCollection { + var lookBackTime:Int = 0 + typealias Element = DataPoint + typealias Index = Array.Index + + @Published var points: [DataPoint] = [] + + var startIndex: Index { points.startIndex } + 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 + + DispatchQueue.global(qos: .userInitiated).async { + self.loadDataFrom() + DispatchQueue.main.async { + self.isLoading = false + } + } + } + + private func loadDataFrom() { + + var db: OpaquePointer? // SQLite database object + + + let fileManager = FileManager.default + let appSupportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first?.appendingPathComponent("gcb_hog") + + if let dir = appSupportDir { + let fileURL = dir.appendingPathComponent("db.db") + + if sqlite3_open(fileURL.path, &db) != SQLITE_OK { // Open database + print("error opening database") + return + } + } else { + print("Directory not found") + return + } + + var queryStatement: OpaquePointer? + + let queryString: String + if self.lookBackTime == 0 { + queryString = "SELECT * FROM power_measurements;" + } else { + queryString = "SELECT * FROM power_measurements WHERE time >= ((CAST(strftime('%s', 'now') AS INTEGER) * 1000) - \(self.lookBackTime));" + } + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + var newPoints: [DataPoint] = [] + while sqlite3_step(queryStatement) == SQLITE_ROW { + let id = sqlite3_column_double(queryStatement, 0) + let combined_energy = sqlite3_column_double(queryStatement, 2) + let cpu_energy = sqlite3_column_double(queryStatement, 3) + let gpu_energy = sqlite3_column_double(queryStatement, 4) + let ane_energy = sqlite3_column_double(queryStatement, 5) + 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 { + self.points = newPoints + } + } + + sqlite3_finalize(queryStatement) + sqlite3_close(db) + } +} + + +struct PointsGraph: View { + @ObservedObject var chartData: ChartData + + init(chartData: ChartData) { + self.chartData = chartData + } + + var body: some View { + if chartData.isLoading { + ProgressView("Loading...") + .scaleEffect(1.5, anchor: .center) + .progressViewStyle(CircularProgressViewStyle(tint: Color.blue)) + .padding() + } else { + VStack { + if chartData.isEmpty { + Text("No Data! Please enable provider app.").font(.largeTitle) + }else{ + Chart(chartData) { + PointMark( + x: .value("Time", $0.time!), + y: .value("Energy", $0.combined_energy) + ) + } + .chartYAxisLabel("mJ") + .chartXAxisLabel("Time") + } + } + } + } +} + +struct TopProcessTable: View { + @ObservedObject var tpData: TopProcessData + + init(tpData: TopProcessData) { + self.tpData = tpData + } + + var body: some View { + if tpData.isLoading { + ProgressView("Loading...") + .scaleEffect(1.5, anchor: .center) + .progressViewStyle(CircularProgressViewStyle(tint: Color.blue)) + .padding() + } else { + if tpData.isEmpty { + } else { + Table(tpData) { + TableColumn(""){ line in + Image(nsImage: getIconByAppName(appName: line.name) ?? NSImage()) + .resizable() + .frame(width: 15, height: 15) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + + }.width(20) + + TableColumn("Name", value: \.name) + TableColumn("Energy Impact"){ line in + Text(String(format: "%.2f", line.energy_impact)) + } + TableColumn("CPU time"){ line in + Text(String(line.cputime_ns)) + } + } + .padding() + .tableStyle(.bordered(alternatesRowBackgrounds: true)) + + } + + } + } +} + + +struct DataView: View { + + @State var chartData = ChartData() + @State var lineData = TopProcessData() + @ObservedObject var valueManager = ValueManager() + @State private var isHovering = false + + var lookBackTime: Int + + + init(lookBackTime: Int = 0) { + self.lookBackTime = lookBackTime + } + + var body: some View { + VStack{ + + HStack { + VStack(alignment: .leading, spacing: 8) { + Text("This is a minimalistic overview of your energy usage and the apps that are using the most resources.") + + } + + Spacer(minLength: 10) + Button("Detailed analytics") { + if let url = URL(string: "https://metrics.green-coding.berlin/hog.html?machine_id=\(getMachineId())") { + NSWorkspace.shared.open(url) + } + } + Button(action: { + self.chartData.refreshData(lookBackTime: self.lookBackTime) + self.lineData.refreshData(lookBackTime: self.lookBackTime) + self.valueManager.refreshData(lookBackTime: self.lookBackTime) + }) { + Image(systemName: "goforward") + } + Button(action: { + exit(0) + }) { + Image(systemName: "x.circle") + } + + } + VStack{ + + VStack(spacing: 0) { + ProcessBadge(title: "App with the highest energy usage", color: Color("chartColor2"), process: valueManager.topApp) + EnergyBadge(title: "Sysmte energy usage", color: Color("chartColor2"), image: "clock.badge.checkmark", value: valueManager.energy, unit: "mJ") + if valueManager.providerRunning { + TextBadge(title: "", color: Color("chartColor2"), image: "checkmark.seal", value: "Provider App running") + } else { + HStack{ + TextBadge(title: "", color: Color("red"), image: "exclamationmark.octagon", value: "Provider App is not running") + Link(destination: URL(string: "https://www.example.com/TOS.html")!) { + Image(systemName: "questionmark.circle.fill") + .font(.system(size: 24)) + } + } + } + + } + + PointsGraph(chartData: chartData) + TopProcessTable(tpData: lineData) + + } + .onAppear { + self.chartData.refreshData(lookBackTime: self.lookBackTime) + self.lineData.refreshData(lookBackTime: self.lookBackTime) + self.valueManager.refreshData(lookBackTime: self.lookBackTime) + } + + }.padding() + } +} + + +@ViewBuilder +func ProcessBadge(title: String, color: Color, process: String)->some View { + HStack { + Image(nsImage: getIconByAppName(appName: process) ?? NSImage()) + .font(.title2) + .foregroundColor(color) + .padding(10) + + Text(getNameByAppName(appName: process)) + .font(.title2.bold()) + + Text(title) + .font(.caption2.bold()) + .foregroundColor(.gray) + + } + .frame(maxWidth: .infinity, alignment: .leading) +} + +@ViewBuilder +func EnergyBadge(title: String, color: Color, image: String, value: CGFloat, unit: String)->some View { + HStack { + Image(systemName: image) + .font(.title2) + .foregroundColor(color) + .padding(10) + + Text(String(format: "%.1f %@", value / 1000, unit)) + .font(.title2.bold()) + + Text(title) + .font(.caption2.bold()) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity, alignment: .leading) +} + +@ViewBuilder +func TextBadge(title: String, color: Color, image: String, value: String)->some View { + HStack { + Image(systemName: image) + .font(.title2) + .foregroundColor(color) + .padding(10) + + Text(value) + .font(.title2.bold()) + + Text(title) + .font(.caption2.bold()) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity, alignment: .leading) +} + + +struct DetailView: View { + + @State private var renderToggle = false + + var body: some View { + if checkDB(){ + TabView { + DataView(lookBackTime: 300000) + .tabItem { + Label("last 5 minutes", systemImage: "list.dash") + } + + DataView(lookBackTime: 86400000) + .tabItem { + Label("last 24 hours", systemImage: "square.and.pencil") + } + DataView() + .tabItem { + Label("all time", systemImage: "square.and.pencil") + } + + }.padding() + } else { + Text("Please run the power logger script first! See https://github.com/green-coding-berlin/hog for detailed install instructions.") + Button("Re-check") { + renderToggle.toggle() + } + + } + + } +} + +struct DetailView_Previews: PreviewProvider { + static var previews: some View { + DetailView().fixedSize() + + } +} diff --git a/app/hog/hog/Info.plist b/app/hog/hog/Info.plist new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/app/hog/hog/Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/app/hog/hog/Preview Content/Preview Assets.xcassets/Contents.json b/app/hog/hog/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/app/hog/hog/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app/hog/hog/hog.entitlements b/app/hog/hog/hog.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/app/hog/hog/hog.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/app/hog/hog/hogApp.swift b/app/hog/hog/hogApp.swift new file mode 100644 index 0000000..bb2b1e0 --- /dev/null +++ b/app/hog/hog/hogApp.swift @@ -0,0 +1,22 @@ +// +// hogApp.swift +// hog +// +// Created by Didi Hoffmann on 19.06.23. +// + +import SwiftUI + +@main +struct hogApp: App { + var body: some Scene { + + MenuBarExtra("QuickView", image: "logo") { + DetailView().frame( + minWidth: 600, maxWidth: 800, + minHeight: 850, maxHeight: 1000) + + + }.menuBarExtraStyle(WindowMenuBarExtraStyle()) + } +} diff --git a/app/hog/hogTests/hogTests.swift b/app/hog/hogTests/hogTests.swift new file mode 100644 index 0000000..4aa25b4 --- /dev/null +++ b/app/hog/hogTests/hogTests.swift @@ -0,0 +1,36 @@ +// +// hogTests.swift +// hogTests +// +// Created by Didi Hoffmann on 19.06.23. +// + +import XCTest +@testable import hog + +final class hogTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/app/hog/hogUITests/hogUITests.swift b/app/hog/hogUITests/hogUITests.swift new file mode 100644 index 0000000..0f4a7f9 --- /dev/null +++ b/app/hog/hogUITests/hogUITests.swift @@ -0,0 +1,41 @@ +// +// hogUITests.swift +// hogUITests +// +// Created by Didi Hoffmann on 19.06.23. +// + +import XCTest + +final class hogUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/app/hog/hogUITests/hogUITestsLaunchTests.swift b/app/hog/hogUITests/hogUITestsLaunchTests.swift new file mode 100644 index 0000000..cda3626 --- /dev/null +++ b/app/hog/hogUITests/hogUITestsLaunchTests.swift @@ -0,0 +1,32 @@ +// +// hogUITestsLaunchTests.swift +// hogUITests +// +// Created by Didi Hoffmann on 19.06.23. +// + +import XCTest + +final class hogUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/hog.app/Contents/Info.plist b/hog.app/Contents/Info.plist new file mode 100644 index 0000000..abdc220 --- /dev/null +++ b/hog.app/Contents/Info.plist @@ -0,0 +1,56 @@ + + + + + BuildMachineOSBuild + 22G90 + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Hog + CFBundleExecutable + hog + CFBundleIconFile + AppIcon + CFBundleIconName + AppIcon + CFBundleIdentifier + berlin.green-coding.hog + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + hog + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + + DTPlatformName + macosx + DTPlatformVersion + 13.3 + DTSDKBuild + 22E245 + DTSDKName + macosx13.3 + DTXcode + 1431 + DTXcodeBuild + 14E300c + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 13.3 + LSUIElement + + + diff --git a/hog.app/Contents/MacOS/hog b/hog.app/Contents/MacOS/hog new file mode 100755 index 0000000..bd47210 Binary files /dev/null and b/hog.app/Contents/MacOS/hog differ diff --git a/hog.app/Contents/PkgInfo b/hog.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/hog.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/hog.app/Contents/Resources/AppIcon.icns b/hog.app/Contents/Resources/AppIcon.icns new file mode 100644 index 0000000..1c5748e Binary files /dev/null and b/hog.app/Contents/Resources/AppIcon.icns differ diff --git a/hog.app/Contents/Resources/Assets.car b/hog.app/Contents/Resources/Assets.car new file mode 100644 index 0000000..598be79 Binary files /dev/null and b/hog.app/Contents/Resources/Assets.car differ diff --git a/hog.app/Contents/_CodeSignature/CodeResources b/hog.app/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..8584f68 --- /dev/null +++ b/hog.app/Contents/_CodeSignature/CodeResources @@ -0,0 +1,139 @@ + + + + + files + + Resources/AppIcon.icns + + IEqC35733lGzlCi4rzsjyYyMkSI= + + Resources/Assets.car + + zIUlYVS50122TcRTB4jHUSLwH5w= + + + files2 + + Resources/AppIcon.icns + + hash2 + + Yb6WRPtOGgUUy+4O9EwusPhUQRBGkQGdB2SjY4vJGAQ= + + + Resources/Assets.car + + hash2 + + 7BIv92peWXXNaN0yViQ/4WWo114V2m2LKRsG0E72IMg= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/hog.green-coding.berlin.plist b/hog.green-coding.berlin.plist new file mode 100644 index 0000000..347ced0 --- /dev/null +++ b/hog.green-coding.berlin.plist @@ -0,0 +1,19 @@ + + + + + Label + hog.green-coding.berlin + + ProgramArguments + + PATH_PLASE_CHANGE/power_logger.py + + + RunAtLoad + + + KeepAlive + + + diff --git a/hog.green-coding.berlin.test.plist b/hog.green-coding.berlin.test.plist new file mode 100644 index 0000000..2816f46 --- /dev/null +++ b/hog.green-coding.berlin.test.plist @@ -0,0 +1,19 @@ + + + + + Label + hog.green-coding.berlin + + ProgramArguments + + /Users/didi/code/hog/power_logger.py + + + RunAtLoad + + + KeepAlive + + + diff --git a/power_logger.py b/power_logger.py new file mode 100644 index 0000000..0446d1c --- /dev/null +++ b/power_logger.py @@ -0,0 +1,193 @@ +import json +import subprocess +import time +import threading +import plistlib +import argparse +import zlib +import base64 +import xml +import signal +import sys +import uuid + +from datetime import timezone +from queue import Queue +from pathlib import Path + +import sqlite3 + +# Shared variable to signal the thread to stop +stop_signal = False + +stats = { + 'combined_power':0 +} + +def sigint_handler(signum, frame): + global stop_signal + if stop_signal: + # If you press CTR-C the second time we bail + sys.exit() + + stop_signal = True + print("Received stop signal. Terminating all processes.") + +def siginfo_handler(signum, frame): + print(stats) + +signal.signal(signal.SIGINT, sigint_handler) +signal.signal(signal.SIGINFO, siginfo_handler) + + +APP_NAME = "gcb_hog" +app_support_path = Path.home() / 'Library' / 'Application Support' / APP_NAME +app_support_path.mkdir(parents=True, exist_ok=True) + +DATABASE_FILE = app_support_path / 'db.db' + +SETTINGS = { + 'powermetrics' : 2100, + 'loop_sleep': 300, +} + +conn = sqlite3.connect(DATABASE_FILE) +c = conn.cursor() + +c.execute('''CREATE TABLE IF NOT EXISTS measurements + (time INT, data STRING, settings STRING)''') + +c.execute('''CREATE TABLE IF NOT EXISTS power_measurements + (time INT, combined_energy REAL, cpu_energy REAL, gpu_energy REAL, ane_energy REAL)''') + +c.execute('''CREATE TABLE IF NOT EXISTS top_processes + (time INT, name STRING, energy_impact REAL, cputime_ns INT)''') + +c.execute('''CREATE TABLE IF NOT EXISTS settings + (machine_id TEXT)''') + + +conn.commit() + + +def run_powermetrics(queue, stop_signal): + + # We ignore stderr here as powermetrics is quite verbose on stderr and the buffer fills up quite fast + cmd = ['powermetrics', + '--show-all', + '-i', str(SETTINGS['powermetrics']), + '-f', 'plist'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) + + buffer = [] + for line in process.stdout: + line = line.strip().replace("&", "&") + + buffer.append(line) + if line == '': + # We only add the data to the queue once it is complete to avoid race conditions + queue.put(''.join(buffer)) + buffer = [] + + if stop_signal: + process.terminate() # or process.kill() + break + +def find_top_processes(data): + # As iterm will probably show up as it spawns the processes called from the shall we look at the tasks + new_data = [] + for coalition in data: + if coalition['name'] == 'com.googlecode.iterm2' or coalition['name'].strip() == '': + new_data.extend(coalition["tasks"]) + else: + new_data.append(coalition) + + for p in sorted(new_data, key=lambda k: k['energy_impact'], reverse=True)[:10]: + yield{ + 'name': p['name'], + 'energy_impact': p['energy_impact'], + 'cputime_ns': p['cputime_ns'] + } + + +def parse_powermetrics_output(output): + global stats + + for data in output.encode('utf-8').split(b'\x00'): + if data: + + if data == b'powermetrics must be invoked as the superuser\n': + raise PermissionError('You need to run this script as root!') + + try: + data=plistlib.loads(data) + except xml.parsers.expat.ExpatError: + print(data) + raise xml.parsers.expat.ExpatError + + compressed_data = zlib.compress(str(data).encode()) + compressed_data_str = base64.b64encode(compressed_data).decode() + + epoch_time = int(data['timestamp'].replace(tzinfo=timezone.utc).timestamp() * 1e3) + + c.execute("INSERT INTO measurements (time, data, settings) VALUES (?, ?, ?)", + (epoch_time, compressed_data_str, json.dumps(SETTINGS))) + + c.execute("INSERT INTO power_measurements (time, combined_energy, cpu_energy, gpu_energy, ane_energy) VALUES (?, ?, ?, ?, ?)", + (epoch_time, + data['processor'].get('combined_power', 0) * data['elapsed_ns'] / 1_000_000_000.0, + data['processor'].get('cpu_energy', 0), + data['processor'].get('gpu_energy', 0), + data['processor'].get('ane_energy', 0), + )) + + stats['combined_power'] += data['processor'].get('combined_power', 0) * data['elapsed_ns'] / 1_000_000_000.0 + + for process in find_top_processes(data['coalitions']): + c.execute("INSERT INTO top_processes (time, name, energy_impact, cputime_ns) VALUES (?, ?, ?, ?)", + (epoch_time, process['name'], process['energy_impact'], process['cputime_ns'])) + + conn.commit() + + +def main(debug=False): + output_queue = Queue() + + # Start powermetrics in a separate thread + powermetrics_thread = threading.Thread(target=run_powermetrics, args=(output_queue, stop_signal)) + powermetrics_thread.daemon = True + powermetrics_thread.start() + + + while True: + time.sleep(SETTINGS['loop_sleep']) + while not output_queue.empty(): + parse_powermetrics_output(output_queue.get()) + + if debug: + print(stats) + + if stop_signal: + powermetrics_thread.join() + break + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description= + """A powermetrics wrapper that does simple parsing and writes to a file.""") + parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mode') + args = parser.parse_args() + if args.debug: + SETTINGS = { 'powermetrics' : 2100, 'loop_sleep': 2 } + + c.execute("SELECT machine_id FROM settings LIMIT 1") + result = c.fetchone() + + if not result: + c.execute("INSERT INTO settings (machine_id) VALUES (?)", (str(uuid.uuid1()),)) + conn.commit() + + main(args.debug) + + c.close()