From 76dec1316c7ea09619de6b8d878fc3466c44a843 Mon Sep 17 00:00:00 2001 From: iamlooper Date: Wed, 6 Nov 2024 15:37:59 +0500 Subject: [PATCH] feat: v1.4.0 --- .github/workflows/build_libs.yml | 8 +- Android.mk | 10 +- Application.mk | 7 +- README.md | 33 +- banner.jpg | Bin 0 -> 30918 bytes changelog.md | 15 +- src/android_enhancer.cpp | 827 +++++++++++------------ src/focused_app_opt.cpp | 98 +++ src/focused_app_opt.hpp | 32 + src/util/android_util.cpp | 25 + src/util/android_util.hpp | 29 + src/util/cgroup_util.cpp | 333 ++++++++++ src/util/cgroup_util.hpp | 219 +++++++ src/util/fs_util.cpp | 105 +++ src/util/fs_util.hpp | 96 +++ src/util/logger.cpp | 37 ++ src/util/logger.hpp | 42 ++ src/util/shell_util.cpp | 30 + src/util/shell_util.hpp | 14 + src/util/thread_process_util.cpp | 244 +++++++ src/util/thread_process_util.hpp | 62 ++ src/util/type_converter_util.cpp | 15 + src/util/type_converter_util.hpp | 42 ++ src/vmtouch.cpp | 1045 ------------------------------ 24 files changed, 1880 insertions(+), 1488 deletions(-) create mode 100644 banner.jpg create mode 100644 src/focused_app_opt.cpp create mode 100644 src/focused_app_opt.hpp create mode 100644 src/util/android_util.cpp create mode 100644 src/util/android_util.hpp create mode 100644 src/util/cgroup_util.cpp create mode 100644 src/util/cgroup_util.hpp create mode 100644 src/util/fs_util.cpp create mode 100644 src/util/fs_util.hpp create mode 100644 src/util/logger.cpp create mode 100644 src/util/logger.hpp create mode 100644 src/util/shell_util.cpp create mode 100644 src/util/shell_util.hpp create mode 100644 src/util/thread_process_util.cpp create mode 100644 src/util/thread_process_util.hpp create mode 100644 src/util/type_converter_util.cpp create mode 100644 src/util/type_converter_util.hpp delete mode 100644 src/vmtouch.cpp diff --git a/.github/workflows/build_libs.yml b/.github/workflows/build_libs.yml index dc794fb..834b66c 100644 --- a/.github/workflows/build_libs.yml +++ b/.github/workflows/build_libs.yml @@ -12,21 +12,17 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - - name: Install dependencies - run: | - git clone -b cpp-utils https://github.com/iamlooper/utils.git src/utils - uses: nttld/setup-ndk@v1 with: - ndk-version: r26b + ndk-version: r27c - name: Compile run: | ndk-build NDK_PROJECT_PATH="." APP_BUILD_SCRIPT="Android.mk" NDK_APPLICATION_MK="Application.mk" - name: Upload libs as artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: libs path: libs \ No newline at end of file diff --git a/Android.mk b/Android.mk index 2d3127d..8bfbab3 100644 --- a/Android.mk +++ b/Android.mk @@ -1,11 +1,7 @@ -LOCAL_PATH := $(call my-dir)/src +LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := android_enhancer -LOCAL_SRC_FILES := android_enhancer.cpp -include $(BUILD_EXECUTABLE) - -include $(CLEAR_VARS) -LOCAL_MODULE := vmtouch -LOCAL_SRC_FILES := vmtouch.cpp +LOCAL_SRC_FILES := src/android_enhancer.cpp src/focused_app_opt.cpp $(wildcard $(LOCAL_PATH)/src/util/*.cpp) +LOCAL_CPP_FEATURES += exceptions include $(BUILD_EXECUTABLE) \ No newline at end of file diff --git a/Application.mk b/Application.mk index ce9e4cb..3c1374d 100644 --- a/Application.mk +++ b/Application.mk @@ -1,4 +1,5 @@ -APP_ABI := all -APP_CPPFLAGS := -std=c++17 -O3 +APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 +APP_CPPFLAGS := -O3 APP_STL := c++_static -APP_PLATFORM := android-27 \ No newline at end of file +APP_PLATFORM := android-26 +APP_STRIP_MODE := --strip-all \ No newline at end of file diff --git a/README.md b/README.md index e3bdcb3..4a4b348 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,24 @@ +![Banner](https://github.com/iamlooper/Android-Enhancer/raw/main/banner.jpg) + # Android Enhancer (formerly AOSP Enhancer) 🚀 + A revolutionary android optimizer. -## Variants 🧩 -- [App](https://github.com/iamlooper/Android-Enhancer/tree/app) -- [Module](https://github.com/iamlooper/Android-Enhancer/tree/module) +## Versions 🧩 + +- [App](https://github.com/iamlooper/Android-Enhancer-App) +- [Module](https://github.com/iamlooper/Android-Enhancer-Module) ## Download 📲 -You can download Android Enhancer from the following: -- [Pling](https://www.pling.com/p/1875251) -- [Buy Me a Coffee](https://buymeacoffee.com/iamlooper/posts) (Early access) -## Description 📝 -Android Enhancer is a specialized tool designed to optimize the performance of Android devices by modifying specific core parameters. Unlike other optimizers, Android Enhancer employs a universal approach, enabling it to function effectively across a wide range of Android devices. Consequently, it can enhance the performance of various devices, encompassing smartphones, tablets, and other Android-powered devices. +[Click here](https://www.pling.com/p/1875251/) to download the latest version of Android Enhancer. Install the app version if you want control over which tweaks to apply. Use the module version if you want a 'flash and forget' experience. Do not use the app and module versions at the same time. ## Working ⚙️ -To understand the functioning of the Android Enhancer, please examine the source code located [here](https://github.com/iamlooper/Android-Enhancer/blob/main/src/android_enhancer.cpp). The code currently lacks extensive documentation, but rest assured, I am actively working on improving it. + +To understand the functioning of the Android Enhancer, please examine the source code from the entrypoint located [here](https://github.com/iamlooper/Android-Enhancer/blob/main/src/android_enhancer.cpp). ## Benchmarks ⚡ + The given benchmarks were conducted on a Poco M3 running the GreenForce kernel on Pixel Experience Android 13. ### Scheduler latency via `hackbench` (Lower values indicate better performance) @@ -53,14 +55,7 @@ min=0, max=102169` - With Android Enhancer: 27.495 seconds ## Credits 👥 -Due to the combined efforts and expertise of the following people, this project has achieved its success: -- [Chirag](https://t.me/selfmuser) -- [Leaf](https://t.me/leafinferno) -- [Jis G Jacob](https://t.me/StudioKeys) - -Message me if I missed anyone. 😉 -## Support Me 💙 -If you liked any one of my projects then consider supporting me via following: -- [Buy Me a Coffee](https://buymeacoffee.com/iamlooper/membership) -- [Telegram Channel](https://loopprojects.t.me) \ No newline at end of file +- [Chirag](https://t.me/selfmuser) - Tester +- [Leaf](https://t.me/leafinferno) - Designer +- [Jis G Jacob](https://t.me/StudioKeys) - Tester \ No newline at end of file diff --git a/banner.jpg b/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b1eee3436b2b161d5af012eb36b50e332a37b177 GIT binary patch literal 30918 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 literal 0 HcmV?d00001 diff --git a/changelog.md b/changelog.md index 09624b3..5b81274 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,8 @@ -# Changelog +# v1.4.0 -## v1.1.0 -- [App] New UI -- [Module] Added KSU support -- [Main] Improved all tweaks - -## v1.0.0 -- Initial release \ No newline at end of file +- Removed memory lock tweak +- Improved /util +- Added focused app optimizer +- Improved overall tweaks +- Improved commandline arguments +- Misc. changes \ No newline at end of file diff --git a/src/android_enhancer.cpp b/src/android_enhancer.cpp index 49e0f32..78e4cda 100644 --- a/src/android_enhancer.cpp +++ b/src/android_enhancer.cpp @@ -1,478 +1,505 @@ #include -#include +#include #include #include +#include +#include +#include +#include -#include "utils/logger.hpp" -#include "utils/android_utils.hpp" -#include "utils/fs_utils.hpp" -#include "utils/shell_utils.hpp" -#include "utils/thread_process_utils.hpp" -#include "utils/vmtouch_utils.hpp" - -// Set standard namespace. -using namespace std; - -// Declare functions. -void configureKernel(); -void configureIo(); -void configureProcessThread(); -void configureCmdServices(); -void disableUnnecessaryServices(); -void configureSysProps(); -void configureDalvikProps(); -void configureDex(); -void configureDexSecondary(); -void cleanJunk(); -void configureMiui(); -void enableMemoryTweak(); -void disableMemoryTweak(); -void executeAllTweaks(); -void executeMainTweak(); -void executeDalvikTweak(); - -void configureKernel() { - // Disable dynamic scheduler parameters adjustment. - FSUtils::mutateFile("/proc/sys/kernel/sched_tunable_scaling", "0"); - - // Allow scheduler boosting on top-app tasks. - FSUtils::mutateFile("/proc/sys/kernel/sched_min_task_util_for_boost", "0"); - FSUtils::mutateFile("/proc/sys/kernel/sched_min_task_util_for_colocation", "0"); - FSUtils::mutateFile("/dev/stune/top-app/schedtune.prefer_idle", "0"); - FSUtils::mutateFile("/dev/stune/top-app/schedtune.boost", "1"); - - // Turn off printk logging and scheduler stats. - FSUtils::mutateFile("/proc/sys/kernel/printk", "0 0 0 0"); - FSUtils::mutateFile("/proc/sys/kernel/printk_devkmsg", "off"); - FSUtils::mutateFile("/proc/sys/kernel/sched_schedstats", "0"); - - // Disable swap readahead for swap devices. - FSUtils::mutateFile("/proc/sys/vm/page-cluster", "0"); - - // Update /proc/stat every 60 sec to reduce jitter. - FSUtils::mutateFile("/proc/sys/vm/stat_interval", "60"); - - // Don't debug OOM events. - FSUtils::mutateFile("/proc/sys/vm/oom_dump_tasks", "0"); - - // Reduce lease break time for quicker reassignment of leases. - FSUtils::mutateFile("/proc/sys/fs/lease-break-time", "15"); - - // Print completion message. - Logger(LogType::INFO) << "Applied kernel tweak" << endl; -} +#include "util/logger.hpp" +#include "util/android_util.hpp" +#include "util/fs_util.hpp" +#include "util/shell_util.hpp" +#include "util/thread_process_util.hpp" +#include "util/cgroup_util.hpp" +#include "focused_app_opt.hpp" -void configureIo() { - // Get I/O block devices paths. - vector paths = FSUtils::getPathsFromWp("/sys/block/*"); - - // Iterate through the list of paths. - for (const string& path: paths) { - // Don't use any I/O scheduler. - FSUtils::mutateFile(path + "/queue/scheduler", "none"); - - // Disable I/O statistics, random I/O addition, and I/O polling. - FSUtils::mutateFile(path + "/queue/iostats", "0"); - FSUtils::mutateFile(path + "/queue/add_random", "0"); - FSUtils::mutateFile(path + "/queue/io_poll", "0"); - } +struct DeviceSpecs { + int totalRam; + int cpuCores; + int maxFreq; +}; - // Print completion message. - Logger(LogType::INFO) << "Tweaked I/O parameters" << endl; -} +struct SchedParams { + int schedPeriod; + int schedTasks; +}; -void configureProcessThread() { - // Initialize variable. - vector processes; - - // List of processes. - processes = { - "rcu_sched", - "writeback", - "system", - "zygote", - "system_server", - "surfaceflinger", - "netd" - }; - - // Loop through each process. - for (const string& process: processes) { - // Set the priority of the process to -20 (highest priority). - ThreadProcessUtils::reniceProcess(process, -20); - } - - // List of processes. - processes = { - "com.android.systemui", - "android.process.acore", - AndroidUtils::getHomePkgName() - }; +DeviceSpecs deviceSpecs; - // Loop through each process. - for (const string& process: processes) { - // Set the priority of the process to -20 (highest priority). - ThreadProcessUtils::reniceProcess(process, -20); - } - - // Print completion message. - Logger(LogType::INFO) << "Optimized priority of system processes" << endl; -} +SchedParams schedParams; +DeviceSpecs getDeviceSpecs(); -// Disable debugging, statistics, unnecessary background apps, phantom process killer, lock profiling (analysis tool), make changes to `DeviceConfig` flags persistent, enable IORAP readahead feature, improve FSTrim time interval and tweak `ActivityManager`. -void configureCmdServices() { - // List of service commands of `cmd` to execute. - vector svcCmds = { - "settings put system anr_debugging_mechanism 0", - "looper_stats disable", - "settings put global netstats_enabled 0", - "power set-fixed-performance-mode-enabled true", - "activity idle-maintenance", - "thermalservice override-status 0", - "settings put global settings_enable_monitor_phantom_procs false", - "device_config put runtime_native_boot disable_lock_profiling true", - "device_config set_sync_disabled_for_tests persistent", - "device_config put runtime_native_boot iorap_readahead_enable true", - "settings put global fstrim_mandatory_interval 3600", - "device_config put activity_manager max_phantom_processes 2147483647", - "device_config put activity_manager max_cached_processes 256", - "device_config put activity_manager max_empty_time_millis 43200000" - }; +SchedParams calculateSchedParams(const DeviceSpecs &specs); - // Iterate through the list of service commands. - for (const string& svcCmd: svcCmds) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("cmd " + svcCmd); - } +void initializeSchedParams(); + +void kernelTweak(); + +void vmTweak(); + +void ioTweak(); + +void priorityTweak(); + +void cmdTweak(); + +void unnecessaryServicesTweak(); + +void sysPropsTweak(); - // Print completion message. - Logger(LogType::INFO) << "Configured cmd services" << endl; +void packagesTweak(); + +void allTweaks(); + +void mainTweak(); + +void artTweak(); + +void logTrimmer(const std::string &fileName); + +void printUsage(); + +DeviceSpecs getDeviceSpecs() { + DeviceSpecs specs; + specs.totalRam = std::stoi( + ShellUtil::exec( + "grep MemTotal /proc/meminfo | cut -f2 -d':' | tr -d ' kB'").second); // In KB + specs.cpuCores = std::thread::hardware_concurrency(); + specs.maxFreq = std::stoi( + FSUtil::readFile("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq")); + return specs; } -// Stop unnecessary services. -void disableUnnecessaryServices() { - // List of services to be killed. - vector services = { - "traced", - "statsd", - "tcpdump", - "cnss_diag", - "ipacm-diag", - "subsystem_ramdump", - "charge_logger", - "wlan_logging" - }; +SchedParams calculateSchedParams(const DeviceSpecs &specs) { + SchedParams params; + int basePeriod = 20000000; + int baseTasks = 10; + int ramFactor = specs.totalRam / 1048576; // Convert to GB + int coreFactor = specs.cpuCores / 4; - // Iterate through the list of services. - for (const string& service: services) { - // Use AndroidUtils::stopSystemService() & ThreadProcessUtils::killProcess() function to stop service. - AndroidUtils::stopSystemService(service); - ThreadProcessUtils::killProcess(service); - } + params.schedPeriod = basePeriod * coreFactor / ramFactor; + params.schedTasks = baseTasks * ramFactor * coreFactor; - // Print completion message. - Logger(LogType::INFO) << "Disabled unnecessary services" << endl; + params.schedPeriod = std::max(10000000, std::min(params.schedPeriod, 30000000)); + params.schedTasks = std::max(5, std::min(params.schedTasks, 20)); + + return params; } -void configureSysProps() { - // List of properties to be modified. - vector props = { - "persist.sys.usap_pool_enabled true", - "persist.device_config.runtime_native.usap_pool_enabled true", - "ro.iorapd.enable true", - "vidc.debug.level 0", - "persist.radio.ramdump 0", - "ro.statsd.enable false", - "persist.debug.sf.statistics 0", - "debug.mdpcomp.logs 0", - "ro.lmk.debug false", - "ro.lmk.log_stats false", - "debug.sf.enable_egl_image_tracker 0", - "persist.ims.disableDebugLogs 1", - "persist.ims.disableADBLogs 1", - "persist.ims.disableQXDMLogs 1", - "persist.ims.disableIMSLogs 1" - }; +void initializeSchedParams() { + deviceSpecs = getDeviceSpecs(); + schedParams = calculateSchedParams(deviceSpecs); +} + +void kernelTweak() { + FSUtil::writeFile("/proc/sys/kernel/sched_tunable_scaling", "0"); + FSUtil::writeFile("/proc/sys/kernel/sched_min_task_util_for_boost", "0"); + FSUtil::writeFile("/proc/sys/kernel/sched_min_task_util_for_colocation", "0"); + FSUtil::writeFile("/dev/stune/top-app/schedtune.prefer_idle", "0"); + FSUtil::writeFile("/dev/stune/top-app/schedtune.boost", "1"); + FSUtil::writeFile("/proc/sys/kernel/printk", "0 0 0 0"); + FSUtil::writeFile("/proc/sys/kernel/printk_devkmsg", "off"); + FSUtil::writeFile("/proc/sys/kernel/sched_schedstats", "0"); + FSUtil::writeFile("/proc/sys/kernel/sched_latency_ns", + std::to_string(schedParams.schedPeriod)); + FSUtil::writeFile("/proc/sys/kernel/sched_min_granularity_ns", + std::to_string(schedParams.schedPeriod / schedParams.schedTasks)); + FSUtil::writeFile("/proc/sys/kernel/sched_wakeup_granularity_ns", + std::to_string(schedParams.schedPeriod / 2)); + FSUtil::writeFile("/proc/sys/kernel/sched_migration_cost_ns", + std::to_string(5000000 * deviceSpecs.cpuCores / 4)); + FSUtil::writeFile("/proc/sys/kernel/sched_nr_migrate", + std::to_string(32 * deviceSpecs.cpuCores / 4)); + FSUtil::writeFile("/proc/sys/kernel/sched_autogroup_enabled", "1"); + FSUtil::writeFile("/proc/sys/kernel/sched_child_runs_first", "1"); + FSUtil::writeFile("/proc/sys/kernel/perf_cpu_time_max_percent", "5"); + + Logger(LogType::INFO) << "Completed kernel tweak" << std::endl; +} + +void vmTweak() { + FSUtil::writeFile("/proc/sys/vm/dirty_ratio", "30"); + FSUtil::writeFile("/proc/sys/vm/dirty_background_ratio", "10"); + FSUtil::writeFile("/proc/sys/vm/dirty_expire_centisecs", "3000"); + FSUtil::writeFile("/proc/sys/vm//proc/sys/vm/dirty_writeback_centisecs", "3000"); + FSUtil::writeFile("/proc/sys/vm/page-cluster", "0"); + FSUtil::writeFile("/proc/sys/vm/swappiness", "100"); + FSUtil::writeFile("/proc/sys/vm/vfs_cache_pressure", "100"); + FSUtil::writeFile("/proc/sys/vm/stat_interval", "10"); + + Logger(LogType::INFO) << "Completed VM tweak" << std::endl; +} - // Iterate through the list of properties. - for (const string& prop: props) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("setprop " + prop); +void cpuTweak() { + std::vector paths = FSUtil::getPathsFromWp( + "/sys/devices/system/cpu/cpu*/cpufreq"); + + for (const std::string &path: paths) { + std::string availableGovernors = FSUtil::readFile(path + "/scaling_available_governors"); + std::vector governors = {"schedutil", "interactive"}; + + for (const std::string &governor: governors) { + if (availableGovernors.find(governor) != std::string::npos) { + FSUtil::writeFile(path + "/scaling_governor", governor); + + if (governor == "schedutil") { + FSUtil::writeFile(path + "/schedutil/up_rate_limit_us", + std::to_string(schedParams.schedPeriod / 1000)); + FSUtil::writeFile(path + "/schedutil/down_rate_limit_us", + std::to_string(4 * schedParams.schedPeriod / 1000)); + FSUtil::writeFile(path + "/schedutil/rate_limit_us", + std::to_string(schedParams.schedPeriod / 1000)); + FSUtil::writeFile(path + "/schedutil/hispeed_load", "90"); + FSUtil::writeFile(path + "/schedutil/hispeed_freq", + std::to_string(deviceSpecs.maxFreq)); + } else if (governor == "interactive") { + FSUtil::writeFile(path + "/interactive/timer_rate", + std::to_string(schedParams.schedPeriod / 1000)); + FSUtil::writeFile(path + "/interactive/min_sample_time", + std::to_string(schedParams.schedPeriod / 1000)); + FSUtil::writeFile(path + "/interactive/go_hispeed_load", "90"); + FSUtil::writeFile(path + "/interactive/hispeed_freq", + std::to_string(deviceSpecs.maxFreq)); + } + + break; + } + } } - // Print completion message. - Logger(LogType::INFO) << "Tweaked system properties" << endl; + Logger(LogType::INFO) << "Completed CPU tweak" << std::endl; } -void configureDalvikProps() { - // List of properties to be modified. - vector props = { - "dalvik.vm.minidebuginfo false", - "dalvik.vm.dex2oat-minidebuginfo false", - "dalvik.vm.check-dex-sum false", - "dalvik.vm.checkjni false", - "dalvik.vm.verify-bytecode false", - "dalvik.gc.type precise", - "persist.bg.dexopt.enable false" - }; +void ioTweak() { + std::vector paths = FSUtil::getPathsFromWp("/sys/block/*"); - // Iterate through the list of properties. - for (const string& prop: props) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("setprop " + prop); + for (const std::string &path: paths) { + std::string availScheds = FSUtil::readFile(path + "/queue/scheduler"); + std::vector preferredScheds = {"cfq", "kyber", "bfq", "mq-deadline", "noop", + "none"}; + + for (const std::string &sched: preferredScheds) { + if (availScheds.find(sched) != std::string::npos) { + FSUtil::writeFile(path + "/queue/scheduler", sched); + break; + } + } + + FSUtil::writeFile(path + "/queue/iostats", "0"); + FSUtil::writeFile(path + "/queue/add_random", "0"); + FSUtil::writeFile(path + "/queue/io_poll", "0"); + + int readAheadKb = std::min(512, 128 * (deviceSpecs.totalRam / 1048576)); + FSUtil::writeFile(path + "/queue/read_ahead_kb", std::to_string(readAheadKb)); + FSUtil::writeFile(path + "/queue/nr_requests", "64"); } - // Print completion message. - Logger(LogType::INFO) << "Applied dalvik optimization props" << endl; + Logger(LogType::INFO) << "Completed I/O tweak" << std::endl; } -// Improve main DEX files. -void configureDex() { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("cmd package compile -m speed-profile -a"); +void priorityTweak() { + CgroupUtil cgroupUtil; + std::string imePkgName = AndroidUtil::getImePkgName(); + std::string homePkgName = AndroidUtil::getHomePkgName(); + + cgroupUtil.pinProcOnPerf("system_server"); + cgroupUtil.changeTaskHighPrio("system_server"); + cgroupUtil.changeTaskRt("system_server", 1); + + cgroupUtil.pinProcOnPerf("zygote"); + cgroupUtil.pinProcOnPerf("zygote64"); + cgroupUtil.changeTaskHighPrio("zygote"); + cgroupUtil.changeTaskHighPrio("zygote64"); + + cgroupUtil.pinProcOnPerf("surfaceflinger"); + cgroupUtil.changeTaskHighPrio("surfaceflinger"); + + cgroupUtil.pinProcOnMid(imePkgName); + cgroupUtil.changeTaskHighPrio(imePkgName); + + cgroupUtil.pinProcOnPerf("com.android.systemui"); + cgroupUtil.changeTaskHighPrio("com.android.systemui"); - // Print completion message. - Logger(LogType::INFO) << "Applied main DEX tweak" << endl; + cgroupUtil.pinProcOnPerf(homePkgName); + cgroupUtil.changeTaskHighPrio(homePkgName); + + cgroupUtil.changeTaskNice("logd", 1); + cgroupUtil.changeTaskNice("statsd", 5); + cgroupUtil.changeTaskNice("tombstoned", 5); + cgroupUtil.changeTaskNice("traced", 5); + cgroupUtil.changeTaskNice("traced_probes", 5); + + cgroupUtil.pinProcOnPwr("logd"); + cgroupUtil.pinProcOnPwr("statsd"); + cgroupUtil.pinProcOnPwr("tombstoned"); + cgroupUtil.pinProcOnPwr("traced"); + cgroupUtil.pinProcOnPwr("traced_probes"); + + Logger(LogType::INFO) << "Completed priority tweak" << std::endl; } -// Improve secondary DEX files. -void configureDexSecondary() { - // List of shell commands to execute. - vector cmds = { - "pm compile -m speed-profile --secondary-dex -a", - "pm reconcile-secondary-dex-files -a", - "pm compile --compile-layouts -a" + +void cmdTweak() { + std::vector svcCmds = { + "settings put system anr_debugging_mechanism 0", + "looper_stats disable", + "settings put global netstats_enabled 0", + "device_config put runtime_native_boot disable_lock_profiling true", + "device_config put runtime_native_boot iorap_readahead_enable true", + "settings put global fstrim_mandatory_interval 3600", + "power set-fixed-performance-mode-enabled true", + "activity idle-maintenance", + "thermalservice override-status 0" }; - // Iterate through the list of commands. - for (const string& cmd: cmds) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec(cmd); + for (const std::string &svcCmd: svcCmds) { + ShellUtil::exec("cmd " + svcCmd); } - // Print completion message. - Logger(LogType::INFO) << "Applied secondary DEX tweak" << endl; + Logger(LogType::INFO) << "Completed CMD tweak" << std::endl; } -void cleanJunk() { - // List of items to be cleaned. - vector items = { - "/data/*.log", - "/data/vendor/wlan_logs", - "/data/*.txt", - "/cache/*.apk", - "/data/anr/*", - "/data/backup/pending/*.tmp", - "/data/cache/*", - "/data/data/*.log", - "/data/data/*.txt", - "/data/log/*.log", - "/data/log/*.txt", - "/data/local/*.apk", - "/data/local/*.log", - "/data/local/*.txt", - "/data/mlog/*", - "/data/system/*.log", - "/data/system/*.txt", - "/data/system/dropbox/*", - "/data/system/usagestats/*", - "/data/tombstones/*", - "/sdcard/LOST.DIR", - "/sdcard/found000", - "/sdcard/LazyList", - "/sdcard/albumthumbs", - "/sdcard/kunlun", - "/sdcard/.CacheOfEUI", - "/sdcard/.bstats", - "/sdcard/.taobao", - "/sdcard/Backucup", - "/sdcard/MIUI/debug_log", - "/sdcard/UnityAdsVideoCache", - "/sdcard/*.log", - "/sdcard/*.CHK", - "/sdcard/duilite", - "/sdcard/DkMiBrowserDemo", - "/sdcard/.xlDownload" +// Stop unnecessary services +void unnecessaryServicesTweak() { + std::vector services = { + "traced", + "statsd", + "tcpdump", + "cnss_diag", + "ipacm-diag", + "subsystem_ramdump", + "charge_logger", + "wlan_logging" }; - // Iterate through the list of items. - for (const string& item: items) { - // Use FSUtils::removePath() function to remove the item. - FSUtils::removePath(item); + for (const std::string &service: services) { + ThreadProcessUtil::killProcess(service, true); } - // Print completion message. - Logger(LogType::INFO) << "Cleaned junk from system" << endl; + Logger(LogType::INFO) << "Completed unnecessary services tweak" << std::endl; } -void configureMiui() { - // List of apps to be disabled. - vector apps = { - "com.miui.analytics", - "com.miui.systemAdSolution" +void sysPropsTweak() { + std::vector props = { + // Common + "persist.sys.usap_pool_enabled true", + "persist.device_config.runtime_native.usap_pool_enabled true", + "ro.iorapd.enable true", + "vidc.debug.level 0", + "vendor.vidc.debug.level 0", + "vendor.swvdec.log.level 0", + "persist.radio.ramdump 0", + "persist.sys.lmk.reportkills false", + "persist.vendor.dpm.loglevel 0", + "persist.vendor.dpmhalservice.loglevel 0", + "persist.debug.sf.statistics 0", + "debug.sf.enable_egl_image_tracker 0", + "debug.mdpcomp.logs 0", + "persist.ims.disableDebugLogs 1", + "persist.ims.disableADBLogs 1", + "persist.ims.disableQXDMLogs 1", + "persist.ims.disableIMSLogs 1", + // Dalvik + "dalvik.vm.minidebuginfo false", + "dalvik.vm.dex2oat-minidebuginfo false", + "dalvik.vm.check-dex-sum false", + "dalvik.vm.checkjni false", + "dalvik.vm.verify-bytecode false", + "dalvik.gc.type generational_cc", + "dalvik.vm.usejit false", + "dalvik.vm.dex2oat-swap true", + "dalvik.vm.dex2oat-resolve-startup-strings true", + "dalvik.vm.systemservercompilerfilter speed-profile", + "dalvik.vm.systemuicompilerfilter speed-profile", + "dalvik.vm.usap_pool_enabled true" }; - // Iterate through the list of apps. - for (const string& app: apps) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("pm disable " + app); + for (const std::string &prop: props) { + ShellUtil::exec("setprop " + prop); } - // Log information about disabled apps. - Logger(LogType::INFO) << "Disabled unnecessary MIUI apps." << endl; + Logger(LogType::INFO) << "Completed system properties tweak" << std::endl; +} - // List of properties to be modified. - vector props = { - "persist.sys.miui.sf_cores 6", - "persist.sys.miui_animator_sched.bigcores 6-7", - "persist.sys.enable_miui_booster 0" +void packagesTweak() { + std::vector cmds = { + // A13 and earlier + "pm compile -m speed-profile -a", + "pm compile -m speed-profile --secondary-dex -a", + "pm compile --compile-layouts -a", + // A14 and later + "pm compile -m speed-profile --full -a", + "pm art dexopt-packages -r bg-dexopt", + "pm art cleanup" }; - // Iterate through the list of properties. - for (const string& prop: props) { - // Use ShellUtils::exec() function to execute shell command. - ShellUtils::exec("setprop " + prop); + for (const std::string &cmd: cmds) { + ShellUtil::exec(cmd); } - // Log information about tuned properties. - Logger(LogType::INFO) << "Tuned MIUI props." << endl; + Logger(LogType::INFO) << "Completed packages tweak" << std::endl; +} - // Kill MIUI booster service. - ThreadProcessUtils::killProcess("miuibooster"); +void allTweaks() { + Logger(LogType::INFO) << "[Start] Android Enhancer Tweaks" << std::endl; + + kernelTweak(); + vmTweak(); + cpuTweak(); + ioTweak(); + priorityTweak(); + cmdTweak(); + unnecessaryServicesTweak(); + sysPropsTweak(); + packagesTweak(); + if (!FocusedAppOptimizer::isRunning()) { + FocusedAppOptimizer::start(); + } - // Log information about turned off services. - Logger(LogType::INFO) << "Turned off unnecessary MIUI services" << endl; + Logger(LogType::INFO) << "[End] Android Enhancer Tweaks" << std::endl; } -// Preload important system objects into memory. -void enableMemoryTweak() { - // Initialize variable. - vector objects; +void mainTweak() { + Logger(LogType::INFO) << "[Start] Main Tweak" << std::endl; - // List of objects. - objects = { - "libc.so", - "libm.so", - "libdl.so" - }; - - // Loop through each object. - for (const string& object: objects) { - // Preload the object from the specified paths. - VMTouchUtils::preloadFull("obj", "/apex/com.android.runtime/lib/bionic/" + object); - VMTouchUtils::preloadFull("obj", "/apex/com.android.runtime/lib64/bionic/" + object); - } - - // List of objects. - objects = { - "libc++.so", - "libandroid_runtime.so", - "libandroid_servers.so", - "libsurfaceflinger.so", - "libgui.so", - "libinputflinger.so", - "libinputreader.so", - "libblas.so", - "libpng.so", - "libjpeg.so", - "liblz4.so", - "liblzma.so", - "libz.so" - }; + kernelTweak(); + vmTweak(); + cpuTweak(); + ioTweak(); + cmdTweak(); + unnecessaryServicesTweak(); + sysPropsTweak(); - // Loop through each object. - for (const string& object: objects) { - // Preload the object from the specified paths. - VMTouchUtils::preloadFull("obj", "/system/lib/" + object); - VMTouchUtils::preloadFull("obj", "/system/lib64/" + object); - } - - // Print completion message. - Logger(LogType::INFO) << "Preloaded important system items into RAM" << endl; + Logger(LogType::INFO) << "[End] Main Tweak" << std::endl; } -void disableMemoryTweak() { - // Kill all `vmtouch` processes. - ThreadProcessUtils::killProcess("vmtouch"); +void artTweak() { + Logger(LogType::INFO) << "[Start] ART Tweak" << std::endl; - // Print completion message. - Logger(LogType::INFO) << "Disabled memory preload tweak" << endl; + sysPropsTweak(); + packagesTweak(); + + Logger(LogType::INFO) << "[End] ART Tweak" << std::endl; } -void executeAllTweaks() { - Logger(LogType::INFO) << "[Start] Android Enhancer Tweaks" << endl; - - configureKernel(); - configureIo(); - configureProcessThread(); - configureCmdServices(); - disableUnnecessaryServices(); - configureSysProps(); - configureDalvikProps(); - configureDex(); - configureDexSecondary(); - cleanJunk(); - enableMemoryTweak(); - - // Execute MIUI tweak if MIUI is installed. - if (AndroidUtils::isAppExists("com.xiaomi.misettings")) { - configureMiui(); +void logTrimmer(const std::string &fileName) { + if (ShellUtil::exec("ps -Ao cmd | grep ae_lt | grep -v grep").first == 0) { + return; } - Logger(LogType::INFO) << "[End] Android Enhancer Tweaks" << endl; -} + ThreadProcessUtil::daemonize([fileName]() { + prctl(PR_SET_NAME, "ae_lt", 0, 0, 0); -void executeMainTweak() { - Logger(LogType::INFO) << "[Start] Main Tweak" << endl; + while (true) { + struct stat logStat; + if (stat(fileName.c_str(), &logStat) == 0) { + if (logStat.st_size > 1 * 1024 * 1024) { // 1MB in bytes + std::ofstream logFile(fileName, + std::ios_base::trunc); + logFile.close(); - configureKernel(); - configureIo(); - configureProcessThread(); - configureCmdServices(); - disableUnnecessaryServices(); - configureSysProps(); + Logger(LogType::INFO) << "Trimmed the log" << std::endl; + } + } - Logger(LogType::INFO) << "[End] Main Tweak" << endl; + std::this_thread::sleep_for(std::chrono::seconds(60)); + } + }, true); } -void executeDalvikTweak() { - Logger(LogType::INFO) << "[Start] Dalvik Tweak" << endl; - - configureDalvikProps(); - configureDex(); - configureDexSecondary(); - - Logger(LogType::INFO) << "[End] Dalvik Tweak" << endl; +void printUsage() { + std::cout << "Usage: program [OPTIONS]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << " -a All tweaks" << std::endl; + std::cout << " -m Main tweak" << std::endl; + std::cout << " -p Priority tweak" << std::endl; + std::cout << " -r ART tweak" << std::endl; + std::cout << " -f Focused app optimizer" << std::endl; + std::cout << " -s Get focused app optimizer status" << std::endl; + std::cout << " -o FILE Specify output file for logging" << std::endl; + std::cout << " -h Display this help message" << std::endl; } -int main(int argc, char * argv[]) { - // Sync device. +int main(int argc, char *argv[]) { sync(); - // Get command line argument. - if (argv[1] == nullptr) { - Logger(LogType::ERROR) << "No command line argument provided" << endl; - exit(1); + if (argc == 1) { + printUsage(); + return 1; } - string flag = argv[1]; - - // Check for matching flag then perform respective operation. - if (flag == "--all-tweaks") { - executeAllTweaks(); - } else if (flag == "--main-tweak") { - executeMainTweak(); - } else if (flag == "--dalvik-tweak") { - executeDalvikTweak(); - } else if (flag == "--clean-junk") { - cleanJunk(); - } else if (flag == "--enable-memory-tweak") { - enableMemoryTweak(); - } else if (flag == "--disable-memory-tweak") { - disableMemoryTweak(); - } else { - Logger(LogType::ERROR) << "No proper command line argument provided" << endl; - exit(1); + + initializeSchedParams(); + + int c; + std::string outputFileName; + std::ofstream outputFile; + bool all_tweaks = false; + bool log_trimmer = false; + + // Reset getopt + optind = 1; + + // Process all options in a single pass + while ((c = getopt(argc, argv, "amprfso:h")) != -1) { + switch (c) { + case 's': + if (argc != 2) { + std::cerr << "-s option must be used alone" << std::endl; + return 1; + } + return FocusedAppOptimizer::isRunning() ? 0 : 1; + case 'a': + all_tweaks = true; + break; + case 'm': + mainTweak(); + break; + case 'p': + priorityTweak(); + break; + case 'r': + artTweak(); + break; + case 'f': + if (FocusedAppOptimizer::isRunning()) { + FocusedAppOptimizer::stop(); + } else { + FocusedAppOptimizer::start(); + } + break; + case 'o': + outputFileName = optarg; + log_trimmer = true; + outputFile.open(outputFileName, std::ios_base::app); + if (!outputFile.is_open()) { + std::cerr << "Unable to open output file " << outputFileName << std::endl; + return 1; + } + std::cout.rdbuf(outputFile.rdbuf()); + break; + case 'h': + printUsage(); + return 0; + case '?': + return 1; + default: + break; + } + } + + if (log_trimmer) { + logTrimmer(outputFileName); + } + + if (all_tweaks) { + allTweaks(); + } + + if (outputFile.is_open()) { + outputFile.close(); } return 0; diff --git a/src/focused_app_opt.cpp b/src/focused_app_opt.cpp new file mode 100644 index 0000000..664860f --- /dev/null +++ b/src/focused_app_opt.cpp @@ -0,0 +1,98 @@ +#include "focused_app_opt.hpp" + +#include +#include +#include + +#include "util/shell_util.hpp" +#include "util/logger.hpp" +#include "util/thread_process_util.hpp" +#include "util/android_util.hpp" + +FocusedAppOptimizer *_focusedAppOptimizer = nullptr; + +void signalHandler(int signum) { + if (_focusedAppOptimizer) { + _focusedAppOptimizer->cleanup(); + } + exit(signum); +} + +FocusedAppOptimizer::FocusedAppOptimizer() : current_focused_app("") { + _focusedAppOptimizer = this; +} + +void FocusedAppOptimizer::cleanup() { + if (!current_focused_app.empty()) { + // Revert the current focused app to default state + cgroupUtil.changeTaskNice(current_focused_app, 0); + cgroupUtil.unpinProc(current_focused_app); + Logger(LogType::INFO) << "Reverted current focused app " << current_focused_app + << " to original state" << std::endl; + } +} + +bool FocusedAppOptimizer::isRunning() { + return ShellUtil::exec("ps -Ao cmd | grep ae_fao | grep -v grep").first == 0; +} + +void FocusedAppOptimizer::start() { + ThreadProcessUtil::daemonize([]() { + prctl(PR_SET_NAME, "ae_fao", 0, 0, 0); + + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + + FocusedAppOptimizer monitor; + monitor.run(); + }, true); +} + + +void FocusedAppOptimizer::stop() { + // Try graceful shutdown first + if (ThreadProcessUtil::killProcess("ae_fao", false)) { + Logger(LogType::INFO) << "Stopped focused app optimizer gracefully" << std::endl; + return; + } + + // If graceful shutdown failed, try forceful termination + if (ThreadProcessUtil::killProcess("ae_fao", true)) { + Logger(LogType::INFO) << "Forcefully stopped focused app optimizer" << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to stop focused app optimizer" << std::endl; + } +} + +void FocusedAppOptimizer::run() { + Logger(LogType::INFO) << "Started focused app optimizer" << std::endl; + + while (true) { + // Get the focused app's package name + std::string focused_app_pkg = ShellUtil::exec( + "dumpsys window displays | grep -E 'mCurrentFocus' | cut -d ' ' -f 5- | cut -d '}' -f 1 | cut -d '/' -f 1").second; + + if (!focused_app_pkg.empty() && AndroidUtil::isAppExists(focused_app_pkg)) { + if (focused_app_pkg != current_focused_app) { + // Revert the previous focused app to default state + if (!current_focused_app.empty()) { + cgroupUtil.changeTaskNice(current_focused_app, 0); + cgroupUtil.unpinProc(current_focused_app); + Logger(LogType::INFO) << "Reverted previous focused app " << current_focused_app + << " to original state" << std::endl; + } + + // Optimize the new focused app + cgroupUtil.changeTaskHighPrio(focused_app_pkg); + cgroupUtil.pinProcOnPerf(focused_app_pkg); + Logger(LogType::INFO) << "Optimized new focused app " << focused_app_pkg + << std::endl; + + current_focused_app = focused_app_pkg; + } + } + + // Sleep for a short interval before checking again + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} \ No newline at end of file diff --git a/src/focused_app_opt.hpp b/src/focused_app_opt.hpp new file mode 100644 index 0000000..1b0b6c8 --- /dev/null +++ b/src/focused_app_opt.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/cgroup_util.hpp" + +class FocusedAppOptimizer { +public: + static void start(); + + static void stop(); + + static bool isRunning(); + + void cleanup(); + +private: + FocusedAppOptimizer(); + + void run(); + + CgroupUtil cgroupUtil; + + std::string current_focused_app; +}; + +extern FocusedAppOptimizer *_focusedAppOptimizer; + +void signalHandler(int signum); \ No newline at end of file diff --git a/src/util/android_util.cpp b/src/util/android_util.cpp new file mode 100644 index 0000000..74284be --- /dev/null +++ b/src/util/android_util.cpp @@ -0,0 +1,25 @@ +#include "android_util.hpp" + +#include + +#include "shell_util.hpp" + +std::string AndroidUtil::getHomePkgName() { + std::pair result = ShellUtil::exec( + "pm resolve-activity -a android.intent.action.MAIN -c android.intent.category.HOME | grep packageName | head -n 1 | cut -d= -f2"); + return result.second; +} + +std::string AndroidUtil::getImePkgName() { + std::pair result = ShellUtil::exec( + "ime list | grep packageName | head -n 1 | cut -d= -f2"); + return result.second; +} + +bool AndroidUtil::isAppExists(const std::string &pkgName) { + if (ShellUtil::exec("pm list packages | grep -q '^package:" + pkgName + "$'").first == 0) { + return true; + } else { + return false; + } +} \ No newline at end of file diff --git a/src/util/android_util.hpp b/src/util/android_util.hpp new file mode 100644 index 0000000..e43381b --- /dev/null +++ b/src/util/android_util.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +class AndroidUtil { +public: + /** + * Get the package name of the default home activity + * + * @return The home package name + */ + static std::string getHomePkgName(); + + /** + * Get the package name of the current input method editor (IME) + * + * @return The IME package name + */ + static std::string getImePkgName(); + + /** + * Check if an app with the given package name exists + * + * @param pkgName, the package name to check + * @return true if the app exists, false otherwise + */ + static bool isAppExists(const std::string &pkgName); +}; \ No newline at end of file diff --git a/src/util/cgroup_util.cpp b/src/util/cgroup_util.cpp new file mode 100644 index 0000000..8a21cb1 --- /dev/null +++ b/src/util/cgroup_util.cpp @@ -0,0 +1,333 @@ +#include "cgroup_util.hpp" + +#include +#include +#include + +#include "shell_util.hpp" +#include "fs_util.hpp" +#include "logger.hpp" +#include "type_converter_util.hpp" + +CgroupUtil::CgroupUtil() { + rebuildProcessScanCache(); +} + +int CgroupUtil::getCpuCount() { + return std::thread::hardware_concurrency(); +} + +std::string CgroupUtil::getFullCpuMask() { + int cpu_count = getCpuCount(); + unsigned long long mask; + if (cpu_count >= (int)(sizeof(unsigned long long) * 8)) { + // CPU count exceeds mask size, set all bits + mask = ~0ULL; + } else { + mask = (1ULL << cpu_count) - 1; + } + std::stringstream ss; + ss << std::hex << mask; + return ss.str(); +} + +std::string CgroupUtil::getHalfCpuMask() { + int cpu_count = getCpuCount(); + int half_count = cpu_count / 2; + unsigned long long mask; + if (half_count >= (int)(sizeof(unsigned long long) * 8)) { + mask = ~0ULL; + } else { + mask = (1ULL << half_count) - 1; + } + std::stringstream ss; + ss << std::hex << mask; + return ss.str(); +} + +bool CgroupUtil::changeTaskCgroup(const std::string &taskName, const std::string &cgroupName, const std::string &cgroupType) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string cgroupPath = "/dev/" + cgroupType + "/" + cgroupName + "/tasks"; + if (!FSUtil::writeFile(cgroupPath, tid)) { + Logger(LogType::ERROR, std::cerr) << "Failed to write to " << cgroupPath << std::endl; + return false; + } + } + } + return true; +} + +bool CgroupUtil::changeProcCgroup(const std::string &processName, const std::string &cgroupName, const std::string &cgroupType) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + processName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string cgroupPath = "/dev/" + cgroupType + "/" + cgroupName + "/cgroup.procs"; + if (!FSUtil::writeFile(cgroupPath, pid)) { + Logger(LogType::ERROR, std::cerr) << "Failed to write to " << cgroupPath << std::endl; + return false; + } + } + return true; +} + +bool CgroupUtil::changeThreadCgroup(const std::string &taskName, const std::string &threadName, const std::string &cgroupName, const std::string &cgroupType) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string commPath = taskPath + tid + "/comm"; + std::string comm = FSUtil::readFile(commPath); + if (ShellUtil::exec("echo \"" + comm + "\" | grep -iE \"" + threadName + "\"").first == 0) { + std::string cgroupPath = "/dev/" + cgroupType + "/" + cgroupName + "/tasks"; + if (!FSUtil::writeFile(cgroupPath, tid)) { + Logger(LogType::ERROR, std::cerr) << "Failed to write to " << cgroupPath << std::endl; + return false; + } + } + } + } + return true; +} + +bool CgroupUtil::changeMainThreadCgroup(const std::string &taskName, const std::string &cgroupName, const std::string &cgroupType) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string cgroupPath = "/dev/" + cgroupType + "/" + cgroupName + "/tasks"; + if (!FSUtil::writeFile(cgroupPath, pid)) { + Logger(LogType::ERROR, std::cerr) << "Failed to write to " << cgroupPath << std::endl; + return false; + } + } + return true; +} + +bool CgroupUtil::changeTaskAffinity(const std::string &taskName, const std::string &hexMask) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string cmd = "taskset -p " + hexMask + " " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed CPU affinity for TID " << tid << " to " << hexMask << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change affinity for TID " << tid << std::endl; + return false; + } + } + } + return true; +} + +bool CgroupUtil::changeThreadAffinity(const std::string &taskName, const std::string &threadName, const std::string &hexMask) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string commPath = taskPath + tid + "/comm"; + std::string comm = FSUtil::readFile(commPath); + if (ShellUtil::exec("echo \"" + comm + "\" | grep -iE \"" + threadName + "\"").first == 0) { + std::string cmd = "taskset -p " + hexMask + " " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed CPU affinity for thread " << threadName << " (TID " << tid << ") to " << hexMask << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change affinity for TID " << tid << std::endl; + return false; + } + } + } + } + return true; +} + +bool CgroupUtil::changeTaskNice(const std::string &taskName, int nice) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string cmd = "renice -n " + std::to_string(nice) + " -p " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed nice value for TID " << tid << " to " << nice << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change nice value for TID " << tid << std::endl; + return false; + } + } + } + return true; +} + +bool CgroupUtil::changeThreadNice(const std::string &taskName, const std::string &threadName, int nice) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string commPath = taskPath + tid + "/comm"; + std::string comm = FSUtil::readFile(commPath); + if (ShellUtil::exec("echo \"" + comm + "\" | grep -iE \"" + threadName + "\"").first == 0) { + std::string cmd = "renice -n " + std::to_string(nice) + " -p " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed nice value for thread " << threadName << " (TID " << tid << ") to " << nice << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change nice value for TID " << tid << std::endl; + return false; + } + } + } + } + return true; +} + +bool CgroupUtil::changeTaskRt(const std::string &taskName, int priority) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string cmd = "chrt -f -p " + std::to_string(priority) + " " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed real-time priority for TID " << tid << " to " << priority << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change real-time priority for TID " << tid << std::endl; + return false; + } + } + } + return true; +} + +bool CgroupUtil::changeThreadRt(const std::string &taskName, const std::string &threadName, int priority) { + std::pair result = ShellUtil::exec("echo \"" + psRet + "\" | grep -iE \"" + taskName + "\" | awk '{print $1}'"); + std::vector pids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &pid : pids) { + std::string taskPath = "/proc/" + pid + "/task/"; + std::pair tidResult = ShellUtil::exec("ls " + taskPath); + std::vector tids = TypeConverterUtil::stringToVectorString(tidResult.second); + + for (const std::string &tid : tids) { + std::string commPath = taskPath + tid + "/comm"; + std::string comm = FSUtil::readFile(commPath); + if (ShellUtil::exec("echo \"" + comm + "\" | grep -iE \"" + threadName + "\"").first == 0) { + std::string cmd = "chrt -f -p " + std::to_string(priority) + " " + tid; + int status = ShellUtil::exec(cmd).first; + if (status == 0) { + Logger(LogType::INFO) << "Changed real-time priority for thread " << threadName << " (TID " << tid << ") to " << priority << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to change real-time priority for TID " << tid << std::endl; + return false; + } + } + } + } + return true; +} + +bool CgroupUtil::changeTaskHighPrio(const std::string &taskName) { + return changeTaskNice(taskName, -15); +} + +bool CgroupUtil::changeThreadHighPrio(const std::string &taskName, const std::string &threadName) { + return changeThreadNice(taskName, threadName, -15); +} + +bool CgroupUtil::unpinThread(const std::string &taskName, const std::string &threadName) { + return changeThreadCgroup(taskName, threadName, "", "cpuset"); +} + +bool CgroupUtil::pinThreadOnPwr(const std::string &taskName, const std::string &threadName) { + return changeThreadCgroup(taskName, threadName, "background", "cpuset"); +} + +bool CgroupUtil::pinThreadOnMid(const std::string &taskName, const std::string &threadName) { + if (!unpinThread(taskName, threadName)) { + return false; + } + std::string halfMask = getHalfCpuMask(); + return changeThreadAffinity(taskName, threadName, halfMask); +} + +bool CgroupUtil::pinThreadOnPerf(const std::string &taskName, const std::string &threadName) { + if (!unpinThread(taskName, threadName)) { + return false; + } + std::string fullMask = getFullCpuMask(); + return changeThreadAffinity(taskName, threadName, fullMask); +} + +bool CgroupUtil::unpinProc(const std::string &taskName) { + return changeTaskCgroup(taskName, "", "cpuset"); +} + +bool CgroupUtil::pinProcOnPwr(const std::string &taskName) { + return changeTaskCgroup(taskName, "background", "cpuset"); +} + +bool CgroupUtil::pinProcOnMid(const std::string &taskName) { + if (!unpinProc(taskName)) { + return false; + } + std::string halfMask = getHalfCpuMask(); + return changeTaskAffinity(taskName, halfMask); +} + +bool CgroupUtil::pinProcOnPerf(const std::string &taskName) { + if (!unpinProc(taskName)) { + return false; + } + std::string fullMask = getFullCpuMask(); + return changeTaskAffinity(taskName, fullMask); +} + +void CgroupUtil::rebuildProcessScanCache() { + std::pair result = ShellUtil::exec("ps -Ao pid,args"); + if (result.first == 0) { + psRet = result.second; + } else { + Logger(LogType::ERROR, std::cerr) << "Failed to rebuild process scan cache" << std::endl; + } +} \ No newline at end of file diff --git a/src/util/cgroup_util.hpp b/src/util/cgroup_util.hpp new file mode 100644 index 0000000..c499cb8 --- /dev/null +++ b/src/util/cgroup_util.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include + +// C++ version of https://github.com/yc9559/uperf/blob/master/magisk/script/libcgroup.sh +class CgroupUtil { +private: + std::string psRet; + + /** + * Get the number of CPU cores + * + * @return The number of CPU cores + */ + int getCpuCount(); + + /** + * Generate CPU mask for all cores + * + * @return The CPU mask as a hex string + */ + std::string getFullCpuMask(); + + /** + * Generate CPU mask for half of the cores + * + * @return The CPU mask as a hex string + */ + std::string getHalfCpuMask(); + +public: + CgroupUtil(); + + /** + * Changes the cgroup of tasks matching the given task name + * + * @param taskName The name of the task to match + * @param cgroupName The name of the cgroup to move the task to + * @param cgroupType The type of cgroup ("cpuset" or "stune") + * @return True if the operation was successful, false otherwise + */ + bool changeTaskCgroup(const std::string &taskName, const std::string &cgroupName, const std::string &cgroupType); + + /** + * Changes the cgroup of processes matching the given process name + * + * @param processName The name of the process to match + * @param cgroupName The name of the cgroup to move the process to + * @param cgroupType The type of cgroup ("cpuset" or "stune") + * @return True if the operation was successful, false otherwise + */ + bool changeProcCgroup(const std::string &processName, const std::string &cgroupName, const std::string &cgroupType); + + /** + * Changes the cgroup of specific threads matching the given task and thread names + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to match + * @param cgroupName The name of the cgroup to move the thread to + * @param cgroupType The type of cgroup ("cpuset" or "stune") + * @return True if the operation was successful, false otherwise + */ + bool changeThreadCgroup(const std::string &taskName, const std::string &threadName, const std::string &cgroupName, const std::string &cgroupType); + + /** + * Changes the cgroup of the main thread of tasks matching the given task name + * + * @param taskName The name of the task to match + * @param cgroupName The name of the cgroup to move the main thread to + * @param cgroupType The type of cgroup ("cpuset" or "stune") + * @return True if the operation was successful, false otherwise + */ + bool changeMainThreadCgroup(const std::string &taskName, const std::string &cgroupName, const std::string &cgroupType); + + /** + * Changes the CPU affinity of tasks matching the given task name + * + * @param taskName The name of the task to match + * @param hexMask The CPU affinity mask in hexadecimal format + * @return True if the operation was successful, false otherwise + */ + bool changeTaskAffinity(const std::string &taskName, const std::string &hexMask); + + /** + * Changes the CPU affinity of specific threads matching the given task and thread names + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to match + * @param hexMask The CPU affinity mask in hexadecimal format + * @return True if the operation was successful, false otherwise + */ + bool changeThreadAffinity(const std::string &taskName, const std::string &threadName, const std::string &hexMask); + + /** + * Changes the nice value of tasks matching the given task name + * + * @param taskName The name of the task to match + * @param nice The nice value to set (relative to 120) + * @return True if the operation was successful, false otherwise + */ + bool changeTaskNice(const std::string &taskName, int nice); + + /** + * Changes the nice value of specific threads matching the given task and thread names + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to match + * @param nice The nice value to set (relative to 120) + * @return True if the operation was successful, false otherwise + */ + bool changeThreadNice(const std::string &taskName, const std::string &threadName, int nice); + + /** + * Changes the real-time priority of tasks matching the given task name + * + * @param taskName The name of the task to match + * @param priority The real-time priority to set (99-x, 1<=x<=99) + * @return True if the operation was successful, false otherwise + */ + bool changeTaskRt(const std::string &taskName, int priority); + + /** + * Changes the real-time priority of specific threads matching the given task and thread names + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to match + * @param priority The real-time priority to set (99-x, 1<=x<=99) + * @return True if the operation was successful, false otherwise + */ + bool changeThreadRt(const std::string &taskName, const std::string &threadName, int priority); + + /** + * Changes the priority of tasks matching the given task name to high priority + * + * @param taskName The name of the task to match + * @return True if the operation was successful, false otherwise + */ + bool changeTaskHighPrio(const std::string &taskName); + + /** + * Changes the priority of specific threads matching the given task and thread names to high priority + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to match + * @return True if the operation was successful, false otherwise + */ + bool changeThreadHighPrio(const std::string &taskName, const std::string &threadName); + + /** + * Unpins a specific thread from its current CPU + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to unpin + * @return True if the operation was successful, false otherwise + */ + bool unpinThread(const std::string &taskName, const std::string &threadName); + + /** + * Pins a specific thread to the power-efficient CPU cores + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to pin + * @return True if the operation was successful, false otherwise + */ + bool pinThreadOnPwr(const std::string &taskName, const std::string &threadName); + + /** + * Pins a specific thread to the mid-performance CPU cores + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to pin + * @return True if the operation was successful, false otherwise + */ + bool pinThreadOnMid(const std::string &taskName, const std::string &threadName); + + /** + * Pins a specific thread to the high-performance CPU cores + * + * @param taskName The name of the task to match + * @param threadName The name of the thread to pin + * @return True if the operation was successful, false otherwise + */ + bool pinThreadOnPerf(const std::string &taskName, const std::string &threadName); + + /** + * Unpins a process from its current CPU + * + * @param taskName The name of the task to unpin + * @return True if the operation was successful, false otherwise + */ + bool unpinProc(const std::string &taskName); + + /** + * Pins a process to the power-efficient CPU cores + * + * @param taskName The name of the task to pin + * @return True if the operation was successful, false otherwise + */ + bool pinProcOnPwr(const std::string &taskName); + + /** + * Pins a process to the mid-performance CPU cores + * + * @param taskName The name of the task to pin + * @return True if the operation was successful, false otherwise + */ + bool pinProcOnMid(const std::string &taskName); + + /** + * Pins a process to the high-performance CPU cores + * + * @param taskName The name of the task to pin + * @return True if the operation was successful, false otherwise + */ + bool pinProcOnPerf(const std::string &taskName); + + // Rebuilds the process scan cache + void rebuildProcessScanCache(); +}; \ No newline at end of file diff --git a/src/util/fs_util.cpp b/src/util/fs_util.cpp new file mode 100644 index 0000000..6878354 --- /dev/null +++ b/src/util/fs_util.cpp @@ -0,0 +1,105 @@ +#include "fs_util.hpp" + +#include +#include +#include +#include + +#include "logger.hpp" +#include "shell_util.hpp" + +std::string FSUtil::readFile(const std::string &filename) { + std::ifstream file(filename); + std::string content; + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + content += line + "\n"; + } + file.close(); + } else { + Logger(LogType::ERROR, std::cerr) << "Unable to open file " << filename << std::endl; + } + return content; +} + +bool FSUtil::writeFile(const std::string &fileName, const std::string &content, bool append) { + // Check if the file exists + if (!isPathExists(fileName)) { + // File does not exist, return false + Logger(LogType::ERROR) << fileName << " does not exist" << std::endl; + return false; + } + + // Determine the open mode based on the append flag + std::ios_base::openmode mode = std::ios::out; + if (append) { + mode |= std::ios::app; + } + + // Write the content + std::ofstream outFile(fileName.c_str(), mode); + if (!outFile.is_open()) { + // Failed to open the file for writing, return false + Logger(LogType::ERROR, std::cerr) << "Failed to open " << fileName << " for writing" << std::endl; + return false; + } + outFile << content; + outFile.close(); + + // Log the success + Logger(LogType::INFO) << fileName << " -> " << content << std::endl; + return true; +} + +bool FSUtil::createFolder(const std::string &folderPath) { + if (mkdir(folderPath.c_str(), 0777) == 0) { + return true; + } else { + Logger(LogType::ERROR, std::cerr) << "Unable to create folder " << folderPath << std::endl; + return false; + } +} + +bool FSUtil::deleteFolder(const std::string &folderPath) { + if (rmdir(folderPath.c_str()) == 0) { + return true; + } else { + Logger(LogType::ERROR, std::cerr) << "Unable to delete folder " << folderPath << std::endl; + return false; + } +} + +bool FSUtil::isPathExists(const std::string &path) { + struct stat info; + int ret = stat(path.c_str(), &info); + return ret == 0; +} + +std::vector FSUtil::getPathsFromWp(const std::string &wildcardPath) { + // Execute a shell command to retrieve the output of the wildcard expansion + std::pair result = ShellUtil::exec( + "for i in " + wildcardPath + "; do echo $i; done"); + + // Split the output by line and store it in a vector + std::vector paths; + std::stringstream stream(result.second); + std::string path; + while (getline(stream, path)) { + paths.push_back(path); + } + return paths; +} + +void FSUtil::removePath(const std::string &path) { + // If the path contains an asterisk, then use the getPathsFromWp() function to get a list of paths and remove them + if (path.find('*') != std::string::npos) { + std::vector paths = getPathsFromWp(path); + for (const std::string &p: paths) { + remove(p.c_str()); + } + } else { + // If path does not contain an asterisk, then continue with normal method + remove(path.c_str()); + } +} \ No newline at end of file diff --git a/src/util/fs_util.hpp b/src/util/fs_util.hpp new file mode 100644 index 0000000..1ae730b --- /dev/null +++ b/src/util/fs_util.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + + +class FSUtil { +public: + /** + * Reads the content of a file and returns it as a string + * + * @param filename, the path to the file + * @return The content of the file as a string + */ + static std::string readFile(const std::string &filename); + + /** + * Writes the given content to a file + * + * @param fileName, the name of the file to mutate + * @param content, the content to be written to the file + * @param append, whether the content should be appended to the file + * @return true if the write operation is successful, false otherwise + */ + static bool writeFile(const std::string &fileName, const std::string &content, bool append = false); + + /** + * Creates a folder at the specified path + * + * @param folderPath, the path where the folder should be created + * @return true if the folder is created successfully, false otherwise + */ + static bool createFolder(const std::string &folderPath); + + /** + * Deletes a folder at the specified path + * + * @param folderPath, the path to the folder to be deleted + * @return true if the folder is deleted successfully, false otherwise + */ + static bool deleteFolder(const std::string &folderPath); + + /** + * Checks if the given path exists + * + * @param path, the path to a file or folder + * @return true if the path exists, false otherwise + */ + static bool isPathExists(const std::string &path); + + /** + * Get a list of paths from a wildcard path + * + * @param wildcardPath, the path containing a wildcard (*) + * @return A vector of expanded paths + */ + static std::vector getPathsFromWp(const std::string &wildcardPath); + + /** + * Remove a path with support for wildcard path deletion + * + * @param path, the path or wildcard path to be removed + */ + static void removePath(const std::string &path); + + /** + * Get the full path of the current executable + * + * @return The full path of the executable or an empty string on failure + */ + static std::string getExecutablePath() { + char result[PATH_MAX]; + ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + if (count != -1) { + result[count] = '\0'; + return std::string(result); + } + return ""; + } + + /** + * Extract the directory path from the full executable path + * + * @return The directory containing the executable or an empty string on failure + */ + static std::string getExecutableDirectory() { + std::string path = getExecutablePath(); + size_t found = path.find_last_of("/\\"); + return (found != std::string::npos) ? path.substr(0, found) : ""; + } +}; \ No newline at end of file diff --git a/src/util/logger.cpp b/src/util/logger.cpp new file mode 100644 index 0000000..c894102 --- /dev/null +++ b/src/util/logger.cpp @@ -0,0 +1,37 @@ +#include "logger.hpp" + +#include +#include +#include + +Logger::Logger(LogType type, std::ostream &logStream) : logType(type), logStream(logStream) { + // Initialize log with timestamp and log type + logStream << "[" << getTimestamp() << "] "; + logStream << "[" << logTypeToString(type) << "] "; +} + +Logger &Logger::operator<<(std::ostream &(*manip)(std::ostream &)) { + // Log manipulators + logStream << manip; + return *this; +} + +std::string Logger::getTimestamp() { + std::time_t current_time = std::time(nullptr); + std::stringstream date; + date << std::put_time(std::localtime(¤t_time), "%Y-%m-%d %H:%M:%S"); + return date.str(); +} + +std::string Logger::logTypeToString(LogType type) { + switch (type) { + case LogType::INFO: + return "INFO"; + case LogType::WARNING: + return "WARNING"; + case LogType::ERROR: + return "ERROR"; + default: + return "UNKNOWN"; + } +} \ No newline at end of file diff --git a/src/util/logger.hpp b/src/util/logger.hpp new file mode 100644 index 0000000..c6cb5f5 --- /dev/null +++ b/src/util/logger.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +// Enumeration for different types of log messages +enum class LogType { + INFO, + WARNING, + ERROR +}; + +class Logger { +public: + // Constructor to initialize log type and stream + Logger(LogType type, std::ostream &logStream = std::cout); + + // Overload stream insertion operator for Logger + template + Logger &operator<<(const T &value); + + // Overload stream insertion operator for manipulators + Logger &operator<<(std::ostream &(*manip)(std::ostream &)); + +private: + // Get current time as a string + static std::string getTimestamp(); + + // Convert LogType enum to corresponding string + static std::string logTypeToString(LogType type); + + // Log type and stream member variables + LogType logType; + std::ostream &logStream; +}; + +// Template implementation +template +Logger &Logger::operator<<(const T &value) { + // Log values + logStream << value; + return *this; +} \ No newline at end of file diff --git a/src/util/shell_util.cpp b/src/util/shell_util.cpp new file mode 100644 index 0000000..18da68a --- /dev/null +++ b/src/util/shell_util.cpp @@ -0,0 +1,30 @@ +#include "shell_util.hpp" + +#include +#include +#include + +std::pair ShellUtil::exec(const std::string &cmd) { + FILE *pipe = popen(cmd.c_str(), "r"); + if (!pipe) return {-1, ""}; // Return error if pipe couldn't be opened + + std::stringstream output; + std::vector buffer(4096); // Initial buffer size + + while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + // If buffer is full, resize it and continue + if (strlen(buffer.data()) == buffer.size() - 1) { + buffer.resize(buffer.size() * 2); + continue; + } + output << buffer.data(); + } + + int status = pclose(pipe); + + // Extract the result and remove the trailing newline, if any + std::string result = output.str(); + if (!result.empty() && result.back() == '\n') result.pop_back(); + + return {WEXITSTATUS(status), result}; +} \ No newline at end of file diff --git a/src/util/shell_util.hpp b/src/util/shell_util.hpp new file mode 100644 index 0000000..8f3db21 --- /dev/null +++ b/src/util/shell_util.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +class ShellUtil { +public: + /** + * Execute a shell command and capture its output + * + * @param cmd the shell command to execute + * @return A pair containing the exit status and the command output + */ + static std::pair exec(const std::string &cmd); +}; \ No newline at end of file diff --git a/src/util/thread_process_util.cpp b/src/util/thread_process_util.cpp new file mode 100644 index 0000000..73e19bf --- /dev/null +++ b/src/util/thread_process_util.cpp @@ -0,0 +1,244 @@ +#include "thread_process_util.hpp" + +#include +#include +#include + +#include "logger.hpp" +#include "shell_util.hpp" +#include "type_converter_util.hpp" + +std::vector +ThreadProcessUtil::getPid(const std::string &processName, bool multiple) { + std::vector pids; + std::string command = "ps -Ao pid,args,cmd | grep -iE '" + processName + "' | grep -v grep | awk '{print $1}'"; + if (!multiple) { + command += " | head -n 1"; + } + std::pair result = ShellUtil::exec(command); + std::vector stringPids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &stringPid: stringPids) { + if (!stringPid.empty()) { + pids.push_back(TypeConverterUtil::to(stringPid)); + } + } + + return pids; +} + +std::vector +ThreadProcessUtil::getProcTid(const std::string &processName, const std::string &threadName, + bool multiple) { + std::vector tids; + pid_t pid = getPid(processName)[0]; + std::pair result = ShellUtil::exec( + "ls /proc/" + std::to_string(pid) + "/task/"); + std::vector stringTids = TypeConverterUtil::stringToVectorString(result.second); + + for (const std::string &stringTid: stringTids) { + if (!stringTid.empty()) { + std::string tidPath = "/proc/" + std::to_string(pid) + "/task/" + stringTid + "/comm"; + std::ifstream tidFile(tidPath); + std::string currentThreadName; + tidFile >> currentThreadName; + + if (currentThreadName.find(threadName) != std::string::npos) { + tids.push_back(TypeConverterUtil::to(stringTid)); + if (!multiple) break; + } + } + } + + return tids; +} + +bool +ThreadProcessUtil::killProcess(const std::string &processName, bool force, bool multiple) { + std::vector pids = getPid(processName, multiple); + std::string killCommand = force ? "kill -9 " : "kill "; + + for (const pid_t &pid: pids) { + std::string command = killCommand + std::to_string(pid); + std::pair result = ShellUtil::exec(command); + if (result.first == 0) { + Logger(LogType::INFO) << "Successfully killed process " << processName << " (PID: " << pid << ")" << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to kill process " << processName << " (PID: " << pid << ")" << std::endl; + return false; + } + } + + return true; +} + +bool +ThreadProcessUtil::killThread(const std::string &processName, const std::string &threadName, + bool force, bool multiple) { + std::vector tids = getProcTid(processName, threadName, multiple); + std::string killCommand = force ? "kill -9 " : "kill "; + + for (const pid_t &tid: tids) { + std::string command = killCommand + std::to_string(tid); + std::pair result = ShellUtil::exec(command); + if (result.first == 0) { + Logger(LogType::INFO) << "Successfully killed thread " << threadName << " (TID: " << tid << ")" << std::endl; + } else { + Logger(LogType::ERROR) << "Failed to kill thread " << threadName << " (TID: " << tid << ")" << std::endl; + return false; + } + } + + return true; +} + +void ThreadProcessUtil::daemonize(const std::function& daemon_task, bool close_standard_fds_only) { + pid_t pid; + + if (daemon_task == nullptr) { + // No function provided; daemonize the entire process + + // Fork off the parent process + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + + // Let the parent terminate + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + // On success: The child process becomes session leader + if (setsid() < 0) { + exit(EXIT_FAILURE); + } + + // Ignore signals + std::signal(SIGCHLD, SIG_IGN); + std::signal(SIGHUP, SIG_IGN); + + // Fork off for the second time + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + + // Let the parent terminate + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + // Set new file permissions + umask(0); + + // Change the working directory to the root directory + if (chdir("/") < 0) { + exit(EXIT_FAILURE); + } + + // Close file descriptors + if (close_standard_fds_only) { + // Close only stdin, stdout, stderr + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } else { + // Close all open file descriptors + for (int fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--) { + close(fd); + } + } + + // Reopen stdin, stdout, stderr to /dev/null + int fd0 = open("/dev/null", O_RDWR); + if (fd0 != -1) { + dup2(fd0, STDIN_FILENO); + dup2(fd0, STDOUT_FILENO); + dup2(fd0, STDERR_FILENO); + + // Close the extra file descriptor if it's not standard input/output/error + if (fd0 > STDERR_FILENO) { + close(fd0); + } + } else { + exit(EXIT_FAILURE); + } + + // Daemon code continues here + } else { + // Function provided; daemonize only the specified task + + // Fork off the parent process + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + + // Let the parent continue + if (pid > 0) { + return; // Parent continues execution + } + + // Child process becomes session leader + if (setsid() < 0) { + exit(EXIT_FAILURE); + } + + // Ignore signals + std::signal(SIGCHLD, SIG_IGN); + std::signal(SIGHUP, SIG_IGN); + + // Fork off for the second time + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + + // Let the first child exit + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + // Set new file permissions + umask(0); + + // Change the working directory to the root directory + if (chdir("/") < 0) { + exit(EXIT_FAILURE); + } + + // Close file descriptors + if (close_standard_fds_only) { + // Close only stdin, stdout, stderr + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } else { + // Close all open file descriptors + for (int fd = sysconf(_SC_OPEN_MAX); fd >= 0; fd--) { + close(fd); + } + } + + // Reopen stdin, stdout, stderr to /dev/null + int fd0 = open("/dev/null", O_RDWR); + if (fd0 != -1) { + dup2(fd0, STDIN_FILENO); + dup2(fd0, STDOUT_FILENO); + dup2(fd0, STDERR_FILENO); + + // Close the extra file descriptor if it's not standard input/output/error + if (fd0 > STDERR_FILENO) { + close(fd0); + } + } else { + exit(EXIT_FAILURE); + } + + // Execute the daemon task + daemon_task(); + + exit(EXIT_SUCCESS); + } +} \ No newline at end of file diff --git a/src/util/thread_process_util.hpp b/src/util/thread_process_util.hpp new file mode 100644 index 0000000..80e1602 --- /dev/null +++ b/src/util/thread_process_util.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ThreadProcessUtil { +public: + /** + * Get the process ID(s) (PID) of the specified process name. + * + * @param processName, the name of the process. + * @param multiple, if true, return all PIDs found, otherwise return only the first PID. + * @return A vector of PIDs corresponding to the process name. + */ + static std::vector getPid(const std::string &processName, bool multiple = false); + + /** + * Get the thread ID(s) (TID) of the specified thread name within a process + * + * @param processName, the name of the process + * @param threadName, the name of the thread + * @param multiple, if true, return all TIDs found, otherwise return only the first TID. + * @return A vector of TIDs corresponding to the thread name. + */ + static std::vector getProcTid(const std::string &processName, const std::string &threadName, + bool multiple = false); + + /** + * Kill process with given name + * + * @param processName, the name of the process to kill + * @param force, if true, use SIGKILL instead of SIGTERM + * @param multiple, if true, kill all processes with the given name, otherwise kill only the first one + * @return true if all targeted processes were successfully killed, false otherwise + */ + static bool killProcess(const std::string &processName, bool force = false, bool multiple = false); + + /** + * Kill thread with given name + * + * @param processName, the name of the process + * @param threadName, the name of the thread to kill + * @param force, if true, use SIGKILL instead of SIGTERM + * @param multiple, if true, kill all threads with the given name, otherwise kill only the first one + * @return true if all targeted threads were successfully killed, false otherwise + */ + static bool killThread(const std::string &processName, const std::string &threadName, bool force = false, + bool multiple = false); + + /** + * Daemonizes the current process or given function. + */ + static void daemonize(const std::function& daemon_task = nullptr, bool close_standard_fds_only = false); +}; diff --git a/src/util/type_converter_util.cpp b/src/util/type_converter_util.cpp new file mode 100644 index 0000000..7a6049e --- /dev/null +++ b/src/util/type_converter_util.cpp @@ -0,0 +1,15 @@ +#include "type_converter_util.hpp" + +std::vector +TypeConverterUtil::stringToVectorString(const std::string &source) { + std::vector result; + std::stringstream stream(source); + std::string line; + + // Process each line of the input string and add it to the result vector + while (std::getline(stream, line)) { + result.push_back(line); + } + + return result; +} \ No newline at end of file diff --git a/src/util/type_converter_util.hpp b/src/util/type_converter_util.hpp new file mode 100644 index 0000000..4208249 --- /dev/null +++ b/src/util/type_converter_util.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +class TypeConverterUtil { +public: + /** + * Converts a value from the source type to the target type + * + * @tparam targetType, the type to convert to + * @tparam sourceType, the type to convert from + * @param source, the value to convert + * @return The converted value of the target type + */ + template + static targetType to(const sourceType &source); + + /** + * Converts a string into a vector of strings, splitting by line + * + * @param source, the input string to convert + * @return A vector of strings where each element corresponds to a line in the input string + */ + static std::vector stringToVectorString(const std::string &source); +}; + +// Template implementation +template +targetType TypeConverterUtil::to(const sourceType &source) { + targetType result; + std::stringstream stream; + + // Convert the source value to a string + stream << source; + + // Extract the converted value from the stringstream + stream >> result; + + return result; +} \ No newline at end of file diff --git a/src/vmtouch.cpp b/src/vmtouch.cpp deleted file mode 100644 index 760de85..0000000 --- a/src/vmtouch.cpp +++ /dev/null @@ -1,1045 +0,0 @@ -/* -Copyright (c) 2009-2017 Doug Hoyte and contributors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#define VMTOUCH_VERSION "1.3.1" -#define RESIDENCY_CHART_WIDTH 60 -#define CHART_UPDATE_INTERVAL 0.1 -#define MAX_CRAWL_DEPTH 1024 -#define MAX_NUMBER_OF_IGNORES 1024 -#define MAX_NUMBER_OF_FILENAME_FILTERS 1024 -#define MAX_FILENAME_LENGTH 1024 - -#if defined(__linux__) || (defined(__hpux) && !defined(__LP64__)) -// Make sure off_t is 64 bits on linux and when creating 32bit programs -// on HP-UX. -#define _FILE_OFFSET_BITS 64 -#endif - -#ifdef __linux__ -// Required for posix_fadvise() on some linux systems -#define _XOPEN_SOURCE 600 -// Required for mincore() on some linux systems -#define _DEFAULT_SOURCE -#define _BSD_SOURCE -// for O_NOATIME -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__linux__) -// Used to find size of block devices -#include -#include -// Used to check kernal version to know if mincore reports correctly -#include -#endif - -/* - * To find out if the stat results from a single file correspond to a file we - * have already seen, we need to compare both the device and the inode - */ -struct dev_and_inode { - dev_t dev; - ino_t ino; -}; - -long pagesize; - -int64_t total_pages = 0; -int64_t total_pages_in_core = 0; -int64_t total_files = 0; -int64_t total_dirs = 0; - -int64_t offset = 0; -int64_t max_len = 0; - -unsigned int junk_counter; // just to prevent any compiler optimizations - -int curr_crawl_depth = 0; -ino_t crawl_inodes[MAX_CRAWL_DEPTH]; - -// remember all inodes (for files with inode count > 1) to find duplicates -void *seen_inodes = NULL; -dev_t orig_device = 0; -int orig_device_inited = 0; - -int o_touch = 0; -int o_evict = 0; -int o_quiet = 0; -int o_verbose = 0; -int o_lock = 0; -int o_lockall = 0; -int o_daemon = 0; -int o_followsymlinks = 0; -int o_singlefilesystem = 0; -int o_ignorehardlinkeduplictes = 0; -size_t o_max_file_size=SIZE_MAX; -int o_wait = 0; -static char *o_batch = NULL; -static char *o_pidfile = NULL; -static char *o_output = NULL; -int o_0_delim = 0; - -char *ignore_list[MAX_NUMBER_OF_IGNORES]; -char *filename_filter_list[MAX_NUMBER_OF_FILENAME_FILTERS]; -int number_of_ignores = 0; -int number_of_filename_filters = 0; - -int exit_pipe[2]; - -int daemon_pid; - -void send_exit_signal(char code) { - if (daemon_pid == 0 && o_wait) { - if (write(exit_pipe[1], &code, 1) < 0) - fprintf(stderr, "vmtouch: FATAL: write: %s", strerror(errno)); - } -} - -void usage() { - printf("\n"); - printf("vmtouch v%s - the Virtual Memory Toucher by Doug Hoyte\n", VMTOUCH_VERSION); - printf("Portable file system cache diagnostics and control\n\n"); - printf("Usage: vmtouch [OPTIONS] ... FILES OR DIRECTORIES ...\n\nOptions:\n"); - printf(" -t touch pages into memory\n"); - printf(" -e evict pages from memory\n"); - printf(" -l lock pages in physical memory with mlock(2)\n"); - printf(" -L lock pages in physical memory with mlockall(2)\n"); - printf(" -d daemon mode\n"); - printf(" -m max file size to touch\n"); - printf(" -p use the specified portion instead of the entire file\n"); - printf(" -f follow symbolic links\n"); - printf(" -F don't crawl different filesystems\n"); - printf(" -h also count hardlinked copies\n"); - printf(" -i ignores files and directories that match this pattern\n"); - printf(" -I only process files that match this pattern\n"); - printf(" -b get files or directories from the list file\n"); - printf(" -0 in batch mode (-b) separate paths with NUL byte instead of newline\n"); - printf(" -w wait until all pages are locked (only useful together with -d)\n"); - printf(" -P write a pidfile (only useful together with -l or -L)\n"); - printf(" -o output in machine friendly format. 'kv' for key=value pairs.\n"); - printf(" -v verbose\n"); - printf(" -q quiet\n"); - exit(1); -} - -static void fatal(const char *fmt, ...) { - va_list ap; - char buf[4096]; - - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - - fprintf(stderr, "vmtouch: FATAL: %s\n", buf); - send_exit_signal(1); - exit(1); -} - -static void warning(const char *fmt, ...) { - va_list ap; - char buf[4096]; - - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - - if (!o_quiet) fprintf(stderr, "vmtouch: WARNING: %s\n", buf); -} - -static void reopen_all() { - if (freopen("/dev/null", "r", stdin) == NULL || - freopen("/dev/null", "w", stdout) == NULL || - freopen("/dev/null", "w", stdout) == NULL) - fatal("freopen: %s", strerror(errno)); -} - -static int wait_for_child() { - int exit_read = 0; - char exit_value = 0; - int wait_status; - - while (1) { - struct timeval tv; - fd_set rfds; - FD_ZERO(&rfds); - tv.tv_sec = 1; - tv.tv_usec = 0; - FD_SET(exit_pipe[0], &rfds); - if (select(exit_pipe[0] + 1, &rfds, NULL, NULL, &tv) < 0) - fatal("select: %s", strerror(errno)); - - if (waitpid(daemon_pid, &wait_status, WNOHANG) > 0) - fatal("daemon shut down unexpectedly"); - - if (FD_ISSET(exit_pipe[0], &rfds)) - break; - } - exit_read = read(exit_pipe[0], &exit_value, 1); - if (exit_read < 0) - fatal("read: %s", strerror(errno)); - return exit_value; -} - -void go_daemon() { - daemon_pid = fork(); - if (daemon_pid == -1) - fatal("fork: %s", strerror(errno)); - if (daemon_pid) { - if (o_wait) - exit(wait_for_child()); - exit(0); - } - - if (setsid() == -1) - fatal("setsid: %s", strerror(errno)); - - if (!o_wait) reopen_all(); -} - -char *pretty_print_size(int64_t inp) { - static char output[100]; - - if (inp < 1024) { - snprintf(output, sizeof(output), "%" PRId64, inp); - return output; - } - inp /= 1024; - if (inp < 1024) { - snprintf(output, sizeof(output), "%" PRId64 "K", inp); - return output; - } - inp /= 1024; - if (inp < 1024) { - snprintf(output, sizeof(output), "%" PRId64 "M", inp); - return output; - } - inp /= 1024; - snprintf(output, sizeof(output), "%" PRId64 "G", inp); - return output; -} - -/* - * Convert ASCII string to int64_t number - * Note: The inp parameter can't be a character constant because it will be overwritten. -*/ -int64_t parse_size(char *inp) { - char *tp; - int len=strlen(inp); - char *errstr = "bad size. examples: 4096, 4k, 100M, 1.5G"; - char mult_char; - int mult = 1; - double val; - - if (len < 1) fatal(errstr); - - mult_char = tolower(inp[len-1]); - - if (isalpha(mult_char)) { - switch(mult_char) { - case 'k': mult = 1024; break; - case 'm': mult = 1024 * 1024; break; - case 'g': mult = 1024 * 1024 * 1024; break; - default: fatal("unknown size multiplier: %c", mult_char); - } - inp[len-1] = '\0'; - } - - val = strtod(inp, &tp); - - if (val < 0 || val == HUGE_VAL || *tp != '\0') fatal(errstr); - - val *= mult; - - if (val > INT64_MAX) fatal(errstr); - - return (int64_t) val; -} - -int64_t bytes2pages(int64_t bytes) { - return (bytes+pagesize-1) / pagesize; -} - -void parse_range(char *inp) { - char *token; - int64_t upper_range = 0; - int64_t lower_range = 0; - - token = strsep(&inp,"-"); - - if (inp == NULL) - upper_range = parse_size(token); // single value provided - else { - if (*token != '\0') - lower_range = parse_size(token); // value before hyphen - - token = strsep(&inp,"-"); - if (*token != '\0') - upper_range = parse_size(token); // value after hyphen - - if ((token = strsep(&inp,"-")) != NULL) fatal("malformed range: multiple hyphens"); - } - - // offset must be multiple of pagesize - offset = (lower_range / pagesize) * pagesize; - - if (upper_range) { - if (upper_range <= offset) fatal("range limits out of order"); - - max_len = upper_range - offset; - } -} - -void parse_ignore_item(char *inp) { - if (inp == NULL) { - return; - } - - if (strlen(inp) > MAX_FILENAME_LENGTH) { - fatal("too long pattern provided to -i: %s", inp); - return; - } - - if (number_of_ignores >= MAX_NUMBER_OF_IGNORES) { - fatal("too many patterns passed to -i. Max is %d", MAX_NUMBER_OF_IGNORES); - return; - } - - ignore_list[number_of_ignores] = strdup(inp); - number_of_ignores++; -} - -void parse_filename_filter_item(char *inp) { - if (inp == NULL) { - return; - } - - if (strlen(inp) > MAX_FILENAME_LENGTH) { - fatal("too long pattern provided to -I: %s", inp); - return; - } - - if (number_of_filename_filters >= MAX_NUMBER_OF_FILENAME_FILTERS) { - fatal("too many patterns passed to -I. Max is %d", MAX_NUMBER_OF_FILENAME_FILTERS); - return; - } - - filename_filter_list[number_of_filename_filters] = strdup(inp); - number_of_filename_filters++; -} - -int aligned_p(void *p) { - return 0 == ((long)p & (pagesize-1)); -} - -int is_mincore_page_resident(char p) { - return p & 0x1; -} - -void increment_nofile_rlimit() { - struct rlimit r; - - if (getrlimit(RLIMIT_NOFILE, &r)) - fatal("increment_nofile_rlimit: getrlimit (%s)", strerror(errno)); - - r.rlim_cur = r.rlim_max + 1; - r.rlim_max = r.rlim_max + 1; - - if (setrlimit(RLIMIT_NOFILE, &r)) { - if (errno == EPERM) { - if (getuid() == 0 || geteuid() == 0) fatal("system open file limit reached"); - fatal("open file limit reached and unable to increase limit. retry as root"); - } - fatal("increment_nofile_rlimit: setrlimit (%s)", strerror(errno)); - } -} - -double gettimeofday_as_double() { - struct timeval tv; - gettimeofday(&tv, NULL); - - return tv.tv_sec + (tv.tv_usec/1000000.0); -} - -void print_page_residency_chart(FILE *out, char *mincore_array, int64_t pages_in_file) { - int64_t pages_in_core = 0; - int64_t pages_per_char; - int64_t i,j = 0,curr = 0; - - if (pages_in_file <= RESIDENCY_CHART_WIDTH) pages_per_char = 1; - else pages_per_char = (pages_in_file / RESIDENCY_CHART_WIDTH) + 1; - - fprintf(out, "\r["); - - for (i = 0; i < pages_in_file; i++) { - if (is_mincore_page_resident(mincore_array[i])) { - curr++; - pages_in_core++; - } - j++; - if (j == pages_per_char) { - if (curr == pages_per_char) fprintf(out, "O"); - else if (curr == 0) fprintf(out, " "); - else fprintf(out, "o"); - - j = curr = 0; - } - } - - if (j) { - if (curr == j) fprintf(out, "O"); - else if (curr == 0) fprintf(out, " "); - else fprintf(out, "o"); - } - - fprintf(out, "] %" PRId64 "/%" PRId64, pages_in_core, pages_in_file); - - fflush(out); -} - -#ifdef __linux__ -// check if mincore will report correctly, due side-channel vulnerabilities -// from 5.2+ it only reports if process has write permission to the file -// https://lwn.net/Articles/778437/ -static int can_do_mincore(struct stat *st) { - - struct utsname utsinfo; - if (uname(&utsinfo) == 0) { - unsigned long ver[16]; - int i = 0; - char *p = utsinfo.release; - while (*p) { - if (isdigit(*p)) { - ver[i] = strtol(p, &p, 10); - i++; - } else { - p++; - } - } - // kernal < 5.2 - if (ver[0] < 5 || ver[1] < 2) - return 1; - } - - uid_t uid = getuid(); - return st->st_uid == uid || - (st->st_gid == getgid() && (st->st_mode&S_IWGRP)) || - (st->st_mode&S_IWOTH) || - uid == 0; -} -#endif - -void vmtouch_file(char *path) { - int fd = -1; - void *mem = NULL; - struct stat sb; - int64_t len_of_file = 0; - int64_t len_of_range = 0; - int64_t pages_in_range; - int i; - int res; - int open_flags; - - retry_open: - - open_flags = O_RDONLY; - -#if defined(O_NOATIME) - open_flags |= O_NOATIME; -#endif - - fd = open(path, open_flags, 0); - -#if defined(O_NOATIME) - if (fd == -1 && errno == EPERM) { - open_flags &= ~O_NOATIME; - fd = open(path, open_flags, 0); - } -#endif - - if (fd == -1) { - if (errno == ENFILE || errno == EMFILE) { - increment_nofile_rlimit(); - goto retry_open; - } - - warning("unable to open %s (%s), skipping", path, strerror(errno)); - goto bail; - } - - res = fstat(fd, &sb); - - if (res) { - warning("unable to fstat %s (%s), skipping", path, strerror(errno)); - goto bail; - } - - if (S_ISBLK(sb.st_mode)) { -#if defined(__linux__) - if (ioctl(fd, BLKGETSIZE64, &len_of_file)) { - warning("unable to ioctl %s (%s), skipping", path, strerror(errno)); - goto bail; - } -#else - fatal("discovering size of block devices not (yet?) supported on this platform"); -#endif - } else { - len_of_file = sb.st_size; - } - - if (len_of_file == 0) { - goto bail; - } - - if (len_of_file > o_max_file_size) { - warning("file %s too large, skipping", path); - goto bail; - } - - if (max_len > 0 && (offset + max_len) < len_of_file) { - len_of_range = max_len; - } else if (offset >= len_of_file) { - warning("file %s smaller than offset, skipping", path); - goto bail; - } else { - len_of_range = len_of_file - offset; - } - - mem = mmap(NULL, len_of_range, PROT_READ, MAP_SHARED, fd, offset); - - if (mem == MAP_FAILED) { - warning("unable to mmap file %s (%s), skipping", path, strerror(errno)); - goto bail; - } - - if (!aligned_p(mem)) fatal("mmap(%s) wasn't page aligned", path); - - pages_in_range = bytes2pages(len_of_range); - - total_pages += pages_in_range; - - if (o_evict) { - if (o_verbose) printf("Evicting %s\n", path); - -#if defined(__linux__) || defined(__hpux) - if (posix_fadvise(fd, offset, len_of_range, POSIX_FADV_DONTNEED)) - warning("unable to posix_fadvise file %s (%s)", path, strerror(errno)); -#elif defined(__FreeBSD__) || defined(__sun__) || defined(__APPLE__) - if (msync(mem, len_of_range, MS_INVALIDATE)) - warning("unable to msync invalidate file %s (%s)", path, strerror(errno)); -#else - fatal("cache eviction not (yet?) supported on this platform"); -#endif - } else { - double last_chart_print_time = 0.0, temp_time; - char *mincore_array = static_cast(malloc(pages_in_range)); - if (mincore_array == NULL) fatal("Failed to allocate memory for mincore array (%s)", strerror(errno)); - - // 3rd arg to mincore is char* on BSD and unsigned char* on linux - if (mincore(mem, len_of_range, reinterpret_cast(mincore_array))) fatal("mincore %s (%s)", path, strerror(errno)); - for (i = 0; i < pages_in_range; i++) { - if (is_mincore_page_resident(mincore_array[i])) { - total_pages_in_core++; - } - } - - if (o_verbose) { - printf("%s\n", path); -#ifdef __linux__ - if (!can_do_mincore(&sb)) { - warning("Process does not have write permission, residency chart will not be accurate"); - } -#endif - last_chart_print_time = gettimeofday_as_double(); - print_page_residency_chart(stdout, mincore_array, pages_in_range); - } - - if (o_touch) { - for (i = 0; i < pages_in_range; i++) { - junk_counter += ((char*)mem)[i*pagesize]; - mincore_array[i] = 1; - - if (o_verbose) { - temp_time = gettimeofday_as_double(); - - if (temp_time > (last_chart_print_time+CHART_UPDATE_INTERVAL)) { - last_chart_print_time = temp_time; - print_page_residency_chart(stdout, mincore_array, pages_in_range); - } - } - } - } - - if (o_verbose) { - print_page_residency_chart(stdout, mincore_array, pages_in_range); - printf("\n"); - } - - free(mincore_array); - } - - if (o_lock) { - if (mlock(mem, len_of_range)) - fatal("mlock: %s (%s)", path, strerror(errno)); - } - - bail: - - if (!o_lock && !o_lockall && mem) { - if (munmap(mem, len_of_range)) warning("unable to munmap file %s (%s)", path, strerror(errno)); - } - - if (fd != -1) { - close(fd); - } -} - -// compare device and inode information -int compare_func(const void *p1, const void *p2) { - const struct dev_and_inode *kp1 = static_cast(p1); - const struct dev_and_inode *kp2 = static_cast(p2); - int cmp1; - cmp1 = (kp1->ino > kp2->ino) - (kp1->ino < kp2->ino); - if (cmp1 != 0) - return cmp1; - return (kp1->dev > kp2->dev) - (kp1->dev < kp2->dev); -} - -// add device and inode information to the tree of known inodes -static inline void add_object (struct stat *st) { - struct dev_and_inode *newp = static_cast(malloc(sizeof(struct dev_and_inode))); - if (newp == NULL) { - fatal("malloc: out of memory"); - } - newp->dev = st->st_dev; - newp->ino = st->st_ino; - if (tsearch(newp, &seen_inodes, compare_func) == NULL) { - fatal("tsearch: out of memory"); - } -} - -int is_ignored(const char* path) { - char *path_copy; - int match, i; - - if (!number_of_ignores) return 0; - - path_copy = strdup(path); - match = 0; - - char *filename = basename(path_copy); - - for (i = 0; i < number_of_ignores; i++) { - if (fnmatch(ignore_list[i], filename, 0) == 0) { - match = 1; - break; - } - } - - free(path_copy); - return match; -} - -int is_filename_filtered(const char* path) { - char *path_copy; - int match, i; - - if (!number_of_filename_filters) return 1; - - path_copy = strdup(path); - match = 0; - - char *filename = basename(path_copy); - - for (i = 0; i < number_of_filename_filters; i++) { - if (fnmatch(filename_filter_list[i], filename, 0) == 0) { - match = 1; - break; - } - } - - free(path_copy); - return match; -} - -// return true only if the device and inode information has not been added before -static inline int find_object(struct stat *st) { - struct dev_and_inode obj; - void *res; - obj.dev = st->st_dev; - obj.ino = st->st_ino; - res = (void *) tfind(&obj, &seen_inodes, compare_func); - return res != (void *) NULL; -} - -void vmtouch_crawl(char *path) { - struct stat sb; - DIR *dirp; - struct dirent *de; - char npath[PATH_MAX]; - int res; - int tp_path_len = strlen(path); - int i; - - if (path[tp_path_len-1] == '/' && tp_path_len > 1) path[tp_path_len-1] = '\0'; // prevent ugly double slashes when printing path names - - if (is_ignored(path)) { - return; - } - - res = o_followsymlinks ? stat(path, &sb) : lstat(path, &sb); - - if (res) { - warning("unable to stat %s (%s)", path, strerror(errno)); - return; - } else { - if (S_ISLNK(sb.st_mode)) { - warning("not following symbolic link %s", path); - return; - } - - if (o_singlefilesystem) { - if (!orig_device_inited) { - orig_device = sb.st_dev; - orig_device_inited = 1; - } else { - if (sb.st_dev != orig_device) { - warning("not recursing into separate filesystem %s", path); - return; - } - } - } - - if (!o_ignorehardlinkeduplictes && sb.st_nlink > 1) { - /* - * For files with more than one link to it, ignore it if we already know - * inode. Without this check files copied as hardlinks (cp -al) are - * counted twice (which may lead to a cache usage of more than 100% of - * RAM). - */ - if (find_object(&sb)) { - // we already saw the device and inode referenced by this file - return; - } else { - add_object(&sb); - } - } - - if (S_ISDIR(sb.st_mode)) { - for (i = 0; i < curr_crawl_depth; i++) { - if (crawl_inodes[i] == sb.st_ino) { - warning("symbolic link loop detected: %s", path); - return; - } - } - - if (curr_crawl_depth == MAX_CRAWL_DEPTH) - fatal("maximum directory crawl depth reached: %s", path); - - total_dirs++; - - crawl_inodes[curr_crawl_depth] = sb.st_ino; - - retry_opendir: - - dirp = opendir(path); - - if (dirp == NULL) { - if (errno == ENFILE || errno == EMFILE) { - increment_nofile_rlimit(); - goto retry_opendir; - } - - warning("unable to opendir %s (%s), skipping", path, strerror(errno)); - return; - } - - while((de = readdir(dirp)) != NULL) { - if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; - - if (snprintf(npath, sizeof(npath), "%s/%s", path, de->d_name) >= sizeof(npath)) { - warning("path too long %s", path); - goto bail; - } - - curr_crawl_depth++; - vmtouch_crawl(npath); - curr_crawl_depth--; - } - - bail: - - if (closedir(dirp)) { - warning("unable to closedir %s (%s)", path, strerror(errno)); - return; - } - } else if (S_ISLNK(sb.st_mode)) { - warning("not following symbolic link %s", path); - return; - } else if (S_ISREG(sb.st_mode) || S_ISBLK(sb.st_mode)) { - if (is_filename_filtered(path)) { - total_files++; - vmtouch_file(path); - } - } else { - warning("skipping non-regular file: %s", path); - } - } -} - -static void vmtouch_batch_crawl(const char *path) { - FILE *f; - char *line = NULL; - size_t len = 0; - ssize_t read; - int delim = o_0_delim ? '\0' : '\n'; - - if (!strcmp(path, "-")) { - f = stdin; - } else { - f = fopen(path, "r"); - if (!f) { - warning("unable to open %s (%s), skipping", path, strerror(errno)); - return; - } - } - - while ((read = getdelim(&line, &len, delim, f)) != -1) { - // strip the newline character - line[read-1] = '\0'; - vmtouch_crawl(line); - } - - free(line); - fclose(f); -} - -static void remove_pidfile() { - int res = 0; - - res = unlink(o_pidfile); - if (res < 0 && errno != ENOENT) { - warning("unable to remove pidfile %s (%s)", o_pidfile, strerror(errno)); - } -} - -static void write_pidfile() { - FILE *f = NULL; - size_t wrote = 0; - - f = fopen(o_pidfile, "w"); - if (!f) { - warning("unable to open pidfile %s (%s), skipping", o_pidfile, strerror(errno)); - return; - } - - wrote = fprintf(f, "%d\n", getpid()); - - fclose(f); - - if (wrote < 0) { - warning("unable to write to pidfile %s (%s), deleting it", o_pidfile, strerror(errno)); - remove_pidfile(); - } -} - -static void signal_handler_clear_pidfile(int signal_num) { - remove_pidfile(); -} - -static void register_signals_for_pidfile() { - struct sigaction sa = {0}; - sa.sa_handler = signal_handler_clear_pidfile; - if (sigaction(SIGINT, &sa, NULL) < 0 || - sigaction(SIGTERM, &sa, NULL) < 0 || - sigaction(SIGQUIT, &sa, NULL) < 0) { - warning("unable to register signals for pidfile (%s), skipping", strerror(errno)); - } -} - -int main(int argc, char **argv) { - int ch, i; - char *prog = argv[0]; - struct timeval start_time; - struct timeval end_time; - - if (pipe(exit_pipe)) - fatal("pipe: %s", strerror(errno)); - - pagesize = sysconf(_SC_PAGESIZE); - - while((ch = getopt(argc, argv, "tevqlLdfFh0i:I:p:b:m:P:wo:")) != -1) { - switch(ch) { - case '?': usage(); break; - case 't': o_touch = 1; break; - case 'e': o_evict = 1; break; - case 'q': o_quiet = 1; break; - case 'v': o_verbose++; break; - case 'l': o_lock = 1; o_touch = 1; break; - case 'L': o_lockall = 1; o_touch = 1; break; - case 'd': o_daemon = 1; break; - case 'f': o_followsymlinks = 1; break; - case 'F': o_singlefilesystem = 1; break; - case 'h': o_ignorehardlinkeduplictes = 1; break; - case 'p': parse_range(optarg); break; - case 'i': parse_ignore_item(optarg); break; - case 'I': parse_filename_filter_item(optarg); break; - case 'm': { - int64_t val = parse_size(optarg); - o_max_file_size = (size_t) val; - if (val != (int64_t) o_max_file_size) fatal("value for -m too big to fit in a size_t"); - break; - } - case 'w': o_wait = 1; break; - case 'b': o_batch = optarg; break; - case '0': o_0_delim = 1; break; - case 'P': o_pidfile = optarg; break; - case 'o': o_output = optarg; break; - } - } - - argc -= optind; - argv += optind; - - if (o_touch) { - if (o_evict) fatal("invalid option combination: -t and -e"); - } - - if (o_evict) { - if (o_lock) fatal("invalid option combination: -e and -l"); - } - - if (o_lock && o_lockall) fatal("invalid option combination: -l and -L"); - - if (o_daemon) { - if (!(o_lock || o_lockall)) fatal("daemon mode must be combined with -l or -L"); - if (!o_wait) { - o_quiet = 1; - o_verbose = 0; - } - } - - if (o_wait && !o_daemon) fatal("wait mode must be combined with -d"); - - if (o_quiet && o_verbose) fatal("invalid option combination: -q and -v"); - - if (o_pidfile && (!o_lock && !o_lockall)) fatal("pidfile can only be created when -l or -L is specified"); - - if (!argc && !o_batch) { - printf("%s: no files or directories specified\n", prog); - usage(); - } - - // Must be done now because mlock() not inherited across fork() - if (o_daemon) go_daemon(); - - gettimeofday(&start_time, NULL); - - if (o_batch) { - vmtouch_batch_crawl(o_batch); - } - - for (i = 0; i < argc; i++) vmtouch_crawl(argv[i]); - - gettimeofday(&end_time, NULL); - - int64_t total_pages_in_core_size = total_pages_in_core * pagesize; - int64_t total_pages_size = total_pages * pagesize; - double total_pages_in_core_perc = 100.0 * total_pages_in_core/total_pages; - double elapsed = (end_time.tv_sec - start_time.tv_sec) + (double)(end_time.tv_usec - start_time.tv_usec) / 1000000.0; - - if (o_lock || o_lockall) { - if (o_lockall) { - if (mlockall(MCL_CURRENT)) - fatal("unable to mlockall (%s)", strerror(errno)); - } - - if (o_pidfile) { - register_signals_for_pidfile(); - write_pidfile(); - } - - if (!o_quiet) printf("LOCKED %" PRId64 " pages (%s)\n", total_pages, pretty_print_size(total_pages_size)); - - if (o_wait) reopen_all(); - - send_exit_signal(0); - select(0, NULL, NULL, NULL, NULL); - exit(0); - } - - if (!o_quiet) { - if (o_output == NULL) { - if (o_verbose) printf("\n"); - printf(" Files: %" PRId64 "\n", total_files); - printf(" Directories: %" PRId64 "\n", total_dirs); - if (o_touch) - printf(" Touched Pages: %" PRId64 " (%s)\n", total_pages, pretty_print_size(total_pages_size)); - else if (o_evict) - printf(" Evicted Pages: %" PRId64 " (%s)\n", total_pages, pretty_print_size(total_pages_size)); - else { - printf(" Resident Pages: %" PRId64 "/%" PRId64 " ", total_pages_in_core, total_pages); - printf("%s/", pretty_print_size(total_pages_in_core_size)); - printf("%s ", pretty_print_size(total_pages_size)); - if (total_pages) - printf("%.3g%%", total_pages_in_core_perc); - printf("\n"); - } - printf(" Elapsed: %.5g seconds\n", elapsed); - } else if (strncmp(o_output, "kv", 2) == 0) { - const char *desc = o_touch ? "Touched" : o_evict ? "Evicted" : "Resident"; - printf("Files=%" PRId64 " Directories=%" PRId64 " %sPages=%" PRId64 " TotalPages=%" PRId64 " %sSize=%" PRId64 " TotalSize=%" PRId64 " %sPercent=%.3g Elapsed=%.5g\n", - total_files, total_dirs, desc, total_pages_in_core, total_pages, desc, total_pages_in_core_size, total_pages_size, desc, total_pages_in_core_perc, elapsed); - } - } - - return 0; -} \ No newline at end of file