├── .gitignore ├── FrequencyStats.xcodeproj └── project.pbxproj ├── FrequencyStats ├── .DS_Store ├── App Settings │ ├── AppSettings.swift │ └── AppSettingsView.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── FrequencyStatsIcon_128.png │ │ ├── FrequencyStatsIcon_128@2x.png │ │ ├── FrequencyStatsIcon_16.png │ │ ├── FrequencyStatsIcon_16@2x.png │ │ ├── FrequencyStatsIcon_256.png │ │ ├── FrequencyStatsIcon_256@2x.png │ │ ├── FrequencyStatsIcon_32.png │ │ ├── FrequencyStatsIcon_32@2x.png │ │ ├── FrequencyStatsIcon_512.png │ │ └── FrequencyStatsIcon_512@2x.png │ ├── Contents.json │ ├── ItemIcon.imageset │ │ ├── Contents.json │ │ └── ItemIcon.pdf │ └── primary.colorset │ │ └── Contents.json ├── FrequencyStats-Bridging-Header.h ├── FrequencyStats.entitlements ├── IOReport.h ├── Info.plist ├── Logger.swift ├── Logic │ ├── Cluster.swift │ ├── SampleManager.swift │ └── SystemConstants.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── Views │ ├── ClusterItem.swift │ ├── ClusterItemCoreMeter.swift │ ├── ClusterItemGraph.swift │ ├── ClusterListView.swift │ └── VisualEffectView.swift ├── FrequencyStatsHelper ├── FrequencyStatsHelper-Bridging-Header.h ├── FrequencyStatsHelper.swift ├── FrequencyStatsHelperProtocol.swift ├── IOReport.h ├── Info.plist └── main.swift ├── LICENSE ├── Localization ├── .DS_Store ├── de.lproj │ └── Localizable.strings ├── en.lproj │ └── Localizable.strings ├── es.lproj │ └── Localizable.strings ├── fr.lproj │ └── Localizable.strings ├── it.lproj │ └── Localizable.strings ├── ja.lproj │ └── Localizable.strings ├── ko.lproj │ └── Localizable.strings ├── nl.lproj │ └── Localizable.strings ├── pt-BR.lproj │ └── Localizable.strings ├── ru.lproj │ └── Localizable.strings ├── tr.lproj │ └── Localizable.strings ├── vi.lproj │ └── Localizable.strings ├── zh-Hans.lproj │ └── Localizable.strings └── zh-Hant.lproj │ └── Localizable.strings ├── README.md └── images ├── beautified-example.png ├── example-img-dark.png ├── example-img-dark2.png ├── example-img-light.png ├── example-img-states-dark.png ├── example-mg-light2.png └── frequency-stats-icon.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | FrequencyStats.xcodeproj/project.xcworkspace/* 3 | FrequencyStats.xcodeproj/xcuserdata/* 4 | FrequencyStats.xcodeproj/project.pbxproj 5 | -------------------------------------------------------------------------------- /FrequencyStats.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3FF298C529BCD69900DA665F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298C429BCD69900DA665F /* AppDelegate.swift */; }; 11 | 3FF298C729BCD69900DA665F /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298C629BCD69900DA665F /* VisualEffectView.swift */; }; 12 | 3FF298C929BCD69A00DA665F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FF298C829BCD69A00DA665F /* Assets.xcassets */; }; 13 | 3FF298CC29BCD69A00DA665F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FF298CB29BCD69A00DA665F /* Preview Assets.xcassets */; }; 14 | 3FF298D629BCD6AC00DA665F /* libIOReport.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF298D529BCD6AC00DA665F /* libIOReport.tbd */; }; 15 | 3FF298DE29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298DD29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift */; }; 16 | 3FF298E029BCD6BB00DA665F /* FrequencyStatsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298DF29BCD6BB00DA665F /* FrequencyStatsHelper.swift */; }; 17 | 3FF298E229BCD6BB00DA665F /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298E129BCD6BB00DA665F /* main.swift */; }; 18 | 3FF298E729BCD6BB00DA665F /* FrequencyStatsHelper.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 3FF298DB29BCD6BA00DA665F /* FrequencyStatsHelper.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 19 | 3FF298EB29BCD6C200DA665F /* libIOReport.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FF298D529BCD6AC00DA665F /* libIOReport.tbd */; }; 20 | 3FF298EC29BCD6D000DA665F /* FrequencyStatsHelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298DD29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift */; }; 21 | 3FF298F729BCD9BB00DA665F /* SampleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298F629BCD9BB00DA665F /* SampleManager.swift */; }; 22 | 3FF298F929BCDBC300DA665F /* Cluster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298F829BCDBC300DA665F /* Cluster.swift */; }; 23 | 3FF298FB29BCDBCC00DA665F /* SystemConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298FA29BCDBCC00DA665F /* SystemConstants.swift */; }; 24 | 3FF298FD29BE9C3700DA665F /* ClusterItemGraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298FC29BE9C3700DA665F /* ClusterItemGraph.swift */; }; 25 | 3FF298FF29BFBEA200DA665F /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF298FE29BFBEA200DA665F /* AppSettings.swift */; }; 26 | 3FF2990129BFBEB300DA665F /* ClusterItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990029BFBEB300DA665F /* ClusterItem.swift */; }; 27 | 3FF2990329BFBEBA00DA665F /* ClusterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990229BFBEBA00DA665F /* ClusterListView.swift */; }; 28 | 3FF2990629BFBFAE00DA665F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990529BFBFAE00DA665F /* Logger.swift */; }; 29 | 3FF2990929BFD5B600DA665F /* ClusterItemCoreMeter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990829BFD5B600DA665F /* ClusterItemCoreMeter.swift */; }; 30 | 3FF2990B29BFD8DA00DA665F /* AppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990A29BFD8DA00DA665F /* AppSettingsView.swift */; }; 31 | 3FF2991029C0FAD000DA665F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FF2990529BFBFAE00DA665F /* Logger.swift */; }; 32 | 3FF2991429C0FBC700DA665F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3FF2991629C0FBC700DA665F /* Localizable.strings */; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 3FF298E429BCD6BB00DA665F /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 3FF298B929BCD69900DA665F /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 3FF298DA29BCD6BA00DA665F; 41 | remoteInfo = FrequencyStatsHelper; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | 3FF298E629BCD6BB00DA665F /* Embed XPC Services */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; 50 | dstSubfolderSpec = 16; 51 | files = ( 52 | 3FF298E729BCD6BB00DA665F /* FrequencyStatsHelper.xpc in Embed XPC Services */, 53 | ); 54 | name = "Embed XPC Services"; 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXCopyFilesBuildPhase section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 3FF298C129BCD69900DA665F /* FrequencyStats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FrequencyStats.app; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 3FF298C429BCD69900DA665F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 62 | 3FF298C629BCD69900DA665F /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = ""; }; 63 | 3FF298C829BCD69A00DA665F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64 | 3FF298CB29BCD69A00DA665F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 65 | 3FF298CD29BCD69A00DA665F /* FrequencyStats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FrequencyStats.entitlements; sourceTree = ""; }; 66 | 3FF298D329BCD69F00DA665F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 67 | 3FF298D529BCD6AC00DA665F /* libIOReport.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libIOReport.tbd; path = usr/lib/libIOReport.tbd; sourceTree = SDKROOT; }; 68 | 3FF298DB29BCD6BA00DA665F /* FrequencyStatsHelper.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = FrequencyStatsHelper.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 3FF298DD29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrequencyStatsHelperProtocol.swift; sourceTree = ""; }; 70 | 3FF298DF29BCD6BB00DA665F /* FrequencyStatsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrequencyStatsHelper.swift; sourceTree = ""; }; 71 | 3FF298E129BCD6BB00DA665F /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 72 | 3FF298E329BCD6BB00DA665F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 3FF298EE29BCD71600DA665F /* FrequencyStats-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FrequencyStats-Bridging-Header.h"; sourceTree = ""; }; 74 | 3FF298F129BCD72100DA665F /* IOReport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOReport.h; sourceTree = ""; }; 75 | 3FF298F229BCD74200DA665F /* FrequencyStatsHelper-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FrequencyStatsHelper-Bridging-Header.h"; sourceTree = ""; }; 76 | 3FF298F529BCD75200DA665F /* IOReport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOReport.h; sourceTree = ""; }; 77 | 3FF298F629BCD9BB00DA665F /* SampleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleManager.swift; sourceTree = ""; }; 78 | 3FF298F829BCDBC300DA665F /* Cluster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cluster.swift; sourceTree = ""; }; 79 | 3FF298FA29BCDBCC00DA665F /* SystemConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemConstants.swift; sourceTree = ""; }; 80 | 3FF298FC29BE9C3700DA665F /* ClusterItemGraph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClusterItemGraph.swift; sourceTree = ""; }; 81 | 3FF298FE29BFBEA200DA665F /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 82 | 3FF2990029BFBEB300DA665F /* ClusterItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClusterItem.swift; sourceTree = ""; }; 83 | 3FF2990229BFBEBA00DA665F /* ClusterListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClusterListView.swift; sourceTree = ""; }; 84 | 3FF2990529BFBFAE00DA665F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 85 | 3FF2990829BFD5B600DA665F /* ClusterItemCoreMeter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClusterItemCoreMeter.swift; sourceTree = ""; }; 86 | 3FF2990A29BFD8DA00DA665F /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; 87 | 3FF2991529C0FBC700DA665F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 88 | 3FF2991729C0FDEF00DA665F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 89 | 3FF2991829C0FDF800DA665F /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 90 | 3FF2991929C0FDFC00DA665F /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 91 | 3FF2991A29C0FE0300DA665F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 92 | 3FF2991B29C0FE0700DA665F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 93 | 3FF2991C29C0FE1200DA665F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 94 | 3FF2991D29C0FE1600DA665F /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 95 | 3FF2991F29C0FE2000DA665F /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 96 | 3FF2992029C0FE2500DA665F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 97 | 3FF2992129C0FE2E00DA665F /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 98 | 3FF2992229C0FE3100DA665F /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 99 | 3FF2992329C0FE3200DA665F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 100 | /* End PBXFileReference section */ 101 | 102 | /* Begin PBXFrameworksBuildPhase section */ 103 | 3FF298BE29BCD69900DA665F /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | 3FF298D629BCD6AC00DA665F /* libIOReport.tbd in Frameworks */, 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | 3FF298D829BCD6BA00DA665F /* Frameworks */ = { 112 | isa = PBXFrameworksBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | 3FF298EB29BCD6C200DA665F /* libIOReport.tbd in Frameworks */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXFrameworksBuildPhase section */ 120 | 121 | /* Begin PBXGroup section */ 122 | 3FF298B829BCD69900DA665F = { 123 | isa = PBXGroup; 124 | children = ( 125 | 3FF2991329C0FBB900DA665F /* Localization */, 126 | 3FF298C329BCD69900DA665F /* FrequencyStats */, 127 | 3FF298DC29BCD6BB00DA665F /* FrequencyStatsHelper */, 128 | 3FF298C229BCD69900DA665F /* Products */, 129 | 3FF298D429BCD6AC00DA665F /* Frameworks */, 130 | ); 131 | sourceTree = ""; 132 | }; 133 | 3FF298C229BCD69900DA665F /* Products */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 3FF298C129BCD69900DA665F /* FrequencyStats.app */, 137 | 3FF298DB29BCD6BA00DA665F /* FrequencyStatsHelper.xpc */, 138 | ); 139 | name = Products; 140 | sourceTree = ""; 141 | }; 142 | 3FF298C329BCD69900DA665F /* FrequencyStats */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 3FF298D329BCD69F00DA665F /* Info.plist */, 146 | 3FF298C829BCD69A00DA665F /* Assets.xcassets */, 147 | 3FF298CD29BCD69A00DA665F /* FrequencyStats.entitlements */, 148 | 3FF298F129BCD72100DA665F /* IOReport.h */, 149 | 3FF298EE29BCD71600DA665F /* FrequencyStats-Bridging-Header.h */, 150 | 3FF298C429BCD69900DA665F /* AppDelegate.swift */, 151 | 3FF2990529BFBFAE00DA665F /* Logger.swift */, 152 | 3FF2990F29BFEAD400DA665F /* Logic */, 153 | 3FF2990C29BFD8E100DA665F /* App Settings */, 154 | 3FF2990429BFBF4B00DA665F /* Views */, 155 | 3FF298CA29BCD69A00DA665F /* Preview Content */, 156 | ); 157 | path = FrequencyStats; 158 | sourceTree = ""; 159 | }; 160 | 3FF298CA29BCD69A00DA665F /* Preview Content */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 3FF298CB29BCD69A00DA665F /* Preview Assets.xcassets */, 164 | ); 165 | path = "Preview Content"; 166 | sourceTree = ""; 167 | }; 168 | 3FF298D429BCD6AC00DA665F /* Frameworks */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 3FF298D529BCD6AC00DA665F /* libIOReport.tbd */, 172 | ); 173 | name = Frameworks; 174 | sourceTree = ""; 175 | }; 176 | 3FF298DC29BCD6BB00DA665F /* FrequencyStatsHelper */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 3FF298DD29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift */, 180 | 3FF298DF29BCD6BB00DA665F /* FrequencyStatsHelper.swift */, 181 | 3FF298E129BCD6BB00DA665F /* main.swift */, 182 | 3FF298F529BCD75200DA665F /* IOReport.h */, 183 | 3FF298F229BCD74200DA665F /* FrequencyStatsHelper-Bridging-Header.h */, 184 | 3FF298E329BCD6BB00DA665F /* Info.plist */, 185 | ); 186 | path = FrequencyStatsHelper; 187 | sourceTree = ""; 188 | }; 189 | 3FF2990429BFBF4B00DA665F /* Views */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 3FF298C629BCD69900DA665F /* VisualEffectView.swift */, 193 | 3FF2990229BFBEBA00DA665F /* ClusterListView.swift */, 194 | 3FF2990029BFBEB300DA665F /* ClusterItem.swift */, 195 | 3FF298FC29BE9C3700DA665F /* ClusterItemGraph.swift */, 196 | 3FF2990829BFD5B600DA665F /* ClusterItemCoreMeter.swift */, 197 | ); 198 | path = Views; 199 | sourceTree = ""; 200 | }; 201 | 3FF2990C29BFD8E100DA665F /* App Settings */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | 3FF298FE29BFBEA200DA665F /* AppSettings.swift */, 205 | 3FF2990A29BFD8DA00DA665F /* AppSettingsView.swift */, 206 | ); 207 | path = "App Settings"; 208 | sourceTree = ""; 209 | }; 210 | 3FF2990F29BFEAD400DA665F /* Logic */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | 3FF298F829BCDBC300DA665F /* Cluster.swift */, 214 | 3FF298F629BCD9BB00DA665F /* SampleManager.swift */, 215 | 3FF298FA29BCDBCC00DA665F /* SystemConstants.swift */, 216 | ); 217 | path = Logic; 218 | sourceTree = ""; 219 | }; 220 | 3FF2991329C0FBB900DA665F /* Localization */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 3FF2991629C0FBC700DA665F /* Localizable.strings */, 224 | ); 225 | path = Localization; 226 | sourceTree = ""; 227 | }; 228 | /* End PBXGroup section */ 229 | 230 | /* Begin PBXNativeTarget section */ 231 | 3FF298C029BCD69900DA665F /* FrequencyStats */ = { 232 | isa = PBXNativeTarget; 233 | buildConfigurationList = 3FF298D029BCD69A00DA665F /* Build configuration list for PBXNativeTarget "FrequencyStats" */; 234 | buildPhases = ( 235 | 3FF298BD29BCD69900DA665F /* Sources */, 236 | 3FF298BE29BCD69900DA665F /* Frameworks */, 237 | 3FF298BF29BCD69900DA665F /* Resources */, 238 | 3FF298E629BCD6BB00DA665F /* Embed XPC Services */, 239 | ); 240 | buildRules = ( 241 | ); 242 | dependencies = ( 243 | 3FF298E529BCD6BB00DA665F /* PBXTargetDependency */, 244 | ); 245 | name = FrequencyStats; 246 | productName = FrequencyStats; 247 | productReference = 3FF298C129BCD69900DA665F /* FrequencyStats.app */; 248 | productType = "com.apple.product-type.application"; 249 | }; 250 | 3FF298DA29BCD6BA00DA665F /* FrequencyStatsHelper */ = { 251 | isa = PBXNativeTarget; 252 | buildConfigurationList = 3FF298E829BCD6BB00DA665F /* Build configuration list for PBXNativeTarget "FrequencyStatsHelper" */; 253 | buildPhases = ( 254 | 3FF298D729BCD6BA00DA665F /* Sources */, 255 | 3FF298D829BCD6BA00DA665F /* Frameworks */, 256 | 3FF298D929BCD6BA00DA665F /* Resources */, 257 | ); 258 | buildRules = ( 259 | ); 260 | dependencies = ( 261 | ); 262 | name = FrequencyStatsHelper; 263 | productName = FrequencyStatsHelper; 264 | productReference = 3FF298DB29BCD6BA00DA665F /* FrequencyStatsHelper.xpc */; 265 | productType = "com.apple.product-type.xpc-service"; 266 | }; 267 | /* End PBXNativeTarget section */ 268 | 269 | /* Begin PBXProject section */ 270 | 3FF298B929BCD69900DA665F /* Project object */ = { 271 | isa = PBXProject; 272 | attributes = { 273 | BuildIndependentTargetsInParallel = 1; 274 | LastSwiftUpdateCheck = 1420; 275 | LastUpgradeCheck = 1420; 276 | TargetAttributes = { 277 | 3FF298C029BCD69900DA665F = { 278 | CreatedOnToolsVersion = 14.2; 279 | LastSwiftMigration = 1420; 280 | }; 281 | 3FF298DA29BCD6BA00DA665F = { 282 | CreatedOnToolsVersion = 14.2; 283 | LastSwiftMigration = 1420; 284 | }; 285 | }; 286 | }; 287 | buildConfigurationList = 3FF298BC29BCD69900DA665F /* Build configuration list for PBXProject "FrequencyStats" */; 288 | compatibilityVersion = "Xcode 13.0"; 289 | developmentRegion = en; 290 | hasScannedForEncodings = 0; 291 | knownRegions = ( 292 | en, 293 | fr, 294 | de, 295 | it, 296 | ja, 297 | ko, 298 | "pt-BR", 299 | ru, 300 | "zh-Hans", 301 | "zh-Hant", 302 | vi, 303 | nl, 304 | es, 305 | ); 306 | mainGroup = 3FF298B829BCD69900DA665F; 307 | productRefGroup = 3FF298C229BCD69900DA665F /* Products */; 308 | projectDirPath = ""; 309 | projectRoot = ""; 310 | targets = ( 311 | 3FF298C029BCD69900DA665F /* FrequencyStats */, 312 | 3FF298DA29BCD6BA00DA665F /* FrequencyStatsHelper */, 313 | ); 314 | }; 315 | /* End PBXProject section */ 316 | 317 | /* Begin PBXResourcesBuildPhase section */ 318 | 3FF298BF29BCD69900DA665F /* Resources */ = { 319 | isa = PBXResourcesBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | 3FF298CC29BCD69A00DA665F /* Preview Assets.xcassets in Resources */, 323 | 3FF2991429C0FBC700DA665F /* Localizable.strings in Resources */, 324 | 3FF298C929BCD69A00DA665F /* Assets.xcassets in Resources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | 3FF298D929BCD6BA00DA665F /* Resources */ = { 329 | isa = PBXResourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | /* End PBXResourcesBuildPhase section */ 336 | 337 | /* Begin PBXSourcesBuildPhase section */ 338 | 3FF298BD29BCD69900DA665F /* Sources */ = { 339 | isa = PBXSourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 3FF298C729BCD69900DA665F /* VisualEffectView.swift in Sources */, 343 | 3FF2990629BFBFAE00DA665F /* Logger.swift in Sources */, 344 | 3FF298EC29BCD6D000DA665F /* FrequencyStatsHelperProtocol.swift in Sources */, 345 | 3FF2990B29BFD8DA00DA665F /* AppSettingsView.swift in Sources */, 346 | 3FF2990129BFBEB300DA665F /* ClusterItem.swift in Sources */, 347 | 3FF298FD29BE9C3700DA665F /* ClusterItemGraph.swift in Sources */, 348 | 3FF2990329BFBEBA00DA665F /* ClusterListView.swift in Sources */, 349 | 3FF298C529BCD69900DA665F /* AppDelegate.swift in Sources */, 350 | 3FF298F729BCD9BB00DA665F /* SampleManager.swift in Sources */, 351 | 3FF2990929BFD5B600DA665F /* ClusterItemCoreMeter.swift in Sources */, 352 | 3FF298FB29BCDBCC00DA665F /* SystemConstants.swift in Sources */, 353 | 3FF298FF29BFBEA200DA665F /* AppSettings.swift in Sources */, 354 | 3FF298F929BCDBC300DA665F /* Cluster.swift in Sources */, 355 | ); 356 | runOnlyForDeploymentPostprocessing = 0; 357 | }; 358 | 3FF298D729BCD6BA00DA665F /* Sources */ = { 359 | isa = PBXSourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | 3FF298E029BCD6BB00DA665F /* FrequencyStatsHelper.swift in Sources */, 363 | 3FF298DE29BCD6BB00DA665F /* FrequencyStatsHelperProtocol.swift in Sources */, 364 | 3FF298E229BCD6BB00DA665F /* main.swift in Sources */, 365 | 3FF2991029C0FAD000DA665F /* Logger.swift in Sources */, 366 | ); 367 | runOnlyForDeploymentPostprocessing = 0; 368 | }; 369 | /* End PBXSourcesBuildPhase section */ 370 | 371 | /* Begin PBXTargetDependency section */ 372 | 3FF298E529BCD6BB00DA665F /* PBXTargetDependency */ = { 373 | isa = PBXTargetDependency; 374 | target = 3FF298DA29BCD6BA00DA665F /* FrequencyStatsHelper */; 375 | targetProxy = 3FF298E429BCD6BB00DA665F /* PBXContainerItemProxy */; 376 | }; 377 | /* End PBXTargetDependency section */ 378 | 379 | /* Begin PBXVariantGroup section */ 380 | 3FF2991629C0FBC700DA665F /* Localizable.strings */ = { 381 | isa = PBXVariantGroup; 382 | children = ( 383 | 3FF2991529C0FBC700DA665F /* en */, 384 | 3FF2991729C0FDEF00DA665F /* it */, 385 | 3FF2991829C0FDF800DA665F /* ja */, 386 | 3FF2991929C0FDFC00DA665F /* ko */, 387 | 3FF2991A29C0FE0300DA665F /* pt-BR */, 388 | 3FF2991B29C0FE0700DA665F /* ru */, 389 | 3FF2991C29C0FE1200DA665F /* zh-Hans */, 390 | 3FF2991D29C0FE1600DA665F /* zh-Hant */, 391 | 3FF2991F29C0FE2000DA665F /* vi */, 392 | 3FF2992029C0FE2500DA665F /* nl */, 393 | 3FF2992129C0FE2E00DA665F /* es */, 394 | 3FF2992229C0FE3100DA665F /* fr */, 395 | 3FF2992329C0FE3200DA665F /* de */, 396 | ); 397 | name = Localizable.strings; 398 | sourceTree = ""; 399 | }; 400 | /* End PBXVariantGroup section */ 401 | 402 | /* Begin XCBuildConfiguration section */ 403 | 3FF298CE29BCD69A00DA665F /* Debug */ = { 404 | isa = XCBuildConfiguration; 405 | buildSettings = { 406 | ALWAYS_SEARCH_USER_PATHS = NO; 407 | CLANG_ANALYZER_NONNULL = YES; 408 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 409 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 410 | CLANG_ENABLE_MODULES = YES; 411 | CLANG_ENABLE_OBJC_ARC = YES; 412 | CLANG_ENABLE_OBJC_WEAK = YES; 413 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 414 | CLANG_WARN_BOOL_CONVERSION = YES; 415 | CLANG_WARN_COMMA = YES; 416 | CLANG_WARN_CONSTANT_CONVERSION = YES; 417 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 418 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 419 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 420 | CLANG_WARN_EMPTY_BODY = YES; 421 | CLANG_WARN_ENUM_CONVERSION = YES; 422 | CLANG_WARN_INFINITE_RECURSION = YES; 423 | CLANG_WARN_INT_CONVERSION = YES; 424 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 425 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 426 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 427 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 428 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 429 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 430 | CLANG_WARN_STRICT_PROTOTYPES = YES; 431 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 432 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 433 | CLANG_WARN_UNREACHABLE_CODE = YES; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | COPY_PHASE_STRIP = NO; 436 | DEBUG_INFORMATION_FORMAT = dwarf; 437 | ENABLE_STRICT_OBJC_MSGSEND = YES; 438 | ENABLE_TESTABILITY = YES; 439 | GCC_C_LANGUAGE_STANDARD = gnu11; 440 | GCC_DYNAMIC_NO_PIC = NO; 441 | GCC_NO_COMMON_BLOCKS = YES; 442 | GCC_OPTIMIZATION_LEVEL = 0; 443 | GCC_PREPROCESSOR_DEFINITIONS = ( 444 | "DEBUG=1", 445 | "$(inherited)", 446 | ); 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 454 | MTL_FAST_MATH = YES; 455 | ONLY_ACTIVE_ARCH = YES; 456 | SDKROOT = macosx; 457 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 458 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 459 | }; 460 | name = Debug; 461 | }; 462 | 3FF298CF29BCD69A00DA665F /* Release */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ALWAYS_SEARCH_USER_PATHS = NO; 466 | CLANG_ANALYZER_NONNULL = YES; 467 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 468 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 469 | CLANG_ENABLE_MODULES = YES; 470 | CLANG_ENABLE_OBJC_ARC = YES; 471 | CLANG_ENABLE_OBJC_WEAK = YES; 472 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 473 | CLANG_WARN_BOOL_CONVERSION = YES; 474 | CLANG_WARN_COMMA = YES; 475 | CLANG_WARN_CONSTANT_CONVERSION = YES; 476 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 477 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 478 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 479 | CLANG_WARN_EMPTY_BODY = YES; 480 | CLANG_WARN_ENUM_CONVERSION = YES; 481 | CLANG_WARN_INFINITE_RECURSION = YES; 482 | CLANG_WARN_INT_CONVERSION = YES; 483 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 484 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 485 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 486 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 487 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 488 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 489 | CLANG_WARN_STRICT_PROTOTYPES = YES; 490 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 491 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 492 | CLANG_WARN_UNREACHABLE_CODE = YES; 493 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 494 | COPY_PHASE_STRIP = NO; 495 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 496 | ENABLE_NS_ASSERTIONS = NO; 497 | ENABLE_STRICT_OBJC_MSGSEND = YES; 498 | GCC_C_LANGUAGE_STANDARD = gnu11; 499 | GCC_NO_COMMON_BLOCKS = YES; 500 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 501 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 502 | GCC_WARN_UNDECLARED_SELECTOR = YES; 503 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 504 | GCC_WARN_UNUSED_FUNCTION = YES; 505 | GCC_WARN_UNUSED_VARIABLE = YES; 506 | MTL_ENABLE_DEBUG_INFO = NO; 507 | MTL_FAST_MATH = YES; 508 | SDKROOT = macosx; 509 | SWIFT_COMPILATION_MODE = wholemodule; 510 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 511 | }; 512 | name = Release; 513 | }; 514 | 3FF298D129BCD69A00DA665F /* Debug */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 518 | ARCHS = arm64; 519 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 520 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 521 | CLANG_ENABLE_MODULES = YES; 522 | CODE_SIGN_ENTITLEMENTS = FrequencyStats/FrequencyStats.entitlements; 523 | CODE_SIGN_STYLE = Automatic; 524 | COMBINE_HIDPI_IMAGES = YES; 525 | CURRENT_PROJECT_VERSION = 4; 526 | DEVELOPMENT_ASSET_PATHS = "\"FrequencyStats/Preview Content\""; 527 | DEVELOPMENT_TEAM = YWGL7275TN; 528 | ENABLE_HARDENED_RUNTIME = YES; 529 | ENABLE_PREVIEWS = YES; 530 | GENERATE_INFOPLIST_FILE = YES; 531 | INFOPLIST_FILE = FrequencyStats/Info.plist; 532 | INFOPLIST_KEY_CFBundleDisplayName = "Frequency Stats"; 533 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 534 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 535 | LD_RUNPATH_SEARCH_PATHS = ( 536 | "$(inherited)", 537 | "@executable_path/../Frameworks", 538 | ); 539 | MACOSX_DEPLOYMENT_TARGET = 11.0; 540 | MARKETING_VERSION = 1.1.1; 541 | PRODUCT_BUNDLE_IDENTIFIER = com.bitespotatobacks.FrequencyStats; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SWIFT_EMIT_LOC_STRINGS = YES; 544 | SWIFT_OBJC_BRIDGING_HEADER = "FrequencyStats/FrequencyStats-Bridging-Header.h"; 545 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 546 | SWIFT_VERSION = 5.0; 547 | }; 548 | name = Debug; 549 | }; 550 | 3FF298D229BCD69A00DA665F /* Release */ = { 551 | isa = XCBuildConfiguration; 552 | buildSettings = { 553 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 554 | ARCHS = arm64; 555 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 556 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 557 | CLANG_ENABLE_MODULES = YES; 558 | CODE_SIGN_ENTITLEMENTS = FrequencyStats/FrequencyStats.entitlements; 559 | CODE_SIGN_STYLE = Automatic; 560 | COMBINE_HIDPI_IMAGES = YES; 561 | CURRENT_PROJECT_VERSION = 4; 562 | DEVELOPMENT_ASSET_PATHS = "\"FrequencyStats/Preview Content\""; 563 | DEVELOPMENT_TEAM = YWGL7275TN; 564 | ENABLE_HARDENED_RUNTIME = YES; 565 | ENABLE_PREVIEWS = YES; 566 | GENERATE_INFOPLIST_FILE = YES; 567 | INFOPLIST_FILE = FrequencyStats/Info.plist; 568 | INFOPLIST_KEY_CFBundleDisplayName = "Frequency Stats"; 569 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 570 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 571 | LD_RUNPATH_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "@executable_path/../Frameworks", 574 | ); 575 | MACOSX_DEPLOYMENT_TARGET = 11.0; 576 | MARKETING_VERSION = 1.1.1; 577 | PRODUCT_BUNDLE_IDENTIFIER = com.bitespotatobacks.FrequencyStats; 578 | PRODUCT_NAME = "$(TARGET_NAME)"; 579 | SWIFT_EMIT_LOC_STRINGS = YES; 580 | SWIFT_OBJC_BRIDGING_HEADER = "FrequencyStats/FrequencyStats-Bridging-Header.h"; 581 | SWIFT_VERSION = 5.0; 582 | }; 583 | name = Release; 584 | }; 585 | 3FF298E929BCD6BB00DA665F /* Debug */ = { 586 | isa = XCBuildConfiguration; 587 | buildSettings = { 588 | ARCHS = arm64; 589 | CLANG_ENABLE_MODULES = YES; 590 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 591 | CODE_SIGN_STYLE = Automatic; 592 | COMBINE_HIDPI_IMAGES = YES; 593 | CURRENT_PROJECT_VERSION = 1; 594 | DEVELOPMENT_TEAM = YWGL7275TN; 595 | ENABLE_HARDENED_RUNTIME = YES; 596 | GENERATE_INFOPLIST_FILE = YES; 597 | INFOPLIST_FILE = FrequencyStatsHelper/Info.plist; 598 | INFOPLIST_KEY_CFBundleDisplayName = FrequencyStatsHelper; 599 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 600 | LD_RUNPATH_SEARCH_PATHS = ( 601 | "$(inherited)", 602 | "@executable_path/../Frameworks", 603 | "@loader_path/../Frameworks", 604 | ); 605 | MARKETING_VERSION = 1.0; 606 | PRODUCT_BUNDLE_IDENTIFIER = com.bitespotatobacks.FrequencyStatsHelper; 607 | PRODUCT_NAME = "$(TARGET_NAME)"; 608 | SKIP_INSTALL = YES; 609 | SWIFT_EMIT_LOC_STRINGS = YES; 610 | SWIFT_OBJC_BRIDGING_HEADER = "FrequencyStatsHelper/FrequencyStatsHelper-Bridging-Header.h"; 611 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 612 | SWIFT_VERSION = 5.0; 613 | }; 614 | name = Debug; 615 | }; 616 | 3FF298EA29BCD6BB00DA665F /* Release */ = { 617 | isa = XCBuildConfiguration; 618 | buildSettings = { 619 | ARCHS = arm64; 620 | CLANG_ENABLE_MODULES = YES; 621 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 622 | CODE_SIGN_STYLE = Automatic; 623 | COMBINE_HIDPI_IMAGES = YES; 624 | CURRENT_PROJECT_VERSION = 1; 625 | DEVELOPMENT_TEAM = YWGL7275TN; 626 | ENABLE_HARDENED_RUNTIME = YES; 627 | GENERATE_INFOPLIST_FILE = YES; 628 | INFOPLIST_FILE = FrequencyStatsHelper/Info.plist; 629 | INFOPLIST_KEY_CFBundleDisplayName = FrequencyStatsHelper; 630 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 631 | LD_RUNPATH_SEARCH_PATHS = ( 632 | "$(inherited)", 633 | "@executable_path/../Frameworks", 634 | "@loader_path/../Frameworks", 635 | ); 636 | MARKETING_VERSION = 1.0; 637 | PRODUCT_BUNDLE_IDENTIFIER = com.bitespotatobacks.FrequencyStatsHelper; 638 | PRODUCT_NAME = "$(TARGET_NAME)"; 639 | SKIP_INSTALL = YES; 640 | SWIFT_EMIT_LOC_STRINGS = YES; 641 | SWIFT_OBJC_BRIDGING_HEADER = "FrequencyStatsHelper/FrequencyStatsHelper-Bridging-Header.h"; 642 | SWIFT_VERSION = 5.0; 643 | }; 644 | name = Release; 645 | }; 646 | /* End XCBuildConfiguration section */ 647 | 648 | /* Begin XCConfigurationList section */ 649 | 3FF298BC29BCD69900DA665F /* Build configuration list for PBXProject "FrequencyStats" */ = { 650 | isa = XCConfigurationList; 651 | buildConfigurations = ( 652 | 3FF298CE29BCD69A00DA665F /* Debug */, 653 | 3FF298CF29BCD69A00DA665F /* Release */, 654 | ); 655 | defaultConfigurationIsVisible = 0; 656 | defaultConfigurationName = Release; 657 | }; 658 | 3FF298D029BCD69A00DA665F /* Build configuration list for PBXNativeTarget "FrequencyStats" */ = { 659 | isa = XCConfigurationList; 660 | buildConfigurations = ( 661 | 3FF298D129BCD69A00DA665F /* Debug */, 662 | 3FF298D229BCD69A00DA665F /* Release */, 663 | ); 664 | defaultConfigurationIsVisible = 0; 665 | defaultConfigurationName = Release; 666 | }; 667 | 3FF298E829BCD6BB00DA665F /* Build configuration list for PBXNativeTarget "FrequencyStatsHelper" */ = { 668 | isa = XCConfigurationList; 669 | buildConfigurations = ( 670 | 3FF298E929BCD6BB00DA665F /* Debug */, 671 | 3FF298EA29BCD6BB00DA665F /* Release */, 672 | ); 673 | defaultConfigurationIsVisible = 0; 674 | defaultConfigurationName = Release; 675 | }; 676 | /* End XCConfigurationList section */ 677 | }; 678 | rootObject = 3FF298B929BCD69900DA665F /* Project object */; 679 | } 680 | -------------------------------------------------------------------------------- /FrequencyStats/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/.DS_Store -------------------------------------------------------------------------------- /FrequencyStats/App Settings/AppSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppSettings.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public final class AppSettings: NSObject, ObservableObject, NSWindowDelegate { 11 | public static let shared: AppSettings = AppSettings() 12 | 13 | public struct GraphColor: Identifiable { 14 | public let id = UUID() 15 | 16 | public let name: String 17 | public let color: Color 18 | } 19 | 20 | public static let graphColorDictionary: [GraphColor] = [ 21 | .init(name: "Accent Color", color: .accentColor), 22 | .init(name: "Red", color: .red), 23 | .init(name: "Orange", color: .orange), 24 | .init(name: "Yellow", color: .yellow), 25 | .init(name: "Green", color: .green), 26 | .init(name: "Blue", color: .blue), 27 | .init(name: "Purple", color: .purple), 28 | .init(name: "Pink", color: .pink), 29 | .init(name: "Solid", color: Color("primary")) 30 | ] 31 | 32 | public var updateInterval: TimeInterval 33 | public var graphColor: GraphColor 34 | 35 | // public var showGraphics: Bool = true 36 | 37 | private var appSettingsWindow: NSWindow? 38 | private var windowIsVisible: Bool = false 39 | 40 | // MARK: - Lifecycle 41 | 42 | override init() { 43 | let interval = UserDefaults.standard.float(forKey: "UpdateInterval") 44 | let colorString = UserDefaults.standard.string(forKey: "GraphColor") 45 | 46 | if interval != 0 { 47 | self.updateInterval = TimeInterval(interval) 48 | } else { 49 | self.updateInterval = 1 50 | } 51 | 52 | if let colorString = colorString { 53 | self.graphColor = AppSettings.graphColorDictionary.first(where: { $0.name == colorString }) ?? AppSettings.graphColorDictionary[0] 54 | } else { 55 | self.graphColor = AppSettings.graphColorDictionary[0] 56 | } 57 | 58 | let appSettingsView = NSHostingView(rootView: AppSettingsView(updateInterval: self.updateInterval, graphColor: colorString ?? "Accent Color")) 59 | let appSettingsWindow = NSWindow() 60 | 61 | appSettingsWindow.titlebarAppearsTransparent = true 62 | appSettingsWindow.styleMask = [.closable, .miniaturizable, .titled, .fullSizeContentView] 63 | appSettingsWindow.contentView = appSettingsView 64 | appSettingsWindow.isReleasedWhenClosed = false 65 | 66 | 67 | self.appSettingsWindow = appSettingsWindow 68 | 69 | super.init() 70 | 71 | self.appSettingsWindow?.delegate = self 72 | } 73 | 74 | // MARK: - Methods 75 | 76 | public final func setUpdateInterval(to interval: TimeInterval) { 77 | SampleManager.shared.stopSampleTimer() 78 | 79 | self.updateInterval = interval 80 | 81 | UserDefaults.standard.set(interval, forKey: "UpdateInterval") 82 | 83 | SampleManager.shared.startSampleTimer() 84 | } 85 | 86 | public final func setGraphColor(to colorString: String) { 87 | SampleManager.shared.stopSampleTimer() 88 | 89 | if let color = AppSettings.graphColorDictionary.first(where: { $0.name == colorString }) { 90 | self.graphColor = color 91 | 92 | UserDefaults.standard.set(colorString, forKey: "GraphColor") 93 | } else { 94 | self.graphColor = AppSettings.graphColorDictionary[0] 95 | 96 | UserDefaults.standard.set("Accent Color", forKey: "GraphColor") 97 | } 98 | 99 | SampleManager.shared.startSampleTimer() 100 | } 101 | 102 | public final func showWindow() { 103 | self.appSettingsWindow?.makeKeyAndOrderFront(self) 104 | 105 | if !self.windowIsVisible { 106 | self.appSettingsWindow?.center() 107 | } 108 | 109 | self.windowIsVisible = true 110 | } 111 | 112 | public func windowWillClose(_ notification: Notification) { 113 | self.windowIsVisible = false 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /FrequencyStats/App Settings/AppSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppSettingsView.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension Bundle { 11 | var releaseVersionNumber: String? { 12 | return infoDictionary?["CFBundleShortVersionString"] as? String 13 | } 14 | var buildVersionNumber: String? { 15 | return infoDictionary?["CFBundleVersion"] as? String 16 | } 17 | } 18 | 19 | public struct AppSettingsView: View { 20 | @State public var updateInterval: TimeInterval 21 | @State public var graphColor: String 22 | 23 | public var body: some View { 24 | ZStack { 25 | VisualEffectView(material: .sidebar, blendingMode: .behindWindow) 26 | .ignoresSafeArea(.all) 27 | 28 | VStack(spacing: 0) { 29 | VStack { 30 | Image(nsImage: NSImage(named: "AppIcon") ?? NSImage()) 31 | .resizable() 32 | .frame(width: 128, height: 128) 33 | 34 | Text("**Frequency Stats** v\(Bundle.main.releaseVersionNumber ?? "?")") 35 | .font(.title2) 36 | .padding(.bottom, 2) 37 | 38 | Text(NSLocalizedString("appsettings.creditString", comment: "")) 39 | .foregroundColor(.secondary) 40 | 41 | if let url = URL(string: "https://github.com/BitesPotatoBacks/FrequencyStats") { 42 | Link(NSLocalizedString("appsettings.githubVisitString", comment: ""), destination: url) 43 | } 44 | } 45 | .padding(.bottom) 46 | 47 | Divider() 48 | 49 | VStack { 50 | // sample interval setting 51 | HStack { 52 | Text(NSLocalizedString("appsettings.updateIntervalString", comment: "")) 53 | Spacer() 54 | 55 | Picker(selection: $updateInterval, content: { 56 | Text("0.5 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 57 | .tag(0.5) 58 | Text("1 \(NSLocalizedString("appsettings.secondString", comment: ""))") 59 | .tag(1.0) 60 | Text("2 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 61 | .tag(2.0) 62 | Text("3 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 63 | .tag(3.0) 64 | Text("4 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 65 | .tag(4.0) 66 | Text("6 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 67 | .tag(6.0) 68 | Text("8 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 69 | .tag(8.0) 70 | Text("10 \(NSLocalizedString("appsettings.pluralSecondString", comment: ""))") 71 | .tag(10.0) 72 | }, label: { 73 | }) 74 | .pickerStyle(.menu) 75 | .frame(width: 120) 76 | } 77 | 78 | // graph color setting 79 | HStack { 80 | Text(NSLocalizedString("appsettings.graphColorString", comment: "")) 81 | Spacer() 82 | 83 | Image(systemName: "circle.fill") 84 | .foregroundColor(AppSettings.graphColorDictionary.first(where: { $0.name == graphColor })?.color ?? .gray) 85 | 86 | Picker(selection: $graphColor, content: { 87 | ForEach(AppSettings.graphColorDictionary) { color in 88 | Text(color.name).tag(color.name) 89 | } 90 | }, label: { 91 | }) 92 | .pickerStyle(.menu) 93 | .frame(width: 120) 94 | } 95 | } 96 | .padding(20) 97 | 98 | Divider() 99 | 100 | Button(NSLocalizedString("appsettings.quitAppString", comment: "")) { 101 | NSApp.terminate(nil) 102 | } 103 | .padding([.bottom, .top]) 104 | } 105 | .frame(width: 300) 106 | .onChange(of: updateInterval, perform: { value in 107 | AppSettings.shared.setUpdateInterval(to: value) 108 | }) 109 | .onChange(of: graphColor, perform: { value in 110 | AppSettings.shared.setGraphColor(to: value) 111 | }) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /FrequencyStats/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | private var dropwdownWindow: NSWindow? 13 | private var statusBarItem: NSStatusItem! 14 | 15 | private var eventMonitor: Any? 16 | private var dropdownVisible: Bool = false 17 | 18 | override init() { 19 | super.init() 20 | 21 | SampleManager.shared.connectToHelper() 22 | SampleManager.shared.startSampleTimer() 23 | } 24 | 25 | static func main() { 26 | let app = NSApplication.shared 27 | let delegate = AppDelegate() 28 | app.delegate = delegate 29 | app.run() 30 | } 31 | 32 | func applicationDidFinishLaunching(_ notification: Notification) { 33 | let dropdownView = NSHostingView(rootView: ClusterListView()) 34 | dropdownView.wantsLayer = true 35 | dropdownView.layer?.masksToBounds = true 36 | dropdownView.layer?.cornerCurve = .continuous 37 | dropdownView.layer?.cornerRadius = 20 38 | 39 | let dropdownWindow = NSWindow() 40 | dropdownWindow.titleVisibility = .hidden 41 | dropdownWindow.styleMask.remove(.titled) 42 | dropdownWindow.styleMask.insert(.resizable) 43 | dropdownWindow.backgroundColor = .clear 44 | dropdownWindow.contentView = dropdownView 45 | dropdownWindow.hasShadow = true 46 | dropdownWindow.isReleasedWhenClosed = false 47 | dropdownWindow.level = .statusBar 48 | 49 | let statusBarItem = NSStatusBar.system.statusItem(withLength: 24) 50 | statusBarItem.behavior = [.terminationOnRemoval, .removalAllowed] 51 | 52 | if let statusBarButton = statusBarItem.button { 53 | statusBarButton.image = NSImage(named: "ItemIcon") 54 | 55 | statusBarButton.action = #selector(toggleDropdown(sender:)) 56 | statusBarButton.target = self 57 | } 58 | 59 | let eventMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.rightMouseDown, .leftMouseDown], handler: { event in 60 | if self.dropdownVisible { 61 | self.dropwdownWindow?.close() 62 | 63 | self.dropdownVisible = false 64 | } 65 | }) 66 | 67 | self.dropwdownWindow = dropdownWindow 68 | self.statusBarItem = statusBarItem 69 | self.eventMonitor = eventMonitor 70 | } 71 | 72 | func applicationWillTerminate(_ notification: Notification) { 73 | if self.eventMonitor != nil { 74 | NSEvent.removeMonitor(self.eventMonitor!) 75 | self.eventMonitor = nil 76 | } 77 | } 78 | 79 | @objc private func toggleDropdown(sender: Any?) { 80 | if !self.dropdownVisible { 81 | guard let itemFrame = NSApp.currentEvent?.window?.frame, let windowFrame = self.dropwdownWindow?.frame else { 82 | return 83 | } 84 | 85 | let itemOrigin = itemFrame.origin 86 | let itemSize = itemFrame.size 87 | 88 | let windowSize = windowFrame.size 89 | let windowTopLeftPos = NSPoint(x: itemOrigin.x + itemSize.width / 2 - windowSize.width / 2, y: itemOrigin.y - 1) 90 | 91 | self.dropwdownWindow?.setFrameTopLeftPoint(windowTopLeftPos) 92 | self.dropwdownWindow?.makeKeyAndOrderFront(self) 93 | 94 | self.dropdownVisible = true 95 | } else { 96 | self.dropwdownWindow?.close() 97 | 98 | self.dropdownVisible = false 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "FrequencyStatsIcon_16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "FrequencyStatsIcon_16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "FrequencyStatsIcon_32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "FrequencyStatsIcon_32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "FrequencyStatsIcon_128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "FrequencyStatsIcon_128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "FrequencyStatsIcon_256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "FrequencyStatsIcon_256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "FrequencyStatsIcon_512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "FrequencyStatsIcon_512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_128.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_128@2x.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_16.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_16@2x.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_256.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_256@2x.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_32.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_32@2x.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_512.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/AppIcon.appiconset/FrequencyStatsIcon_512@2x.png -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/ItemIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ItemIcon.pdf", 5 | "idiom" : "mac" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/ItemIcon.imageset/ItemIcon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/FrequencyStats/Assets.xcassets/ItemIcon.imageset/ItemIcon.pdf -------------------------------------------------------------------------------- /FrequencyStats/Assets.xcassets/primary.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "universal", 6 | "reference" : "labelColor" 7 | }, 8 | "idiom" : "universal" 9 | } 10 | ], 11 | "info" : { 12 | "author" : "xcode", 13 | "version" : 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FrequencyStats/FrequencyStats-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "IOReport.h" 6 | -------------------------------------------------------------------------------- /FrequencyStats/FrequencyStats.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FrequencyStats/IOReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // IOReport.h 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | #ifndef IOReport_h 9 | #define IOReport_h 10 | 11 | #import 12 | 13 | enum { 14 | kIOReportIterOk 15 | }; 16 | 17 | typedef struct IOReportSubscriptionCustom* IOReportSubscriptionCustomRef; 18 | 19 | typedef struct IOReportSubscriptionRef* IOReportSubscriptionRef; 20 | typedef CFDictionaryRef IOReportSampleRef; 21 | 22 | extern IOReportSubscriptionRef IOReportCreateSubscription(void* a, CFMutableDictionaryRef desiredChannels, CFMutableDictionaryRef* subbedChannels, uint64_t channel_id, CFTypeRef b); 23 | extern CFDictionaryRef IOReportCreateSamples(IOReportSubscriptionRef iorsub, CFMutableDictionaryRef subbedChannels, CFTypeRef a); 24 | extern CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef prev, CFDictionaryRef current, CFTypeRef a); 25 | 26 | extern CFMutableDictionaryRef IOReportCopyChannelsInGroup(NSString*, NSString*, uint64_t, uint64_t, uint64_t); 27 | 28 | typedef int (^ioreportiterateblock)(IOReportSampleRef ch); 29 | extern void IOReportIterate(CFDictionaryRef samples, ioreportiterateblock); 30 | 31 | extern int IOReportStateGetCount(CFDictionaryRef); 32 | extern uint64_t IOReportStateGetResidency(CFDictionaryRef, int); 33 | extern uint64_t IOReportArrayGetValueAtIndex(CFDictionaryRef, int); 34 | extern NSString* IOReportChannelGetChannelName(CFDictionaryRef); 35 | extern NSString* IOReportChannelGetSubGroup(CFDictionaryRef); 36 | extern NSString* IOReportStateGetNameForIndex(CFDictionaryRef, int); 37 | 38 | extern void IOReportMergeChannels(CFMutableDictionaryRef, CFMutableDictionaryRef, CFTypeRef); 39 | extern NSString* IOReportChannelGetGroup(CFDictionaryRef); 40 | 41 | #endif /* IOReport_h */ 42 | -------------------------------------------------------------------------------- /FrequencyStats/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSUIElement 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FrequencyStats/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Logger { 11 | public static func log(_ message: String, isError: Bool = false, class className: AnyClass? = nil, function: String = #function, line: Int = #line) { 12 | #if DEBUG 13 | let stringStub = isError ? " (ERROR) " : " " 14 | 15 | if let className = className { 16 | print("***\(stringStub)[\(line):\(NSStringFromClass(className)).\(function)] \(message) ***") 17 | return 18 | } 19 | 20 | print("***\(stringStub)[\(line):\(function)] \(message) ***") 21 | return 22 | #endif 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FrequencyStats/Logic/Cluster.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cluster.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct DvfsState { 11 | public let nominalFrequency: UInt32 12 | public var residency: CGFloat 13 | 14 | public var rawResidency: UInt64 = 0 15 | } 16 | 17 | public struct Core { 18 | public let coreKey: String 19 | 20 | public var dvfsStates: (array: [DvfsState], rawSums: UInt64) 21 | 22 | public var frequency: CGFloat 23 | } 24 | 25 | public struct Cluster { 26 | public static let maximumFrequencyCount = 72 27 | public static let maximumCoreFrequencyCount = 20 28 | 29 | public let clusterKey: String 30 | 31 | public var cores: [Core] 32 | public var dvfsStates: (array: [DvfsState], rawSums: UInt64) 33 | 34 | public var frequency: [CGFloat] 35 | 36 | public mutating func resetRawSums() { 37 | self.dvfsStates.rawSums = 0 38 | 39 | for i in self.cores.indices { 40 | self.cores[i].dvfsStates.rawSums = 0 41 | } 42 | } 43 | 44 | public var clusterType: String { 45 | if self.clusterKey.contains("ECPU") { 46 | return NSLocalizedString("efficiencyString", comment: "") 47 | } else if self.clusterKey.contains("PCPU") { 48 | return NSLocalizedString("performanceString", comment: "") 49 | } else if self.clusterKey.contains("GPU") { 50 | return NSLocalizedString("graphicsString", comment: "") 51 | } 52 | 53 | return NSLocalizedString("unknownString", comment: "") 54 | } 55 | 56 | public var clusterPrefix: String { 57 | if self.clusterKey.contains("ECPU") { 58 | return "E-" 59 | } else if self.clusterKey.contains("PCPU") { 60 | return "P-" 61 | } 62 | 63 | return "" 64 | } 65 | 66 | public var clusterImage: String { 67 | if self.clusterKey.contains("ECPU") { 68 | return "snowflake" 69 | } else if self.clusterKey.contains("PCPU") { 70 | return "flame" 71 | } else if self.clusterKey.contains("GPU") { 72 | return "display" 73 | } 74 | 75 | return "cpu" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /FrequencyStats/Logic/SampleManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleManager.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | public final class SampleManager: ObservableObject { 12 | public static let shared = SampleManager() 13 | 14 | @Published public var sampledClusters: [Cluster] 15 | 16 | private let systemConstants: SystemConstants 17 | private var helperConnection: NSXPCConnection? 18 | private var helperProxy: FrequencyStatsHelperProtocol? 19 | 20 | private var sampleTimer: Timer? 21 | 22 | // MARK: - Lifecycle 23 | 24 | public init() { 25 | Logger.log("Initializing", class: SampleManager.self) 26 | 27 | let systemConstants = SystemConstants() 28 | 29 | // for every CPU and GPU cluster key, make a new cluster 30 | var clusters: [Cluster] = [] 31 | 32 | for i in systemConstants.clusterKeys.indices { 33 | let key: String = systemConstants.clusterKeys[i] 34 | 35 | var dvfsStates: [DvfsState]? 36 | 37 | if key.contains("ECPU") { 38 | dvfsStates = systemConstants.dvfsStateDictionary["ECPU"] 39 | } else if key.contains("PCPU") { 40 | dvfsStates = systemConstants.dvfsStateDictionary["PCPU"] 41 | } else if key.contains("GPU") { 42 | dvfsStates = systemConstants.dvfsStateDictionary["GPU"] 43 | } 44 | 45 | guard let dvfsStates = dvfsStates else { 46 | fatalError("No Voltage States available") 47 | } 48 | 49 | var cores: [Core] = [] 50 | 51 | // if the cluster is not a GPU, for every core in the corresponding index of our core counts, make a new core 52 | if key != "GPUPH" { 53 | let coreKey: String = systemConstants.coreKeys[i] // This is solution of the E-core frequency problem 54 | for coreIndex in 0..= residenceIndex { 138 | let value = value[residenceIndex] 139 | 140 | clusters[clusterIndex].dvfsStates.array[residenceIndex].rawResidency = value // add to state residency 141 | clusters[clusterIndex].dvfsStates.rawSums += value // increment rawSums 142 | } 143 | 144 | var frequency: CGFloat = 0 145 | 146 | // format 147 | for dvfmIndex in clusters[clusterIndex].dvfsStates.array.indices { 148 | autoreleasepool { 149 | let rawSums = clusters[clusterIndex].dvfsStates.rawSums 150 | let nominalFrequency = clusters[clusterIndex].dvfsStates.array[dvfmIndex].nominalFrequency 151 | let rawResidency = clusters[clusterIndex].dvfsStates.array[dvfmIndex].rawResidency 152 | 153 | let residency = CGFloat(rawResidency) / CGFloat(rawSums) 154 | 155 | clusters[clusterIndex].dvfsStates.array[dvfmIndex].residency = residency 156 | 157 | frequency += residency * CGFloat(nominalFrequency) 158 | } 159 | } 160 | 161 | if frequency.isNaN { 162 | frequency = 0 163 | } 164 | 165 | clusters[clusterIndex].frequency.append(frequency) 166 | 167 | // handle frequency history 168 | if clusters[clusterIndex].frequency.count > Cluster.maximumFrequencyCount { 169 | clusters[clusterIndex].frequency.removeFirst() 170 | } 171 | 172 | } else { // if is core 173 | if let coreIndex = clusters[clusterIndex].cores.firstIndex(where: { $0.coreKey == key }) { 174 | 175 | for residenceIndex in value.indices where clusters[clusterIndex].cores[coreIndex].dvfsStates.array.count - 1 >= residenceIndex { 176 | let value = value[residenceIndex] 177 | 178 | clusters[clusterIndex].cores[coreIndex].dvfsStates.array[residenceIndex].rawResidency = value 179 | clusters[clusterIndex].cores[coreIndex].dvfsStates.rawSums += value 180 | } 181 | 182 | clusters[clusterIndex].cores[coreIndex].frequency = 0 183 | 184 | // format 185 | for dvfmIndex in clusters[clusterIndex].cores[coreIndex].dvfsStates.array.indices { 186 | autoreleasepool { 187 | let rawSums = clusters[clusterIndex].cores[coreIndex].dvfsStates.rawSums 188 | let nominalFrequency = clusters[clusterIndex].cores[coreIndex].dvfsStates.array[dvfmIndex].nominalFrequency 189 | let rawResidency = clusters[clusterIndex].cores[coreIndex].dvfsStates.array[dvfmIndex].rawResidency 190 | 191 | let residency = CGFloat(rawResidency) / CGFloat(rawSums) 192 | 193 | clusters[clusterIndex].cores[coreIndex].dvfsStates.array[dvfmIndex].residency = residency 194 | 195 | clusters[clusterIndex].cores[coreIndex].frequency += residency * CGFloat(nominalFrequency) 196 | } 197 | } 198 | 199 | if clusters[clusterIndex].cores[coreIndex].frequency.isNaN { 200 | clusters[clusterIndex].cores[coreIndex].frequency = 0 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | DispatchQueue.main.async { 208 | self.sampledClusters = clusters 209 | } 210 | 211 | Logger.log("Added new metrics", class: SampleManager.self) 212 | }) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /FrequencyStats/Logic/SystemConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemConstants.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Data { 11 | var uint32: UInt32 { 12 | let array = self.withUnsafeBytes { $0.load(as: UInt32.self) } 13 | return UInt32(bigEndian: array) 14 | } 15 | } 16 | 17 | public struct SystemConstants { 18 | public static let voltageStates: [String : CFString] = ["ECPU" : "voltage-states1-sram" as CFString, 19 | "PCPU" : "voltage-states5-sram" as CFString, 20 | "GPU" : "voltage-states9" as CFString] 21 | public var clusterKeys: [String] = [] 22 | public var coreKeys: [String] = [] 23 | public var clusterCoreCounts: [Int] = [] 24 | public var dvfsStateDictionary: [String : [DvfsState]] = [:] 25 | 26 | public lazy var systemModel: String = { 27 | var size: Int = 0 28 | let ret = sysctlbyname("hw.model", nil, &size, nil, 0) 29 | 30 | guard ret == 0 else { 31 | return "Unknown SoC" 32 | } 33 | 34 | var string = [CChar](repeating: 0, count: Int(size)) 35 | 36 | sysctlbyname("hw.model", &string, &size, nil, 0) 37 | 38 | let modelname = String(cString: string).lowercased() 39 | 40 | guard !modelname.contains("virtual") else { 41 | fatalError("This app cannot be run on a VM") 42 | } 43 | 44 | return modelname 45 | }() 46 | 47 | // MARK: - Lifecycle 48 | 49 | public init() { 50 | guard let service = IOServiceMatching("AppleARMIODevice") else { 51 | fatalError("Failed to access AppleARMIODevice") 52 | } 53 | 54 | var iterator: io_iterator_t = 0 55 | 56 | if #available(macOS 12.0, *) { 57 | guard IOServiceGetMatchingServices(kIOMainPortDefault, service, &iterator) == kIOReturnSuccess else { 58 | return 59 | } 60 | } else { 61 | guard IOServiceGetMatchingServices(kIOMasterPortDefault, service, &iterator) == kIOReturnSuccess else { 62 | return 63 | } 64 | } 65 | 66 | while case let entry = IOIteratorNext(iterator), entry != IO_OBJECT_NULL { 67 | guard self.clusterCoreCounts.isEmpty, self.dvfsStateDictionary.isEmpty else { 68 | break 69 | } 70 | 71 | var properties: Unmanaged? = nil 72 | 73 | IORegistryEntryCreateCFProperties(entry, &properties, kCFAllocatorDefault, 0) 74 | 75 | guard let array = properties?.takeUnretainedValue() as? [CFString: Any] else { 76 | break 77 | } 78 | 79 | for property in array { 80 | self.getCoreCounts(from: property) 81 | self.getDvfsStates(from: property) 82 | } 83 | } 84 | 85 | self.createKeys() 86 | } 87 | 88 | // MARK: - Mutating Funcs 89 | 90 | private mutating func createKeys() { 91 | var coreKeys: [String] = [] 92 | var clusterKeys: [String] = [] 93 | 94 | if self.systemModel.contains("pro") || self.systemModel.contains("max") { 95 | clusterKeys = ["ECPU","PCPU","PCPU1","GPUPH"] 96 | 97 | coreKeys = ["ECPU0","PCPU0","PCPU1"] 98 | } else if self.systemModel.contains("ultra") { 99 | clusterKeys = ["DIE_0_ECPU","DIE_1_ECPU","DIE_0_PCPU","DIE_0_PCPU1","DIE_1_PCPU","DIE_1_PCPU1","GPUPH"] 100 | 101 | coreKeys = ["DIE_0_ECPU_CPU","DIE_1_ECPU_CPU","DIE_0_PCPU_CPU","DIE_0_PCPU1_CPU","DIE_1_PCPU_CPU","DIE_1_PCPU1_CPU"] 102 | } else { 103 | clusterKeys = ["ECPU","PCPU","GPUPH"] 104 | 105 | coreKeys = ["ECPU","PCPU"] 106 | } 107 | 108 | Logger.log("Got new cluster key array: \(clusterKeys)") 109 | Logger.log("Got new core key array: \(coreKeys)") 110 | 111 | self.coreKeys = coreKeys 112 | self.clusterKeys = clusterKeys 113 | } 114 | 115 | private mutating func getCoreCounts(from property: (key: CFString, value: Any)) { 116 | guard property.key as String == "clusters", let data = property.value as? Data else { 117 | return 118 | } 119 | 120 | var clusterCores: [Int] = [] 121 | 122 | for i in stride(from: 0, to: data.count, by: 4) { 123 | clusterCores.append(Int(data[i])) 124 | 125 | if self.systemModel.contains("ultra") { 126 | clusterCores.append(Int(data[i])) 127 | } 128 | } 129 | 130 | Logger.log("Got new core count array: \(clusterCores)") 131 | 132 | self.clusterCoreCounts = clusterCores 133 | } 134 | 135 | private mutating func getDvfsStates(from property: (key: CFString, value: Any)) { 136 | guard 137 | let state = SystemConstants.voltageStates.first(where: { $0.value == property.key }), 138 | let data = property.value as? Data 139 | else { 140 | return 141 | } 142 | 143 | var dvfsStates: [DvfsState] = [] 144 | 145 | for i in stride(from: 0, to: data.count, by: 8) { 146 | let bytes = [data[i+3], data[i+2], data[i+1], data[i]] 147 | let data = Data(bytes) 148 | 149 | let dvfsState = DvfsState(nominalFrequency: data.uint32 / 1_000_000, residency: 0) 150 | 151 | dvfsStates.append(dvfsState) 152 | } 153 | 154 | if state.key == "GPU" { 155 | dvfsStates.removeFirst() // doing this because the first GPU voltage state is 0 156 | } 157 | 158 | Logger.log("Got new dvfs stable: \(dvfsStates)") 159 | 160 | self.dvfsStateDictionary.updateValue(dvfsStates, forKey: state.key) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /FrequencyStats/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FrequencyStats/Views/ClusterItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClusterItemView.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct ClusterItem: View { 11 | public let cluster: Cluster 12 | 13 | @State private var showCores: Bool = false 14 | @State private var showDvfm: Bool = false 15 | 16 | private let roundedRectangle = RoundedRectangle(cornerRadius: 12, style: .continuous) 17 | 18 | public var body: some View { 19 | ZStack { 20 | VisualEffectView(material: .popover, blendingMode: .behindWindow) 21 | 22 | VStack(spacing: 0) { 23 | // header 24 | HStack(spacing: 4) { 25 | if #available(macOS 12.0, *) { 26 | Image(systemName: cluster.clusterImage) 27 | .symbolRenderingMode(.monochrome) 28 | } else { 29 | Image(systemName: cluster.clusterImage) 30 | } 31 | Text(cluster.clusterType) 32 | 33 | Spacer() 34 | } 35 | .padding([.leading, .trailing, .top], 10) 36 | .foregroundColor(.secondary) 37 | .font(.headline) 38 | 39 | ClusterItemGraph(maxFrequency: cluster.dvfsStates.array.last?.nominalFrequency, frequencies: cluster.frequency) 40 | 41 | // cluster metrics 42 | HStack { 43 | Text(NSLocalizedString("frequencyString", comment: "")) 44 | Spacer() 45 | 46 | if let freq = cluster.frequency.last { 47 | Text(String(format: "%.f MHz", freq)) 48 | .foregroundColor(Color("primary").opacity(0.7)) 49 | } else { 50 | Text("... MHz") 51 | .foregroundColor(Color("primary").opacity(0.7)) 52 | } 53 | 54 | Button(action: { 55 | showDvfm.toggle() 56 | }, label: { 57 | Image(systemName: "tablecells") 58 | .popover(isPresented: $showDvfm, arrowEdge: .trailing, content: { 59 | dvfmListView 60 | }) 61 | }) 62 | .buttonStyle(.borderless) 63 | } 64 | .padding(cluster.cores.isEmpty ? [.leading, .trailing, .bottom] : [.leading, .trailing], 10) 65 | 66 | // core metrics 67 | if !cluster.cores.isEmpty { 68 | Button(action: { 69 | showCores.toggle() 70 | }, label: { 71 | HStack { 72 | Text(NSLocalizedString("coreMetricsString", comment: "")) 73 | Spacer() 74 | Image(systemName: "chevron.right") 75 | .font(.body.bold()) 76 | .foregroundColor(.secondary) 77 | .popover(isPresented: $showCores, arrowEdge: .trailing, content: { 78 | coreListView 79 | }) 80 | } 81 | .padding(10) 82 | }) 83 | .buttonStyle(ClusterItemButtonStyle()) 84 | } 85 | } 86 | } 87 | .overlay(roundedRectangle.stroke(Color("primary").opacity(0.2), style: StrokeStyle(lineWidth: 1))) 88 | .clipShape(roundedRectangle) 89 | .shadow(color: .black.opacity(0.3), radius: 3, y: 2) 90 | } 91 | 92 | @State private var showTimeOfResidency: Bool = false 93 | 94 | @ViewBuilder private var dvfmListView: some View { 95 | VStack(spacing: 10) { 96 | Text(NSLocalizedString("distributionString", comment: "")) 97 | .foregroundColor(Color("primary")) 98 | .font(.caption2) 99 | 100 | Picker("", selection: $showTimeOfResidency, content: { 101 | Text(NSLocalizedString("percentString", comment: "")) 102 | .tag(false) 103 | Text(NSLocalizedString("timeString", comment: "")) 104 | .tag(true) 105 | }) 106 | .pickerStyle(.segmented) 107 | 108 | ForEach(cluster.dvfsStates.array, id:\.nominalFrequency) { state in 109 | HStack { 110 | Text(String(format: "%u MHz", state.nominalFrequency)) 111 | .foregroundColor(Color("primary")) 112 | Spacer() 113 | 114 | ZStack { 115 | if !showTimeOfResidency { 116 | if state.residency == 0 { 117 | Text("0 %") 118 | } else { 119 | Text(String(format: "%.2f %%", state.residency * 100)) 120 | } 121 | } else { 122 | Text(String(format: "%llu ms", UInt64(state.residency * (AppSettings.shared.updateInterval * 1000)))) 123 | } 124 | } 125 | .foregroundColor(Color("primary").opacity(0.7)) 126 | } 127 | } 128 | } 129 | .frame(width: 175) 130 | .padding(12) 131 | } 132 | 133 | @ViewBuilder private var coreListView: some View { 134 | VStack(spacing: 10) { 135 | Text("\(cluster.clusterPrefix)Cores x\(cluster.cores.count)") 136 | .foregroundColor(Color("primary")) 137 | .font(.caption2) 138 | 139 | let cores = cluster.cores.sorted(by: { $0.coreKey < $1.coreKey }) 140 | 141 | ForEach(cores.indices, id: \.self) { i in 142 | HStack { 143 | Text("\(cluster.clusterPrefix)Core #\(i)") 144 | .foregroundColor(Color("primary")) 145 | Spacer() 146 | Text(String(format: "%.f MHz", cores[i].frequency)) 147 | .foregroundColor(Color("primary").opacity(0.7)) 148 | 149 | ClusterItemCoreMeter(maxFrequency: cluster.dvfsStates.array.last?.nominalFrequency, frequency: cores[i].frequency) 150 | } 151 | } 152 | } 153 | .frame(width: 200) 154 | .padding(12) 155 | } 156 | } 157 | 158 | public struct ClusterItemButtonStyle: ButtonStyle { 159 | @State private var isHovering: Bool = false 160 | 161 | public func makeBody(configuration: Configuration) -> some View { 162 | configuration.label 163 | .background( 164 | RoundedRectangle(cornerRadius: 6, style: .continuous) 165 | .foregroundColor(isHovering && !configuration.isPressed ? Color("primary").opacity(0.125) : .clear) 166 | .padding(6) 167 | ) 168 | .onHover { hover in 169 | withAnimation(.linear(duration: 0.05)) { 170 | isHovering = hover 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /FrequencyStats/Views/ClusterItemCoreMeter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClusterItemCoreMeter.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct ClusterItemCoreMeter: View { 11 | public let maxFrequency: UInt32? 12 | public var frequency: CGFloat 13 | 14 | private let width: CGFloat = 40 15 | private let capsule = Capsule(style: .continuous) 16 | 17 | public var body: some View { 18 | ZStack(alignment: .leading) { 19 | capsule 20 | .foregroundColor(Color("primary").opacity(0.1)) 21 | 22 | if let maxFrequency = maxFrequency { 23 | capsule 24 | .frame(width: frequency / CGFloat(maxFrequency) * width) 25 | .foregroundColor(AppSettings.shared.graphColor.color) 26 | } 27 | } 28 | .frame(width: width, height: 4) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /FrequencyStats/Views/ClusterItemGraph.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GraphView.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/12/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct ClusterItemGraph: View { 11 | public let maxFrequency: UInt32? 12 | public var frequencies: [CGFloat] 13 | 14 | private let height: CGFloat = 45 15 | private let roundedRectangle = RoundedRectangle(cornerRadius: 4, style: .continuous) 16 | 17 | @State private var showMaxFrequency: Bool = false 18 | 19 | public var body: some View { 20 | ZStack(alignment: .topTrailing) { 21 | roundedRectangle 22 | .foregroundColor(Color("primary").opacity(0.05)) 23 | .padding(8) 24 | 25 | AppSettings.shared.graphColor.color 26 | .frame(height: height) 27 | .mask(graphView) 28 | .padding(12) 29 | 30 | if let maxFrequency = maxFrequency, showMaxFrequency { 31 | Text(String(format: "%u", maxFrequency)) 32 | .font(.caption2) 33 | .foregroundColor(.secondary) 34 | .padding(12) 35 | } 36 | 37 | } 38 | .clipShape(roundedRectangle) 39 | .onHover { hover in 40 | withAnimation(.linear(duration: 0.1)) { 41 | showMaxFrequency = hover 42 | } 43 | } 44 | } 45 | 46 | @ViewBuilder private var graphView: some View { 47 | Path { path in 48 | if let maxFrequency = maxFrequency { 49 | for i in frequencies.indices { 50 | let frequency = frequencies[i] 51 | let y: CGFloat = height - (frequency / CGFloat(maxFrequency) * height) 52 | let x: CGFloat = CGFloat(i) * 3 53 | 54 | path.addLines([CGPoint(x: x, y: y), CGPoint(x: x, y: height)]) 55 | } 56 | } 57 | } 58 | .strokedPath(StrokeStyle(lineWidth: 2, lineCap: .round)) 59 | .frame(height: height) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /FrequencyStats/Views/ClusterListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClusterListView.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/13/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct ClusterListView: View { 11 | @StateObject private var sampleManager: SampleManager 12 | 13 | init() { 14 | self._sampleManager = .init(wrappedValue: .shared) 15 | } 16 | 17 | public var body: some View { 18 | ZStack { 19 | VisualEffectView(material: .popover, blendingMode: .behindWindow) 20 | 21 | VStack(spacing: 10) { 22 | ForEach(sampleManager.sampledClusters, id:\.clusterKey) { cluster in 23 | ClusterItem(cluster: cluster) 24 | } 25 | 26 | HStack { 27 | Button(action: { 28 | NSApp.terminate(nil) 29 | }, label: { 30 | 31 | Image(systemName: "xmark.circle.fill") 32 | }) 33 | .buttonStyle(.borderless) 34 | 35 | Spacer() 36 | 37 | Button(action: { 38 | AppSettings.shared.showWindow() 39 | }, label: { 40 | 41 | Image(systemName: "gearshape.fill") 42 | }) 43 | .buttonStyle(.borderless) 44 | } 45 | } 46 | .padding(10) 47 | .frame(width: 258) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /FrequencyStats/Views/VisualEffectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisualEffectView.swift 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct VisualEffectView: NSViewRepresentable { 11 | public let material: NSVisualEffectView.Material 12 | public let blendingMode: NSVisualEffectView.BlendingMode 13 | 14 | public func makeNSView(context: Context) -> NSVisualEffectView { 15 | let visualEffectView = NSVisualEffectView() 16 | 17 | visualEffectView.material = self.material 18 | visualEffectView.blendingMode = self.blendingMode 19 | visualEffectView.state = NSVisualEffectView.State.active 20 | 21 | return visualEffectView 22 | } 23 | 24 | public func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) { 25 | visualEffectView.material = self.material 26 | visualEffectView.blendingMode = self.blendingMode 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FrequencyStatsHelper/FrequencyStatsHelper-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "IOReport.h" 6 | -------------------------------------------------------------------------------- /FrequencyStatsHelper/FrequencyStatsHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FrequencyStatsHelper.swift 3 | // FrequencyStatsHelper 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public class FrequencyStatsHelper: NSObject, FrequencyStatsHelperProtocol { 11 | private var subscription: IOReportSubscriptionRef? 12 | private var subbedChannels: CFMutableDictionary? 13 | private var samples: [CFDictionary] = [] 14 | 15 | @objc public func createSubscription() { 16 | var subbbedChannels: Unmanaged? 17 | 18 | let cpuStatsChannel: Unmanaged = IOReportCopyChannelsInGroup("CPU Stats", nil, 0, 0, 0) 19 | let gpuStatsChannel: Unmanaged = IOReportCopyChannelsInGroup("GPU Stats", nil, 0, 0, 0) 20 | 21 | IOReportMergeChannels(cpuStatsChannel.takeUnretainedValue(), gpuStatsChannel.takeUnretainedValue(), nil) 22 | 23 | let subscription: IOReportSubscriptionRef = IOReportCreateSubscription(nil, cpuStatsChannel.takeUnretainedValue(), &subbbedChannels, 0, nil) 24 | 25 | self.subscription = subscription 26 | self.subbedChannels = subbbedChannels?.takeUnretainedValue() 27 | 28 | Logger.log("Succesfully created subscription reference: \(subscription)", class: FrequencyStatsHelper.self) 29 | } 30 | 31 | @objc public func sample(clusterKeys: NSArray, with reply: @escaping (NSDictionary) -> Void) { 32 | let sample = IOReportCreateSamples(self.subscription, self.subbedChannels, nil) 33 | 34 | guard let clusterKeys = clusterKeys as? [String], let cfsample = sample?.takeUnretainedValue() else { 35 | reply(NSDictionary()) 36 | return 37 | } 38 | 39 | self.samples.append(cfsample) 40 | sample?.release() 41 | 42 | if self.samples.count == 2 { 43 | guard 44 | let firstSample = self.samples.first, 45 | let lastSample = self.samples.last, 46 | let sampleDelta = IOReportCreateSamplesDelta(firstSample, lastSample, nil) 47 | else { 48 | reply(NSDictionary()) 49 | return 50 | } 51 | 52 | var dictionary: [String : [UInt64]] = [:] 53 | 54 | IOReportIterate(sampleDelta.takeUnretainedValue(), { sample in 55 | autoreleasepool { 56 | let subGroup: String = IOReportChannelGetSubGroup(sample) 57 | 58 | // make sure we have the right subgroups 59 | guard subGroup == "CPU Complex Performance States" || 60 | subGroup == "CPU Core Performance States" || 61 | subGroup == "GPU Performance States" else { 62 | return Int32(kIOReportIterOk) 63 | } 64 | 65 | let channelName: String = IOReportChannelGetChannelName(sample) 66 | 67 | 68 | // make sure we have the right channels 69 | guard clusterKeys.contains(where: { channelName.contains($0) }), channelName != "BSTGPUPH" else { 70 | return Int32(kIOReportIterOk) 71 | } 72 | 73 | var states: [UInt64] = [] 74 | 75 | // loop through every state of the channel 76 | for state in 0.. Void) 13 | } 14 | -------------------------------------------------------------------------------- /FrequencyStatsHelper/IOReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // IOReport.h 3 | // FrequencyStats 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | #ifndef IOReport_h 9 | #define IOReport_h 10 | 11 | #import 12 | 13 | enum { 14 | kIOReportIterOk 15 | }; 16 | 17 | typedef struct IOReportSubscriptionCustom* IOReportSubscriptionCustomRef; 18 | 19 | typedef struct IOReportSubscriptionRef* IOReportSubscriptionRef; 20 | typedef CFDictionaryRef IOReportSampleRef; 21 | 22 | extern IOReportSubscriptionRef IOReportCreateSubscription(void* a, CFMutableDictionaryRef desiredChannels, CFMutableDictionaryRef* subbedChannels, uint64_t channel_id, CFTypeRef b); 23 | extern CFDictionaryRef IOReportCreateSamples(IOReportSubscriptionRef iorsub, CFMutableDictionaryRef subbedChannels, CFTypeRef a); 24 | extern CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef prev, CFDictionaryRef current, CFTypeRef a); 25 | 26 | extern CFMutableDictionaryRef IOReportCopyChannelsInGroup(NSString*, NSString*, uint64_t, uint64_t, uint64_t); 27 | 28 | typedef int (^ioreportiterateblock)(IOReportSampleRef ch); 29 | extern void IOReportIterate(CFDictionaryRef samples, ioreportiterateblock); 30 | 31 | extern int IOReportStateGetCount(CFDictionaryRef); 32 | extern uint64_t IOReportStateGetResidency(CFDictionaryRef, int); 33 | extern uint64_t IOReportArrayGetValueAtIndex(CFDictionaryRef, int); 34 | extern NSString* IOReportChannelGetChannelName(CFDictionaryRef); 35 | extern NSString* IOReportChannelGetSubGroup(CFDictionaryRef); 36 | extern NSString* IOReportStateGetNameForIndex(CFDictionaryRef, int); 37 | 38 | extern void IOReportMergeChannels(CFMutableDictionaryRef, CFMutableDictionaryRef, CFTypeRef); 39 | extern NSString* IOReportChannelGetGroup(CFDictionaryRef); 40 | 41 | #endif /* IOReport_h */ 42 | -------------------------------------------------------------------------------- /FrequencyStatsHelper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XPCService 6 | 7 | ServiceType 8 | Application 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /FrequencyStatsHelper/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // FrequencyStatsHelper 4 | // 5 | // Created by BitesPotatoBacks on 3/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class ServiceDelegate: NSObject, NSXPCListenerDelegate { 11 | 12 | /// This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection. 13 | func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { 14 | 15 | // Configure the connection. 16 | // First, set the interface that the exported object implements. 17 | newConnection.exportedInterface = NSXPCInterface(with: FrequencyStatsHelperProtocol.self) 18 | 19 | // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object. 20 | let exportedObject = FrequencyStatsHelper() 21 | newConnection.exportedObject = exportedObject 22 | 23 | // Resuming the connection allows the system to deliver more incoming messages. 24 | newConnection.resume() 25 | 26 | // Returning true from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call invalidate() on the connection and return false. 27 | return true 28 | } 29 | } 30 | 31 | // Create the delegate for the service. 32 | let delegate = ServiceDelegate() 33 | 34 | // Set up the one NSXPCListener for this service. It will handle all incoming connections. 35 | let listener = NSXPCListener.service() 36 | listener.delegate = delegate 37 | 38 | // Resuming the serviceListener starts this service. This method does not return. 39 | listener.resume() 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 BitesPotatoBacks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Localization/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/Localization/.DS_Store -------------------------------------------------------------------------------- /Localization/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Effizienz"; 11 | "performanceString" = "Leistung"; 12 | "graphicsString" = "Grafik"; 13 | "unknownString" = "Unbekannt"; 14 | 15 | "frequencyString" = "Frequenz"; 16 | "coreMetricsString" = "Kernkennzahlen"; 17 | 18 | "percentString" = "Prozent"; 19 | "timeString" = "Zeit"; 20 | "distributionString" = "Staatliche Verteilung"; 21 | 22 | // app settings 23 | "appsettings.creditString" = "Mit Liebe gemacht von BitesPotatoBacks"; 24 | "appsettings.updateIntervalString" = "Updateintervall"; 25 | "appsettings.secondString" = "Sekunden"; 26 | "appsettings.pluralSecondString" = "Sekunden"; 27 | "appsettings.quitAppString" = "App beenden"; 28 | "appsettings.githubVisitString" = "Besuchen Sie auf Github"; 29 | "appsettings.graphColorString" = "Diagrammfarbe"; 30 | -------------------------------------------------------------------------------- /Localization/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficiency"; 11 | "performanceString" = "Performance"; 12 | "graphicsString" = "Graphics"; 13 | "unknownString" = "Unknown"; 14 | 15 | "frequencyString" = "Frequency"; 16 | "coreMetricsString" = "Core Metrics"; 17 | 18 | "percentString" = "Percent"; 19 | "timeString" = "Time"; 20 | "distributionString" = "State Distribution"; 21 | 22 | // app settings 23 | "appsettings.creditString" = "Made with love by BitesPotatoBacks"; 24 | "appsettings.updateIntervalString" = "Update Interval"; 25 | "appsettings.secondString" = "Second"; 26 | "appsettings.pluralSecondString" = "Seconds"; 27 | "appsettings.quitAppString" = "Quit App"; 28 | "appsettings.githubVisitString" = "Visit on Github"; 29 | "appsettings.graphColorString" = "Graph Color"; 30 | -------------------------------------------------------------------------------- /Localization/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Eficiencia"; 11 | "performanceString" = "Actuación"; 12 | "graphicsString" = "Gráficos"; 13 | "unknownString" = "Desconocido"; 14 | 15 | "frequencyString" = "Frecuencia"; 16 | "coreMetricsString" = "Métricas por núcleo"; 17 | 18 | "percentString" = "Por ciento"; 19 | "timeString" = "Tiempo"; 20 | "distributionString" = "Distribución estatal"; 21 | // app settings 22 | "appsettings.creditString" = "Hecho con amor por BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Frecuencia de muestreo"; 24 | "appsettings.secondString" = "Segundo"; 25 | "appsettings.pluralSecondString" = "Segundos"; 26 | "appsettings.quitAppString" = "Salir de la aplicación"; 27 | "appsettings.githubVisitString" = "Visita en Github"; 28 | "appsettings.graphColorString" = "Color del gráfico"; 29 | -------------------------------------------------------------------------------- /Localization/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficacité"; 11 | "performanceString" = "Performance"; 12 | "graphicsString" = "Graphics"; 13 | "unknownString" = "Inconnu"; 14 | 15 | "frequencyString" = "Fréquence"; 16 | "coreMetricsString" = "Métriques par cœur"; 17 | 18 | "percentString" = "Pour cent"; 19 | "timeString" = "Temps"; 20 | "distributionString" = "Répartition de l'état"; 21 | // app settings 22 | "appsettings.creditString" = "Fait avec amour par BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Update Interval"; 24 | "appsettings.secondString" = "Secondes"; 25 | "appsettings.pluralSecondString" = "Secondes"; 26 | "appsettings.quitAppString" = "Quitter l'application"; 27 | "appsettings.githubVisitString" = "Visite sur Github"; 28 | "appsettings.graphColorString" = "Couleur du graphique"; 29 | -------------------------------------------------------------------------------- /Localization/it.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficienza"; 11 | "performanceString" = "Prestazione"; 12 | "graphicsString" = "Grafica"; 13 | "unknownString" = "Sconosciuto"; 14 | 15 | "frequencyString" = "Frequenza"; 16 | "coreMetricsString" = "Metriche per core"; 17 | 18 | "percentString" = "Per cento"; 19 | "timeString" = "Tempo"; 20 | "distributionString" = "Distribuzione statale"; 21 | // app settings 22 | "appsettings.creditString" = "Realizzato con amore da BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Intervallo di aggiornamento"; 24 | "appsettings.secondString" = "Secondo"; 25 | "appsettings.pluralSecondString" = "Secondi"; 26 | "appsettings.quitAppString" = "Esci dall'app"; 27 | "appsettings.githubVisitString" = "Visita su Github"; 28 | "appsettings.graphColorString" = "Colore del grafico"; 29 | -------------------------------------------------------------------------------- /Localization/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficiency"; 11 | "performanceString" = "Performance"; 12 | "graphicsString" = "Graphics"; 13 | "unknownString" = "知らない"; 14 | 15 | "frequencyString" = "周波数"; 16 | "coreMetricsString" = "コア指標"; 17 | 18 | "percentString" = "パーセント"; 19 | "timeString" = "時間"; 20 | "distributionString" = "状態分布"; 21 | // app settings 22 | "appsettings.creditString" = "BitesPotatoBacks が愛情を込めて作成"; 23 | "appsettings.updateIntervalString" = "更新間隔"; 24 | "appsettings.secondString" = "秒"; 25 | "appsettings.pluralSecondString" = "秒"; 26 | "appsettings.quitAppString" = "アプリを終了"; 27 | "appsettings.githubVisitString" = "Github にアクセス"; 28 | "appsettings.graphColorString" = "グラフの色"; 29 | -------------------------------------------------------------------------------- /Localization/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "효율 코어"; 11 | "performanceString" = "성능 코어"; 12 | "graphicsString" = "GPU"; 13 | "unknownString" = "정의되지 않은 내용"; 14 | 15 | "frequencyString" = "클럭"; 16 | "coreMetricsString" = "코어 별 클럭"; 17 | 18 | "percentString" = "퍼센트"; 19 | "timeString" = "밀리초"; 20 | "distributionString" = "상태 분포"; 21 | // app settings 22 | "appsettings.creditString" = "BitesPotatoBacks의 사랑으로 제작"; 23 | "appsettings.updateIntervalString" = "업데이트 간격"; 24 | "appsettings.secondString" = "초"; 25 | "appsettings.pluralSecondString" = "초"; 26 | "appsettings.quitAppString" = "앱 종료"; 27 | "appsettings.githubVisitString" = "Github 방문"; 28 | "appsettings.graphColorString" = "그래프 색상"; 29 | -------------------------------------------------------------------------------- /Localization/nl.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficiëntie"; 11 | "performanceString" = "Prestatie"; 12 | "graphicsString" = "Grafische kaart"; 13 | "unknownString" = "Onbekend"; 14 | 15 | "frequencyString" = "Frequentie"; 16 | "coreMetricsString" = "Kernstatistieken"; 17 | 18 | "percentString" = "Procent"; 19 | "timeString" = "Tijd"; 20 | "distributionString" = "Staatsdistributie"; 21 | // app settings 22 | "appsettings.creditString" = "Met liefde gemaakt door BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Update tijd"; 24 | "appsettings.secondString" = "Seconde"; 25 | "appsettings.pluralSecondString" = "Seconden"; 26 | "appsettings.quitAppString" = "Sluit de app af"; 27 | "appsettings.githubVisitString" = "Bezoek op Github"; 28 | "appsettings.graphColorString" = "Grafiek kleur"; 29 | -------------------------------------------------------------------------------- /Localization/pt-BR.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Eficiência"; 11 | "performanceString" = "Desempenho"; 12 | "graphicsString" = "Gráficos"; 13 | "unknownString" = "Desconhecido"; 14 | 15 | "frequencyString" = "Frequência"; 16 | "coreMetricsString" = "Métricas por núcleo"; 17 | 18 | "percentString" = "Por cento"; 19 | "timeString" = "Tempo"; 20 | "distributionString" = "Distribuição Estadual"; 21 | // app settings 22 | "appsettings.creditString" = "Feito com amor por BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Intervalo de atualização"; 24 | "appsettings.secondString" = "Segundo"; 25 | "appsettings.pluralSecondString" = "Segundos"; 26 | "appsettings.quitAppString" = "Sair do aplicativo"; 27 | "appsettings.githubVisitString" = "Visite no Github"; 28 | "appsettings.graphColorString" = "Cor do gráfico"; 29 | -------------------------------------------------------------------------------- /Localization/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Эффективность"; 11 | "performanceString" = "Производительность"; 12 | "graphicsString" = "Видеокарта"; 13 | "unknownString" = "Неизвестный"; 14 | 15 | "frequencyString" = "Частота"; 16 | "coreMetricsString" = "Статистика по ядрам"; 17 | 18 | "percentString" = "Процент"; 19 | "timeString" = "Время"; 20 | "distributionString" = "распределение"; 21 | // app settings 22 | "appsettings.creditString" = "Сделано с любовью BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Интервал обновления"; 24 | "appsettings.secondString" = "Второй"; 25 | "appsettings.pluralSecondString" = "Секунды"; 26 | "appsettings.quitAppString" = "Выйти из приложения"; 27 | "appsettings.githubVisitString" = "Посетите Github"; 28 | "appsettings.graphColorString" = "Цвет графика"; 29 | -------------------------------------------------------------------------------- /Localization/tr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by Taevon Turner on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficiency"; 11 | "performanceString" = "Performance"; 12 | "graphicsString" = "Graphics"; 13 | "unknownString" = "Unknown"; 14 | 15 | "frequencyString" = "Frequency"; 16 | "coreMetricsString" = "Core Metrics"; 17 | 18 | // app settings 19 | "appsettings.creditString" = "Made with love by BitesPotatoBacks"; 20 | "appsettings.updateIntervalString" = "Update Interval"; 21 | "appsettings.secondString" = "Second"; 22 | "appsettings.pluralSecondString" = "Seconds"; 23 | "appsettings.quitAppString" = "Quit App"; 24 | -------------------------------------------------------------------------------- /Localization/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "Efficiency"; 11 | "performanceString" = "Performance"; 12 | "graphicsString" = "Graphics"; 13 | "unknownString" = "không xác định"; 14 | 15 | "frequencyString" = "Tốc độ"; 16 | "coreMetricsString" = "Mỗi lõi"; 17 | 18 | "percentString" = "Phần trăm"; 19 | "timeString" = "Thời gian"; 20 | "distributionString" = "Những trạng thái"; 21 | // app settings 22 | "appsettings.creditString" = "Được làm bằng tình yêu của BitesPotatoBacks"; 23 | "appsettings.updateIntervalString" = "Khoảng thời gian cập nhật"; 24 | "appsettings.secondString" = "giây"; 25 | "appsettings.pluralSecondString" = "giây"; 26 | "appsettings.quitAppString" = "Thoát ứng dụng"; 27 | "appsettings.githubVisitString" = "Truy cập trên Github"; 28 | "appsettings.graphColorString" = "Màu đồ thị"; 29 | -------------------------------------------------------------------------------- /Localization/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "效率"; 11 | "performanceString" = "效率"; 12 | "graphicsString" = "图形"; 13 | "unknownString" = "未知"; 14 | 15 | "frequencyString" = "频率"; 16 | "coreMetricsString" = "每核"; 17 | 18 | "percentString" = "百分"; 19 | "timeString" = "时间"; 20 | "distributionString" = "状态分布"; 21 | // app settings 22 | "appsettings.creditString" = "BitesPotatoBacks 用爱制作"; 23 | "appsettings.updateIntervalString" = "更新间隔"; 24 | "appsettings.secondString" = "秒"; 25 | "appsettings.pluralSecondString" = "秒"; 26 | "appsettings.quitAppString" = "退出应用程序"; 27 | "appsettings.githubVisitString" = "在 Github 上访问"; 28 | "appsettings.graphColorString" = "图形颜色"; 29 | -------------------------------------------------------------------------------- /Localization/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | FrequencyStats 4 | 5 | Created by BitesPotatoBacks on 3/14/23. 6 | 7 | */ 8 | 9 | // main 10 | "efficiencyString" = "效率"; 11 | "performanceString" = "效率"; 12 | "graphicsString" = "图形"; 13 | "unknownString" = "未知"; 14 | 15 | "frequencyString" = "频率"; 16 | "coreMetricsString" = "每核心指標"; 17 | 18 | "percentString" = "百分"; 19 | "timeString" = "時間"; 20 | "distributionString" = "狀態分佈"; 21 | // app settings 22 | "appsettings.creditString" = "BitesPotatoBacks 用愛打造"; 23 | "appsettings.updateIntervalString" = "更新间隔"; 24 | "appsettings.secondString" = "秒"; 25 | "appsettings.pluralSecondString" = "秒"; 26 | "appsettings.quitAppString" = "退出应用程序"; 27 | "appsettings.githubVisitString" = "在 Github 上訪問"; 28 | "appsettings.graphColorString" = "圖形顏色"; 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Frequency Stats - for Apple Silicon

5 | 6 |

7 | Daemonless and kextless CPU and GPU frequency stats in your menubar! 8 |

9 |
10 | 11 |

12 | 13 | Releases 14 | 15 | 16 | Software Support 17 | 18 | 19 | Software Support 20 | 21 | 22 | License 23 | 24 |

25 | Download from Github Releases 26 | 31 |

32 | 33 |

34 | Example Screenshot 35 |

36 | 37 | ## Project Deets 38 | Once upon a time, I created a command line tool, the [SocPowerBuddy](https://github.com/BitesPotatoBacks/SocPowerBuddy); the soul purpose of which was to provide _real_ per-core frequency metrics on Apple Silicon without needing sudo. I thought that it would be cool to make an efficient little menu bar app based on it, which doesn't need a daemon or a kext! 39 | 40 | I hope to add Intel support someday, but to follow my personal rule for this project, it needs to be a rootless implementation... 41 | 42 | ### Features 43 | The current feature set covers cluster frequencies and state distribution, per-core frequencies with colorized meters (for CPUs), and colorized per-cluster graphs (that I need to expand on)... 44 | 45 | ### WIP Features 46 | - [ ] Extended scrollable history for graphs 47 | - [ ] CSV Dumping 48 | - [ ] Menubar Widgets 49 | 50 | ## Installation 51 | You can download the latest version from the [Github Releases.](https://github.com/BitesPotatoBacks/FrequencyStats/releases) 52 | 53 | ## System Requirements 54 | - Apple Silicon 55 | - MacOS Big Sur (macOS 11) or newer 56 | 57 | Full compatibility notes for silicon support can be found [here.](https://github.com/BitesPotatoBacks/SocPowerBuddy#compatibility-notes) 58 | 59 | ## Supported Languages 60 | If you can call it that, for what little there is lol... 61 | - English 62 | - Italian 63 | - Japanese 64 | - Korean 65 | - Portuguese 66 | - Russian 67 | - Chinese (Simplified, Traditional) 68 | - Vietnamese 69 | - Dutch 70 | - Spanish 71 | - French 72 | - German 73 | 74 | ## Contribution 75 | If you find any bugs or wish to add any features, open an issue or PR and i'll get my eyes on it as soon as possible! 76 | 77 | ## License 78 | [MIT License](https://github.com/BitesPotatoBacks/FrequencyStats/blob/main/LICENSE) 79 | 80 | 81 | -------------------------------------------------------------------------------- /images/beautified-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/beautified-example.png -------------------------------------------------------------------------------- /images/example-img-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/example-img-dark.png -------------------------------------------------------------------------------- /images/example-img-dark2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/example-img-dark2.png -------------------------------------------------------------------------------- /images/example-img-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/example-img-light.png -------------------------------------------------------------------------------- /images/example-img-states-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/example-img-states-dark.png -------------------------------------------------------------------------------- /images/example-mg-light2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/example-mg-light2.png -------------------------------------------------------------------------------- /images/frequency-stats-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dehydratedpotato/FrequencyStats/9d2b88757fc937fd75fc98adc6a03a0aacfe63bc/images/frequency-stats-icon.png --------------------------------------------------------------------------------