Skip to content

Instantly share code, notes, and snippets.

@david-saint
Last active February 3, 2026 07:09
Show Gist options
  • Select an option

  • Save david-saint/6e62006d9ec6f4aea11e64b23dcbcdb5 to your computer and use it in GitHub Desktop.

Select an option

Save david-saint/6e62006d9ec6f4aea11e64b23dcbcdb5 to your computer and use it in GitHub Desktop.
macOS Application and Support Storage Usage Calculator
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
type ItemInfo struct {
Path string
Size int64
}
func main() {
home := os.Getenv("HOME")
// Strategy 1: Find .app bundles recursively
appDirs := []string{
"/Applications",
filepath.Join(home, "Applications"),
}
// Strategy 2: Aggregate immediate subdirectories
supportDirs := []string{
filepath.Join(home, "Library", "Application Support"),
}
fmt.Println("Calculating storage usage... This might take a moment.")
start := time.Now()
var items []ItemInfo
var totalSize int64
var mu sync.Mutex
var wg sync.WaitGroup
// 1. Scan Applications folders
for _, dir := range appDirs {
wg.Add(1)
go func(root string) {
defer wg.Done()
if _, err := os.Stat(root); os.IsNotExist(err) {
return
}
// Walk looking for .app
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() && strings.HasSuffix(d.Name(), ".app") {
size, err := getDirSize(path)
if err != nil {
return nil
}
mu.Lock()
items = append(items, ItemInfo{Path: path, Size: size})
totalSize += size
mu.Unlock()
return filepath.SkipDir // Don't look inside the app bundle
}
return nil
})
}(dir)
}
// 2. Scan Support folders (Top-level aggregation)
for _, dir := range supportDirs {
wg.Add(1)
go func(root string) {
defer wg.Done()
entries, err := os.ReadDir(root)
if err != nil {
return
}
for _, entry := range entries {
if entry.Name() == ".DS_Store" {
continue
}
fullPath := filepath.Join(root, entry.Name())
var size int64
if entry.IsDir() {
size, _ = getDirSize(fullPath)
} else {
info, err := entry.Info()
if err == nil {
size = info.Size()
}
}
if size > 0 {
mu.Lock()
items = append(items, ItemInfo{Path: fullPath, Size: size})
totalSize += size
mu.Unlock()
}
}
}(dir)
}
wg.Wait()
// Sort by size (descending)
sort.Slice(items, func(i, j int) bool {
return items[i].Size > items[j].Size
})
fmt.Printf("\nFound %d items in %v\n", len(items), time.Since(start).Round(time.Millisecond))
fmt.Println("--------------------------------------------------------------------------------")
// Print top 25
limit := 25
if len(items) < limit {
limit = len(items)
}
for i := 0; i < limit; i++ {
fmt.Printf("% -10s %s\n", formatBytes(items[i].Size), items[i].Path)
}
if len(items) > limit {
fmt.Printf("... and %d more ...\n", len(items)-limit)
}
fmt.Println("--------------------------------------------------------------------------------")
fmt.Printf("Total Scanned Size: %s\n", formatBytes(totalSize))
}
func getDirSize(path string) (int64, error) {
var size int64
err := filepath.WalkDir(path, func(_ string, d fs.DirEntry, err error) error {
if err != nil {
return nil
}
if !d.IsDir() {
info, err := d.Info()
if err == nil {
size += info.Size()
}
}
return nil
})
return size, err
}
func formatBytes(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment