| mcloc(1) | FreeBSD General Commands Manual | mcloc(1) |
mcloc —
mcloc |
[-C] [-E
extension(s)] [-L
language(s)] [-S
scanner(s)] [-d |
-e | -l |
-s] [-g]
[-j numThreads]
[-o] [-q]
[-a | -r]
path(s) |
The count is rough because the speed of processing is more important to me than high accuracy. For high accuracy, we'd have to fully parse the source code, which is a non-trivial activity (slow). The whole point of mcloc(1) is quick assessments of source code size, mainly to give a developer a sense of the effort required to maintain a particular set of source code files. It's particularly useful in refactoring efforts, where reduction of source code size may be a stated objective.
-C-E
extension(s)-L
language(s)-S
scanners(s)-a-d-e-l-s-g-j
numThreadsThe default thread count for parsing source files is the concurrency reported by std::thread::hardware_concurrency().
-o-q-rThe second column of a data row contains the lines of code counter.
The third column of a data row contains the comment counter.
The fourth column contains the row's label. This may be a path to a directory, a filename, a file extension, a language, a scanner, or TOTAL.
See EXAMPLES for example output.
Below is a table showing lines/second processed on a few machines in my home.
| CPU | I/O | O/S | lines/sec |
| Threadripper 3960X | NVMe (PCI 4.0 x4) | Ubuntu 20.04 | 20,000,000+ |
| Core i9 2.9GHz | NVMe (PCI 3.0 x4) | MacOS Mojave | 3,000,000+ |
| E3-1270 V2 3.50GHz | SATA SSD | FreeBSD 11.3 | 2,700,000 |
| dual Xeon L5640 | SATA SSD | FreeBSD 11.3 | 4,000,000+ |
| Core i5 2405S 2.50GHz | SATA SSD | FreeBSD 11.3 | 1,500,000 |
| ARM Cortex-A72 1.5GHz | microSD | Raspbian 10 | 900,000 |
I find these numbers sufficient for my own use. Much of the time I'm looking at a smaller code base or a smaller piece of a larger code base. Usually less than 200,000 lines of code.
% mcloc . CODE COMMENTS PATH D 3431 2854 . F 24 52 DwmMclocAdaFileScanner.hh F 40 42 DwmMclocAdaFileScanner.lex F 24 52 DwmMclocClojureFileScanner.hh F 101 42 DwmMclocClojureFileScanner.lex F 70 83 DwmMclocCodeCounter.hh F 99 72 DwmMclocCodeCounterMap.hh F 155 82 DwmMclocCodeUtils.cc F 25 74 DwmMclocCodeUtils.hh F 232 84 DwmMclocConfig.cc F 66 140 DwmMclocConfig.hh F 46 39 DwmMclocConfigLex.lex F 140 43 DwmMclocConfigParse.y F 24 54 DwmMclocCppFileScanner.hh F 83 44 DwmMclocCppFileScanner.lex F 24 52 DwmMclocCssFileScanner.hh F 62 42 DwmMclocCssFileScanner.lex F 24 52 DwmMclocDFileScanner.hh F 71 43 DwmMclocDFileScanner.lex F 24 52 DwmMclocErlangFileScanner.hh F 40 42 DwmMclocErlangFileScanner.lex F 16 50 DwmMclocFileScanner.hh F 24 52 DwmMclocFlexFileScanner.hh F 94 47 DwmMclocFlexFileScanner.lex F 24 52 DwmMclocHaskellFileScanner.hh F 51 42 DwmMclocHaskellFileScanner.lex F 13 42 DwmMclocHowToSort.hh F 24 52 DwmMclocKotlinFileScanner.hh F 62 43 DwmMclocKotlinFileScanner.lex F 31 57 DwmMclocLanguage.cc F 27 42 DwmMclocLanguage.hh F 24 52 DwmMclocLispFileScanner.hh F 40 42 DwmMclocLispFileScanner.lex F 24 54 DwmMclocM4FileScanner.hh F 40 42 DwmMclocM4FileScanner.lex F 24 52 DwmMclocPythonFileScanner.hh F 59 42 DwmMclocPythonFileScanner.lex F 24 52 DwmMclocRubyFileScanner.hh F 50 42 DwmMclocRubyFileScanner.lex F 37 51 DwmMclocScanWorker.cc F 26 51 DwmMclocScanWorker.hh F 24 52 DwmMclocShellFileScanner.hh F 40 42 DwmMclocShellFileScanner.lex F 512 123 DwmMclocSourceCollection.cc F 103 105 DwmMclocSourceCollection.hh F 40 67 DwmMclocSourceFileInfo.hh F 55 53 DwmMclocTerminalTricks.cc F 71 64 DwmMclocTerminalTricks.hh F 225 146 DwmThreadQueue.hh F 17 1 configure.ac F 220 54 mcloc.cc F 36 2 mcloc.m4 D 58 0 ./docs/man F 58 0 mcloc.css 3491 2862 TOTAL (0.002729 secs, 2,327,958 lines/sec, 672% cpu)
What if I include generated source files and sort by size? Note how in this case, the flex-generated code (which we never modify) is 10 times more code than all of the code we need to maintain for mcloc(1). This is a case where it's clearly misleading to include generated code in a lines-of-code metric.
% mcloc -a -g . CODE COMMENTS PATH D 58 0 ./docs/man F 58 0 mcloc.css D 45547 10507 . F 13 42 DwmMclocHowToSort.hh F 16 50 DwmMclocFileScanner.hh F 17 1 configure.ac F 24 52 DwmMclocShellFileScanner.hh F 24 52 DwmMclocClojureFileScanner.hh F 24 54 DwmMclocCppFileScanner.hh F 24 52 DwmMclocCssFileScanner.hh F 24 52 DwmMclocKotlinFileScanner.hh F 24 52 DwmMclocAdaFileScanner.hh F 24 52 DwmMclocDFileScanner.hh F 24 52 DwmMclocFlexFileScanner.hh F 24 52 DwmMclocLispFileScanner.hh F 24 52 DwmMclocRubyFileScanner.hh F 24 52 DwmMclocHaskellFileScanner.hh F 24 52 DwmMclocErlangFileScanner.hh F 24 54 DwmMclocM4FileScanner.hh F 24 52 DwmMclocPythonFileScanner.hh F 25 74 DwmMclocCodeUtils.hh F 26 51 DwmMclocScanWorker.hh F 27 42 DwmMclocLanguage.hh F 31 57 DwmMclocLanguage.cc F 36 2 mcloc.m4 F 37 51 DwmMclocScanWorker.cc F 40 42 DwmMclocErlangFileScanner.lex F 40 42 DwmMclocLispFileScanner.lex F 40 67 DwmMclocSourceFileInfo.hh F 40 42 DwmMclocM4FileScanner.lex F 40 42 DwmMclocShellFileScanner.lex F 40 42 DwmMclocAdaFileScanner.lex F 46 39 DwmMclocConfigLex.lex G 47 42 DwmMclocConfigParse.hh (DwmMclocConfigParse.y) F 50 42 DwmMclocRubyFileScanner.lex F 51 42 DwmMclocHaskellFileScanner.lex F 55 53 DwmMclocTerminalTricks.cc F 59 42 DwmMclocPythonFileScanner.lex F 62 42 DwmMclocCssFileScanner.lex F 62 43 DwmMclocKotlinFileScanner.lex F 66 140 DwmMclocConfig.hh F 70 83 DwmMclocCodeCounter.hh F 71 64 DwmMclocTerminalTricks.hh F 71 43 DwmMclocDFileScanner.lex F 83 44 DwmMclocCppFileScanner.lex F 94 47 DwmMclocFlexFileScanner.lex F 99 72 DwmMclocCodeCounterMap.hh F 101 42 DwmMclocClojureFileScanner.lex F 103 105 DwmMclocSourceCollection.hh F 140 43 DwmMclocConfigParse.y F 155 82 DwmMclocCodeUtils.cc F 220 54 mcloc.cc F 225 146 DwmThreadQueue.hh F 232 84 DwmMclocConfig.cc F 512 123 DwmMclocSourceCollection.cc G 1161 391 DwmMclocConfigParse.cc (DwmMclocConfigParse.y) G 1384 448 DwmMclocConfigLex.cc (DwmMclocConfigLex.lex) G 1733 478 DwmMclocLispFileScanner.cc (DwmMclocLispFileScanner.lex) G 1733 478 DwmMclocErlangFileScanner.cc (DwmMclocErlangFileScanner.lex) G 1733 478 DwmMclocShellFileScanner.cc (DwmMclocShellFileScanner.lex) G 1761 478 DwmMclocAdaFileScanner.cc (DwmMclocAdaFileScanner.lex) G 1789 478 DwmMclocM4FileScanner.cc (DwmMclocM4FileScanner.lex) G 2250 482 DwmMclocHaskellFileScanner.cc (DwmMclocHaskellFileScanner.lex) G 2348 482 DwmMclocRubyFileScanner.cc (DwmMclocRubyFileScanner.lex) G 2551 483 DwmMclocPythonFileScanner.cc (DwmMclocPythonFileScanner.lex) G 2617 483 DwmMclocCssFileScanner.cc (DwmMclocCssFileScanner.lex) G 2793 485 DwmMclocKotlinFileScanner.cc (DwmMclocKotlinFileScanner.lex) G 3192 488 DwmMclocDFileScanner.cc (DwmMclocDFileScanner.lex) G 3856 489 DwmMclocClojureFileScanner.cc (DwmMclocClojureFileScanner.lex) G 4553 498 DwmMclocFlexFileScanner.cc (DwmMclocFlexFileScanner.lex) G 6615 492 DwmMclocCppFileScanner.cc (DwmMclocCppFileScanner.lex) 45605 10507 TOTAL (0.002858 secs, 19,636,808 lines/sec, 640% cpu)
What about per file extension, sorted by lines of code?
% mcloc -ae . CODE COMMENTS EXTENSION/EXPRESSION E 2 8 .d E 17 1 .ac E 36 2 .m4 E 58 0 .css E 63 2 "Makefile" E 206 43 .y E 337 111 ".+\-sh" E 481 1 ".+\.[1-9]" E 958 719 .lex E 1,265 1,843 .hh E 1,410 536 .cc 4,833 3,266 TOTAL (2.60ms, 3.12M lines/s, 166% sys 485% usr)
How many lines of code per language in libDwm, sorted by lines of code?
% mcloc -al libDwm/trunk CODE COMMENTS LANGUAGE L 217 8 CSS L 323 0 man L 439 111 Shell L 593 19 Make L 698 58 m4 L 880 308 flex L 1,061 102 bison L 34,307 22,730 C++ 38,518 23,336 TOTAL (9.65ms, 6.41M lines/s, 219% sys 141% usr)
How many lines of code per language in qt5?
% mcloc -a -l ~/gits/qt5 CODE COMMENTS LANGUAGE L 222 51 Scheme L 351 58 Swift L 662 275 Ruby L 1,116 47 D L 4,057 160 flex L 7,887 2,804 Make L 10,098 8,415 PHP L 12,155 904 bison L 16,257 2,564 Go L 40,095 466 man L 42,749 524 Lisp L 53,406 18,938 Shell L 54,689 5,720 CSS L 58,464 13,021 C# L 64,504 2,531 m4 L 130,627 42,713 Java L 233,967 42,445 Objective-C L 475,669 187,102 Python L 492,351 2,086 TypeScript L 765,125 0 JSON L 1,418,368 1,077,973 JavaScript L 7,807,396 2,711,326 C L 13,029,190 2,361,738 C++ 24,719,405 6,481,861 TOTAL (3.39s, 9.20M lines/s, 258% sys 153% usr)
How many lines of code in each directory in libDwm?
% mcloc -d libDwm/trunk CODE COMMENTS DIR D 1,191 169 libDwm/trunk/ D 16 0 libDwm/trunk/apps D 112 44 libDwm/trunk/apps/dwmnet D 121 60 libDwm/trunk/apps/dwmspath D 256 56 libDwm/trunk/apps/dwmwhat D 191 51 libDwm/trunk/apps/fbsddeps D 47 43 libDwm/trunk/apps/fixcflags D 276 54 libDwm/trunk/apps/mcping D 537 111 libDwm/trunk/apps/mkfbsdmnfst D 139 50 libDwm/trunk/apps/psg D 239 8 libDwm/trunk/doc D 176 13 libDwm/trunk/doc/examples D 8,342 11,515 libDwm/trunk/include D 12 0 libDwm/trunk/packaging D 16,486 6,996 libDwm/trunk/src D 10,377 4,166 /Users/dwm/svn/dwm/libDwm/trunk/tests 38,518 23,336 TOTAL (2.65ms, 23.4M lines/s, 455% sys 450% usr)
mcloc(1) was updated with relatively minor tweaks throughout the 2000's and 2010's.
In 2019 and early 2020, I rewrote mcloc(1) to use a reentrant lexical analyzer and allow multiple threads. With today's solid state storage, multiple threads can be an advantage (we're not always I/O bound). I also restructured to use a two-level storage (directories and files within a directory) and std::unordered_map. The original std::map based implementation wasn't terribly slow, but the way I used it (full paths as keys) meant longer keys versus the two-level scheme and no fast way to calculate numbers per directory. These changes increased performance by roughly a factor of 3.
Since I'm now using std::filesystem, the code should be portable to Windows with a getopt() replacement (I haven't tried yet).
I also added the ability to show lines of code and comments per file extension or per type, as well as the ability to sort by size. In addition, I added smarter code to recognize source code generated by flex(1) or bison(1); the current version recognizes comments and preprocessor directives in the generated code instead of relying on matching filenames. This allows me to ignore generated files even if I can't find the generator source code in the given directories, and doesn't require parsing flex and bison options in the generator sources nor peeking at build infrastructure. However, flex(1) and bison(1) don't produce anything in their output to indicate the source of the generated code if you disable #line directives. If you use the -L option of flex(1) or the -l option of bison(1), for example, mcloc(1) will still recognize the generated code as generated, but will not be able to name the source.
| January 18, 2020 | mcplex.net |