Compare commits

..
Author SHA1 Message Date
dependabot[bot] b27503a385 CI: Bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [uraimo/run-on-arch-action](https://github.com/uraimo/run-on-arch-action), [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `uraimo/run-on-arch-action` from 3.1.0 to 3.2.0
- [Release notes](https://github.com/uraimo/run-on-arch-action/releases)
- [Commits](https://github.com/uraimo/run-on-arch-action/compare/f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9...460cb8e6d9f726a588fc9b5e681c8a6cab09ae41)

Updates `github/codeql-action/init` from 4.37.7 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28)

Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28)

---
updated-dependencies:
- dependency-name: uraimo/run-on-arch-action
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-30 18:07:08 +00:00
李通洲 39024abae9 Processes (Haiku): honors options->countKprocs 2026-08-30 19:54:58 +08:00
Carter Li 4dba32aabb WM (macOS): improves reliability of WM plugin detection 2026-08-30 19:04:09 +08:00
李通洲 753ddec3c5 Memory (Windows): prefers NQSI 2026-08-29 20:09:48 +08:00
Carter Li b04587e0cb Presets: moves top to the bottom of running modules [ci skip] 2026-08-29 13:03:49 +08:00
Carter Li 3abcf4195f Processes: corrects the structure of JSON result 2026-08-29 12:14:09 +08:00
李通洲 af8a42270c Top (Hurd): reports startTime; uses time_value64_t 2026-08-29 12:09:40 +08:00
李通洲 1c9e521775 Top: disables FF_TOP_TYPE_DISK on Hurd and Haiku 2026-08-29 12:09:31 +08:00
Carter Li 7fe08bddd0 Processes: adds option countKprocs 2026-08-29 00:58:15 +08:00
Carter Li 41da49f579 Top (SunOS / Hurd): skips kernel tasks 2026-08-29 00:40:08 +08:00
Carter Li f169165599 Top (Linux): improves performance of stat parsing 2026-08-28 18:45:30 +08:00
Carter Li 2c4caf8e0d Top: removes realloc retries 2026-08-28 16:22:33 +08:00
Carter Li 51b370a105 Processes: adds total thread count detection 2026-08-28 15:46:23 +08:00
Carter Li 14cf6bfdc1 Top (macOS): adds comments 2026-08-28 15:46:23 +08:00
Carter Li 4d00dc6802 Logo (Builtin): adds omarchy 2026-08-28 09:49:48 +08:00
Carter Li d7de7c41ca CI: disables fail on alert 2026-08-27 15:28:18 +08:00
Carter Li d80fe3f004 Logo (Builtin): adds macOS_old 2026-08-27 14:58:32 +08:00
Carter Li dcce4ccdb0 CI: moves benchmark-index to /.github as required by the script 2026-08-27 14:57:22 +08:00
Carter Li 191c5f2ef5 Doc: updates README 2026-08-27 14:29:17 +08:00
Carter Li 1dc794fac0 Doc: updates changelog [ci skip] 2026-08-27 14:23:16 +08:00
Carter Li 5936c1cef4 CI: adds benchmark job 2026-08-27 14:23:16 +08:00
Carter Li 04f26a29eb CI: enables bluetooth module on platforms other than macOS 2026-08-27 09:45:58 +08:00
Carter Li c9ad429a1b Global: introduces global macro FF_PATH_PKG_BASE to replace _PATH_LOCALBASE 2026-08-26 23:12:25 +08:00
李通洲 2b42db58da LM (OpenBSD): adds support 2026-08-26 17:43:25 +08:00
Carter Li 32757b0e43 LM (Windows): adds support 2026-08-26 17:43:25 +08:00
Carter Li c5598d2361 LM (macOS): adds support 2026-08-26 17:43:25 +08:00
Carter Li 203cd7cbcf LM (Linux): adds pretty name 2026-08-26 17:43:25 +08:00
Carter Li fb309134b5 LM (Linux): queries systemd session info via dbus 2026-08-26 17:43:25 +08:00
Carter Li 8b162e89de WMDE: improves performance 2026-08-26 14:14:24 +08:00
Carter Li 9670b585fb LM: detects LM from processes list if systemd is not available
2. removes type field
3. small optimizations
2026-08-26 14:12:08 +08:00
Carter Li 3b76ade662 Shell: fixes the fast path of ash version detection 2026-08-26 08:50:08 +08:00
李通洲 1b1df2e731 Common (Settings): tidy 2026-08-25 23:27:36 +08:00
李通洲 6c19a8b4ec Top (DragonFly): fixes building 2026-08-25 23:27:24 +08:00
李通洲 71728247ab Common (Sysctl): handle varying data lengths in integer retrieval functions 2026-08-25 23:24:31 +08:00
李通洲 4344aa2ff1 Top: adds option showType: <cpu|mem|disk> 2026-08-25 20:48:34 +08:00
李通洲 b7d8c12015 CI: adds top module 2026-08-25 18:24:32 +08:00
Carter Li 63d16ca854 Top: removes option kernelTask (never implemented) 2026-08-25 16:30:04 +08:00
Carter Li f15a1e96ce Top (Linux): don't error out when reading /proc/pid/io fails 2026-08-25 16:27:53 +08:00
Carter Li 55bfcd6d86 Top (Android): fixes building 2026-08-25 13:35:32 +08:00
Carter Li 880284fa9c DisplayServer (Android): enables Wayland support
Fixes #2530
2026-08-25 13:32:28 +08:00
Carter Li 143939f19b DE (Android): adds asus zenfone support
Fixes #2541
2026-08-25 09:08:35 +08:00
dependabot[bot] a979d8ae81 CI: Bump the github-actions group with 4 updates
Bumps the github-actions group with 4 updates: [cross-platform-actions/action](https://github.com/cross-platform-actions/action), [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request).


Updates `cross-platform-actions/action` from 1.3.0 to 1.4.0
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/5ea7e8e4677bd726033a10b094ba1c5762b15dee...24ef01df165c76df1ed2b9f9e9212e78dc2fc963)

Updates `github/codeql-action/init` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

Updates `signpath/github-action-submit-signing-request` from 2.2 to 2.3
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/b9d91eadd323de506c0c81cf0c7fe7438f3360fd...c92b958760219087e01f8d67a1669ed57afe2627)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 11:31:38 -05:00
Carter Li b5897f784f Processes (GNU): uses native impl 2026-08-25 00:05:49 +08:00
Carter Li 83cc7a94a4 Top: adds new module 2026-08-25 00:05:49 +08:00
李通洲 5edda068f2 PhysicalDisk (DragonFly): eliminates‌ compiler warnings 2026-08-24 20:58:50 +08:00
Carter Li 6d5cb520ed Logo (Builtin): adds artix2 in 2026-08-24 13:21:25 +08:00
75 changed files with 3046 additions and 263 deletions

No files matched your search

