Не знаю, на чём делать акцент в статье: Смотрите, я сделал классное приложение! Может быть, вам пригодится. Или: Смотрите, я написал приложение на Swift и Rust не зная этих языков, полагаясь на LLM, и у меня получилось! Или, может быть: Смотрите, я хотел сделать лучше другой опенсорсный проект, но у меня не вышло. В общем, это будет история обо всём понемногу.
TL;DR — Смотрите, я сделал классное приложение!

Итак, StillCore — это полностью открытое приложение, которое показывает потребление энергии на эпловских чипах по компонентам, общее потребление энергии, частоты и загруженность разных кластеров CPU и GPU, а также пытается показывать температуры (что самое сложное и не документированное в системе). Оно является нативным SwiftUI-приложением, не требует админских прав и старается потреблять как можно меньше ресурсов.
В статье «Зачем я написал ещё одну утилиту мониторинга CPU для Мака» я уже рассказывал, зачем мне это нужно и почему не подходят существующие решения. Но хочу отдельно отметить, что та утилита не имеет никакого отношения к этому приложению. StillCore — это зрелое отдельное приложение, а не просто обёртка над консольной командой. Оно намного проще в установке и эксплуатации.
Дизайн, интерфейс, возможности
Очевидно, что дизайн берёт истоки ещё от Intel Power Gadget. Как ясно из названия, эта утилита не годится для мониторинга Apple Silicon-чипов. Но цель была не просто скопировать 1 в 1, а улучшить многие моменты.
Во-первых, IPG был просто окном. Ты открывал приложение, смотрел, закрывал. Если приложение не было запущено заранее, ты не мог посмотреть, что случилось с системой вот только что. Вместо этого StillCore живёт в строке меню и рассчитано на то, чтобы быть запущенным всегда. Дополнительно, в иконку можно вывести какой-то один показатель, чтобы видеть его всегда. Например, можно вывести иконку заряда батареи и скрыть системную. При этом осталась возможность «отцепить» окно от строки меню и использовать как обычное всегда-открытое окно.
Кроме того, я постарался объединить графики частот и загруженности кластеров. Идея такая — линией показана частота, а полупрозрачная заливка показывает загруженность для этой частоты. Т.е. если использование — 100%, то заливка идёт до линии частоты, если меньше, то заливка пропорционально ниже.
Частота обновления графика регулируется прямо в окне с помощью клавиш -/+ на клавиатуре. Можно обновлять хоть 10 раз в секунду, хоть раз в 10 секунд.
Ну и отдельная функция StillCore — мониторинг текущей сессии работы от батареи. Это требует установки системного хелпера, запущенного даже когда приложение не работает. Мне всегда казалось странным, что есть метрика (по крайней мере, в старых версиях macOS) «ваш ноутбук будет работать ещё 1,5 часа», которая не имеет ничего общего с реальностью, а понятной и доступной метрики «ваш ноутбук работает 8 часов с последней зарядки» нет. Поэтому я её добавил в своё приложение и теперь оно показывает, например: Drained 56% over 8h 12m + 12h sleep.
Ядро метрик от macmon
Изначально я вообще не собирался писать какое-то своё приложение. Есть такая консольная утилита, написанная на Rust — macmon. Её особенность в том, что автор по сути сделал реверс-инжениринг стандартной утилиты powermetrics и нашёл способ получать всё, что нужно, без админских прав.
popowermetics, обернутая в Swift-приложениеА ещё был мой пайтоновский скрипт, про который я рассказывал в предыдущей статье. И тот пайтоновский скрипт однажды я просто обернул в довольно убогое Swift-приложение, которым пользовался довольно продолжительное время.
И вот одним мартовским днём я нашёл, как мне показалось, баг в macmon и, совершенно ничего не зная про Rust, решил с помощью Codex попробовать раскопать и исправить его. Пока я копал код, я всё больше разбирался, как он работает, мне приходили всё новые идеи, что можно улучшить, и в конце концов я дошёл до главной идеи: а ведь ядро, которое собирает метрики, можно полностью отделить от консольного интерфейса и переиспользовать в других приложениях!
В этот момент обратной дороги уже не было, я понял, что смогу сделать полностью своё нормальное приложение для мониторинга, без обёртки над консольными приложениями, без админских прав, с нормальным интерфейсом. Единственной проблемой было то, что изменения, которые я делал в исходниках macmon, были чудовищными: по сути там всё было так или иначе переписано или изменено. Но именно конечная версия (с разделением на библиотеку и приложение, с кучей исправлений, изменёнными интерфейсами и выводом) мне была необходима для моего приложения.
Я сделал draft pull request, где постарался объяснить мотивацию всех изменений, честно сказал, что сам бы такое мержить не стал, хотел узнать у автора, что он думает. Ответа не было. Тогда я попытался вычленить из этого месива хотя бы те изменения, которые не затрагивают структуру проекта, не затрагивают контракты по выводу, в общем то, что точно ничего не ломает, и сделал ещё три пулреквеста: Io report caller interval, Cpu usage semantics, Optimizations. Ответа снова не поступило.
Это грустный для меня опыт, потому что я бы хотел контрибьютить в уже существующий проект, а не бекпортить изменения в отдельный форк. Ну посмотрим, может быть автор ещё найдётся.
Отдельно отмечу, что язык Rust конечно очень сложен и далёк от интуитивности. Я не пытался в нём разобраться, просто смотрел, что делает LLM, просил исправить явные косяки или переусложнённую логику. Но вот что мне понравилось, так это tooling. Cargo — просто песня, все команды четкие, интуитивные, ошибки максимально конкретные, вывод в консоль красивый!
Приложение на SwiftUI для macOS
И чтобы далеко не отходить, я хочу сразу же поделиться впечатлениями от Swift. Это полная противоположность Rust во всём ) Сам язык интуитивный, простой, даже с некоторыми откровениями. Например, мне очень зашли enums + associated values, теперь очень не хватает такого в Python. Но tooling у Свифта просто мрак. По дефолту xcodebuild срёт в консоль просто невероятное количество мусора. А что будет, если вы ошибётесь где-то во View — это просто ужас. Будет ошибка о том, что компилятор не смог вывести тип по таймауту (!) и иди ищи сам, где именно ты ошибся. И отдельная история — выпуск приложения: подписи, нотаризация, сэндбоксинг, hardened runtime ещё долго будут сниться мне в кошмарах.
Дальше сгруппирую рассказ по челленджам, которые были при разработке.
Графики
Основная функция окна — показывать графики. Изначально я пробовал нативные Charts, но быстро обнаружил, что они очень неоптимальны по производительности из-за SwiftUI-интерфейса — по сути все отображаемые данные заново строятся и копируются при каждом обновлении. Я перешёл на DGCharts и отрисовка ускорилась раз в пять. Благо, у меня было приложение, которое это могло точно показать, хе-хе.
Окна и их состояния
Формально в приложении одно окно, просто иногда оно привязано к иконке, а иногда его можно сделать отдельным. Я пытался договориться с macOS, переставлял флаги, пробовал разные неочевидные свойства, но это всё работало плохо. В macOS у окон слишком много скрытых состояний, на которые нельзя повлиять программно (или я не знаю как) — изначальные свойства окна влияют на тень и активацию, переключение режима окна привязывает его состояние к текущему экрану и так далее.
В итоге я попробовал сделать два разных окна и пересаживать между ними контент при переключении режимов. Оказалось, что это работает сильно лучше. Теперь StillCore корректно работает с полноэкранными приложениями и имеет правильный порядок по ⌘+Tab.
Меню для иконки в строке меню

Мне казалось, что это базовая функциональность, с которой не будет проблем. Но если вы покликаете по виджетам у вас в строке меню, вы заметите, что большинство из них реагируют на левый и правый клик одинаково, либо не реагируют на правый клик вовсе. А если всё же по-разному (как, например, 1password), то реакция на левый клик идёт по mouse up, а не mouse down.
Сейчас я знаю, что это не случайность, а выкрутасы SwiftUI, который даёт удобный инструмент для выпадающих из меню окон, но никак не даёт его кастомизировать. Мне повезло, что я давно пользовался Itsycal, у которого было нужное мне поведение и открытый код. Поэтому мы смогли на пару с Кодексом разобраться, как добиться нужного.
ссылка на оригинал статьи https://habr.com/ru/articles/1041786/