{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction\n", "In this blog post, I want to show you a nice complexity metric that works for most major programming languages that we use for our software systems – the **indentation-based complexity** metric. Adam Tornhill describes this metric in his fabulous book [Software Design X-Rays](https://pragprog.com/book/atevol/software-design-x-rays) on page 25 as follows:\n", " \n", "> With indentation-based complexity we count the leading tabs and whitespaces\n", "to convert them into logical indentations. ... This works because indentations in code carry meaning. Indentations\n", "are used to increase readability by separating code blocks from each other.\n", "\n", "He further says that the trend of the indentation-based complexity in combination with the lines of code metric a good indicator for complexity-growth. If the lines of code don't change but the indentation does, you got a complexity problem in your code.\n", "\n", "In this blog post, we are just interested in a static view of the indentation of a code (the evolution of the indentation-based complexity will be discussed in another blog post). I want to show you how you can spot complex areas in your application using the [Pandas data analysis framework](http://pandas.pydata.org/). As application under analysis, I choose the [Linux kernel project](https://github.com/torvalds/linux/) because we want to apply our analysis at big scale." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# The Idea\n", "\n", "This analysis works as follows:\n", "\n", "1. We search for relevant source code files using `glob`\n", "1. We read in the source code by hacking Pandas' `read_csv()` method\n", "1. We extract the preceding whitespaces/tabs in the code\n", "1. We calculate the ratio between the lines of code of a file and the indentation complexity\n", "1. We spot areas in the Linux kernel that are most complex\n", "1. We visualize the complete result with a treemap\n", "\n", "So let's start!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Analysis\n", "First, let's get all the files with a recursive file search by using `glob`. Because the Linux kernel code is mostly written in the C programming language, we search for the corresponding file endings `.c` (C program code files) and `.h` (header files)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:52.876Z" } }, "outputs": [ { "data": { "text/plain": [ "['../../linux\\\\arch\\\\alpha\\\\boot\\\\bootp.c',\n", " '../../linux\\\\arch\\\\alpha\\\\boot\\\\bootpz.c',\n", " '../../linux\\\\arch\\\\alpha\\\\boot\\\\main.c',\n", " '../../linux\\\\arch\\\\alpha\\\\boot\\\\misc.c',\n", " '../../linux\\\\arch\\\\alpha\\\\boot\\\\stdio.c']" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import glob\n", "file_list = glob.glob(\"../../linux/**/*.[c|h]\", recursive=True)\n", "file_list[:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read in in the content\n", "With the `file_list` at hand, we can now read in the content of the source code files. We could have used a standard Python `with as f` and read in the content with a `f.read()`, but we want to get the data as early into a Pandas' DataFrame (abbreviated \"df\") to leverage the power of the data analysis framework. \n", "\n", "For each path in our file list, we create a single DataFrame `content_df`. We use a line break `\\n` as column separator, ensuring that we create a single row for each source code line. We also specify the `encoding` parameter with the file encoding `latin-1` to make sure that we can read in weird file contents, too (this is totally normal when working in an international team). We also set `skip_blank_lines` to `False` to keep blank lines. Keeping the blank lines is necessary for debugging purposes (you can inspect certain source code lines with a text editor more easily) as well as for later analysis.\n", "\n", "The process of reading in all the source code could take a while for the thousands of files of the Linux kernel. After looping through and reading in all the data, we concatenate all DataFrames with the `concat` method. This gives us a single DataFrame with all source code content for all source code files." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:37.301239Z" }, "code_folding": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathline
0../../linux\\arch\\alpha\\boot\\bootp.c/*
1../../linux\\arch\\alpha\\boot\\bootp.c* arch/alpha/boot/bootp.c
2../../linux\\arch\\alpha\\boot\\bootp.c*
3../../linux\\arch\\alpha\\boot\\bootp.c* Copyright (C) 1997 Jay Estabrook
4../../linux\\arch\\alpha\\boot\\bootp.c*
\n", "
" ], "text/plain": [ " filepath line\n", "0 ../../linux\\arch\\alpha\\boot\\bootp.c /*\n", "1 ../../linux\\arch\\alpha\\boot\\bootp.c * arch/alpha/boot/bootp.c\n", "2 ../../linux\\arch\\alpha\\boot\\bootp.c *\n", "3 ../../linux\\arch\\alpha\\boot\\bootp.c * Copyright (C) 1997 Jay Estabrook\n", "4 ../../linux\\arch\\alpha\\boot\\bootp.c *" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandas as pd\n", "\n", "content_dfs = []\n", "\n", "for path in file_list:\n", " \n", " content_df = pd.read_csv(\n", " path,\n", " encoding='latin-1',\n", " sep='\\n',\n", " skip_blank_lines=False,\n", " names=['line']\n", " )\n", " \n", " content_df.insert(0, 'filepath', path)\n", " content_dfs.append(content_df)\n", "\n", "content = pd.concat(content_dfs)\n", "content.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see what we've got here in our DataFrame named `content`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:38.844Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Int64Index: 22278494 entries, 0 to 266\n", "Data columns (total 2 columns):\n", "filepath object\n", "line object\n", "dtypes: object(2)\n", "memory usage: 509.9+ MB\n" ] } ], "source": [ "content.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have over 22 million lines of code (including comments and blank lines) in our DataFrame. Let's clean up that data a little bit." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Clean the data\n", "We convert `filepath` into a categorical data type to optimize performance and memory consumption. We then get the operating system specific directory separator right and get rid of the superfluous parts of the path." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:39.246Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathline
0arch/alpha/boot/bootp.c/*
\n", "
" ], "text/plain": [ " filepath line\n", "0 arch/alpha/boot/bootp.c /*" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content['filepath'] = pd.Categorical(content['filepath'])\n", "content['filepath'] = content['filepath']\\\n", " .str.replace(\"\\\\\", \"/\")\\\n", " .str.replace(\"../../linux/\", \"\")\n", "content.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We further replace all empty lines with blanks and tabular spaces with a suitable number of whitespaces (we assume here that a tab corresponds to four whitespaces)." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:39.638Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathline
0arch/alpha/boot/bootp.c/*
\n", "
" ], "text/plain": [ " filepath line\n", "0 arch/alpha/boot/bootp.c /*" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content['line'] = content['line'].fillna(\"\")\n", "FOUR_SPACES = \" \" * 4\n", "content['line'] = content['line'].str.replace(\"\\t\", FOUR_SPACES)\n", "content.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get the measures\n", "Let's get some measures that can help us to spot complex code. We add some additional information to make further analysis and debugging easier: We keep track of the line number of each source code file and create a single continuous index for all source code lines. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:07:05.790298Z", "start_time": "2018-05-09T21:06:38.482Z" }, "code_folding": [], "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathlineline_number
0arch/alpha/boot/bootp.c/*1
\n", "
" ], "text/plain": [ " filepath line line_number\n", "0 arch/alpha/boot/bootp.c /* 1" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content['line_number'] = content.index + 1\n", "content = content.reset_index(drop=True)\n", "content.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we mark comment lines and blank lines. For the identification of the comments, we use a simple heuristic that should be sufficient in most cases." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.927988Z", "start_time": "2018-05-09T21:06:09.334Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathlineline_numberis_commentis_empty
0arch/alpha/boot/bootp.c/*1TrueFalse
\n", "
" ], "text/plain": [ " filepath line line_number is_comment is_empty\n", "0 arch/alpha/boot/bootp.c /* 1 True False" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content['is_comment'] = content['line'].str.match(r'^ *(//|/\\*|\\*).*')\n", "content['is_empty'] = content['line'].str.replace(\" \",\"\").str.len() == 0\n", "content.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we come to the key part: We calculate the indentation for each source code line. An indent is the number of whitespaces that come before the first non-blank character in a line. We just subtract the length from a line without the preceding whitespaces from the length of the whole line." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.927988Z", "start_time": "2018-05-09T21:06:09.344Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
filepathlineline_numberis_commentis_emptyindent
0arch/alpha/boot/bootp.c/*1TrueFalse0
\n", "
" ], "text/plain": [ " filepath line line_number is_comment is_empty indent\n", "0 arch/alpha/boot/bootp.c /* 1 True False 0" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "content['indent'] = content['line'].str.len() - content['line'].str.lstrip().str.len()\n", "content.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Get the source code\n", "We make sure to only inspect the real source code lines. Because we have the information about the blank lines and comments, we can filter these out very easily. We immediately aggregate the indentations with a `count` to get the number of source code lines as well as the summary of all indents for each `filepath` aka source code file. We also rename the columns of our new DataFrame accordingly." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.943615Z", "start_time": "2018-05-09T21:06:09.362Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
linesindents
filepath
Documentation/scheduler/sched-pelt.c3958
Documentation/usb/usbdevfs-drop-permissions.c2638
arch/alpha/boot/bootp.c95201
arch/alpha/boot/bootpz.c184334
arch/alpha/boot/main.c6067
\n", "
" ], "text/plain": [ " lines indents\n", "filepath \n", "Documentation/scheduler/sched-pelt.c 39 58\n", "Documentation/usb/usbdevfs-drop-permissions.c 26 38\n", "arch/alpha/boot/bootp.c 95 201\n", "arch/alpha/boot/bootpz.c 184 334\n", "arch/alpha/boot/main.c 60 67" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "source_code_content = content[content['is_comment'] | content['is_empty']]\n", "source_code = source_code_content.groupby('filepath')['indent'].agg(['count', 'sum'])\n", "source_code.columns = ['lines', 'indents']\n", "source_code.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get a quick overview of the data we have now, we plot the results in a scatter diagram." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.943615Z", "start_time": "2018-05-09T21:06:09.392Z" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%matplotlib inline\n", "source_code.plot.scatter('lines', 'indents', alpha=0.3);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alright, we have a very few outliners in both dimensions and a slight correlation between the lines of code and the indentation (who guessed it?). But for now, we leave the outliers where the are." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analyze complexity\n", "Let's build the ratio between the indentations and the lines of code to kind of normalize the data. This is the complexity measure that we are using further on." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
linesindentscomplexity
filepath
Documentation/scheduler/sched-pelt.c39581.487179
\n", "
" ], "text/plain": [ " lines indents complexity\n", "filepath \n", "Documentation/scheduler/sched-pelt.c 39 58 1.487179" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "source_code['complexity'] = source_code['indents'] / source_code['lines']\n", "source_code.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So how complex is our code in general? Let's get also an overview of the distribution of the complexity. We use a histogram to visualize this." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "source_code['complexity'].hist(bins=50)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Almost 30,000 files are probably okayish regarding our complexity measure. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Complexity per component\n", "Next, we execute an analysis per component to find out where the most complex areas are in the application. For this, we first sum up the metrics for each component. We can identify a component in the Linux kernel roughly by using the first parts of the file path." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.943615Z", "start_time": "2018-05-09T21:06:09.378Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
linesindentscomplexitycomponent
filepath
Documentation/scheduler/sched-pelt.c39581.487179Documentation:scheduler
\n", "
" ], "text/plain": [ " lines indents complexity \\\n", "filepath \n", "Documentation/scheduler/sched-pelt.c 39 58 1.487179 \n", "\n", " component \n", "filepath \n", "Documentation/scheduler/sched-pelt.c Documentation:scheduler " ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "source_code['component'] = source_code.index\\\n", " .str.split(\"/\", n=2)\\\n", " .str[:2].str.join(\":\")\n", "source_code.head(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then sum up all our measures per component." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.943615Z", "start_time": "2018-05-09T21:06:09.386Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
linesindentscomplexity
component
Documentation:scheduler39581.487179
Documentation:usb26381.461538
arch:alpha1669020468189.999151
arch:arc783610610114.869605
arch:arm1251561354521563.318216
\n", "
" ], "text/plain": [ " lines indents complexity\n", "component \n", "Documentation:scheduler 39 58 1.487179\n", "Documentation:usb 26 38 1.461538\n", "arch:alpha 16690 20468 189.999151\n", "arch:arc 7836 10610 114.869605\n", "arch:arm 125156 135452 1563.318216" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "measures_per_component = source_code.groupby('component').sum()\n", "measures_per_component.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Last, we print out the TOP 10 most complex parts of the Linux kernel measured by our indents per lines ratio `complexity`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "ExecuteTime": { "end_time": "2018-05-09T21:06:33.959237Z", "start_time": "2018-05-09T21:06:09.410Z" } }, "outputs": [ { "data": { "text/plain": [ "component\n", "drivers:net 3526.370717\n", "drivers:staging 2612.145956\n", "drivers:gpu 2504.533202\n", "drivers:media 1946.165103\n", "arch:arm 1563.318216\n", "include:linux 1549.853606\n", "arch:mips 1370.794157\n", "arch:powerpc 1168.349146\n", "arch:x86 1047.736624\n", "drivers:scsi 967.622213\n", "Name: complexity, dtype: float64" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "measures_per_component['complexity'].nlargest(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Visualization\n", "Finally, we plot our the complexity per component with a treemap. We use the Python visualization library `pygal` (http://www.pygal.org) which is very easy to use and just fits our use case perfectly. As size for the treemap's rectangles, we use the lines of code of the components. As color, we choose red and visualize with the help of the red color's alpha level (aka normalized complexity `comp_norm` between 0 and 1), how red a rectangle of the treemap should be. \n", "\n", "This gives us a treemap with the following properties:\n", "* The bigger the component/rectangle, the more lines of code.\n", "* The redder you'll see a rectangle, the more complex is a component.\n", "\n", "We render the treemap as PNG image (to save space) and display it directly in the notebook." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pygal\n", "from IPython.display import Image\n", "\n", "config = pygal.Config(show_legend=False)\n", "treemap = pygal.Treemap(config)\n", "\n", "max_comp = measures_per_component['complexity'].max()\n", "\n", "for row in measures_per_component.iterrows():\n", " \n", " filename = row[0]\n", " entry = row[1]\n", " comp_norm = entry['complexity'] / max_comp\n", " \n", " data = {}\n", " data['value'] = entry['lines']\n", " data['color'] = 'rgba(255,0,0,' + str(comp_norm) + ')'\n", " treemap.add(filename, [data])\n", " \n", "Image(treemap.render_to_png())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is also an [interactive treemap](https://feststelltaste.github.io/software-analytics/notebooks/vis/indentation_complexity/linux.html) where you can interact with the graphic. But beware: It's almost a megabyte big!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conclusion\n", "In this blog post, I showed you how we can obtain an almost programming language agnostic measure for complexity by measuring the indentation of source code. We also spotted the TOP 10 most complex components and visualized the completed results as a treemap.\n", "\n", "All this was relatively easy and straightforward to do by using standard Data Science tooling. I hope you liked it and I would be happy, if you could provide some feedback in the comment section." ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }