|
68 | 68 | #include <string> |
69 | 69 | #include <system_error> |
70 | 70 | #include <thread> |
| 71 | +#include <unordered_map> |
| 72 | +#include <unordered_set> |
71 | 73 | #include <utility> |
72 | 74 | #include <vector> |
73 | 75 |
|
74 | 76 | #include <fcntl.h> |
75 | 77 | #include <pwd.h> |
76 | 78 | #include <sys/mman.h> |
| 79 | +#include <sys/stat.h> |
77 | 80 | #include <unistd.h> |
78 | 81 |
|
79 | 82 | using namespace linglong::utils::error; |
@@ -283,6 +286,169 @@ bool delegateToContainerInit(const std::string &containerID, |
283 | 286 | return result == 0; |
284 | 287 | } |
285 | 288 |
|
| 289 | +struct ModuleSize |
| 290 | +{ |
| 291 | + std::uint64_t exclusiveSize{ 0 }; |
| 292 | + std::uint64_t sharedSize{ 0 }; |
| 293 | + std::uint64_t logicalSize{ 0 }; |
| 294 | + std::uint64_t actualSize{ 0 }; |
| 295 | +}; |
| 296 | + |
| 297 | +struct InodeUsage |
| 298 | +{ |
| 299 | + std::uint64_t diskUsage{ 0 }; |
| 300 | + std::unordered_set<std::size_t> modules; |
| 301 | +}; |
| 302 | + |
| 303 | +struct ModuleSizeCalculation |
| 304 | +{ |
| 305 | + std::vector<ModuleSize> moduleSizes; |
| 306 | + std::uint64_t actualTotalSize{ 0 }; |
| 307 | +}; |
| 308 | + |
| 309 | +Result<ModuleSizeCalculation> |
| 310 | +calculateModuleSizes(const std::vector<std::filesystem::path> &moduleDirs) noexcept |
| 311 | +{ |
| 312 | + LINGLONG_TRACE("calculate module sizes"); |
| 313 | + |
| 314 | + std::unordered_map<std::string, InodeUsage> inodeUsages; |
| 315 | + std::error_code ec; |
| 316 | + |
| 317 | + auto addEntry = [&](const std::filesystem::path &path, |
| 318 | + std::size_t moduleIndex) -> Result<void> { |
| 319 | + struct stat64 st{}; |
| 320 | + if (::lstat64(path.c_str(), &st) == -1) { |
| 321 | + const auto err = errno; |
| 322 | + return LINGLONG_ERR(fmt::format("failed to stat {}: {}", |
| 323 | + path, |
| 324 | + linglong::common::error::errorString(err))); |
| 325 | + } |
| 326 | + |
| 327 | + const auto diskUsage = static_cast<std::uint64_t>(st.st_blocks) * 512; |
| 328 | + const auto inodeKey = std::to_string(st.st_dev) + ":" + std::to_string(st.st_ino); |
| 329 | + |
| 330 | + auto &usage = inodeUsages[inodeKey]; |
| 331 | + usage.diskUsage = diskUsage; |
| 332 | + usage.modules.insert(moduleIndex); |
| 333 | + |
| 334 | + return LINGLONG_OK; |
| 335 | + }; |
| 336 | + |
| 337 | + for (std::size_t moduleIndex = 0; moduleIndex < moduleDirs.size(); ++moduleIndex) { |
| 338 | + const auto &dir = moduleDirs[moduleIndex]; |
| 339 | + auto addRoot = addEntry(dir, moduleIndex); |
| 340 | + if (!addRoot) { |
| 341 | + return LINGLONG_ERR(addRoot); |
| 342 | + } |
| 343 | + |
| 344 | + auto iterator = std::filesystem::recursive_directory_iterator{ |
| 345 | + dir, |
| 346 | + std::filesystem::directory_options::skip_permission_denied, |
| 347 | + ec |
| 348 | + }; |
| 349 | + if (ec) { |
| 350 | + return LINGLONG_ERR(fmt::format("failed to open module directory {}", dir), ec); |
| 351 | + } |
| 352 | + |
| 353 | + const auto end = std::filesystem::recursive_directory_iterator{}; |
| 354 | + while (iterator != end) { |
| 355 | + const auto &entry = *iterator; |
| 356 | + auto addResult = addEntry(entry.path(), moduleIndex); |
| 357 | + if (!addResult) { |
| 358 | + return LINGLONG_ERR(addResult); |
| 359 | + } |
| 360 | + |
| 361 | + iterator.increment(ec); |
| 362 | + if (ec) { |
| 363 | + return LINGLONG_ERR(fmt::format("failed to iterate module directory {}", dir), ec); |
| 364 | + } |
| 365 | + } |
| 366 | + } |
| 367 | + |
| 368 | + ModuleSizeCalculation calculation; |
| 369 | + calculation.moduleSizes.resize(moduleDirs.size()); |
| 370 | + for (const auto &[_, usage] : inodeUsages) { |
| 371 | + const auto moduleCount = usage.modules.size(); |
| 372 | + if (moduleCount == 0) { |
| 373 | + continue; |
| 374 | + } |
| 375 | + |
| 376 | + calculation.actualTotalSize += usage.diskUsage; |
| 377 | + const auto actualSize = usage.diskUsage / moduleCount; |
| 378 | + for (auto moduleIndex : usage.modules) { |
| 379 | + auto &moduleSize = calculation.moduleSizes[moduleIndex]; |
| 380 | + moduleSize.logicalSize += usage.diskUsage; |
| 381 | + if (moduleCount == 1) { |
| 382 | + moduleSize.exclusiveSize += usage.diskUsage; |
| 383 | + moduleSize.actualSize += usage.diskUsage; |
| 384 | + } else { |
| 385 | + moduleSize.sharedSize += usage.diskUsage; |
| 386 | + moduleSize.actualSize += actualSize; |
| 387 | + } |
| 388 | + } |
| 389 | + } |
| 390 | + |
| 391 | + return calculation; |
| 392 | +} |
| 393 | + |
| 394 | +Result<std::uint64_t> calculateRealDiskUsage(const std::filesystem::path &dir) noexcept |
| 395 | +{ |
| 396 | + LINGLONG_TRACE("calculate real disk usage"); |
| 397 | + |
| 398 | + std::uint64_t size{ 0 }; |
| 399 | + std::unordered_set<std::string> visitedInodes; |
| 400 | + |
| 401 | + auto addPath = [&](const std::filesystem::path &path) -> Result<void> { |
| 402 | + struct stat64 st{}; |
| 403 | + |
| 404 | + if (::lstat64(path.c_str(), &st) == -1) { |
| 405 | + const auto err = errno; |
| 406 | + return LINGLONG_ERR(fmt::format("failed to stat {}: {}", |
| 407 | + path, |
| 408 | + linglong::common::error::errorString(err))); |
| 409 | + } |
| 410 | + |
| 411 | + const auto inodeKey = std::to_string(st.st_dev) + ":" + std::to_string(st.st_ino); |
| 412 | + if (st.st_nlink > 1 && !visitedInodes.insert(inodeKey).second) { |
| 413 | + return LINGLONG_OK; |
| 414 | + } |
| 415 | + |
| 416 | + size += static_cast<std::uint64_t>(st.st_blocks) * 512; |
| 417 | + return LINGLONG_OK; |
| 418 | + }; |
| 419 | + |
| 420 | + auto rootResult = addPath(dir); |
| 421 | + if (!rootResult) { |
| 422 | + return LINGLONG_ERR(rootResult); |
| 423 | + } |
| 424 | + |
| 425 | + std::error_code ec; |
| 426 | + auto iterator = std::filesystem::recursive_directory_iterator{ |
| 427 | + dir, |
| 428 | + std::filesystem::directory_options::skip_permission_denied, |
| 429 | + ec |
| 430 | + }; |
| 431 | + if (ec) { |
| 432 | + return LINGLONG_ERR(fmt::format("failed to open repository directory {}", dir), ec); |
| 433 | + } |
| 434 | + |
| 435 | + const auto end = std::filesystem::recursive_directory_iterator{}; |
| 436 | + while (iterator != end) { |
| 437 | + const auto &entry = *iterator; |
| 438 | + auto result = addPath(entry.path()); |
| 439 | + if (!result) { |
| 440 | + return LINGLONG_ERR(result); |
| 441 | + } |
| 442 | + |
| 443 | + iterator.increment(ec); |
| 444 | + if (ec) { |
| 445 | + return LINGLONG_ERR(fmt::format("failed to iterate repository directory {}", dir), ec); |
| 446 | + } |
| 447 | + } |
| 448 | + |
| 449 | + return size; |
| 450 | +} |
| 451 | + |
286 | 452 | } // namespace |
287 | 453 |
|
288 | 454 | namespace linglong::cli { |
@@ -1679,6 +1845,107 @@ int Cli::list(const ListOptions &options) |
1679 | 1845 | return 0; |
1680 | 1846 | } |
1681 | 1847 |
|
| 1848 | +int Cli::size(const SizeOptions &options) |
| 1849 | +{ |
| 1850 | + auto repo = this->getRepo(); |
| 1851 | + if (!repo) { |
| 1852 | + this->printer.printErr(repo.error()); |
| 1853 | + return -1; |
| 1854 | + } |
| 1855 | + |
| 1856 | + auto items = (*repo)->listLayerItem(); |
| 1857 | + if (!items) { |
| 1858 | + this->printer.printErr(items.error()); |
| 1859 | + return -1; |
| 1860 | + } |
| 1861 | + |
| 1862 | + std::vector<Printer::ModuleSizeInfo> moduleSizes; |
| 1863 | + moduleSizes.reserve(items->size()); |
| 1864 | + std::vector<std::filesystem::path> modulePaths; |
| 1865 | + modulePaths.reserve(items->size()); |
| 1866 | + |
| 1867 | + for (const auto &item : *items) { |
| 1868 | + if (item.deleted.value_or(false)) { |
| 1869 | + continue; |
| 1870 | + } |
| 1871 | + |
| 1872 | + auto ref = package::Reference::fromPackageInfo(item.info); |
| 1873 | + if (!ref) { |
| 1874 | + this->printer.printErr(ref.error()); |
| 1875 | + return -1; |
| 1876 | + } |
| 1877 | + |
| 1878 | + auto layerDir = (*repo)->getLayerDir(*ref, item.info.packageInfoV2Module); |
| 1879 | + if (!layerDir) { |
| 1880 | + this->printer.printErr(layerDir.error()); |
| 1881 | + return -1; |
| 1882 | + } |
| 1883 | + |
| 1884 | + moduleSizes.push_back(Printer::ModuleSizeInfo{ |
| 1885 | + .id = item.info.id, |
| 1886 | + .name = item.info.name, |
| 1887 | + .version = item.info.version, |
| 1888 | + .channel = item.info.channel, |
| 1889 | + .module = item.info.packageInfoV2Module, |
| 1890 | + }); |
| 1891 | + modulePaths.push_back(layerDir->path()); |
| 1892 | + } |
| 1893 | + |
| 1894 | + auto calculatedSizes = calculateModuleSizes(modulePaths); |
| 1895 | + if (!calculatedSizes) { |
| 1896 | + this->printer.printErr(calculatedSizes.error()); |
| 1897 | + return -1; |
| 1898 | + } |
| 1899 | + |
| 1900 | + for (std::size_t index = 0; index < moduleSizes.size(); ++index) { |
| 1901 | + moduleSizes[index].exclusiveSize = calculatedSizes->moduleSizes.at(index).exclusiveSize; |
| 1902 | + moduleSizes[index].sharedSize = calculatedSizes->moduleSizes.at(index).sharedSize; |
| 1903 | + moduleSizes[index].logicalSize = calculatedSizes->moduleSizes.at(index).logicalSize; |
| 1904 | + moduleSizes[index].actualSize = calculatedSizes->moduleSizes.at(index).actualSize; |
| 1905 | + } |
| 1906 | + |
| 1907 | + const auto tieByName = [](const auto &info) { |
| 1908 | + return std::tie(info.id, info.channel, info.module, info.version); |
| 1909 | + }; |
| 1910 | + std::sort(moduleSizes.begin(), |
| 1911 | + moduleSizes.end(), |
| 1912 | + [&options, &tieByName](const auto &lhs, const auto &rhs) { |
| 1913 | + auto compareSize = [&options, &tieByName, &lhs, &rhs](std::uint64_t lhsSize, |
| 1914 | + std::uint64_t rhsSize) { |
| 1915 | + if (lhsSize == rhsSize) { |
| 1916 | + return tieByName(lhs) < tieByName(rhs); |
| 1917 | + } |
| 1918 | + |
| 1919 | + return options.ascending ? lhsSize < rhsSize : lhsSize > rhsSize; |
| 1920 | + }; |
| 1921 | + |
| 1922 | + if (options.sortBy == "id") { |
| 1923 | + return options.ascending ? tieByName(lhs) < tieByName(rhs) |
| 1924 | + : tieByName(rhs) < tieByName(lhs); |
| 1925 | + } |
| 1926 | + if (options.sortBy == "logical") { |
| 1927 | + return compareSize(lhs.logicalSize, rhs.logicalSize); |
| 1928 | + } |
| 1929 | + if (options.sortBy == "exclusive") { |
| 1930 | + return compareSize(lhs.exclusiveSize, rhs.exclusiveSize); |
| 1931 | + } |
| 1932 | + if (options.sortBy == "shared") { |
| 1933 | + return compareSize(lhs.sharedSize, rhs.sharedSize); |
| 1934 | + } |
| 1935 | + |
| 1936 | + return compareSize(lhs.actualSize, rhs.actualSize); |
| 1937 | + }); |
| 1938 | + |
| 1939 | + auto repoSize = calculateRealDiskUsage((*repo)->getRepoDir()); |
| 1940 | + if (!repoSize) { |
| 1941 | + this->printer.printErr(repoSize.error()); |
| 1942 | + return -1; |
| 1943 | + } |
| 1944 | + |
| 1945 | + this->printer.printModuleSizes(moduleSizes, calculatedSizes->actualTotalSize, *repoSize); |
| 1946 | + return 0; |
| 1947 | +} |
| 1948 | + |
1682 | 1949 | utils::error::Result<std::vector<api::types::v1::UpgradeListResult>> Cli::listUpgradable() |
1683 | 1950 | { |
1684 | 1951 | LINGLONG_TRACE("list upgradable"); |
|
0 commit comments