+405
View File
@@ -0,0 +1,405 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fastfetch · Benchmark</title>
<style>
:root {
color-scheme: light;
--page: #f5f7fb;
--card: #fff;
--text: #172033;
--muted: #64748b;
--border: #dbe3ef;
--accent: #2563eb;
--accent-soft: #dbeafe;
--chart-text: #334155;
--chart-grid: #cbd5e1;
}
:root[data-theme="dark"] {
color-scheme: dark;
--page: #0f172a; --card: #172033; --text: #e5edf8; --muted: #9aabc1; --border: #334155; --accent-soft: #1e3a8a;
--chart-text: #f1f5f9;
--chart-grid: #475569;
}
* { box-sizing: border-box; }
body { max-width: 1440px; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; background: var(--page); color: var(--text); font: 16px/1.5 system-ui, -apple-system, sans-serif; }
header { margin-bottom: 1.75rem; }
.header-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
h1 { margin: 0; font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -.04em; }
h2 { margin: 0; font-size: 1.25rem; }
p { color: var(--muted); }
.intro { margin: .6rem 0 1rem; }
.links a { color: var(--accent); font-weight: 600; text-decoration: none; }
.links a:hover { text-decoration: underline; }
.theme-toggle, .fullscreen { border: 1px solid var(--border); border-radius: .55rem; padding: .5rem .8rem; background: var(--card); color: var(--text); cursor: pointer; font: inherit; font-weight: 600; }
.theme-toggle:hover, .fullscreen:hover { border-color: var(--accent); color: var(--accent); }
.tabs { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1.5rem 0; padding: .35rem; border: 1px solid var(--border); border-radius: .8rem; background: var(--card); }
.tab { border: 0; border-radius: .55rem; padding: .65rem 1.15rem; background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-weight: 700; }
.tab:hover, .tab[aria-selected="true"] { background: var(--accent); color: #fff; }
.tab-panel[hidden] { display: none; }
.platform-title { display: flex; align-items: center; gap: .65rem; margin: 1.5rem 0 1rem; }
.platform-title::before { content: ''; width: .35rem; height: 1.4rem; border-radius: 1rem; background: var(--accent); }
.charts { display: flex; flex-direction: column; gap: 1.25rem; }
.chart { min-width: 0; padding: 1.25rem; border: 1px solid var(--border); border-radius: 1rem; background: var(--card); box-shadow: 0 8px 28px #0f172a0c; }
.chart:fullscreen { overflow: auto; padding: 2rem; background: var(--page); }
.chart:fullscreen .canvas-wrap { height: calc(100vh - 13rem); }
.chart-header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .75rem; }
.chart-header span { color: var(--muted); font-size: .85rem; }
.canvas-wrap { position: relative; height: 360px; }
.chart-actions { display: flex; align-items: center; gap: .75rem; }
.legend-actions { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }
.legend-action { border: 1px solid var(--border); border-radius: .45rem; padding: .35rem .6rem; background: var(--card); color: var(--muted); cursor: pointer; font: inherit; font-size: .8rem; }
.legend-action:hover { border-color: var(--accent); color: var(--accent); }
.legend { display: flex; flex-wrap: wrap; gap: .35rem .9rem; margin-top: 1rem; }
.legend-item { display: inline-flex; align-items: center; gap: .4rem; border: 0; padding: .15rem 0; background: transparent; color: var(--text); cursor: pointer; font: inherit; font-size: .85rem; }
.legend-item.is-hidden { color: var(--muted); }
.legend-swatch { width: .75rem; height: .75rem; border-radius: 50%; }
.legend-item.is-hidden .legend-swatch { opacity: .35; }
.chart-tooltip { position: fixed; z-index: 1000; pointer-events: none; width: min(90vw, 900px); max-height: calc(100vh - 24px); overflow: hidden; padding: .7rem .85rem; border: 1px solid var(--border); border-radius: .6rem; background: var(--card); color: var(--text); box-shadow: 0 10px 30px #0f172a33; font-size: .8rem; opacity: 0; transition: opacity .1s ease; }
.chart-tooltip-title { margin-bottom: .35rem; font-weight: 700; white-space: normal; overflow-wrap: anywhere; }
.chart-tooltip-body { max-height: calc(100vh - 76px); overflow: auto; columns: 220px auto; column-gap: 1.5rem; }
.chart-tooltip-row { display: flex; align-items: center; gap: .45rem; break-inside: avoid; white-space: nowrap; }
.chart-tooltip-swatch { width: .55rem; height: .55rem; border-radius: 50%; flex: 0 0 auto; }
.empty { padding: 2rem 0; color: var(--muted); }
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
<script src="data.js"></script>
</head>
<body>
<header>
<div class="header-row">
<h1>Fastfetch Benchmark</h1>
<button id="theme-toggle" class="theme-toggle" type="button"></button>
</div>
<p class="intro">Performance history grouped by CPU family. Each chart contains all modules and total process time for one operating system and architecture. Module curves are available from the legend and hidden by default.</p>
<p class="links"><a href="data.js">Download raw benchmark data</a></p>
</header>
<nav id="tabs" class="tabs" aria-label="CPU architecture"></nav>
<main id="panels"></main>
<script>
(() => {
const benchmarkData = window.BENCHMARK_DATA || {};
const entries = Object.values(benchmarkData.entries || {}).flat();
const groups = new Map();
function architectureFamily(arch) {
const normalized = arch.toLowerCase();
if (/^(amd64|x86_64|x86|i[3-6]86)$/.test(normalized)) return 'Intel';
if (/^(aarch64|arm64|armv[5-8].*|arm)$/.test(normalized)) return 'ARM';
return 'Other';
}
function parseName(name) {
let match = name.match(/^fastfetch (linux|macos|windows) ([^ ]+) (module .+|total process time)$/i);
if (match) {
return { platform: match[1].toLowerCase(), arch: match[2], metric: match[3] };
}
// Keep older macOS records visible while new records use the platform field.
match = name.match(/^fastfetch ([^ ]+) (module .+|total process time)$/i);
return match ? { platform: 'macos', arch: match[1], metric: match[2] } : null;
}
function entryMetadata(entry) {
const raw = entry.commit && entry.commit.timestamp || entry.date;
const date = new Date(raw);
return {
date: Number.isNaN(date.valueOf()) ? String(entry.date || '') : date.toISOString(),
sha: entry.commit && entry.commit.id ? entry.commit.id : 'unknown'
};
}
for (const entry of entries) {
const metricsByGroup = new Map();
for (const bench of entry.benches || []) {
const parsed = parseName(bench.name);
if (!parsed) continue;
const key = `${parsed.platform} ${parsed.arch}`;
if (!metricsByGroup.has(key)) metricsByGroup.set(key, new Map());
metricsByGroup.get(key).set(parsed.metric, bench.value);
}
for (const [key, metrics] of metricsByGroup) {
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push({ ...entryMetadata(entry), metrics });
}
}
const colors = [
'#2563eb', '#dc2626', '#16a34a', '#9333ea', '#ea580c', '#0891b2',
'#db2777', '#65a30d', '#4f46e5', '#ca8a04', '#0f766e', '#be123c'
];
const tabs = document.getElementById('tabs');
const panels = document.getElementById('panels');
const root = document.documentElement;
const themeToggle = document.getElementById('theme-toggle');
const savedTheme = localStorage.getItem('fastfetch-benchmark-theme');
let theme = savedTheme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
const chartsByInstance = [];
function chartColors() {
const styles = getComputedStyle(root);
return {
text: styles.getPropertyValue('--chart-text').trim(),
grid: styles.getPropertyValue('--chart-grid').trim()
};
}
function externalTooltipHandler(context) {
const { chart, tooltip } = context;
let tooltipElement = chart.canvas.parentNode.querySelector('.chart-tooltip');
if (!tooltipElement) {
tooltipElement = document.createElement('div');
tooltipElement.className = 'chart-tooltip';
chart.canvas.parentNode.appendChild(tooltipElement);
}
if (tooltip.opacity === 0) {
tooltipElement.style.opacity = '0';
return;
}
tooltipElement.replaceChildren();
const title = document.createElement('div');
title.className = 'chart-tooltip-title';
const row = chart._benchmarkRows[tooltip.dataPoints[0].dataIndex];
title.textContent = `${new Date(row.date).toLocaleString()} · ${row.sha}`;
tooltipElement.appendChild(title);
const bodyElement = document.createElement('div');
bodyElement.className = 'chart-tooltip-body';
for (const body of [...tooltip.dataPoints].sort((a, b) => b.parsed.y - a.parsed.y)) {
const item = document.createElement('div');
item.className = 'chart-tooltip-row';
const swatch = document.createElement('span');
swatch.className = 'chart-tooltip-swatch';
swatch.style.background = body.dataset.borderColor;
const value = document.createElement('span');
value.textContent = `${body.dataset.label}: ${body.formattedValue} ms`;
item.append(swatch, value);
bodyElement.appendChild(item);
}
tooltipElement.appendChild(bodyElement);
const canvasRect = chart.canvas.getBoundingClientRect();
const margin = 12;
let left = canvasRect.left + tooltip.caretX + margin;
let top = canvasRect.top + tooltip.caretY + margin;
tooltipElement.style.left = `${left}px`;
tooltipElement.style.top = `${top}px`;
tooltipElement.style.opacity = '0';
const tooltipRect = tooltipElement.getBoundingClientRect();
if (left + tooltipRect.width > window.innerWidth - margin) left = canvasRect.left + tooltip.caretX - tooltipRect.width - margin;
if (top + tooltipRect.height > window.innerHeight - margin) top = canvasRect.top + tooltip.caretY - tooltipRect.height - margin;
tooltipElement.style.left = `${Math.max(margin, left)}px`;
tooltipElement.style.top = `${Math.max(margin, top)}px`;
tooltipElement.style.opacity = '1';
}
function updateTheme() {
root.dataset.theme = theme;
themeToggle.textContent = theme === 'dark' ? '☀ Light mode' : '☾ Dark mode';
themeToggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`);
const colors = chartColors();
Chart.defaults.color = colors.text;
Chart.defaults.borderColor = colors.grid;
for (const chart of chartsByInstance) {
chart.options.scales.x.title.color = colors.text;
chart.options.scales.x.ticks.color = colors.text;
chart.options.scales.x.grid.color = colors.grid;
chart.options.scales.y.title.color = colors.text;
chart.options.scales.y.ticks.color = colors.text;
chart.options.scales.y.grid.color = colors.grid;
chart.update();
}
}
themeToggle.addEventListener('click', () => {
theme = theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('fastfetch-benchmark-theme', theme);
updateTheme();
});
updateTheme();
const families = new Map();
for (const [key, rows] of groups) {
const arch = key.slice(key.lastIndexOf(' ') + 1);
const family = architectureFamily(arch);
if (!families.has(family)) families.set(family, []);
families.get(family).push({ key, rows });
}
const familyOrder = ['Intel', 'ARM', 'Other'];
const availableFamilies = familyOrder.filter(family => families.has(family));
for (const [index, family] of availableFamilies.entries()) {
const tab = document.createElement('button');
const panelId = `panel-${family.toLowerCase()}`;
tab.className = 'tab';
tab.type = 'button';
tab.id = `tab-${family.toLowerCase()}`;
tab.setAttribute('role', 'tab');
tab.textContent = family;
tab.setAttribute('aria-controls', panelId);
tab.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
tabs.appendChild(tab);
const panel = document.createElement('section');
panel.className = 'tab-panel';
panel.id = panelId;
panel.setAttribute('role', 'tabpanel');
panel.hidden = index !== 0;
panel.setAttribute('aria-labelledby', tab.id);
const title = document.createElement('h2');
title.className = 'platform-title';
title.textContent = `${family} processors`;
panel.appendChild(title);
const chartGrid = document.createElement('div');
chartGrid.className = 'charts';
panel.appendChild(chartGrid);
panels.appendChild(panel);
tab.addEventListener('click', () => {
for (const otherTab of tabs.children) otherTab.setAttribute('aria-selected', otherTab === tab ? 'true' : 'false');
for (const otherPanel of panels.children) otherPanel.hidden = otherPanel !== panel;
});
for (const { key, rows } of families.get(family).sort((a, b) => a.key.localeCompare(b.key))) {
rows.sort((a, b) => a.date.localeCompare(b.date));
const metrics = [...new Set(rows.flatMap(row => [...row.metrics.keys()]))].sort((a, b) => {
if (a === 'total process time') return -1;
if (b === 'total process time') return 1;
return a.localeCompare(b);
});
const section = document.createElement('article');
section.className = 'chart';
const chartHeader = document.createElement('div');
chartHeader.className = 'chart-header';
const chartTitle = document.createElement('h2');
chartTitle.textContent = key;
const chartActions = document.createElement('div');
chartActions.className = 'chart-actions';
const chartHint = document.createElement('span');
chartHint.textContent = `${rows.length} run${rows.length === 1 ? '' : 's'}`;
const fullscreen = document.createElement('button');
fullscreen.className = 'fullscreen';
fullscreen.type = 'button';
fullscreen.textContent = '⛶ Fullscreen';
fullscreen.addEventListener('click', () => {
if (document.fullscreenElement) document.exitFullscreen();
else section.requestFullscreen();
});
chartActions.append(chartHint, fullscreen);
chartHeader.append(chartTitle, chartActions);
const canvasWrap = document.createElement('div');
canvasWrap.className = 'canvas-wrap';
const canvas = document.createElement('canvas');
canvasWrap.appendChild(canvas);
section.append(chartHeader, canvasWrap);
chartGrid.appendChild(section);
const colorsForChart = chartColors();
const chart = new Chart(canvas, {
type: 'line',
data: {
labels: rows.map(row => row.sha.slice(0, 7)),
datasets: metrics.map((metric, index) => ({
label: metric,
data: rows.map(row => row.metrics.has(metric) ? row.metrics.get(metric) : null),
borderColor: colors[index % colors.length],
backgroundColor: colors[index % colors.length],
pointRadius: 3,
tension: 0.2,
spanGaps: true,
hidden: metric !== 'total process time'
}))
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { display: false },
tooltip: {
enabled: false,
external: externalTooltipHandler,
callbacks: {
title: tooltipItems => {
const row = rows[tooltipItems[0].dataIndex];
return `${row.date} · ${row.sha}`;
}
}
}
},
scales: {
x: { title: { display: true, text: 'Date', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } },
y: { beginAtZero: true, title: { display: true, text: 'Milliseconds', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } }
}
}
});
chart._benchmarkRows = rows;
chartsByInstance.push(chart);
const legendActions = document.createElement('div');
legendActions.className = 'legend-actions';
const legend = document.createElement('div');
legend.className = 'legend';
const legendItems = [];
function refreshLegend() {
legendItems.forEach((item, metricIndex) => {
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
});
}
function setAllLegends(visible) {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, visible));
refreshLegend();
chart.update();
}
function addLegendAction(label, action) {
const button = document.createElement('button');
button.className = 'legend-action';
button.type = 'button';
button.textContent = label;
button.addEventListener('click', action);
legendActions.appendChild(button);
}
addLegendAction('Show all', () => setAllLegends(true));
addLegendAction('Toggle all', () => {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex)));
refreshLegend();
chart.update();
});
addLegendAction('Reset', () => {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, metric === 'total process time'));
refreshLegend();
chart.update();
});
metrics.forEach((metric, metricIndex) => {
const item = document.createElement('button');
item.className = `legend-item${chart.isDatasetVisible(metricIndex) ? '' : ' is-hidden'}`;
item.type = 'button';
item.innerHTML = `<span class="legend-swatch" style="background:${colors[metricIndex % colors.length]}"></span>${metric}`;
item.addEventListener('click', () => {
chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex));
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
chart.update();
});
legendItems.push(item);
legend.appendChild(item);
});
section.append(legendActions, legend);
}
}
if (!groups.size) {
tabs.hidden = true;
panels.innerHTML = '<p class="empty">No benchmark data available.</p>';
}
})();
</script>
</body>
</html>
+55
View File
@@ -0,0 +1,55 @@
name: Reusable Benchmark
on:
workflow_call:
permissions:
contents: write
jobs:
benchmark:
runs-on: ubuntu-latest
concurrency:
group: benchmark
cancel-in-progress: false
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: download benchmark data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: benchmark-*
path: benchmarks
- name: merge benchmark data
run: jq -s 'add' benchmarks/*/benchmark.json > benchmark.json
- name: save benchmark dashboard
run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html"
- name: store benchmark result
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: Fastfetch
tool: customSmallerIsBetter
output-file-path: benchmark.json
github-token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.sha }}
auto-push: true
summary-always: true
benchmark-data-dir-path: dev/bench
- name: publish benchmark dashboard
run: |
git fetch origin gh-pages
git switch --force-create gh-pages FETCH_HEAD
mkdir -p dev/bench
cp "$RUNNER_TEMP/benchmark-index.html" dev/bench/index.html
git config user.name github-action-benchmark
git config user.email github@users.noreply.github.com
git add dev/bench/index.html
if ! git diff --cached --quiet; then
git commit -m "Update benchmark dashboard"
git push origin gh-pages
fi
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: armv7
+32 -4
View File
@@ -43,7 +43,7 @@ jobs:
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- name: install required packages
run: sudo apt-get update && sudo apt-get install -y gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -52,7 +52,7 @@ jobs:
- name: Initialize CodeQL
if: inputs.arch == 'amd64'
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: c
@@ -64,7 +64,7 @@ jobs:
- name: perform CodeQL analysis
if: inputs.arch == 'amd64'
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
- name: list features
run: ./fastfetch --list-features
@@ -73,7 +73,29 @@ jobs:
run: time ./fastfetch -c presets/ci.jsonc --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "linux" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -105,3 +127,9 @@ jobs:
with:
name: fastfetch-linux-${{ inputs.arch }}
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-linux-${{ inputs.arch }}
path: benchmark.json
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: ${{ inputs.arch }}
+31 -3
View File
@@ -29,7 +29,7 @@ jobs:
- name: install required packages
run: |
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng jq
- name: configure project
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
@@ -41,10 +41,32 @@ jobs:
run: ./fastfetch --list-features
- name: run fastfetch
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --stat false
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "macos" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -60,3 +82,9 @@ jobs:
with:
name: fastfetch-macos-${{ inputs.arch }}
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-macos-${{ inputs.arch }}
path: benchmark.json
+30 -2
View File
@@ -49,7 +49,7 @@ jobs:
with:
msystem: ${{ inputs.msystem }}
update: true
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
- name: print msys version
run: uname -a
@@ -70,7 +70,29 @@ jobs:
run: time ./fastfetch -c presets/ci.jsonc --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "windows" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -116,3 +138,9 @@ jobs:
name: fastfetch-windows-${{ inputs.arch }}
path: ./fastfetch-windows-${{ inputs.arch }}.*
overwrite: true
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-windows-${{ inputs.arch }}
path: benchmark.json
+11
View File
@@ -176,6 +176,17 @@ jobs:
secrets:
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
benchmark:
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && github.repository == 'fastfetch-cli/fastfetch'
name: Benchmark
needs:
- linux-hosts
- macos-hosts
- windows-hosts
permissions:
contents: write
uses: ./.github/workflows/build-benchmark.yml
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'fastfetch-cli/fastfetch'
name: Release
+13
View File
@@ -4,6 +4,10 @@ Changes:
* The DE / WM / LM modules now reports the full name "Desktop Environment" / "Window Manager" / "Login Manager" instead of the abbreviations.
Features:
* Improved Login Manager (LM) module
* Added support on macOS, Windows and POSIX systems without systemd (by process list enumeration).
* Reports pretty name on Linux.
* Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top)
* Added module key localization support. (General)
* Added a new option `--key-language <?lang>` (`display.key.language: "<?lang>"` in JSON config). When left empty, it defaults to the system locale.
* See `fastfetch -h key-language` for all supported languages.
@@ -15,11 +19,20 @@ Features:
* Added terminal font size detection support on Termux. (TerminalFont, Android)
* Improved GTK theme/icons/font detection on standalone WMs such as Hyprland, sway, niri and i3. (Theme, Linux)
* Improved fish completion scripts to print enum descriptions. (Completion)
* Enabled Wayland support on Android. (DisplayServer, Android)
* Added DE detection support for ASUS Zenfone. (DE, Android)
Bugfixes:
* Fixed I/O rate calculation precision in DiskIO and NetIO, and prevented division by zero. (DiskIO / NetIO)
* Fixed the fast path of ash version detection. (Shell)
* Fixed memory usage detection support on x86-32 FreeBSD (Memory, FreeBSD)
* Note: Although 32-bit systems are still supported, they are deprecated and barely tested. Users are encouraged to upgrade to 64-bit systems.
* Some internal cleanups and optimizations.
Logos:
* Added macOS_old
* Added Omarchy, Omarchy2
# 2.67.1
Bugfixes:
+31 -9
View File
@@ -78,7 +78,7 @@ endif()
include(CMakeDependentOption)
cmake_dependent_option(ENABLE_VULKAN "Enable vulkan" ON "LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "ANDROID OR LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
cmake_dependent_option(ENABLE_XCB_RANDR "Enable xcb-randr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_XRANDR "Enable xrandr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_DRM "Enable libdrm" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF)
@@ -484,6 +484,7 @@ set(LIBFASTFETCH_SRC
src/detection/physicalmemory/physicalmemory.c
src/detection/publicip/publicip.c
src/detection/terminaltheme/terminaltheme.c
src/detection/top/top.c
src/detection/terminalfont/terminalfont.c
src/detection/terminalshell/terminalshell.c
src/detection/version/version.c
@@ -559,6 +560,7 @@ set(LIBFASTFETCH_SRC
src/modules/terminalsize/terminalsize.c
src/modules/theme/theme.c
src/modules/title/title.c
src/modules/top/top.c
src/modules/tpm/tpm.c
src/modules/uptime/uptime.c
src/modules/users/users.c
@@ -651,6 +653,7 @@ if(LINUX)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_linux.c
src/detection/tpm/tpm_linux.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -691,6 +694,13 @@ elseif(ANDROID)
src/detection/physicalmemory/physicalmemory_nosupport.c
src/detection/diskio/diskio_linux.c
src/detection/displayserver/displayserver_android.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c
src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/wmde.c
src/detection/displayserver/linux/xcb.c
@@ -726,6 +736,7 @@ elseif(ANDROID)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -802,7 +813,6 @@ elseif(FreeBSD)
src/detection/os/os_linux.c
src/detection/packages/packages_bsd.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_bsd.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_bsd.c
src/detection/swap/swap_bsd.c
@@ -822,11 +832,15 @@ elseif(FreeBSD)
if(DragonFly)
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_nosupport.c
src/detection/processes/processes_dbsd.c
src/detection/top/top_dbsd.c
src/detection/wifi/wifi_nosupport.c
)
else()
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_bsd.c
src/detection/processes/processes_bsd.c
src/detection/top/top_bsd.c
src/detection/wifi/wifi_bsd.c
)
endif()
@@ -904,6 +918,7 @@ elseif(NetBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_nbsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_linux.c
@@ -964,7 +979,7 @@ elseif(OpenBSD)
src/detection/gpu/gpu_bsddrm.c
src/detection/gtk_qt/gtk.c
src/detection/host/host_obsd.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_linux.c
src/detection/icons/icons_linux.c
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_nosupport.c
@@ -990,6 +1005,7 @@ elseif(OpenBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_obsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_obsd.c
src/detection/wallpaper/wallpaper_linux.c
@@ -1035,7 +1051,7 @@ elseif(APPLE)
src/detection/icons/icons_nosupport.c
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_apple.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_apple.m
src/detection/loadavg/loadavg_bsd.c
src/detection/libc/libc_apple.c
src/detection/locale/locale_linux.c
@@ -1050,13 +1066,14 @@ elseif(APPLE)
src/detection/packages/packages_apple.c
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_apple.c
src/detection/processes/processes_bsd.c
src/detection/processes/processes_apple.c
src/detection/sound/sound_apple.c
src/detection/swap/swap_apple.c
src/detection/terminalfont/terminalfont_apple.m
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_apple.c
src/detection/top/top_apple.c
src/detection/tpm/tpm_apple.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
@@ -1128,7 +1145,7 @@ elseif(WIN32)
src/detection/initsystem/initsystem_windows.c
src/detection/keyboard/keyboard_windows.c
src/detection/libc/libc_windows.cpp
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_windows.c
src/detection/loadavg/loadavg_nosupport.c
src/detection/locale/locale_windows.c
src/detection/localip/localip_windows.c
@@ -1149,6 +1166,7 @@ elseif(WIN32)
src/detection/terminalshell/terminalshell_windows.c
src/detection/terminalsize/terminalsize_windows.c
src/detection/theme/theme_windows.c
src/detection/top/top_windows.c
src/detection/tpm/tpm_windows.c
src/detection/uptime/uptime_windows.c
src/detection/users/users_windows.c
@@ -1210,7 +1228,7 @@ elseif(SunOS)
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_nosupport.c
src/detection/libc/libc_nosupport.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_linux.c
src/detection/loadavg/loadavg_sunos.c
src/detection/locale/locale_linux.c
src/detection/localip/localip_linux.c
@@ -1223,7 +1241,7 @@ elseif(SunOS)
src/detection/os/os_sunos.c
src/detection/packages/packages_sunos.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/processes/processes_sunos.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_sunos.c
src/detection/swap/swap_sunos.c
@@ -1231,6 +1249,7 @@ elseif(SunOS)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_sunos.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_sunos.c
src/detection/users/users_linux.c
@@ -1301,6 +1320,7 @@ elseif(Haiku)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_nosupport.c
src/detection/top/top_haiku.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_haiku.c
src/detection/users/users_linux.c
@@ -1377,7 +1397,7 @@ elseif(GNU)
src/detection/packages/packages_linux.c
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/processes/processes_gnu.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_linux.c
src/detection/swap/swap_linux.c
@@ -1385,6 +1405,7 @@ elseif(GNU)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_gnu.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -1995,6 +2016,7 @@ elseif(SunOS)
elseif(GNU)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "ps"
)
elseif(ANDROID)
target_link_libraries(libfastfetch
+1
View File
@@ -1,5 +1,6 @@
# Fastfetch
[![Benchmark](https://img.shields.io/badge/GitHub%20Pages-live-blue?logo=github)](https://fastfetch-cli.github.io/fastfetch/dev/bench)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/fastfetch-cli/fastfetch/ci.yml)](https://github.com/fastfetch-cli/fastfetch/actions)
[![GitHub license](https://img.shields.io/github/license/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/blob/dev/LICENSE)
[![GitHub contributors](https://img.shields.io/github/contributors/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/graphs/contributors)
+106 -3
View File
@@ -322,7 +322,7 @@
"type": "string"
},
"cpuFormat": {
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name\n 14. {technology}: CPU technology",
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count",
"type": "string"
},
"cpucacheFormat": {
@@ -505,6 +505,10 @@
"description": "Output format for the `Title` module. See Wiki for formatting syntax\n 1. {user-name}: User name\n 2. {host-name}: Host name\n 3. {home-dir}: Home directory\n 4. {exe-path}: Executable path of current process\n 5. {user-shell}: User's default shell\n 6. {user-name-colored}: User name (colored)\n 7. {at-symbol-colored}: @ symbol (colored)\n 8. {host-name-colored}: Host name (colored)\n 9. {full-user-name}: Full user name\n 10. {user-id}: UID (*nix) / SID (Windows)\n 11. {pid}: PID of current process\n 12. {cwd}: CWD with home dir replaced by `~`",
"type": "string"
},
"topFormat": {
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {path}: Executable path\n 3. {pid}: Process ID\n 4. {cpu}: CPU usage\n 5. {mem}: Memory usage (RSS) in bytes\n 6. {disk-read}: Disk read bytes per second\n 7. {disk-write}: Disk write bytes per second\n 8. {cpu-percentage}: CPU usage percentage\n 9. {mem-formatted}: Memory usage (RSS) formatted\n 10. {disk-read-formatted}: Disk read formatted\n 11. {disk-write-formatted}: Disk write formatted",
"type": "string"
},
"themeFormat": {
"description": "Output format for the `Theme` module. See Wiki for formatting syntax\n 1. {theme1}: Theme part 1\n 2. {theme2}: Theme part 2",
"type": "string"
@@ -542,7 +546,7 @@
"type": "string"
},
"wifiFormat": {
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {band}: Connection channel band in GHz",
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {channel-width}: Connection channel width in MHz\n 14. {band}: Connection channel band in GHz",
"type": "string"
},
"wmthemeFormat": {
@@ -1592,6 +1596,7 @@
"poweradapter",
"processes",
"publicip",
"top",
"separator",
"shell",
"sound",
@@ -3979,7 +3984,12 @@
"properties": {
"type": {
"const": "processes",
"description": "Print the number of running processes"
"description": "Print the number of running processes and threads"
},
"countKprocs": {
"description": "Whether to take kernel processes and threads into account",
"type": "boolean",
"default": false
},
"key": {
"$ref": "#/$defs/key"
@@ -4424,6 +4434,99 @@
}
}
},
{
"title": "Top",
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"const": "top",
"description": "Print processes with the highest CPU or memory usage"
},
"sort": {
"type": "string",
"description": "Sort processes by CPU, memory or disk IO usage",
"enum": [
"cpu",
"memory",
"disk-read",
"disk-write"
],
"default": "cpu"
},
"showTypes": {
"description": "Specify which resource usages are detected and displayed.\nIf memory is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped",
"default": [
"cpu",
"memory",
"disk"
],
"oneOf": [
{
"type": "string",
"enum": [
"cpu",
"memory",
"disk"
]
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"enum": [
"cpu",
"memory",
"disk"
]
}
}
]
},
"processes": {
"type": "integer",
"description": "Number of top processes to print",
"minimum": 1,
"default": 5
},
"waitTime": {
"type": "integer",
"description": "Wait time between process samples, in milliseconds. Ignored when showTypes contains \"memory\" only",
"minimum": 1,
"default": 500
},
"compact": {
"type": "boolean",
"description": "Print all process on a single line",
"default": false
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
"keyColor": {
"$ref": "#/$defs/keyColor"
},
"keyIcon": {
"$ref": "#/$defs/keyIcon"
},
"keyWidth": {
"$ref": "#/$defs/keyWidth"
},
"outputColor": {
"$ref": "#/$defs/outputColor"
},
"format": {
"$ref": "#/$defs/topFormat"
},
"condition": {
"$ref": "#/$defs/conditions"
}
}
},
{
"title": "TPM",
"type": "object",
+5 -4
View File
@@ -99,16 +99,17 @@
"gamepad",
"mouse",
"keyboard",
{
"type": "weather",
"timeout": 1000
},
"top",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
{
"type": "weather",
"timeout": 1000
},
"tpm",
"version",
"break",
+7 -6
View File
@@ -94,23 +94,24 @@
"opengl",
"opencl",
"users",
// "bluetooth", // doesn't work on macOS because it requires bluetooth permissions
// "bluetoothradio",
"bluetooth",
"bluetoothradio",
"sound",
"camera",
"gamepad",
"mouse",
"keyboard",
{
"type": "weather",
"timeout": 1000
},
"top",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
{
"type": "weather",
"timeout": 1000
},
"tpm",
"version",
"logo",
+2 -4
View File
@@ -3,7 +3,7 @@
#include "common/arrutil.h"
#include "common/strutil.h"
#include "common/io.h"
#include "fastfetch_config.h"
#include "common/path.h"
#include <unistd.h>
#include <pwd.h>
@@ -238,9 +238,7 @@ static void getDataDirs(FFPlatform* platform) {
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS");
#ifdef _PATH_LOCALBASE
ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE "/share/");
#endif
ffPlatformPathAddAbsolute(&platform->dataDirs, FF_PATH_PKG_BASE "/share/");
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/");
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/");
}
+11
View File
@@ -111,6 +111,17 @@ void ffPrepareCommandOption(FFdata* data) {
break;
#endif
#if !FF_MODULE_DISABLE_TOP
case 'T':
case 't':
FF_IF_MODULE_MATCH(ffTopModuleInfo.name) {
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
ffInitTopOptions(&options);
ffPrepareTopProcesses(options.showTypes);
}
break;
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'W':
case 'w':
+15
View File
@@ -195,6 +195,21 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
}
#endif
#if !FF_MODULE_DISABLE_TOP
case 't':
case 'T': {
if (ffStrEqualsIgnCase(type, ffTopModuleInfo.name)) {
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
ffInitTopOptions(&options);
if (module) {
ffTopModuleInfo.parseJsonObject(&options, module);
}
ffPrepareTopProcesses(options.showTypes);
}
break;
}
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'w':
case 'W': {
+2 -4
View File
@@ -450,12 +450,10 @@ bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf*
return true;
}
#else // FF_HAVE_SQLITE3
int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) {
FF_UNUSED(dbPath, query)
int ffSettingsGetSQLite3Int(const char*, const char*) {
return 0;
}
bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) {
FF_UNUSED(dbPath, query, result)
bool ffSettingsGetSQLite3String(const char*, const char*, FFstrbuf*) {
return false;
}
#endif // FF_HAVE_SQLITE3
+48 -23
View File
@@ -22,21 +22,41 @@ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
}
int ffSysctlGetInt(int mib1, int mib2, int defaultValue) {
int result;
alignas(int) uint8_t result[sizeof(int)];
size_t neededLength = sizeof(result);
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
int64_t result;
alignas(int64_t) uint8_t result[sizeof(int64_t)];
size_t neededLength = sizeof(result);
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int64_t):
return *(int64_t*)result;
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
#else
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
@@ -59,35 +79,40 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
}
int ffSysctlGetInt(const char* propName, int defaultValue) {
int result;
alignas(int) uint8_t result[sizeof(int)];
size_t neededLength = sizeof(result);
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue) {
int64_t result;
alignas(int64_t) uint8_t result[sizeof(int64_t)];
size_t neededLength = sizeof(result);
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int64_t):
return *(int64_t*)result;
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
#endif // OpenBSD
void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength) {
if (sysctl(request, requestLength, nullptr, resultLength, nullptr, 0) != 0) {
return nullptr;
}
void* data = malloc(*resultLength);
if (sysctl(request, requestLength, data, resultLength, nullptr, 0) != 0) {
free(data);
return nullptr;
}
return data;
}
+16
View File
@@ -2,6 +2,7 @@
#include "common/FFstrbuf.h"
#include "common/strutil.h"
#include "fastfetch_config.h"
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result);
static inline bool ffIsAbsolutePath(const char* path) {
@@ -19,3 +20,18 @@ char* realpath(const char* __restrict file_name, char* __restrict resolved_name
ssize_t freadlink(void* hFile, char* buf, size_t bufsiz);
ssize_t readlink(const char* path, char* buf, size_t bufsiz);
#endif
#ifdef __FreeBSD__
#include <paths.h>
#ifdef _PATH_LOCALBASE
#define FF_PATH_PKG_BASE _PATH_LOCALBASE
#else
#define FF_PATH_PKG_BASE "/usr/local"
#endif
#elif __OpenBSD__
#define FF_PATH_PKG_BASE "/usr/local"
#elif __NetBSD__
#define FF_PATH_PKG_BASE "/usr/pkg"
#else
#define FF_PATH_PKG_BASE FASTFETCH_TARGET_DIR_USR
#endif
-1
View File
@@ -14,4 +14,3 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result);
[[nodiscard]] int ffSysctlGetInt(const char* propName, int defaultValue);
[[nodiscard]] int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue);
#endif
[[nodiscard]] void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength);
+56
View File
@@ -11,6 +11,7 @@ enum {
SystemFirmwareTableInformation = 76,
SystemBootEnvironmentInformation = 90,
SystemLogicalProcessorAndGroupInformation = 107,
SystemBasicPerformanceInformation = 123,
SystemSecureBootInformation = 146,
};
@@ -198,6 +199,13 @@ typedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION {
_Field_size_bytes_(TableBufferLength) UCHAR TableBuffer[];
} SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION;
typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION {
SIZE_T AvailablePages;
SIZE_T CommittedPages;
SIZE_T CommitLimit;
SIZE_T PeakCommitment;
} SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION;
NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);
/**
@@ -599,6 +607,54 @@ typedef struct _KUSER_SHARED_DATA {
};
};
//
// Reserved padding field to preserve structure alignment and compatibility.
//
ULONG DataFlagsPad[1];
//
// Depending on the processor, the code for fast system call will differ,
// Stub code is provided pointers below to access the appropriate code.
//
// N.B. The following field is only used on 32-bit systems.
//
ULONGLONG TestRetInstruction;
//
// Query-performance counter (QPC) frequency, in counts per second.
//
// N.B. This value represents the fixed frequency of the system's high-resolution
// performance counter. It is used by user-mode time routines to convert QPC
// ticks into elapsed time without requiring a system call. The frequency is
// constant for the lifetime of the system and reflects the hardware or
// virtualized timer source selected by the kernel.
//
LONGLONG QpcFrequency;
//
// On AMD64, this value is initialized to a nonzero value if the system
// operates with an altered view of the system service call mechanism.
//
ULONG SystemCall;
//
// Reserved field - do not use. Used to be UserCetAvailableEnvironments.
//
ULONG Reserved2;
//
// Full 64 bit version of the number of physical pages in the system.
// This can dynamically change as physical memory can be added or removed
// from a running system.
//
ULONGLONG FullNumberOfPhysicalPages;
// ... more fields follow, but we don't need them
} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;
+2 -19
View File
@@ -11,29 +11,12 @@
#include "detection/displayserver/displayserver.h"
#include <ctype.h>
#ifdef __FreeBSD__
#include <paths.h>
#ifndef _PATH_LOCALBASE
#define _PATH_LOCALBASE "/usr/local"
#endif
#elif __OpenBSD__
#define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
#define _PATH_LOCALBASE "/usr/pkg"
#endif
static void getKDE(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
#ifdef _PATH_LOCALBASE
ffParsePropFile(_PATH_LOCALBASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
ffParsePropFile(FF_PATH_PKG_BASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
if (result->length == 0) {
ffParsePropFile(_PATH_LOCALBASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
ffParsePropFile(FF_PATH_PKG_BASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
}
#else
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
if (result->length == 0) {
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
}
#endif
if (result->length == 0) {
ffParsePropFileData("xsessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
@@ -174,6 +174,11 @@ static bool detectDE(FFDisplayServerResult* ds) {
ffStrbufPrependS(&ds->dePrettyName, "OxygenOS");
return true;
}
if (ffSettingsGetAndroidProperty("ro.product.brand", &ds->dePrettyName) && ffStrbufEqualS(&ds->dePrettyName, "asus")) {
ffStrbufClear(&ds->dePrettyName);
ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName);
return true;
}
if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) {
if (ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) {
ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' ');
@@ -187,13 +192,23 @@ static bool detectDE(FFDisplayServerResult* ds) {
}
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
const char* error = ffdsConnectXcbRandr(ds);
if (error) {
error = ffdsConnectXrandr(ds);
}
if (!error) {
ffdsDetectWMDE(ds);
return;
// Keep the same display-server preference as Linux: Wayland provides the
// richest output information, followed by XCB and XRandR.
if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) {
ffdsConnectWayland(ds);
if (ds->displays.length == 0) {
ffdsConnectXcbRandr(ds);
}
if (ds->displays.length == 0) {
ffdsConnectXrandr(ds);
}
if (ds->displays.length > 0) {
ffdsDetectWMDE(ds);
return;
}
}
// https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager
+34 -21
View File
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#if __FreeBSD__
#include <sys/sysctl.h>
@@ -376,48 +377,60 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
ffStrbufAppendS(&procPath, "/proc/");
uint32_t procPathLength = procPath.length;
FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); // Some processes have large command lines (looking at you chrome)
int procfd = dirfd(procdir);
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
// Match only folders starting with a number (the pid folders)
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
if ((dirent->d_type != DT_DIR && dirent->d_type != DT_UNKNOWN) || !ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufAppendS(&procPath, dirent->d_name);
ffStrbufSetS(&procPath, dirent->d_name);
uint32_t procFolderPathLength = procPath.length;
// Don't check for processes not owned by the current user.
char loginuid[32];
ffStrbufAppendS(&procPath, "/loginuid");
ffReadFileBuffer(procPath.chars, &loginuid);
if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
ffStrbufSubstrBefore(&procPath, procPathLength);
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
if (bytesRead <= 0) {
continue;
}
loginuid[bytesRead] = '\0';
if (strtol(loginuid, nullptr, 10) != (long) userId) {
continue;
}
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
// We check the cmdline for the process name, because it is not trimmed.
ffStrbufAppendS(&procPath, "/cmdline");
ffReadFileBuffer(procPath.chars, &processName);
ffStrbufTrimRightSpace(&processName);
ffStrbufSubstrBeforeFirstC(&processName, '\0'); // Trim the arguments
ffStrbufSubstrAfterLastC(&processName, '/');
ffStrbufSubstrBefore(&procPath, procPathLength);
// We check the realpath for the process name
ffStrbufAppendS(&procPath, "/exe");
char exePath[PATH_MAX];
ssize_t exePathLength = readlinkat(procfd, procPath.chars, exePath, sizeof(exePath) - 1);
const char* processName;
if (exePathLength >= 0) {
exePath[exePathLength] = '\0';
processName = basename(exePath);
} else {
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
ffStrbufAppendS(&procPath, "/comm");
exePathLength = ffReadFileDataRelative(procfd, procPath.chars, sizeof(exePath) - 1, exePath);
if (exePathLength <= 0) {
continue;
}
if (exePath[exePathLength - 1] == '\n') {
--exePathLength;
}
exePath[exePathLength] = '\0';
processName = exePath;
}
if (result->dePrettyName.length == 0) {
applyPrettyNameIfDE(result, processName.chars);
applyPrettyNameIfDE(result, processName);
}
if (result->wmPrettyName.length == 0) {
applyNameIfWM(result, processName.chars);
applyNameIfWM(result, processName);
}
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+3 -14
View File
@@ -1,21 +1,10 @@
#include "gpu.h"
#include "common/io.h"
#include "common/path.h"
#include "common/properties.h"
#include "common/memrchr.h"
#include "common/strutil.h"
#include <stdlib.h>
#ifdef __FreeBSD__
#include <paths.h>
#ifndef _PATH_LOCALBASE
#define _PATH_LOCALBASE "/usr/local"
#endif
#elif __OpenBSD__
#define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
#define _PATH_LOCALBASE "/usr/pkg"
#endif
#if FF_HAVE_EMBEDDED_PCIIDS
#include "fastfetch_pciids.c.inc"
#endif
@@ -46,9 +35,9 @@ static const FFstrbuf* loadPciIds() {
}
}
#elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids);
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/hwdata/pci.ids", &pciids);
if (pciids.length == 0) {
ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids);
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/pciids/pci.ids", &pciids);
}
#elif __sun
ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
+1 -1
View File
@@ -5,7 +5,7 @@
typedef struct FFLMResult {
FFstrbuf service;
FFstrbuf type;
FFstrbuf prettyName;
FFstrbuf version;
} FFLMResult;
+18
View File
@@ -0,0 +1,18 @@
#include "lm.h"
#import <Foundation/Foundation.h>
const char* ffDetectLM(FFLMResult* result) {
ffStrbufSetStatic(&result->service, "loginwindow");
ffStrbufSetStatic(&result->prettyName, "Login Window");
NSError* error;
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@"/System/Library/CoreServices/loginwindow.app/Contents/Info.plist" isDirectory:NO]
error:&error];
if (dict) {
ffStrbufSetS(&result->version, ((NSString*) dict[@"CFBundleShortVersionString"]).UTF8String);
}
return nullptr;
}
+361 -26
View File
@@ -1,18 +1,35 @@
#include "lm.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include "common/properties.h"
#include "common/dbus.h"
#include "common/processing.h"
#include "detection/displayserver/displayserver.h"
#include "common/strutil.h"
#ifdef FF_HAVE_DBUS
#include "common/dbus.h"
#endif
#include <unistd.h>
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
#if __FreeBSD__
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#elif __OpenBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#include <kvm.h>
#elif __sun
#include <procfs.h>
#elif __NetBSD__
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
static const char* getGdmVersion(FFstrbuf* version) {
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
if (error || version->length == 0) {
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
if (error || version->length == 0) {
return "Failed to get GDM version";
}
@@ -37,6 +54,8 @@ static const char* getSshdVersion(FFstrbuf* version) {
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
#include "common/path.h"
#include <stdlib.h>
#include <zlib.h>
@@ -49,7 +68,7 @@ static const char* getSddmVersion(FFstrbuf* version) {
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose)
gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR "/share/man/man1/sddm.1.gz", "rb");
gzFile file = ffgzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
if (file == Z_NULL) {
return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
}
@@ -112,35 +131,360 @@ static const char* getLightdmVersion(FFstrbuf* version) {
return nullptr;
}
const char* ffDetectLM(FFLMResult* result) {
#if __linux__ && !__ANDROID__
#ifdef FF_HAVE_DBUS
static const char* detectBySystemdDbus(FFLMResult* result) {
// This is the standard way to query the systemd session service name,
// and requires no $XDG_SESSION_ID being available
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
if (ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus) != nullptr) {
return "Failed to load system DBus";
}
if (!ffDBusGetPropertyString(
&dbus,
"org.freedesktop.login1",
"/org/freedesktop/login1/session/auto",
"org.freedesktop.login1.Session",
"Service",
&result->service)) {
return "Failed to get systemd session Service property";
}
return nullptr;
}
#endif
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
static const char* detectBySystemdPrivate(FFLMResult* result) {
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID"));
if (sessionId.length == 0) {
// On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself
// WARNING: This is private data. Do not parse
ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH "%d", instance.state.platform.uid);
ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
ffStrbufAppendUInt(&path, instance.state.platform.uid);
// This is actually buggy, and assumes current user is using DE
// `sd_pid_get_session` can be a better option, but we need to find a pid to use
if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) {
{ "DISPLAY=", &sessionId },
})) {
if (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
return "Failed to get $XDG_SESSION_ID";
}
}
ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH);
ffStrbufClear(&path);
ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
ffStrbufAppend(&path, &sessionId);
// WARNING: This is private data. Do not parse
if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) {
{ "SERVICE=", &result->service },
{ "TYPE=", &result->type },
})) {
if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID";
}
return nullptr;
}
#endif
static const char* testLms(const char* psName) {
static const char* A[] = {
"atrium",
};
static const char* C[] = {
"cdm",
};
static const char* E[] = {
"entrance",
};
static const char* G[] = {
"gdm",
"gdm3",
"greetd",
};
static const char* L[] = {
"lemurs",
"lightdm",
"lxdm",
"ly",
};
static const char* P[] = {
"plasmalogin",
};
static const char* S[] = {
"sddm",
"slim",
};
static const char* T[] = {
"tbsm",
};
static const char* X[] = {
"xdm",
"xenodm",
};
switch (psName[0]) {
#define PS_CASE(letter, array) \
case letter: \
for (uint32_t i = 0; i < ARRAY_SIZE(array); ++i) { \
if (ffStrEqualsIgnCase(psName, array[i])) { \
return array[i]; \
} \
} \
break;
PS_CASE('a', A)
PS_CASE('c', C)
PS_CASE('e', E)
PS_CASE('g', G)
PS_CASE('l', L)
PS_CASE('p', P)
PS_CASE('s', S)
PS_CASE('t', T)
PS_CASE('x', X)
}
return nullptr;
}
const char* detectByProcesses(FFLMResult* result) {
#if __FreeBSD__
#ifdef __DragonFly__
#define ki_comm kp_comm
#endif
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0 };
size_t length = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
}
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
}
length /= sizeof(*procs);
for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
const char* lm = testLms(proc->ki_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
#elif __OpenBSD__
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
int count = 0;
const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, 0, sizeof(*proc), &count);
if (proc) {
for (int i = 0; i < count; ++i) {
const char* lm = testLms(proc[i].p_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
}
kvm_close(kd);
#elif __sun
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
if (procdir == nullptr) {
return "opendir(\"/proc\") failed";
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
ffStrbufAppendS(&procPath, "/proc/");
uint32_t procPathLength = procPath.length;
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
if (!ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufAppendS(&procPath, dirent->d_name);
ffStrbufAppendS(&procPath, "/psinfo");
psinfo_t proc;
if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
ffStrbufSubstrBefore(&procPath, procPathLength);
if (proc.pr_uid != 0) {
continue;
}
const char* lm = testLms(proc.pr_fname);
if (lm) {
ffStrbufSetS(&result->service, lm);
break;
}
}
}
#elif __linux__ || __GNU__
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
if (procdir == nullptr) {
return "opendir(\"/proc\") failed";
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
int procfd = dirfd(procdir);
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
// Match only folders starting with a number (the pid folders)
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufSetS(&procPath, dirent->d_name);
uint32_t procFolderPathLength = procPath.length;
// Don't check for processes not owned by root (login managers run as root).
char loginuid[32];
ffStrbufAppendS(&procPath, "/loginuid");
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
if (bytesRead <= 0) {
continue;
}
loginuid[bytesRead] = '\0';
if (strtol(loginuid, nullptr, 10) != (long) (uint32_t) -1 /* no loginuid */) {
continue;
}
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
ffStrbufAppendS(&procPath, "/comm");
char comm[256];
bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(comm) - 1, comm);
if (bytesRead <= 0) {
continue;
}
if (comm[bytesRead - 1] == '\n') {
--bytesRead;
}
comm[bytesRead] = '\0';
const char* lm = testLms(comm);
if (lm) {
ffStrbufSetS(&result->service, lm);
break;
}
}
#elif __NetBSD__
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, 0, sizeof(struct kinfo_proc2), INT_MAX };
size_t size = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
return "sysctl(KERN_PROC_UID, nullptr) failed";
}
FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
return "sysctl(KERN_PROC_UID, procs) failed";
}
for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
const char* lm = testLms(proc->p_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
#endif
if (result->service.length == 0) {
return "Failed to detect login manager by processes";
}
return nullptr;
}
const char* ffDetectLM(FFLMResult* result) {
const char* error = "";
#if __linux__
#ifdef FF_HAVE_DBUS
error = detectBySystemdDbus(result);
#endif
if (error != nullptr) {
error = detectBySystemdPrivate(result);
}
#endif
if (error != nullptr) {
error = detectByProcesses(result);
}
if (error != nullptr) {
return error;
}
switch (result->service.chars[0]) {
case 'a':
if (ffStrEqualsIgnCase(result->service.chars, "atrium")) {
ffStrbufSetStatic(&result->prettyName, "Atrium");
}
break;
case 'c':
if (ffStrEqualsIgnCase(result->service.chars, "cdm")) {
ffStrbufSetStatic(&result->prettyName, "CDM");
}
break;
case 'e':
if (ffStrEqualsIgnCase(result->service.chars, "entrance")) {
ffStrbufSetStatic(&result->prettyName, "Entrance");
}
break;
case 'g':
if (ffStrbufStartsWithIgnCaseS(&result->service, "gdm")) {
ffStrbufSetStatic(&result->prettyName, "GDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "greetd")) {
ffStrbufSetStatic(&result->prettyName, "greetd");
}
break;
case 'l':
if (ffStrEqualsIgnCase(result->service.chars, "lemurs")) {
ffStrbufSetStatic(&result->prettyName, "Lemurs");
} else if (ffStrEqualsIgnCase(result->service.chars, "lightdm")) {
ffStrbufSetStatic(&result->prettyName, "LightDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "lxdm")) {
ffStrbufSetStatic(&result->prettyName, "LXDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "ly")) {
ffStrbufSetStatic(&result->prettyName, "Ly");
}
break;
case 'p':
if (ffStrEqualsIgnCase(result->service.chars, "plasmalogin")) {
ffStrbufSetStatic(&result->prettyName, "Plasma Login");
}
break;
case 's':
if (ffStrEqualsIgnCase(result->service.chars, "sddm")) {
ffStrbufSetStatic(&result->prettyName, "SDDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "slim")) {
ffStrbufSetStatic(&result->prettyName, "SLiM");
}
break;
case 't':
if (ffStrEqualsIgnCase(result->service.chars, "tbsm")) {
ffStrbufSetStatic(&result->prettyName, "TBSM");
}
break;
case 'x':
if (ffStrEqualsIgnCase(result->service.chars, "xdm")) {
ffStrbufSetStatic(&result->prettyName, "XDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "xenodm")) {
ffStrbufSetStatic(&result->prettyName, "Xenodm");
}
break;
}
if (result->prettyName.length == 0) {
ffStrbufSet(&result->prettyName, &result->service);
}
if (instance.config.general.detectVersion) {
if (ffStrbufStartsWithS(&result->service, "gdm")) {
getGdmVersion(&result->version);
@@ -155,14 +499,5 @@ const char* ffDetectLM(FFLMResult* result) {
}
}
// Correct char cases
if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_WAYLAND)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_WAYLAND);
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_X11)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_X11);
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_TTY)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_TTY);
}
return nullptr;
}
+19
View File
@@ -0,0 +1,19 @@
#include "lm.h"
#include "common/windows/version.h"
#include "common/windows/nt.h"
#include <wchar.h>
const char* ffDetectLM(FFLMResult* result) {
ffStrbufSetStatic(&result->service, "LogonUI");
ffStrbufSetStatic(&result->prettyName, "Logon User Interface");
if (instance.config.general.detectVersion) {
wchar_t exePath[MAX_PATH];
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%ls", (const wchar_t*) SharedUserData->NtSystemRoot, L"LogonUI.exe");
ffGetFileVersion(exePath, nullptr, &result->version);
}
return nullptr;
}
+19 -12
View File
@@ -1,19 +1,26 @@
#include "memory.h"
#include <windows.h>
#include "common/windows/nt.h"
const char* ffDetectMemory(FFMemoryResult* ram) {
MEMORYSTATUSEX statex = {
.dwLength = sizeof(statex),
};
// GlobalMemoryStatusEx() internally uses
// NtQuerySystemInformation(SystemBasicPerformanceInformation) in Win 7, and
// NtQuerySystemInformation(SystemMemoryUsageInformation) in Win 10
if (!GlobalMemoryStatusEx(&statex)) {
return "GlobalMemoryStatusEx() failed";
}
// Note: GlobalMemoryStatusEx() internally uses SystemMemoryUsageInformation in Win 10
ram->bytesTotal = statex.ullTotalPhys;
ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys;
SYSTEM_BASIC_PERFORMANCE_INFORMATION sbpi;
if (!NT_SUCCESS(NtQuerySystemInformation(SystemBasicPerformanceInformation, &sbpi, sizeof(sbpi), NULL)))
return "Failed to query memory information";
uint64_t phyPages =
#if _WIN64
SharedUserData->FullNumberOfPhysicalPages;
if (__builtin_expect(phyPages == 0, false)) { // Not supported on Windows 8.1
phyPages = SharedUserData->NumberOfPhysicalPages; // ULONG, maximum 16 TB
}
#else
SharedUserData->NumberOfPhysicalPages;
#endif
uint64_t pageSize = instance.state.platform.sysinfo.pageSize;
ram->bytesTotal = phyPages * pageSize;
ram->bytesUsed = (phyPages - sbpi.AvailablePages) * pageSize;
return nullptr;
}
@@ -122,6 +122,7 @@ const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)
}
#else
const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
FF_UNUSED(result, options);
return "Fastfetch was compiled without libgeom support";
}
#endif
+8 -1
View File
@@ -1,5 +1,12 @@
#pragma once
#include "fastfetch.h"
#include "modules/processes/option.h"
const char* ffDetectProcesses(uint32_t* result);
typedef struct FFProcessesResult
{
uint32_t processes;
uint32_t threads;
} FFProcessesResult;
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result);
+41
View File
@@ -0,0 +1,41 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#import <libproc.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (!options->countKprocs && (proc->kp_proc.p_flag & P_SYSTEM)) {
continue;
}
result->processes++;
pid_t pid = proc->kp_proc.p_pid;
struct proc_taskinfo taskInfo;
if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) != sizeof(taskInfo)) {
continue;
}
result->threads += taskInfo.pti_threadnum;
}
return nullptr;
}
+23 -11
View File
@@ -1,23 +1,35 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/user.h>
#endif
#include <sys/types.h>
#include <sys/user.h>
#ifndef KERN_PROC_PROC
#define KERN_PROC_PROC KERN_PROC_ALL // Apple
#endif
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
*result = (uint32_t) (length / sizeof(struct kinfo_proc));
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &procs[i];
if (!options->countKprocs && (proc->ki_flag & P_KPROC)) {
continue;
}
result->processes++;
result->threads += (uint32_t) proc->ki_numthreads;
}
return nullptr;
}
+38
View File
@@ -0,0 +1,38 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/proc.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL | (options->countKprocs ? KERN_PROC_FLAG_LWKT : 0) };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &procs[i];
if (proc->kp_pid == -1) {
// Kernel thread, only returned when countKprocs is set via KERN_PROC_FLAG_LWKT
result->threads++;
continue;
}
result->processes++;
result->threads += (uint32_t) proc->kp_nthreads;
}
return nullptr;
}
+45
View File
@@ -0,0 +1,45 @@
#include "processes.h"
#include <hurd.h>
#include <ps.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
struct ps_context* context = nullptr;
if (ps_context_create(getproc(), &context) != 0) {
return "ps_context_create(getproc()) failed";
}
const char* error = nullptr;
struct proc_stat_list* list = nullptr;
if (proc_stat_list_create(context, &list) != 0) {
error = "proc_stat_list_create() failed";
goto done;
}
if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) {
error = "proc_stat_list_add_all() failed";
goto done;
}
proc_stat_list_set_flags(list, PSTAT_NUM_THREADS);
for (uint32_t i = 0; i < list->num_procs; i++) {
struct proc_stat* stat = list->proc_stats[i];
if (!options->countKprocs && proc_stat_pid(stat) == 2) { // Kernel Task
continue;
}
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
result->threads += proc_stat_num_threads(stat);
}
}
result->processes = (uint32_t) list->num_procs;
done:
if (list) {
proc_stat_list_free(list);
}
ps_context_free(context);
return error;
}
+11 -2
View File
@@ -2,13 +2,22 @@
#include <OS.h>
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
system_info info;
if (get_system_info(&info) != B_OK) {
return "Error getting system info";
}
*result = info.used_teams;
result->processes = info.used_teams;
result->threads = info.used_threads;
if (!options->countKprocs) {
team_info ti;
if (get_team_info(B_SYSTEM_TEAM, &ti) == B_OK) {
result->processes--;
result->threads -= (uint32_t) ti.thread_count;
}
}
return nullptr;
}
+56 -10
View File
@@ -1,27 +1,73 @@
#include "processes.h"
#include "common/io.h"
#include "common/memrchr.h"
#include "common/strutil.h"
const char* ffDetectProcesses(uint32_t* result) {
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
if (dir == nullptr) {
return "opendir(\"/proc\") failed";
}
uint32_t num = 0;
int procfd = dirfd(dir);
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (
#ifdef _DIRENT_HAVE_D_TYPE
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) &&
#endif
ffCharIsDigit(entry->d_name[0]))
++num;
}
if ((entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) && ffCharIsDigit(entry->d_name[0])) {
char statPath[32];
strcpy(ffStrCopy(statPath, entry->d_name, sizeof(statPath)), "/stat");
*result = num;
char statBuffer[512];
ssize_t statLength = ffReadFileDataRelative(procfd, statPath, sizeof(statBuffer) - 1, statBuffer);
if (statLength < 0) {
continue;
}
statBuffer[statLength] = '\0';
const char* cursor = (const char*) memrchr(statBuffer, ')', (size_t) statLength);
if (!cursor) {
continue;
}
cursor += 2; // skip ") "
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
goto skip_process;
}
char* p;
for (uint32_t field = 3; field <= 20 /*num_threads*/; ++field) {
switch (field) {
case 9: // flags
if ((strtoul(cursor, &p, 10) & PF_KTHREAD) && !options->countKprocs) {
goto skip_process;
}
cursor = p;
break;
case 20: // num_threads
result->threads += (uint32_t) strtoul(cursor, &p, 10);
cursor = p;
break;
default:
while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
++cursor;
}
break;
}
++cursor;
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
goto skip_process;
}
}
++result->processes;
}
skip_process:;
}
return nullptr;
}
+26 -4
View File
@@ -1,15 +1,37 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
const char* ffDetectProcesses(uint32_t* result) {
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), 0 };
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
size_t length = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL}) failed";
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, nullptr}) failed";
}
FF_AUTO_FREE struct kinfo_proc2* procs = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, procs}) failed";
}
uint32_t procCount = (uint32_t) (length / sizeof(struct kinfo_proc2));
for (uint32_t i = 0; i < procCount; ++i) {
const struct kinfo_proc2* proc = &procs[i];
if (!options->countKprocs && (proc->p_flag & P_SYSTEM)) {
continue;
}
++result->processes;
int lwpRequest[] = { CTL_KERN, KERN_LWP, procs[i].p_pid, sizeof(struct kinfo_lwp), 0 };
size_t lwpLength = 0;
if (sysctl(lwpRequest, ARRAY_SIZE(lwpRequest), nullptr, &lwpLength, nullptr, 0) == 0) {
result->threads += (uint32_t) (lwpLength / sizeof(struct kinfo_lwp));
}
}
*result = (uint32_t) (length / sizeof(struct kinfo_proc2));
return nullptr;
}
@@ -1,5 +1,5 @@
#include "processes.h"
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses([[maybe_unused]] FFProcessesResult* result) {
return "Not supported on this platform";
}
+25 -4
View File
@@ -1,15 +1,36 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/param.h>
#include <sys/sysctl.h>
#include <kvm.h>
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
const void* ret = kvm_getprocs(kd, KERN_PROC_ALL, 0, 1, result);
kvm_close(kd);
if (!ret) {
if (!kd) {
return "kvm_open() failed";
}
int count = 0;
// KERN_PROC_ALL returns all user-level processes, excluding kernel processes and threads
const struct kinfo_proc* procs = kvm_getprocs(kd,
(options->countKprocs ? KERN_PROC_KTHREAD : KERN_PROC_ALL) | KERN_PROC_SHOW_THREADS,
0, sizeof(struct kinfo_proc), &count);
if (!procs) {
kvm_close(kd);
return "kvm_getprocs() failed";
}
for (int i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &procs[i];
++result->threads;
if (proc->p_tid == -1) {
++result->processes;
}
}
kvm_close(kd);
return nullptr;
}
+40
View File
@@ -0,0 +1,40 @@
#include "processes.h"
#include "common/io.h"
#include "common/strutil.h"
#include <procfs.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
if (dir == nullptr) {
return "opendir(\"/proc\") failed";
}
int procfd = dirfd(dir);
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (ffCharIsDigit(entry->d_name[0])) {
char path[32];
char* pidEnd = ffStrCopy(path, entry->d_name, sizeof(path));
if (!options->countKprocs) {
pstatus_t status;
strcpy(pidEnd, "/status");
if (ffReadFileDataRelative(procfd, path, sizeof(status), &status) != (ssize_t) sizeof(status) ||
status.pr_flags & PR_ISSYS) {
continue; // The process may have exited, no permission or is a kernel process
}
}
strcpy(pidEnd, "/psinfo");
psinfo_t info;
if (ffReadFileDataRelative(procfd, path, sizeof(info), &info) == (ssize_t) sizeof(info)) {
++result->processes;
result->threads += (uint32_t) info.pr_nlwp;
}
}
}
return nullptr;
}
+10 -4
View File
@@ -4,7 +4,7 @@
#include <ntstatus.h>
#include <winternl.h>
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
// Multiple attempts in case processes change while
@@ -25,9 +25,15 @@ const char* ffDetectProcesses(uint32_t* result) {
}
}
*result = 1; // Init with 1 because we test for ptr->NextEntryOffset
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ptr->NextEntryOffset; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
++*result;
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
if (!options->countKprocs && ptr->CreateTime.QuadPart == 0) {
continue;
}
++result->processes;
result->threads += ptr->NumberOfThreads;
if (ptr->NextEntryOffset == 0) {
break;
}
}
return nullptr;
+5 -11
View File
@@ -8,17 +8,8 @@
#include <ctype.h>
#include <stdint.h>
#ifdef __FreeBSD__
#include <paths.h>
#ifndef _PATH_LOCALBASE
#define _PATH_LOCALBASE "/usr/local"
#endif
#elif __OpenBSD__
#define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
#define _PATH_LOCALBASE "/usr/pkg"
#elif _WIN32
#if _WIN32
#include "common/windows/registry.h"
#include "common/windows/version.h"
#include <windows.h>
@@ -194,6 +185,9 @@ static bool extractBusyboxVersion(const char* line, uint32_t len, void* userdata
static bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version) {
ffBinaryExtractStrings(exe->chars, extractBusyboxVersion, version, (uint32_t) strlen("BusyBox v0.0.0"));
if (version->length > 0) {
return true;
}
const char* error = ffStrbufEndsWithS(exe, "busybox")
? ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "ash", "--help", nullptr })
@@ -660,7 +654,7 @@ static bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version) {
ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib64/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) ||
ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
#else
ffReadFileData(_PATH_LOCALBASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
ffReadFileData(FF_PATH_PKG_BASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
#endif
) {
// Starts from version 0.17.0
+161
View File
@@ -0,0 +1,161 @@
#include "top.h"
#include "common/time.h"
static FFlist first;
static double startTick;
static FFTopTypes preparedShowTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK;
void ffPrepareTopProcesses(FFTopTypes showTypes) {
if ((showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0) {
return; // Memory usage is instantaneous; no baseline snapshot is needed
}
if (startTick != 0 && preparedShowTypes == showTypes) {
return; // Already prepared
}
if (startTick != 0) {
// The set of requested types changed; discard the stale baseline
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, first) {
ffStrbufDestroy(&item->name);
}
ffListDestroy(&first);
}
ffListInit(&first);
startTick = ffTimeGetTick();
preparedShowTypes = showTypes;
ffTopGetProcessSnapshot(&first, showTypes);
}
// clang-format off
static int compareCpuResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->cpuPercent < b->cpuPercent) return 1;
if (a->cpuPercent > b->cpuPercent) return -1;
return (int) (a->pid - b->pid);
}
static int compareMemoryResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->memBytes < b->memBytes) return 1;
if (a->memBytes > b->memBytes) return -1;
return (int) (a->pid - b->pid);
}
static int compareDiskReadResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->bytesRead < b->bytesRead) return 1;
if (a->bytesRead > b->bytesRead) return -1;
return (int) (a->pid - b->pid);
}
static int compareDiskWriteResults(const FFTopProcessResult* a, const FFTopProcessResult* b) {
if (a->bytesWritten < b->bytesWritten) return 1;
if (a->bytesWritten > b->bytesWritten) return -1;
return (int) (a->pid - b->pid);
}
// clang-format on
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result) {
ffListClear(result);
if (options->nProcesses == 0) {
return nullptr;
}
// Memory usage is instantaneous; when neither CPU time nor disk IO counters
// are requested, a single snapshot suffices and no sampling wait is needed.
const bool sampleOnce = (options->showTypes & (FF_TOP_TYPE_CPU | FF_TOP_TYPE_DISK)) == 0;
if (sampleOnce) {
FF_LIST_AUTO_DESTROY snapshots = ffListCreate();
const char* error = ffTopGetProcessSnapshot(&snapshots, options->showTypes);
if (error) {
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, snapshots) {
ffStrbufDestroy(&item->name);
}
return error;
}
if (snapshots.length == 0) {
return "No processes found";
}
FF_LIST_FOR_EACH (FFTopProcessSnapshot, snap, snapshots) {
FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
item->pid = snap->pid;
item->memBytes = snap->memBytes;
item->bytesRead = 0;
item->bytesWritten = 0;
item->cpuPercent = 0;
item->startTime = snap->startTime;
ffStrbufInitMove(&item->name, &snap->name);
}
} else {
if (startTick == 0 || preparedShowTypes != options->showTypes) {
ffPrepareTopProcesses(options->showTypes);
}
double elapsedTime = ffTimeGetTick() - startTick;
if (elapsedTime < (double) options->waitTime) {
ffTimeSleep(options->waitTime - (uint32_t) elapsedTime);
}
if (first.length == 0) {
return "No processes found";
}
ffListReserve(result, sizeof(FFTopProcessResult), first.length < options->nProcesses ? first.length : options->nProcesses);
FF_LIST_AUTO_DESTROY second = ffListCreateA(sizeof(FFTopProcessSnapshot), first.length);
const char* error = ffTopGetProcessSnapshot(&second, options->showTypes);
const double elapsed = ffTimeGetTick() - startTick;
if (error || elapsed <= 0) {
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
ffStrbufDestroy(&item->name);
}
return error ?: "Invalid process sampling interval";
}
FF_LIST_FOR_EACH (FFTopProcessSnapshot, oldItem, first) {
FFTopProcessSnapshot* newItem = nullptr;
FF_LIST_FOR_EACH (FFTopProcessSnapshot, item, second) {
if (item->pid == oldItem->pid) {
newItem = item;
break;
}
}
if (!newItem || newItem->startTime != oldItem->startTime || newItem->cpuTime < oldItem->cpuTime || newItem->bytesRead < oldItem->bytesRead || newItem->bytesWritten < oldItem->bytesWritten) {
ffStrbufDestroy(&oldItem->name);
continue;
}
FFTopProcessResult* item = FF_LIST_ADD(FFTopProcessResult, *result);
item->pid = newItem->pid;
item->memBytes = newItem->memBytes;
item->bytesRead = (newItem->bytesRead - oldItem->bytesRead) * 1000u / (uint64_t) elapsed;
item->bytesWritten = (newItem->bytesWritten - oldItem->bytesWritten) * 1000u / (uint64_t) elapsed;
item->cpuPercent = (double) (newItem->cpuTime - oldItem->cpuTime) / elapsed * 100.0;
item->startTime = newItem->startTime;
ffStrbufInitMove(&item->name, &oldItem->name);
}
// Reuse `second` as the baseline of the next call
ffListDestroy(&first);
ffListInitMove(&first, &second);
startTick = ffTimeGetTick();
}
const void* compare = options->sort == FF_TOP_TYPE_DISK_WRITE ? (void*) compareDiskWriteResults
: options->sort == FF_TOP_TYPE_DISK_READ ? (void*) compareDiskReadResults
: options->sort == FF_TOP_TYPE_MEMORY ? (void*) compareMemoryResults
: (void*) compareCpuResults;
ffListSort(result, sizeof(FFTopProcessResult), (void*) compare);
if (result->length > options->nProcesses) {
FFTopProcessResult* items = (FFTopProcessResult*) result->data;
for (uint32_t i = options->nProcesses; i < result->length; ++i) {
ffStrbufDestroy(&items[i].name);
}
result->length = options->nProcesses;
}
return nullptr;
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "fastfetch.h"
#include "modules/top/option.h"
typedef struct FFTopProcessResult {
FFstrbuf name;
double cpuPercent;
uint64_t memBytes;
uint64_t bytesRead;
uint64_t bytesWritten;
uint64_t startTime;
uint32_t pid;
} FFTopProcessResult;
typedef struct FFTopProcessSnapshot {
FFstrbuf name; // comm
uint64_t cpuTime; // user_time + system_time
uint64_t memBytes; // RSS
uint64_t bytesRead; // Physical storage read
uint64_t bytesWritten; // Physical storage written
uint64_t startTime; // Differs between platforms
uint32_t pid;
} FFTopProcessSnapshot;
const char* ffDetectTopProcesses(FFTopOptions* options, FFlist* result);
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes);
+60
View File
@@ -0,0 +1,60 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <errno.h>
#include <sys/sysctl.h>
#include <libproc.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (proc->kp_proc.p_flag & P_SYSTEM) {
continue;
}
pid_t pid = proc->kp_proc.p_pid;
struct rusage_info_v2 rusage;
if (proc_pid_rusage(pid, RUSAGE_INFO_V2, (rusage_info_t*) &rusage) != 0) {
continue; // The process may have exited
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->kp_proc.p_comm);
item->pid = (uint32_t) pid;
// Note: Do NOT use proc->kp_proc.p_pctcpu for CPU usage. p_pctcpu is a
// decaying average (fixpt_t with FSCALE=2048) updated roughly once per
// second by the kernel. It is heavily smoothed, lags short bursts, has
// low resolution, and its multicore scaling (>100%) is inconsistent
// across XNU versions. It also breaks the unified model where
// FFTopProcessSnapshot.cpuTime is cumulative time and top.c computes
// (new - old) / elapsed * 100 for all platforms. proc_pid_rusage with
// RUSAGE_INFO_V2 provides precise cumulative ri_user_time +
// ri_system_time in nanoseconds and is the modern recommended API on
// Darwin, consistent with Linux/BSD differential sampling and accurate
// for the short waitTime interval (e.g. 100ms).
item->cpuTime = (rusage.ri_user_time + rusage.ri_system_time) / 1000000u; // ns -> ms
item->memBytes = rusage.ri_resident_size;
item->bytesRead = rusage.ri_diskio_bytesread;
item->bytesWritten = rusage.ri_diskio_byteswritten;
item->startTime = rusage.ri_proc_start_abstime;
}
return nullptr;
}
+57
View File
@@ -0,0 +1,57 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (proc->ki_flag & P_KPROC) {
continue;
}
uint32_t pid = (uint32_t) proc->ki_pid;
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->ki_comm);
item->pid = pid;
uint64_t userMs = (uint64_t) proc->ki_rusage.ru_utime.tv_sec * 1000 +
(uint64_t) proc->ki_rusage.ru_utime.tv_usec / 1000;
uint64_t sysMs = (uint64_t) proc->ki_rusage.ru_stime.tv_sec * 1000 +
(uint64_t) proc->ki_rusage.ru_stime.tv_usec / 1000;
item->cpuTime = userMs + sysMs;
item->memBytes = (uint64_t) proc->ki_rssize * (uint64_t) pageSize;
item->startTime = (uint64_t) proc->ki_start.tv_sec * 1000 +
(uint64_t) proc->ki_start.tv_usec / 1000;
item->bytesRead = (uint64_t) proc->ki_rusage.ru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->ki_rusage.ru_oublock * DEV_BSIZE;
}
return NULL;
}
+58
View File
@@ -0,0 +1,58 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/user.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
// KERN_PROC_ALL won't return kernel threads
if (proc->kp_stat == SZOMB) { // Ignore zombie processes
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->kp_comm);
item->pid = (uint32_t) proc->kp_pid;
// kp_ru only accounts for exited lwps; the cpu times of live lwps are
// kept in microseconds in kp_lwp.kl_uticks/kl_sticks (see calcru(9)).
uint64_t exitedMs = (uint64_t) (proc->kp_ru.ru_utime.tv_sec + proc->kp_ru.ru_stime.tv_sec) * 1000 +
(uint64_t) (proc->kp_ru.ru_utime.tv_usec + proc->kp_ru.ru_stime.tv_usec) / 1000;
uint64_t liveMs = (proc->kp_lwp.kl_uticks + proc->kp_lwp.kl_sticks) / 1000;
item->cpuTime = exitedMs + liveMs;
item->memBytes = (uint64_t) proc->kp_vm_rssize * pageSize;
item->startTime = (uint64_t) proc->kp_start.tv_sec * 1000 +
(uint64_t) proc->kp_start.tv_usec / 1000;
item->bytesRead = (uint64_t) (proc->kp_ru.ru_inblock + proc->kp_lwp.kl_ru.ru_inblock) * DEV_BSIZE;
item->bytesWritten = (uint64_t) (proc->kp_ru.ru_oublock + proc->kp_lwp.kl_ru.ru_oublock) * DEV_BSIZE;
}
return nullptr;
}
+83
View File
@@ -0,0 +1,83 @@
#include "top.h"
#include <hurd.h>
#include <string.h>
#include <ps.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
struct ps_context* context = nullptr;
if (ps_context_create(getproc(), &context) != 0) {
return "ps_context_create(getproc()) failed";
}
const char* error = nullptr;
struct proc_stat_list* list = nullptr;
if (proc_stat_list_create(context, &list) != 0) {
error = "proc_stat_list_create() failed";
goto done;
}
if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) {
error = "proc_stat_list_add_all() failed";
goto done;
}
// Flags that cannot be fetched for some process are simply left unset;
// each field must be checked before use.
if (proc_stat_list_set_flags(list, PSTAT_ARGS | PSTAT_STATE | PSTAT_TASK_BASIC) != 0) {
error = "proc_stat_list_set_flags() failed";
goto done;
}
for (unsigned i = 0; i < list->num_procs; ++i) {
struct proc_stat* stat = list->proc_stats[i];
if (!proc_stat_has(stat, PSTAT_PID | PSTAT_STATE)) {
continue;
}
if (proc_stat_state(stat) & PSTAT_STATE_P_ZOMBIE) {
continue;
}
uint32_t pid = (uint32_t) proc_stat_pid(stat);
if (pid == 2) { // Kernel Task
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
// There is no kernel-side comm on the Hurd; use the first argument.
ffStrbufInit(&item->name);
if (proc_stat_has(stat, PSTAT_ARGS) && proc_stat_args(stat)) {
size_t len = strnlen(proc_stat_args(stat), proc_stat_args_len(stat));
ffStrbufAppendNS(&item->name, (uint32_t) len, proc_stat_args(stat));
}
if (item->name.length == 0) {
ffStrbufAppendS(&item->name, "(unknown)");
}
item->pid = pid;
if (proc_stat_has(stat, PSTAT_TASK_BASIC)) {
const task_basic_info_t info = proc_stat_task_basic_info(stat);
item->cpuTime = (uint64_t) info->user_time64.seconds * 1000 +
(uint64_t) info->user_time64.nanoseconds / 1000000 +
(uint64_t) info->system_time64.seconds * 1000 +
(uint64_t) info->system_time64.nanoseconds / 1000000;
item->memBytes = (uint64_t) info->resident_size;
item->startTime = (uint64_t) info->creation_time64.seconds * 1000 +
(uint64_t) info->creation_time64.nanoseconds / 1000000;
}
// Storage io counters aren't exposed by kernel; leave them zeroed.
item->bytesRead = 0;
item->bytesWritten = 0;
}
done:
if (list) {
proc_stat_list_free(list);
}
ps_context_free(context);
return error;
}
+47
View File
@@ -0,0 +1,47 @@
#include "top.h"
#include <OS.h>
#include <string.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
int32 cookie = 0;
team_info team;
while (get_next_team_info(&cookie, &team) == B_OK) {
if (team.team == B_SYSTEM_TEAM) {
continue; // The kernel team
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, team.name);
item->pid = (uint32_t) team.team;
// User and kernel time of all threads of the team combined in microseconds.
item->cpuTime = 0;
team_usage_info usage;
if (get_team_usage_info(team.team, B_TEAM_USAGE_SELF, &usage) == B_OK) {
item->cpuTime = ((uint64_t) usage.user_time + (uint64_t) usage.kernel_time) / 1000;
}
// Sum up the physically resident memory of all areas of the team;
// ram_size is the amount of physical pages committed to an area,
// whereas size is just its virtual reservation.
uint64_t memBytes = 0;
ssize_t areaCookie = 0;
area_info area;
while (get_next_area_info(team.team, &areaCookie, &area) == B_OK) {
memBytes += area.ram_size;
}
item->memBytes = memBytes;
// start_time is microseconds since boot.
item->startTime = (uint64_t) team.start_time;
// Storage io counters are not exposed by Haiku; leave them zeroed.
item->bytesRead = 0;
item->bytesWritten = 0;
}
return nullptr;
}
+160
View File
@@ -0,0 +1,160 @@
#include "top.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/memrchr.h"
#include "common/strutil.h"
#include <inttypes.h>
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
static bool parseStat(const char* buffer, size_t length, FFTopProcessSnapshot* result) {
const char* open = strchr(buffer, '(');
const char* close = (const char*) memrchr(buffer, ')', length);
if (!open || !close || close <= open || close + 2 >= buffer + length) {
FF_DEBUG("parseStat: malformed stat content");
return false;
}
ffStrbufSetNS(&result->name, (uint32_t) (close - open - 1), open + 1);
const char* cursor = close + 2; // skip ") "
if (__builtin_expect(cursor >= buffer + length, false)) {
return false;
}
result->cpuTime = 0;
char* p;
for (uint32_t field = 3; field <= 22 /*start time*/; ++field) {
switch (field) {
case 9: // flags
if ((strtoul(cursor, &p, 10) & PF_KTHREAD)) {
FF_DEBUG("parseStat: skipping kernel task %s", result->name.chars);
return false;
}
cursor = p;
break;
case 14: // user
case 15: // system
result->cpuTime += (uint64_t) strtoull(cursor, &p, 10);
cursor = p;
break;
case 22: // start time
result->startTime = (uint64_t) strtoull(cursor, &p, 10);
cursor = p;
break;
default:
while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
++cursor;
}
break;
}
++cursor;
if (__builtin_expect(cursor >= buffer + length, false)) {
return false;
}
}
return true;
}
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
const long ticks = sysconf(_SC_CLK_TCK);
const long pageSize = instance.state.platform.sysinfo.pageSize;
FF_DEBUG("Scanning /proc: clk_tck=%ld, pageSize=%ld", ticks, pageSize);
if (ticks <= 0 || pageSize <= 0) {
return "sysconf(_SC_CLK_TCK or _SC_PAGESIZE) failed";
}
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
if (!dir) {
FF_DEBUG("opendir(\"/proc\") failed: %s", strerror(errno));
return "opendir(\"/proc\") failed";
}
int procfd = dirfd(dir);
struct dirent* entry;
char statBuffer[1024];
char statmBuffer[256];
while ((entry = readdir(dir))) {
if (!entry->d_name[0] || !ffCharIsDigit(entry->d_name[0])) {
continue;
}
unsigned long pid = strtoul(entry->d_name, nullptr, 10);
if (pid == 0 || pid > UINT32_MAX) {
FF_DEBUG("Skip invalid pid directory: %s", entry->d_name);
continue;
}
FF_AUTO_CLOSE_FD int subfd = openat(procfd, entry->d_name, O_RDONLY | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(/proc/%s) failed: %s", entry->d_name, strerror(errno));
continue;
}
ssize_t statLength;
if ((statLength = ffReadFileDataRelative(subfd, "stat", sizeof(statBuffer) - 1, statBuffer)) < 0) {
FF_DEBUG("Failed to read /proc/%s/stat: %s", entry->d_name, strerror(errno));
continue;
}
statBuffer[statLength] = '\0';
ssize_t statmLength = 0;
if (showTypes & FF_TOP_TYPE_MEMORY) {
if ((statmLength = ffReadFileDataRelative(subfd, "statm", sizeof(statmBuffer) - 1, statmBuffer)) < 0) {
FF_DEBUG("Failed to read /proc/%s/statm: %s", entry->d_name, strerror(errno));
statmLength = 0;
}
statmBuffer[statmLength] = '\0';
}
char ioBuffer[512];
ssize_t ioLength = 0;
if (showTypes & FF_TOP_TYPE_DISK) {
if ((ioLength = ffReadFileDataRelative(subfd, "io", sizeof(ioBuffer) - 1, ioBuffer)) < 0) {
FF_DEBUG("Failed to read /proc/%s/io: %s", entry->d_name, strerror(errno));
ioLength = 0;
}
ioBuffer[ioLength] = '\0';
}
auto snapshot = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInit(&snapshot->name);
if (!parseStat(statBuffer, (size_t) statLength, snapshot)) {
ffStrbufDestroy(&snapshot->name);
--snapshots->length;
continue;
}
uint64_t rssPages = 0;
if (__builtin_expect((showTypes & FF_TOP_TYPE_MEMORY) && sscanf(statmBuffer, "%*u %" SCNu64, &rssPages) != 1, false)) {
FF_DEBUG("Failed to parse statm of /proc/%lu", pid);
ffStrbufDestroy(&snapshot->name);
--snapshots->length;
continue;
}
snapshot->pid = (uint32_t) pid;
snapshot->memBytes = rssPages * (uint64_t) pageSize;
snapshot->bytesRead = 0;
snapshot->bytesWritten = 0;
if (ioLength > 0) {
sscanf(ioBuffer, "rchar: %*u\nwchar: %*u\nsyscr: %*u\nsyscw: %*u\nread_bytes: %" SCNu64 "\nwrite_bytes: %" SCNu64, &snapshot->bytesRead, &snapshot->bytesWritten);
}
snapshot->cpuTime = snapshot->cpuTime * 1000u / (uint64_t) ticks;
FF_DEBUG(
"Captured process %u (%s): cpuTime=%" PRIu64 "ms, mem=%" PRIu64 "B, diskRead=%" PRIu64 "B, diskWrite=%" PRIu64 "B",
snapshot->pid, snapshot->name.chars, snapshot->cpuTime, snapshot->memBytes, snapshot->bytesRead, snapshot->bytesWritten);
}
FF_DEBUG("Captured %u process snapshots in total", snapshots->length);
return nullptr;
}
+56
View File
@@ -0,0 +1,56 @@
#include "top.h"
#include "common/mallocHelper.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <errno.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_ALL, -1, sizeof(struct kinfo_proc2), INT_MAX };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, nullptr}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc2);
FF_AUTO_FREE struct kinfo_proc2* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC2, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc2));
uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc2* proc = &processes[i];
if ((proc->p_flag & P_SYSTEM) || !proc->p_uvalid) { // Kernel process or zombie
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->p_comm);
item->pid = (uint32_t) proc->p_pid;
uint64_t userMs = (uint64_t) proc->p_uutime_sec * 1000 +
(uint64_t) proc->p_uutime_usec / 1000;
uint64_t sysMs = (uint64_t) proc->p_ustime_sec * 1000 +
(uint64_t) proc->p_ustime_usec / 1000;
item->cpuTime = userMs + sysMs;
item->memBytes = (uint64_t) proc->p_vm_rssize * (uint64_t) pageSize;
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 +
(uint64_t) proc->p_ustart_usec / 1000;
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
}
return NULL;
}
+44
View File
@@ -0,0 +1,44 @@
#include "top.h"
#include <sys/types.h>
#include <sys/param.h> // DEV_BSIZE
#include <sys/sysctl.h>
#include <kvm.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
if (!kd) {
return "kvm_open() failed";
}
int count = 0;
// KERN_PROC_ALL returns all user-level processes, excluding kernel processes and threads
const struct kinfo_proc* processes = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &count);
if (!processes) {
kvm_close(kd);
return "kvm_getprocs() failed";
}
const uint32_t pageSize = instance.state.platform.sysinfo.pageSize;
for (int i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (!proc->p_uvalid) { // Zombie process; p_u* members are invalid
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, proc->p_comm);
item->pid = (uint32_t) proc->p_pid;
item->cpuTime = ((uint64_t) proc->p_uutime_sec * 1000 + (uint64_t) proc->p_uutime_usec / 1000) +
((uint64_t) proc->p_ustime_sec * 1000 + (uint64_t) proc->p_ustime_usec / 1000);
item->memBytes = (uint64_t) proc->p_vm_rssize * pageSize;
item->startTime = (uint64_t) proc->p_ustart_sec * 1000 + (uint64_t) proc->p_ustart_usec / 1000;
item->bytesRead = (uint64_t) proc->p_uru_inblock * DEV_BSIZE;
item->bytesWritten = (uint64_t) proc->p_uru_oublock * DEV_BSIZE;
}
kvm_close(kd);
return nullptr;
}
+73
View File
@@ -0,0 +1,73 @@
#include "top.h"
#include "common/io.h"
#include "common/strutil.h"
#include <stdio.h>
#include <procfs.h>
#include <sys/param.h> // DEV_BSIZE
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes showTypes) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/proc");
if (!dirp) {
return "opendir(\"/proc\") failed";
}
int procfd = dirfd(dirp);
struct dirent* dp;
while ((dp = readdir(dirp))) {
if (!ffCharIsDigit(dp->d_name[0])) { // Skip "." and ".."
continue;
}
FF_AUTO_CLOSE_FD int subfd = openat(procfd, dp->d_name, O_RDONLY | O_DIRECTORY);
if (subfd < 0) {
continue;
}
pstatus_t status;
if (ffReadFileDataRelative(subfd, "status", sizeof(status), &status) != (ssize_t) sizeof(status) ||
status.pr_flags & PR_ISSYS) {
continue; // The process may have exited, no permission or is a kernel process
}
// The data model of the returned structure depends on the data model of
// the calling process, not of the observed process.
psinfo_t psinfo;
if (ffReadFileDataRelative(subfd, "psinfo", sizeof(psinfo), &psinfo) != (ssize_t) sizeof(psinfo)) {
continue;
}
// zombies
if (psinfo.pr_lwp.pr_sname == 'Z') {
continue;
}
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
ffStrbufInitS(&item->name, psinfo.pr_fname);
item->pid = (uint32_t) psinfo.pr_pid;
item->cpuTime = (uint64_t) psinfo.pr_time.tv_sec * 1000 +
(uint64_t) psinfo.pr_time.tv_nsec / 1000000;
item->memBytes = (uint64_t) psinfo.pr_rssize * 1024;
item->startTime = (uint64_t) psinfo.pr_start.tv_sec * 1000 +
(uint64_t) psinfo.pr_start.tv_nsec / 1000000;
item->bytesRead = 0;
item->bytesWritten = 0;
if (showTypes & FF_TOP_TYPE_DISK) {
prusage_t usage;
if (ffReadFileDataRelative(subfd, "usage", sizeof(usage), &usage) == (ssize_t) sizeof(usage)) {
item->bytesRead = (uint64_t) usage.pr_inblk * DEV_BSIZE;
item->bytesWritten = (uint64_t) usage.pr_oublk * DEV_BSIZE;
}
}
}
return nullptr;
}
+48
View File
@@ -0,0 +1,48 @@
#include "top.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h"
#include <ntstatus.h>
#include <windows.h>
#include <winternl.h>
const char* ffTopGetProcessSnapshot(FFlist* snapshots, FFTopTypes) {
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
// Multiple attempts in case processes change while
// we are in the middle of querying them.
ULONG size = 0;
for (int attempts = 0;; ++attempts) {
if (size) {
pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size);
assert(pstart);
}
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);
if (NT_SUCCESS(status)) {
break;
} else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) {
size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;
} else {
return "NtQuerySystemInformation(SystemProcessInformation) failed";
}
}
for (SYSTEM_PROCESS_INFORMATION* info = pstart;; info = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) info + info->NextEntryOffset)) {
uintptr_t pidValue = (uintptr_t) info->UniqueProcessId;
if (pidValue <= UINT32_MAX && info->CreateTime.QuadPart > 0) {
FFTopProcessSnapshot* item = FF_LIST_ADD(FFTopProcessSnapshot, *snapshots);
item->pid = (uint32_t) pidValue;
item->startTime = (uint64_t) info->CreateTime.QuadPart;
item->cpuTime = ((uint64_t) info->UserTime.QuadPart + (uint64_t) info->KernelTime.QuadPart) / 10000u;
item->memBytes = (uint64_t) info->VirtualMemoryCounters.WorkingSetSize;
item->bytesRead = (uint64_t) info->IoCounters.ReadTransferCount;
item->bytesWritten = (uint64_t) info->IoCounters.WriteTransferCount;
ffStrbufInitNWS(&item->name, info->ImageName.Length / sizeof(wchar_t), info->ImageName.Buffer);
}
if (info->NextEntryOffset == 0) {
break;
}
}
return nullptr;
}
+14 -7
View File
@@ -11,16 +11,23 @@
const char* ffDetectWMPlugin(FFstrbuf* pluginName) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
u_int requestLength = ARRAY_SIZE(request);
size_t length;
size_t length = 0;
FF_AUTO_FREE struct kinfo_proc* processes = ffSysctlGetData(request, requestLength, &length);
if (processes == nullptr) {
return "sysctl(CTL_KERN, KERN_PROC, KERN_PROC_ALL) failed";
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
}
assert(length % sizeof(struct kinfo_proc) == 0);
for (size_t i = 0; i < length / sizeof(struct kinfo_proc); i++) {
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (size_t i = 0; i < count; i++) {
const struct kinfo_proc* proc = &processes[i];
if (proc->kp_eproc.e_ppid != 1) {
continue;
+2 -3
View File
@@ -43,9 +43,8 @@ static const char* getHyprland(FFstrbuf* result) {
FF_DEBUG("Detecting Hyprland version");
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
FF_DEBUG("Checking for " FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h"
" file");
if (ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/include/hyprland/src/version.h", result)) {
FF_DEBUG("Checking for " FF_PATH_PKG_BASE "/include/hyprland/src/version.h file");
if (ffReadFileBuffer(FF_PATH_PKG_BASE "/include/hyprland/src/version.h", result)) {
FF_DEBUG("Found version.h file, extracting version");
if (ffStrbufSubstrAfterFirstS(result, "\n#define GIT_TAG ")) {
ffStrbufSubstrAfterFirstC(result, '"');
+12
View File
@@ -633,6 +633,18 @@ static const FFlogo A[] = {
.colorTitle = FF_COLOR_FG_CYAN,
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_ARTIX2
// Artix2
{
.names = { "artix2" },
.lines = FASTFETCH_DATATEXT_LOGO_ARTIX2,
.colors = {
FF_COLOR_FG_CYAN,
},
.colorKeys = FF_COLOR_FG_CYAN,
.colorTitle = FF_COLOR_FG_CYAN,
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_ARTIX_SMALL
// ArtixSmall
{
+20 -20
View File
@@ -1,20 +1,20 @@
.:
.==:
.====:
.======:
.========:
.==========:
.============:
.-===========:
.:-========:
.-:. .:======:
.======:.. :-===.
.============:.. .:=.
.==================:..
.=======================:
.====================::. .
================::. :-===
============-:. .:========
========-:. :-==========
=====:. ..:-=====
=::. ..::
.:
.==:
.====:
.======:
.========:
.==========:
.============:
.-===========:
.:-========:
.-:. .:======:
.======:.. :-===.
.============:.. .:=.
.==================:..
.=======================:
.====================::. .
================::. :-===
============-:. .:========
========-:. :-==========
=====:. ..:-=====
=::. ..::
+13
View File
@@ -103,6 +103,19 @@ static const FFlogo M[] = {
.colorTitle = FF_COLOR_FG_GREEN,
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_MACOS_OLD
// MacOS_old
{
.names = { "macOS_old" },
.type = FF_LOGO_LINE_TYPE_ALTER_BIT,
.lines = FASTFETCH_DATATEXT_LOGO_MACOS_OLD,
.colors = {
FF_COLOR_FG_CYAN,
FF_COLOR_FG_BLUE,
FF_COLOR_FG_DEFAULT,
},
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_MAINSAILOS
// MainsailOS
{
+17
View File
@@ -0,0 +1,17 @@
$3@@
$3@@
$2((((((((((((((((((((($3@@$1******************
$2(((((((((((((((((((($3@@$1*******************
$2(((((((((($3%%$2((((((($3@@$1********$3@@$1**********
$2(((((((((($3%%$2(((((($3@@$1*********$3@@$1**********
$2((((((((((((((((($3@@$1**********************
$2(((((((((((((((($3@@$1***********************
$2((((((((((((((($3@@$1************************
$2(((((((((((((($3@@@@@@@@$1*******************
$2(((((((((((((((((((($3@@$1*******************
$2(((((((($3%%%%%%%%%%%@@@@@@@@@@@@@@$1********
$2(((((((((((((((((((($3@@$1*******************
$2((((((((((((((((((((($3@@$1******************
$3@@
@@
@@
+25
View File
@@ -37,6 +37,31 @@ static const FFlogo O[] = {
},
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY
// Omarchy
{
.names = { "Omarchy" },
.lines = FASTFETCH_DATATEXT_LOGO_OMARCHY,
.colors = {
FF_COLOR_FG_256 "149",
},
.colorKeys = FF_COLOR_FG_256 "149",
.colorTitle = FF_COLOR_FG_256 "111",
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY2
// Omarchy2
{
.names = { "Omarchy2" },
.lines = FASTFETCH_DATATEXT_LOGO_OMARCHY2,
.type = FF_LOGO_LINE_TYPE_ALTER_BIT,
.colors = {
FF_COLOR_FG_256 "149",
},
.colorKeys = FF_COLOR_FG_256 "149",
.colorTitle = FF_COLOR_FG_256 "111",
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_OMNIOS
// OmniOS
{
+15
View File
@@ -0,0 +1,15 @@
██████████████████████████████████
██ ██ ██
██ █████████████ ████ ██
██ ██ ██ ██
██ ██ ██ ██
██ ██ ██ ██
██ ██ ██ ██
███████ ██ ██
██ ██ ██ ██
██ ██ ██ ██
██ ██ ██ ██
██ ██ ██ ██
██ ████████████████████████ ██
██ ██ ██
██████████████████ █████████████
+10
View File
@@ -0,0 +1,10 @@
▄▄▄
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
███ █▀
+9 -12
View File
@@ -8,7 +8,7 @@ bool ffPrintLM(FFLMOptions* options) {
bool success = false;
FFLMResult result;
ffStrbufInit(&result.service);
ffStrbufInit(&result.type);
ffStrbufInit(&result.prettyName);
ffStrbufInit(&result.version);
const char* error = ffDetectLM(&result);
@@ -24,18 +24,15 @@ bool ffPrintLM(FFLMOptions* options) {
if (options->moduleArgs.outputFormat.length == 0) {
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(LM), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
ffStrbufWriteTo(&result.service, stdout);
ffStrbufWriteTo(&result.prettyName, stdout);
if (result.version.length) {
printf(" %s", result.version.chars);
}
if (result.type.length) {
printf(" (%s)", result.type.chars);
}
putchar('\n');
} else {
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(LM), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
FF_ARG(result.service, "service"),
FF_ARG(result.type, "type"),
FF_ARG(result.prettyName, "pretty-name"),
FF_ARG(result.version, "version"),
}));
}
@@ -43,7 +40,7 @@ bool ffPrintLM(FFLMOptions* options) {
exit:
ffStrbufDestroy(&result.service);
ffStrbufDestroy(&result.type);
ffStrbufDestroy(&result.prettyName);
ffStrbufDestroy(&result.version);
return success;
@@ -69,7 +66,7 @@ bool ffGenerateLMJsonResult([[maybe_unused]] FFLMOptions* options, yyjson_mut_do
bool success = false;
FFLMResult result;
ffStrbufInit(&result.service);
ffStrbufInit(&result.type);
ffStrbufInit(&result.prettyName);
ffStrbufInit(&result.version);
const char* error = ffDetectLM(&result);
@@ -85,13 +82,13 @@ bool ffGenerateLMJsonResult([[maybe_unused]] FFLMOptions* options, yyjson_mut_do
yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result");
yyjson_mut_obj_add_strbuf(doc, obj, "service", &result.service);
yyjson_mut_obj_add_strbuf(doc, obj, "type", &result.type);
yyjson_mut_obj_add_strbuf(doc, obj, "prettyName", &result.prettyName);
yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version);
success = true;
exit:
ffStrbufDestroy(&result.service);
ffStrbufDestroy(&result.type);
ffStrbufDestroy(&result.prettyName);
ffStrbufDestroy(&result.version);
return success;
@@ -137,8 +134,8 @@ FFModuleBaseInfo ffLMModuleInfo = {
.generateJsonResult = (void*) ffGenerateLMJsonResult,
.generateJsonConfig = (void*) ffGenerateLMJsonConfig,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
{ "LM service", "service" },
{ "LM type", "type" },
{ "LM service / process name", "service" },
{ "LM pretty name", "pretty-name" },
{ "LM version", "version" },
})),
.defaultOrder = 20,
+3
View File
@@ -271,6 +271,9 @@ static FFModuleBaseInfo* T[] = {
#if !FF_MODULE_DISABLE_TITLE
&ffTitleModuleInfo,
#endif
#if !FF_MODULE_DISABLE_TOP
&ffTopModuleInfo,
#endif
#if !FF_MODULE_DISABLE_THEME
&ffThemeModuleInfo,
#endif
+1
View File
@@ -64,6 +64,7 @@
#include "modules/terminaltheme/terminaltheme.h"
#include "modules/theme/theme.h"
#include "modules/title/title.h"
#include "modules/top/top.h"
#include "modules/tpm/tpm.h"
#include "modules/uptime/uptime.h"
#include "modules/users/users.h"
+1
View File
@@ -64,6 +64,7 @@
#include "modules/terminaltheme/option.h"
#include "modules/theme/option.h"
#include "modules/title/option.h"
#include "modules/top/option.h"
#include "modules/tpm/option.h"
#include "modules/uptime/option.h"
#include "modules/users/option.h"
+2
View File
@@ -4,6 +4,8 @@
typedef struct FFProcessesOptions {
FFModuleArgs moduleArgs;
bool countKprocs;
} FFProcessesOptions;
static_assert(sizeof(FFProcessesOptions) <= FF_OPTION_MAX_SIZE, "FFProcessesOptions size exceeds maximum allowed size");
+18 -8
View File
@@ -5,8 +5,8 @@
#include "modules/processes/processes.h"
bool ffPrintProcesses(FFProcessesOptions* options) {
uint32_t numProcesses = 0;
const char* error = ffDetectProcesses(&numProcesses);
FFProcessesResult result = {};
const char* error = ffDetectProcesses(options, &result);
if (error) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
@@ -16,9 +16,9 @@ bool ffPrintProcesses(FFProcessesOptions* options) {
if (options->moduleArgs.outputFormat.length == 0) {
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
printf("%u\n", numProcesses);
printf("%u (%u threads)\n", result.processes, result.threads);
} else {
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(numProcesses, "result") }));
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.processes, "result") }));
}
return true;
@@ -32,30 +32,40 @@ void ffParseProcessesJsonObject(FFProcessesOptions* options, yyjson_val* module)
continue;
}
if (unsafe_yyjson_equals_str(key, "countKprocs")) {
options->countKprocs = yyjson_get_bool(val);
continue;
}
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
}
}
void ffGenerateProcessesJsonConfig(FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
yyjson_mut_obj_add_bool(doc, module, "countKprocs", options->countKprocs);
}
bool ffGenerateProcessesJsonResult([[maybe_unused]] FFProcessesOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
uint32_t result;
const char* error = ffDetectProcesses(&result);
FFProcessesResult result = {};
const char* error = ffDetectProcesses(options, &result);
if (error) {
yyjson_mut_obj_add_str(doc, module, "error", error);
return false;
}
yyjson_mut_obj_add_uint(doc, module, "result", result);
yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result");
yyjson_mut_obj_add_uint(doc, obj, "processes", result.processes);
yyjson_mut_obj_add_uint(doc, obj, "threads", result.threads);
return true;
}
void ffInitProcessesOptions(FFProcessesOptions* options) {
ffOptionInitModuleArg(&options->moduleArgs, "");
options->countKprocs = false;
}
void ffDestroyProcessesOptions(FFProcessesOptions* options) {
@@ -64,7 +74,7 @@ void ffDestroyProcessesOptions(FFProcessesOptions* options) {
FFModuleBaseInfo ffProcessesModuleInfo = {
.name = "Processes",
.description = "Print number of running processes",
.description = "Print number of running processes and threads",
.displayName = {
.en = "Processes",
.ar = "العمليات",
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "common/option.h"
#include "common/percent.h"
typedef enum FFTopTypes: uint8_t {
FF_TOP_TYPE_CPU = 1 << 0,
FF_TOP_TYPE_MEMORY = 1 << 1,
FF_TOP_TYPE_DISK_READ = 1 << 2,
FF_TOP_TYPE_DISK_WRITE = 1 << 3,
FF_TOP_TYPE_DISK = FF_TOP_TYPE_DISK_READ | FF_TOP_TYPE_DISK_WRITE,
} FFTopTypes;
typedef struct FFTopOptions {
FFModuleArgs moduleArgs;
FFTopTypes sort;
FFTopTypes showTypes; // bitfields
uint32_t nProcesses;
uint32_t waitTime;
FFPercentageModuleConfig percent;
bool compact;
} FFTopOptions;
static_assert(sizeof(FFTopOptions) <= FF_OPTION_MAX_SIZE, "FFTopOptions size exceeds maximum allowed size");
+286
View File
@@ -0,0 +1,286 @@
#include "common/jsonconfig.h"
#include "common/percent.h"
#include "common/printing.h"
#include "common/size.h"
#include "detection/top/top.h"
#include "modules/top/top.h"
static void printTopResult(FFTopOptions* options, uint32_t index, uint32_t total, FFTopProcessResult* process) {
FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type;
if (options->moduleArgs.outputFormat.length == 0) {
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), total == 1 ? 0 : (uint8_t) (index + 1), &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
FF_STRBUF_AUTO_DESTROY output = ffStrbufCreate();
ffStrbufAppendF(&output, "%s (%u)", process->name.chars, process->pid);
if ((options->showTypes & FF_TOP_TYPE_CPU) && (percentType & FF_PERCENTAGE_TYPE_NUM_BIT)) {
ffStrbufAppendS(&output, " - CPU ");
ffPercentAppendNum(&output, process->cpuPercent, options->percent, false, &options->moduleArgs);
}
if (!(percentType & FF_PERCENTAGE_TYPE_HIDE_OTHERS_BIT)) {
if (options->showTypes & FF_TOP_TYPE_MEMORY) {
ffStrbufAppendS(&output, " - MEM ");
ffSizeAppendNum(process->memBytes, &output);
}
if (options->showTypes & FF_TOP_TYPE_DISK) {
ffStrbufAppendS(&output, " - DSK ");
ffSizeAppendNum(process->bytesRead, &output);
ffStrbufAppendS(&output, "/s / ");
ffSizeAppendNum(process->bytesWritten, &output);
ffStrbufAppendS(&output, "/s");
}
}
ffStrbufPutTo(&output, stdout);
} else {
FF_STRBUF_AUTO_DESTROY cpuFormatted = ffStrbufCreate();
if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) {
ffPercentAppendNum(&cpuFormatted, process->cpuPercent, options->percent, false, &options->moduleArgs);
}
FF_STRBUF_AUTO_DESTROY memFormatted = ffStrbufCreate();
ffSizeAppendNum(process->memBytes, &memFormatted);
FF_STRBUF_AUTO_DESTROY diskReadFormatted = ffStrbufCreate();
ffSizeAppendNum(process->bytesRead, &diskReadFormatted);
ffStrbufAppendS(&diskReadFormatted, "/s");
FF_STRBUF_AUTO_DESTROY diskWriteFormatted = ffStrbufCreate();
ffSizeAppendNum(process->bytesWritten, &diskWriteFormatted);
ffStrbufAppendS(&diskWriteFormatted, "/s");
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Top), (uint8_t) index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){
FF_ARG(process->name, "name"),
FF_ARG(process->pid, "pid"),
FF_ARG(process->cpuPercent, "cpu"),
FF_ARG(process->memBytes, "mem"),
FF_ARG(process->bytesRead, "disk-read"),
FF_ARG(process->bytesWritten, "disk-write"),
FF_ARG(cpuFormatted, "cpu-percentage"),
FF_ARG(memFormatted, "mem-formatted"),
FF_ARG(diskReadFormatted, "disk-read-formatted"),
FF_ARG(diskWriteFormatted, "disk-write-formatted"),
}));
}
}
bool ffPrintTop(FFTopOptions* options) {
FF_LIST_AUTO_DESTROY results = ffListCreate();
const char* error = ffDetectTopProcesses(options, &results);
if (error) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
return false;
}
if (!options->compact) {
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
printTopResult(options, (uint32_t) (process - (FFTopProcessResult*) results.data), results.length, process);
}
} else {
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
ffStrbufWriteTo(&process->name, stdout);
if ((void*) process != results.data) {
putchar(' ');
}
}
}
FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
ffStrbufDestroy(&item->name);
}
return true;
}
void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
yyjson_val *key, *val;
size_t idx, max;
yyjson_obj_foreach (module, idx, max, key, val) {
if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) {
continue;
}
if (unsafe_yyjson_equals_str(key, "sort")) {
int value;
const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){
{ "cpu", FF_TOP_TYPE_CPU },
{ "memory", FF_TOP_TYPE_MEMORY },
{ "disk-read", FF_TOP_TYPE_DISK_READ },
{ "disk-write", FF_TOP_TYPE_DISK_WRITE },
{},
});
if (error) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
} else {
options->sort = (FFTopTypes) value;
}
continue;
} else if (unsafe_yyjson_equals_str(key, "showTypes")) {
if (yyjson_is_arr(val)) {
options->showTypes = 0;
yyjson_val* item;
size_t aidx, amax;
yyjson_arr_foreach (val, aidx, amax, item) {
int value;
const char* error = ffJsonConfigParseEnum(item, &value, (FFKeyValuePair[]){
{ "cpu", FF_TOP_TYPE_CPU },
{ "memory", FF_TOP_TYPE_MEMORY },
{ "disk", FF_TOP_TYPE_DISK },
{},
});
if (error == nullptr) {
options->showTypes |= (FFTopTypes) value;
}
if (error) {
options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY | FF_TOP_TYPE_DISK;
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
break;
}
}
} else {
int value;
const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]){
{ "cpu", FF_TOP_TYPE_CPU },
{ "memory", FF_TOP_TYPE_MEMORY },
{ "disk", FF_TOP_TYPE_DISK },
{},
});
if (error == nullptr) {
options->showTypes = (FFTopTypes) value;
}
if (error) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Invalid %s value: %s", unsafe_yyjson_get_str(key), error);
}
}
continue;
} else if (unsafe_yyjson_equals_str(key, "processes")) {
options->nProcesses = (uint32_t) yyjson_get_uint(val);
} else if (unsafe_yyjson_equals_str(key, "waitTime")) {
options->waitTime = (uint32_t) yyjson_get_uint(val);
} else if (unsafe_yyjson_equals_str(key, "compact")) {
options->compact = yyjson_get_bool(val);
} else if (ffPercentParseJsonObject(key, val, &options->percent)) {
continue;
} else {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Top), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key));
}
}
}
void ffGenerateTopJsonConfig(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
yyjson_mut_obj_add_str(doc, module, "type", "top");
yyjson_mut_val* showTypes = yyjson_mut_arr(doc);
if (options->showTypes & FF_TOP_TYPE_CPU) {
yyjson_mut_arr_add_str(doc, showTypes, "cpu");
}
if (options->showTypes & FF_TOP_TYPE_MEMORY) {
yyjson_mut_arr_add_str(doc, showTypes, "memory");
}
if (options->showTypes & FF_TOP_TYPE_DISK) {
yyjson_mut_arr_add_str(doc, showTypes, "disk");
}
yyjson_mut_obj_add_val(doc, module, "showTypes", showTypes);
if (options->sort == FF_TOP_TYPE_CPU) {
yyjson_mut_obj_add_str(doc, module, "sort", "cpu");
} else if (options->sort == FF_TOP_TYPE_MEMORY) {
yyjson_mut_obj_add_str(doc, module, "sort", "memory");
} else if (options->sort == FF_TOP_TYPE_DISK_READ) {
yyjson_mut_obj_add_str(doc, module, "sort", "disk-read");
} else if (options->sort == FF_TOP_TYPE_DISK_WRITE) {
yyjson_mut_obj_add_str(doc, module, "sort", "disk-write");
}
yyjson_mut_obj_add_uint(doc, module, "processes", options->nProcesses);
yyjson_mut_obj_add_uint(doc, module, "waitTime", options->waitTime);
yyjson_mut_obj_add_bool(doc, module, "compact", options->compact);
ffPercentGenerateJsonConfig(doc, module, options->percent);
}
bool ffGenerateTopJsonResult(FFTopOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
FF_LIST_AUTO_DESTROY results = ffListCreate();
const char* error = ffDetectTopProcesses(options, &results);
if (error) {
yyjson_mut_obj_add_str(doc, module, "error", error);
return false;
}
yyjson_mut_val* array = yyjson_mut_obj_add_arr(doc, module, "result");
FF_LIST_FOR_EACH (FFTopProcessResult, process, results) {
yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, array);
yyjson_mut_obj_add_strbuf(doc, item, "name", &process->name);
yyjson_mut_obj_add_uint(doc, item, "pid", process->pid);
if (options->showTypes & FF_TOP_TYPE_CPU) {
yyjson_mut_obj_add_real(doc, item, "cpuPercent", process->cpuPercent);
}
if (options->showTypes & FF_TOP_TYPE_MEMORY) {
yyjson_mut_obj_add_uint(doc, item, "mem", process->memBytes);
}
if (options->showTypes & FF_TOP_TYPE_DISK) {
yyjson_mut_obj_add_uint(doc, item, "bytesRead", process->bytesRead);
yyjson_mut_obj_add_uint(doc, item, "bytesWritten", process->bytesWritten);
}
yyjson_mut_obj_add_uint(doc, item, "startTime", process->startTime);
}
FF_LIST_FOR_EACH (FFTopProcessResult, item, results) {
ffStrbufDestroy(&item->name);
}
return true;
}
void ffInitTopOptions(FFTopOptions* options) {
ffOptionInitModuleArg(&options->moduleArgs, "󰍛");
options->sort = FF_TOP_TYPE_CPU;
options->showTypes = FF_TOP_TYPE_CPU | FF_TOP_TYPE_MEMORY
#if !__GNU__ && !__HAIKU__
| FF_TOP_TYPE_DISK
#endif
;
options->nProcesses = 5;
options->waitTime = 500;
options->compact = false;
options->percent = (FFPercentageModuleConfig){ 50, 80, 0 };
}
void ffDestroyTopOptions(FFTopOptions* options) {
ffOptionDestroyModuleArg(&options->moduleArgs);
}
FFModuleBaseInfo ffTopModuleInfo = {
.name = "Top",
.description = "Print processes with the highest CPU, memory or disk I/O usage",
.displayName = {
.en = "Top Processes",
.ar = "أعلى العمليات",
.cs = "Nejvytíženější procesy",
.de = "Top-Prozesse",
.es = "Procesos principales",
.fr = "Processus principaux",
.gl = "Procesos principais",
.he = "התהליכים המובילים",
.id = "Proses teratas",
.it = "Processi principali",
.ja = "上位プロセス",
.ko = "상위 프로세스",
.pl = "Najaktywniejsze procesy",
.pt = "Processos principais",
.ru = "Топ процессов",
.tr = "En yoğun işlemler",
.uk = "Найактивніші процеси",
.vi = "Tiến trình hàng đầu",
.zh_CN = "进程排行",
.zh_TW = "進程排行",
},
.initOptions = (void*) ffInitTopOptions,
.destroyOptions = (void*) ffDestroyTopOptions,
.parseJsonObject = (void*) ffParseTopJsonObject,
.printModule = (void*) ffPrintTop,
.generateJsonResult = (void*) ffGenerateTopJsonResult,
.generateJsonConfig = (void*) ffGenerateTopJsonConfig,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]){
{ "Process name", "name" },
{ "Executable path", "path" },
{ "Process ID", "pid" },
{ "CPU usage", "cpu" },
{ "Memory usage (RSS) in bytes", "mem" },
{ "Disk read bytes per second (0 if unsupported)", "disk-read" },
{ "Disk write bytes per second (0 if unsupported)", "disk-write" },
{ "CPU usage percentage", "cpu-percentage" },
{ "Memory usage (RSS) formatted", "mem-formatted" },
{ "Disk read formatted", "disk-read-formatted" },
{ "Disk write formatted", "disk-write-formatted" },
})),
.defaultOrder = 36,
};
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "option.h"
void ffPrepareTopProcesses(FFTopTypes showTypes);
bool ffPrintTop(FFTopOptions* options);
void ffInitTopOptions(FFTopOptions* options);
void ffDestroyTopOptions(FFTopOptions* options);
extern FFModuleBaseInfo ffTopModuleInfo;