#!/bin/bash
# (C) 2019 Behan Webster <behanw@converseincode.com>
# Based on boardfiles_boardloc_png_generator.sh by Jan-Simon Möller <dl9pf@gmx.de>
# Licenced GPLv2
#
# Generate pretty Linux kernel graphs with gnuplot and sloccount

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

VERSION=1.1

RED="\e[0;31m"
GREEN="\e[0;32m"
YELLOW="\e[0;33m"
#CYAN="\e[0;36m"
BLUE="\e[0;34m"
BACK="\e[0m"

POSTFIX="${POSTFIX:--count}"
PINGHOST="${PINGHOST:-8.8.8.8}"

################################################################################
debug() {
	[[ -z "$DEBUG" ]] || echo -e "${BLUE}D:" "$@" "${BACK}" >&2
}
error() {
	set +x
	echo -e "${RED}E:" "$@" "$BACK" >&2
	exit 1
}
info() {
	echo -e "${GREEN}I:" "$@" "$BACK" >&2
}
verbose() {
	[[ -z $VERBOSE ]] || info "$@"
}
warn() {
	echo -e "${YELLOW}W:" "$@" "$BACK" >&2
}

################################################################################
CMD=$(basename "$0")
usage() {
	echo "usage: $CMD <kernel_directory>" >&2
	echo
	echo "    -A, --arch <ARCH>  Make graphs about arch/ARCH"
	echo "    -C, --clone <dir>  Clone the kernel from this local repo"
	echo "    -G, --graph-only   Only graph the previously collected data"
	echo "        --no-gather    Don't gather new kernel data"
	echo "        --no-graph     Don't graph results"
	echo "        --no-histogram Don't generate kernel release histgrams"
	echo "    -V, --version      Print the version of $CMD"
	echo
	echo "The kernel repo used must end in '$POSTFIX' to indicate it is used for this script."
	echo "Best to use '$CMD --clone <existing_dir>' the first time to setup such a checkout."
	echo
	echo "First time you run this:"
	echo "    $ $CMD --clone linux-checkout"
	echo "Then you can do a:"
	echo "    $ $CMD linux-checkout$POSTFIX"
	echo "Or more simply:"
	echo "    $ cd linux-checkout$POSTFIX"
	echo "    $ $CMD"
	echo
	echo "The resulting files and graphs will be stored in the equivalent of"
	echo "linux-checkout$POSTFIX/tmp"
	exit 1
}

################################################################################
# Is there internet?
INTERNET=
isinternet() {
	if [[ -n $INTERNET ]] || ping -q -c 1 "$PINGHOST" >/dev/null 2>&1 ; then
		INTERNET=y
	else
		warn "No internet detected"
		return 1
	fi
}

################################################################################
# Remove regex bits from name
regex_to_name() {
	local REGEX=$1
	sed -e 's/[\.*(]//g; s/\[[^[]*\]//g; s|/|-|g' <<<"${REGEX#^}"
}

################################################################################
git_checkout() {
	local KVER=$1 CFILE=$2
	git clean -f -d -q
	git reset -q --hard "$KVER" 2>/dev/null || sleep 10
	mkdir -p "${CFILE%/*}"
}

######################################################################
declare -A KCONFIG

################################################################################
get_kversion() {
	local KDIR=${1:-.}
	cd "$KDIR"
	echo -n v
	make kernelversion | sed -e 's/\.0$//'
}

######################################################################
check_arch() {
	local DIR=$1 SRCARCH=$2

	cd "$DIR"
	if [[ -d "arch/$SRCARCH" ]] ; then
		echo "$SRCARCH"
	elif [[ $SRCARCH = arm64 && -d arch/arm ]] ; then
		echo arm
	elif [[ $SRCARCH = powerpc && -d arch/ppc ]] ; then
		echo ppc
	elif [[ $SRCARCH = x86 && -d arch/x86_64 ]] ; then
		echo x86_64
	else
		warn "No arch/$SRCARCH found"
		return 1
	fi
}

######################################################################
make_expand() {
	local FILE=$1
	#debug "  make_expand: FILE=$FILE"
	if [[ $FILE =~ \$ ]] ; then
		local PAT VALUE
		PAT=$(sed -re 's/^.*\$[{(]?([0-9A-Z_]*)[)}]?.*$/\1/' <<<"$FILE")
		VALUE=$(eval "echo \${$PAT}")
		sed -re "s|\\\$[{(]?$PAT[)}]?|$VALUE|g" <<<"$FILE"
	else
		echo "$FILE"
	fi
}

######################################################################
walk_kconfig() {
	local DIR=$1 FILE=$2 KVER=${3} SRCARCH=${4}
	local LINE CMD DATA COUNT=0 SUBCOUNT=0
	local CFILE="$CACHE/$KVER/count-$SRCARCH-${FILE//\//-}.txt"
	#debug "0 walk_kconfig: DIR=$DIR FILE=$FILE CFILE=$CFILE"

	if [[ -s $CFILE ]] ; then
		cat "$CFILE"
		return 0
	fi

	while IFS= read -r LINE ; do
		IFS=' ' read -r CMD DATA <<<"$LINE"
		CMD=$(sed -e 's/^[[:space:]]*//' <<<"$CMD")
		if [[ $CMD =~ ^# || $LINE =~ ^# ]] ; then
			continue
		fi
		debug "  walk_kconfig: LINE='$LINE' CMD='$CMD' DATA='$DATA'"
		case "$CMD" in
			config) COUNT=$(( COUNT + 1 )) ;;
			source) [[ $DATA =~ \" ]] || continue
				DATA=$(make_expand "${DATA//\"/}")
				SUBCOUNT=$(walk_kconfig "$DIR" "$DATA" "$KVER" "$SRCARCH")
				verbose "        source ($KVER/$SRCARCH) $DATA: $SUBCOUNT"
				COUNT=$(( COUNT + SUBCOUNT )) ;;
		esac
	done <"$DIR/$FILE"

	echo "${COUNT:-failed}" | tee "$CFILE"
}

######################################################################
count_kconfig() {
	local DIR=$1 FILE=$2 KVER=${3:-} SRCARCH=${4:-arm} ARCH COUNT=0
	local CFILE="$CACHE/$KVER/count-$SRCARCH-${FILE//\//-}.txt"

	if [[ -s $CFILE ]] ; then
		COUNT=$(cat "$CFILE")
		info "    Count Kconfig KVER=$KVER ARCH=$SRCARCH COUNT=$COUNT"
		echo "$COUNT"
		return 0
	fi

	git_checkout "$KVER" "$CFILE"
	ARCH=$(check_arch "$DIR" "$SRCARCH")
	[[ -n $ARCH ]] || return 1
	export SRCARCH
	info "      Counting Kconfig KVER=$KVER DIR=$DIR FILE=$FILE ARCH=$SRCARCH ($ARCH)"

	if [[ $SRCARCH = arm64 && $ARCH = arm ]] ; then
		echo 0 | tee "$CFILE"
		return 0
	fi

	if [[ -f "$DIR/$FILE" ]] ; then
		walk_kconfig "$DIR" "$FILE" "$KVER" "$SRCARCH"
	else
		local CONFIG NUM
		warn "No central $ARCH ($SRCARCH) Kconfig. Finding Kconfigs"
		for CONFIG in arch/$ARCH/Kconfig $(find * -maxdepth 1 -name "$FILE" -print) ; do
			NUM=$(walk_kconfig "$DIR" "$CONFIG" "$KVER" "$SRCARCH")
			debug "  count_config: $COUNT + $NUM"
			COUNT=$(( COUNT + NUM ))
		done
		info "    Count Kconfig KVER=$KVER ARCH=$SRCARCH COUNT=$COUNT"
		echo "$COUNT" | tee "$CFILE"
	fi
}

######################################################################
count_arch_config() {
	local DIR=$1 KVER=$2 SRCARCH=${3} COUNT
	local CFILE="$CACHE/$KVER/config-$SRCARCH-wc.txt"

	if [[ -s $CFILE ]] ; then
		COUNT=$(cat "$CFILE")
		info "    Count config KVER=$KVER ARCH=$SRCARCH COUNT=$COUNT"
		echo "$COUNT"
		return 0
	fi

	git_checkout "$KVER" "$CFILE"
	ARCH=$(check_arch "$DIR" "$SRCARCH")
	[[ -n $ARCH ]] || return 1
	info "      Counting config KVER=$KVER ARCH=$ARCH ($SRCARCH)"

	if [[ $SRCARCH = arm64 && $ARCH = arm ]] ; then
		echo 0 | tee "$CFILE"
		return 0
	fi

	sed -i 's/^config %config/%config/; s|^/ %/|%/|' Makefile
	make -s ARCH="$ARCH" allyesconfig >/dev/null 2>&1
	[[ -f .config ]] || error "No .config file for KVER=$KVER ARCH=$SRCARCH"
	COUNT=$(grep -c CONFIG_ .config)
	make -s mrproper
	git checkout Makefile
	info "    Count config KVER=$KVER ARCH=$ARCH COUNT=$COUNT"
	echo $COUNT | tee "$CFILE"
}

################################################################################
# Find all released kernel tags
kernel_version_tags() {
	debug "kernel_version_tags:"

	if [[ -f ${TAGS:-} ]] ; then
		cat $TAGS
		return
	fi

	[[ -d .git && -d $ARCHDIR ]] || error "kernel_version_tags: Invalid kernel git directory"
	git tag | grep -E '^v(2\.6|[1-9])\.[0-9]+$' \
		| tail -n +2 \
		| sed -e 's/^v//' \
		| sort -t. -k 1,1n -k 2,2n -k 3,3n \
		| sed -e 's/^/v/'
}

################################################################################
# Find all released stable kernel tags
kernel_version_stable_tags() {
	local KVER=$1
	debug "kernel_version_stable_tags: KVER=$KVER"

	git tag | grep -E "^${KVER/./\\.}\.[0-9]+$" \
		| sed -e 's/^v//' \
		| sort -t. -k 1,1n -k 2,2n -k 3,3n \
		| sed -e 's/^/v/' \
		|| true
}

################################################################################
# Get the date of a release tag
kernel_git_tag_date() {
	local KVER=$1
	debug "kernel_git_tag_date: KVER=$KVER"

	git log -1 --format=%ai "$KVER" | awk '{print $1}'
}

################################################################################
declare -A PERYEAR
kernel_git_tag_years() {
	echo ${!PERYEAR[@]} | sed -e 's/ /\n/g' | sort
}
kernel_kernel_per_year() {
	local KVER YEAR

	if [[ -n $DATES ]] ; then
		while read -r YEAR KVER ; do
			if [[ -n ${PERYEAR[$YEAR]:-} ]] ; then
				PERYEAR[$YEAR]="${PERYEAR[$YEAR]}
$KVER"
			else
				PERYEAR[$YEAR]="$KVER"
			fi
		done <"$DATES"
	fi

	for KVER in $(kernel_version_tags) ; do
		YEAR=$(kernel_git_tag_date "$KVER" | cut -d- -f1)
		if [[ -n ${PERYEAR[$YEAR]:-} ]] ; then
			PERYEAR[$YEAR]="${PERYEAR[$YEAR]}
$KVER"
		else
			PERYEAR[$YEAR]="$KVER"
		fi
	done
	for YEAR in $(kernel_git_tag_years) ; do
		debug "$YEAR: ${PERYEAR[$YEAR]}"
	done
}

################################################################################
# Checkout a version of the kernel, git-grep for a REGEX, and count the results
kernel_git_grep_count() {
	local KVER=$1 REGEX=$2 DIR=${3:-$ARCHDIR} CFILE
	CFILE="$CACHE/$KVER/$(regex_to_name "$REGEX").txt"
	debug "kernel_git_grep_count: KVER=$KVER REGEX=$REGEX DIR=$DIR CFILE=$CFILE"

	if [[ -s $CFILE ]] ; then
		cat "$CFILE"
	else
		info "  kernel git grep wc $KVER for $REGEX ($DIR) ${CFILE##*/}"
		git_checkout "$KVER" "$CFILE"
		(git grep "$REGEX" "$DIR" || true) | wc -l | tee "$CFILE"
	fi
}

################################################################################
# Count the number of files in a directory for a kernel version which match a regex
kernel_git_ls_tree_count() {
	local KVER=$1 REGEX=$2 DIR=${3:-$ARCHDIR}
	debug "kernel_git_ls_tree_count: KVER=$KVER REGEX=$REGEX DIR=$DIR"

	git ls-tree "$KVER" "$DIR" | (grep -c -E "$REGEX" || true)
}

################################################################################
# Count the number of lines of code in a directory with wc for a version of the kernel
kernel_wc() {
	local KVER=$1 DIR=${2:-.} NAME=${3:-} REGEX=${4:-}
	if [[ -n $NAME && -z $REGEX ]] ; then
		case "$NAME" in
			asm) REGEX='.*\.S';;
			c) REGEX='.*\.c';;
			code)  REGEX='.*\.[chS]';;
			header)  REGEX='.*\.h';;
		esac
	elif [[ -n $REGEX && -z $NAME ]] ; then
		NAME=$(regex_to_name "$REGEX")
	fi
	if [[ $DIR = . ]] ; then
		DIR=
	fi
	local CFILE="$CACHE/$KVER/wc-${DIR//\//-}${NAME:+-$NAME}.txt"
	CFILE="${CFILE//--/-}"
	debug "kernel_wc: KVER=$KVER DIR=${DIR:-.} CFILE=$CFILE"

	if [[ -s $CFILE ]] ; then
		cat "$CFILE"
	else
		info "  kernel wc $KVER (${DIR:-.}) ${CFILE##*/}"
		git_checkout "$KVER" "$CFILE"
		find "${DIR:-.}" -type f ${REGEX:+-regex "$REGEX"} -print0 \
			| xargs -0 cat | wc -l | tee "$CFILE"
	fi
}

################################################################################
# Run sloccount on a version of the kernel
kernel_sloccount() {
	local KVER=$1
	local CFILE="$CACHE/$KVER/sloccount.txt"
	debug "kernel_sloccount: KVER=$KVER CFILE=$CFILE"

	if [[ -s $CFILE ]] && grep -q ansic "$CFILE" ; then
		cat "$CFILE"
	else
		info "  kernel sloccount $KVER ${CFILE##*/}"
		git_checkout "$KVER" "$CFILE"
		sloccount . | tee "$CFILE"
	fi
}

################################################################################
# Query the output of sloccount for a version of the kernel
# REGEX is used to match a line, and FIELD is used to chose what will be returned from the line
# If FIELD is a number, then it's used with awk to print the column
# If FIELD is a string, it's used as a key to find the associated key=value
kernel_sloc_query() {
	local KVER=$1 REGEX=$2 FIELD=$3 VAL
	debug "kernel_sloc_query: KVER=$KVER REGEX=$REGEX FIELD=$FIELD"

	VAL=$(kernel_sloccount "$KVER" | grep "$REGEX")
	if [[ $FIELD =~ ^[0-9]+ ]] ; then
		awk -e "{print \$$FIELD}" <<<"$VAL"
	else
		sed -e "s/^.*$FIELD=//; s/,.*$//" <<<"$VAL"
	fi
}

################################################################################
write_stats() {
	local NAME=$1; shift
	local OUTPUT

	OUTPUT=$(echo "$@" | tee -a "$TMPDIR/$NAME.txt")
	verbose "  Writing $NAME:" "$OUTPUT"
}

################################################################################
# Generate gnuplot scripts and plots
graph_kernel() {
	local DATA="$1.txt"
	local PLOT="$TMPDIR/$1${2:+-$2}.gnuplot"
	local PNG="$1${2:+-$2}.png"
	YAXIS=$(tail -1 "$DATA" | cut -d' ' -f1)
	#info "Generating $PNG with $YAXIS entries"

	########################################################################
	(cat <<ENDPLOT
set term png size 1024,768
set key top left
set xrange [0:$YAXIS] 
set xtics ($(cat "$KVERTIC"))
set xtics rotate by 90 offset -0,-3
set output '$PNG'
ENDPLOT
	cat | sed -re "s|DATA|'$DATA'|g") >"$PLOT"
	gnuplot "$PLOT"
	info "Created graph: $PNG"
}

################################################################################
# Generate gnuplot scripts and plots
graph_years() {
	local DATA="$1.txt"
	local PLOT="$TMPDIR/$1${2:+-$2}.gnuplot"
	local PNG="$1${2:+-$2}.png"
	#info "Generating $PNG with $YAXIS entries"

	########################################################################
	(cat <<ENDPLOT
set term png size 1024,768
set key top left
set boxwidth 0.75
set style fill solid
set output '$PNG'
ENDPLOT
	cat | sed -re "s|DATA|'$DATA'|g") >"$PLOT"
	gnuplot "$PLOT"
	info "Created graph: $PNG"
}

################################################################################
declare -A GRAPHS
declare -A GYEARS

#------------------------------------------------------------------------------
GRAPHS[boardfiles]="boardfiles"
gather_boardfiles() {
	local KVER=$1
	kernel_git_grep_count "$KVER" '^MACHINE_START' "$ARCHDIR"
	kernel_git_grep_count "$KVER" '^DT_MACHINE_START' "$ARCHDIR"
	kernel_git_ls_tree_count "$KVER" '\.dts$' "$ARCHDIR/boot/dts/"
}
graph_boardfiles() {
	graph_kernel "boardfiles" <<ENDPLOT
set title 'Platform Boardfiles vs DT Boardfiles in $ARCHDIR'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'Number of Boardfiles'
plot DATA using 1:4 title "Board files" with linespoints, \
     DATA using 1:5 title "DT files" with linespoints
ENDPLOT

	graph_kernel "boardfiles" "filled" <<ENDPLOT
set title 'Platform Boardfiles vs DT Boardfiles in $ARCHDIR'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'Number of Boardfiles'
plot DATA using 1:(\$4+\$5+\$6) title "Number of DTS Files" with filledcurves x1,\
     DATA using 1:(\$4+\$5) title "Number of DT Machine Types" with filledcurves x1,\
     DATA using 1:(\$4) title "Number of Platform Board Machine Types" with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[boardloc]="boardloc"
gather_boardloc() {
	local KVER=$1
	kernel_wc "$KVER" "$ARCHDIR"
	kernel_sloc_query "$KVER" '^ansic:' 2
}
graph_boardloc() {
	graph_kernel "boardloc" <<ENDPLOT
set title 'Board MLOC in $ARCHDIR'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'MLOC of Boardfiles'
plot DATA using 1:(\$4/1000000.) title 'MLOC of ARM Board files' with linespoints, \
     DATA using 1:(\$5/1000000.) title 'MLOC of Linux repo' with linespoints
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_drivers]="kernel-drivers"
gather_kernel_drivers() {
	local KVER=$1
	kernel_sloc_query "$KVER" '^ansic:' 2
	kernel_sloc_query "$KVER" '^[0-9].*drivers' "ansic"
	kernel_git_grep_count "$KVER" "module.*_driver(" "drivers"
	kernel_git_grep_count "$KVER" "[^n]register.*_driver(" "drivers"
}
graph_kernel_drivers() {
	graph_kernel "kernel-drivers" <<ENDPLOT
set title 'Lines of C in Linux Kernel Drivers'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Linux repo' with filledcurves x1, \
     DATA using 1:((\$5)/1000.) title 'KLOC of driver code' with filledcurves x1, \
     DATA using 1:(\$6) title 'Number of newer drivers using module macro' with linespoints, \
     DATA using 1:(\$7) title 'Number of older drivers using "register drivers"' with linespoints
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_loc]="kernel-loc"
gather_kernel_loc() {
	local KVER=$1
	kernel_sloc_query "$KVER" '^ansic:' 2
	kernel_sloc_query "$KVER" '^[0-9].*drivers' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*arch' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*fs' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*sound' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*net' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*include' "ansic"
	kernel_sloc_query "$KVER" '^[0-9].*kernel' "ansic"
}
graph_kernel_loc() {
	graph_kernel "kernel-loc" <<ENDPLOT
set title 'Lines of C in the Linux Kernel (sloccount)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Linux' with linespoints, \
     DATA using 1:(\$5/1000.) title 'KLOC of Drivers' with linespoints, \
     DATA using 1:(\$6/1000.) title 'KLOC of Arch' with linespoints, \
     DATA using 1:(\$7/1000.) title 'KLOC of FS' with linespoints, \
     DATA using 1:(\$8/1000.) title 'KLOC of Sound' with linespoints, \
     DATA using 1:(\$9/1000.) title 'KLOC of Net' with linespoints, \
     DATA using 1:(\$10/1000.) title 'KLOC of Include' with linespoints
ENDPLOT
	graph_kernel "kernel-loc" "filled" <<ENDPLOT
set title 'Lines of C in the Linux Kernel (sloccount)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Linux' with filledcurves x1, \
     DATA using 1:((\$5+\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Drivers' with filledcurves x1, \
     DATA using 1:((\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Arch' with filledcurves x1, \
     DATA using 1:((\$7+\$8+\$9+\$10)/1000.) title 'KLOC of FS' with filledcurves x1, \
     DATA using 1:((\$8+\$9+\$10)/1000.) title 'KLOC of Sound' with filledcurves x1, \
     DATA using 1:((\$9+\$10)/1000.) title 'KLOC of Net' with filledcurves x1, \
     DATA using 1:(\$10/1000.) title 'KLOC of Include' with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_loc_compare]="kernel-loc-compare"
gather_kernel_loc_compare() {
	local KVER=$1
	kernel_sloc_query "$KVER" '^ansic:' 2
	kernel_sloc_query "$KVER" '^asm:' 2
	kernel_wc "$KVER" '.' 'code'
	kernel_wc "$KVER" '.' 'c'
	kernel_wc "$KVER" '.' 'header'
	kernel_wc "$KVER" '.' 'asm'
}
graph_kernel_loc_compare() {
	graph_kernel "kernel-loc-compare" <<ENDPLOT
set title 'Lines of C in the Linux Kernel (sloccount vs wc)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:((\$7+\$8+\$9)/1000.) title 'KLOC of ASM (wc)' with filledcurves x1, \
     DATA using 1:((\$7+\$8)/1000.) title 'KLOC of Headers (wc)' with filledcurves x1, \
     DATA using 1:(\$7/1000.) title 'KLOC of C (wc)' with filledcurves x1, \
     DATA using 1:((\$4+\$5)/1000.) title 'KLOC of ASM (sloccount stacked on C)' with lines, \
     DATA using 1:(\$4/1000.) title 'KLOC of C (sloccount C+headers)' with lines
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_stable_releases]="kernel-stable-releases"
gather_kernel_stable_releases() {
	local KVER=$1
	kernel_wc "$KVER" '.' 'code'
	RELS=$(kernel_version_stable_tags "$KVER" | wc -l)
	echo "$RELS"
	echo "$KVER.$RELS"
	if [[ $RELS -gt 0 ]] ; then
		kernel_wc "$KVER.$RELS" '.' 'code'
	else
		echo 0
	fi
}
graph_kernel_stable_releases() {
	graph_kernel "kernel-stable-releases" <<ENDPLOT
set ylabel ''
set yrange [0:] 
set title 'Linux Kernel Stable Releases'
set boxwidth 0.75
set style fill solid
plot DATA using 1:5 title "number stable releases" with boxes, \
     DATA using 1:((\$7-\$4)/1000.) title "stable KLOC added" with point pointtype 9 ps 2 lc rgb "black"
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_wc]="kernel-wc"
gather_kernel_wc() {
	local KVER=$1
	kernel_wc "$KVER" '.' 'code'
	kernel_wc "$KVER" '.' 'c'
	kernel_wc "$KVER" '.' 'header'
	kernel_wc "$KVER" '.' 'asm'
}
graph_kernel_wc() {
	graph_kernel "kernel-wc" "filled" <<ENDPLOT
set title 'Lines of code in the Linux Kernel (wc)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:((\$5+\$6+\$7)/1000.) title 'KLOC of ASM' with filledcurves x1, \
     DATA using 1:((\$5+\$6)/1000.) title 'KLOC of Headers' with filledcurves x1, \
     DATA using 1:(\$5/1000.) title 'KLOC of C code' with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_wc_code]="kernel-wc-code"
gather_kernel_wc_code() {
	local KVER=$1
	kernel_wc "$KVER" '.' 'code'
	kernel_wc "$KVER" "drivers" 'code'
	kernel_wc "$KVER" "arch" 'code'
	kernel_wc "$KVER" "fs" 'code'
	kernel_wc "$KVER" "sound" 'code'
	kernel_wc "$KVER" "net" 'code'
	kernel_wc "$KVER" "include" 'code'
	kernel_wc "$KVER" "kernel" 'code'
}
graph_kernel_wc_code() {
	graph_kernel "kernel-wc-code" "filled" <<ENDPLOT
set title 'Lines of code per directory in the Linux Kernel (wc)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Drivers' with filledcurves x1, \
     DATA using 1:((\$4-\$5)/1000.) title 'KLOC of Arch' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6)/1000.) title 'KLOC of FS' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6-\$7)/1000.) title 'KLOC of Sound' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6-\$7-\$8)/1000.) title 'KLOC of Net' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6-\$7-\$8-\$9)/1000.) title 'KLOC of Include' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6-\$7-\$8-\$9-\$10)/1000.) title 'KLOC of Kernel' with filledcurves x1, \
     DATA using 1:((\$4-\$5-\$6-\$7-\$8-\$9-\$10-\$11)/1000.) title 'KLOC of Everything else' with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_wc_c]="kernel-wc-c"
gather_kernel_wc_c() {
	local KVER=$1
	kernel_wc "$KVER" '.' 'c'
	kernel_wc "$KVER" "drivers" 'c'
	kernel_wc "$KVER" "arch" 'c'
	kernel_wc "$KVER" "fs" 'c'
	kernel_wc "$KVER" "sound" 'c'
	kernel_wc "$KVER" "net" 'c'
	kernel_wc "$KVER" "include" 'c'
	kernel_wc "$KVER" "kernel" 'c'
}
graph_kernel_wc_c() {
	graph_kernel "kernel-wc-c" "filled" <<ENDPLOT
set title 'Lines of C per directory in the Linux Kernel (wc)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Linux' with filledcurves x1, \
     DATA using 1:((\$5+\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Drivers' with filledcurves x1, \
     DATA using 1:((\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Arch' with filledcurves x1, \
     DATA using 1:((\$7+\$8+\$9+\$10)/1000.) title 'KLOC of FS' with filledcurves x1, \
     DATA using 1:((\$8+\$9+\$10)/1000.) title 'KLOC of Sound' with filledcurves x1, \
     DATA using 1:((\$9+\$10)/1000.) title 'KLOC of Net' with filledcurves x1, \
     DATA using 1:(\$10/1000.) title 'KLOC of Include' with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kernel_wc_header]="kernel-wc-header"
gather_kernel_wc_header() {
	local KVER=$1
	kernel_wc "$KVER" '.' 'header'
	kernel_wc "$KVER" "drivers" 'header'
	kernel_wc "$KVER" "arch" 'header'
	kernel_wc "$KVER" "fs" 'header'
	kernel_wc "$KVER" "sound" 'header'
	kernel_wc "$KVER" "net" 'header'
	kernel_wc "$KVER" "include" 'header'
	kernel_wc "$KVER" "kernel" 'header'
}
graph_kernel_wc_header() {
	graph_kernel "kernel-wc-header" "filled" <<ENDPLOT
set title 'Lines of C headers per directory in the Linux Kernel (wc)'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel 'KLOC'
plot DATA using 1:(\$4/1000.) title 'KLOC of Linux' with filledcurves x1, \
     DATA using 1:((\$5+\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Drivers' with filledcurves x1, \
     DATA using 1:((\$6+\$7+\$8+\$9+\$10)/1000.) title 'KLOC of Arch' with filledcurves x1, \
     DATA using 1:((\$7+\$8+\$9+\$10)/1000.) title 'KLOC of FS' with filledcurves x1, \
     DATA using 1:((\$8+\$9+\$10)/1000.) title 'KLOC of Sound' with filledcurves x1, \
     DATA using 1:((\$9+\$10)/1000.) title 'KLOC of Net' with filledcurves x1, \
     DATA using 1:(\$10/1000.) title 'KLOC of Include' with filledcurves x1
ENDPLOT
}

#------------------------------------------------------------------------------
GRAPHS[kconfig]="kconfig"
gather_kconfig() {
	local KVER=$1
	if [[ -n $PARALLEL ]] ; then
		count_arch_config "$KDIR" "$KVER" arm >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" arm >/dev/null &
		count_arch_config "$KDIR" "$KVER" arm64 >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" arm64 >/dev/null &
		count_arch_config "$KDIR" "$KVER" mips >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" mips >/dev/null &
		count_arch_config "$KDIR" "$KVER" powerpc >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" powerpc >/dev/null &
		count_arch_config "$KDIR" "$KVER" riscv >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" riscv >/dev/null &
		count_arch_config "$KDIR" "$KVER" x86 >/dev/null
		count_kconfig "$KDIR" 'Kconfig' "$KVER" x86 >/dev/null &
		wait
	fi

	count_arch_config "$KDIR" "$KVER" arm \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" arm
	count_arch_config "$KDIR" "$KVER" arm64 \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" arm64
	count_arch_config "$KDIR" "$KVER" mips \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" mips
	count_arch_config "$KDIR" "$KVER" powerpc \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" powerpc
	count_arch_config "$KDIR" "$KVER" riscv \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" riscv
	count_arch_config "$KDIR" "$KVER" x86 \
	&& count_kconfig "$KDIR" 'Kconfig' "$KVER" x86
}
graph_kconfig() {
	graph_kernel "kconfig" <<ENDPLOT
set title 'Lines of config options in the Linux Kernel'
set xlabel 'Kernel Versions' offset 0,-2
set ylabel '1000s config options'
plot DATA using 1:(\$4/1000.) title 'K .config arm' with lines, \
     DATA using 1:(\$6/1000.) title 'K .config arm64' with lines, \
     DATA using 1:(\$8/1000.) title 'K .config mips' with lines, \
     DATA using 1:(\$10/1000.) title 'K .config powerpc' with lines, \
     DATA using 1:(\$10/1000.) title 'K .config riscv' with lines, \
     DATA using 1:(\$12/1000.) title 'K .config x86' with lines
ENDPLOT
}

#------------------------------------------------------------------------------
GYEARS[kernel_dates]="kernel-dates"
gather_kernel_dates() {
	local YEAR=$1
	debug "gather_kernel_dates: YEAR=$YEAR"

	echo "${PERYEAR[$YEAR]}" | wc -l
	echo "${PERYEAR[$YEAR]}" | tail -1
}
graph_kernel_dates() {
	graph_years "kernel-dates" <<ENDPLOT
set title 'Linux Kernel Releases per Year (with most recent release in the year)'
set yrange [0:7] 
set key off
plot DATA using 1:3:xtic(2) with boxes fill pattern 3, '' u 1:(\$3 + 0.2):(\$4) with labels
ENDPLOT
}

################################################################################
# Build up all the data files for the eventual gnuplot
# shellcheck disable=SC2046,SC2086
gather_kernel_stats() {
	local FILE YAXIS=0 KVER DATE HEADER
	debug "gather_kernel_stats:"

	rm -f "$KVERTIC"
	for FILE in ${!GRAPHS[@]} ; do
		rm -f "$TMPDIR/$FILE.txt"
	done

	local EVERYOTHER=
	for KVER in $(kernel_version_tags) ; do
		info "Gather kernel stats for $KVER"

		if [[ -z $EVERYOTHER ]] ; then
			(	if [[ -s $KVERTIC ]] ; then echo -n ', '; fi
				echo -n "'$KVER' $YAXIS"
			) >>"$KVERTIC"
			EVERYOTHER=1
		else
			EVERYOTHER=
		fi

		DATE=$(kernel_git_tag_date "$KVER")
		HEADER="$YAXIS $DATE $KVER "

		write_stats "boardfiles" $HEADER $(gather_boardfiles "$KVER")
		write_stats "boardloc" $HEADER $(gather_boardloc "$KVER")
		write_stats "kernel-drivers" $HEADER $(gather_kernel_drivers "$KVER")
		write_stats "kernel-loc" $HEADER $(gather_kernel_loc "$KVER")
		write_stats "kernel-loc-compare" $HEADER $(gather_kernel_loc_compare "$KVER")
		write_stats "kernel-stable-releases" $HEADER $(gather_kernel_stable_releases "$KVER")
		write_stats "kernel-wc" $HEADER $(gather_kernel_wc "$KVER")
		write_stats "kernel-wc-code" $HEADER $(gather_kernel_wc_code "$KVER")
		write_stats "kernel-wc-c" $HEADER $(gather_kernel_wc_c "$KVER")
		write_stats "kernel-wc-header" $HEADER $(gather_kernel_wc_header "$KVER")
		write_stats "kconfig" $HEADER $(gather_kconfig "$KVER")

		YAXIS=$((YAXIS+1));
	done
}

################################################################################
# Build up histogram of kernel release dates
kernel_histogram() {
	local FILE YAXIS=0 YEAR HEADER
	debug "kernel_histogram:"

	for FILE in ${!GYEARS[@]} ; do
		info rm -f "$TMPDIR/$FILE.txt"
		rm -f "$TMPDIR/$FILE.txt"
	done

	for YEAR in $(kernel_git_tag_years) ; do
		HEADER="$YAXIS $YEAR"

		write_stats "kernel-dates" $HEADER $(gather_kernel_dates "$YEAR")

		YAXIS=$((YAXIS+1));
	done
}

################################################################################
# Generate gnuplot scripts and plots
generate_graph() {
	cd "$TMPDIR"
	graph_boardfiles
	graph_boardloc
	graph_kernel_drivers
	graph_kernel_loc
	graph_kernel_loc_compare
	graph_kernel_stable_releases
	graph_kernel_wc
	graph_kernel_wc_code
	graph_kernel_wc_c
	graph_kernel_wc_header
	graph_kernel_dates
	graph_kconfig
}

################################################################################
# Command line options
CONF="$HOME/.conf/${CMD%.sh}/${CMD/sh/conf}"
CACHE="$HOME/.cache/${CMD%.sh}"
ARCHDIR="${ARCHDIR:-arch/arm}"
CLONE=
COUNT=
DATES=
DEBUG=
NOFETCH=
NOGATHER=
NOHISTO=
NOGRAPH=
PARALLEL=
TAGSONLY=
VERBOSE=
WCONLY=
while [[ ${1:-} =~ ^- ]] ; do
	case "$1" in
		-A|--arch) ARCHDIR="arch/$2"; shift ;;
		-C|--clone) CLONE=y ;;
		--count-config) COUNT=$2; shift ;;
		--date*) DATES=$2; shift ;;
		--debug) DEBUG=y ;;
		-G|--graph-only) NOGATHER=y; NOHISTO=y ;;
		--no-fetch) NOFETCH=y ;;
		--no-gather) NOGATHER=y ;;
		--no-graph) NOGRAPH=y ;;
		--no-histograms) NOHISTO=y ;;
		--parallel) PARALLEL=y ;;
		--tags) TAGSONLY=major ;;
		--tags-stable) TAGSONLY=$2; shift ;;
		--tags-years) TAGSONLY=years ;;
		--trace) set -x ;;
		--use-tags) TAGS=$2; shift ;;
		--wc) WCONLY=y ;;
		-v|--verbose) VERBOSE=y ;;
		-V|--version) echo "$VERSION"; exit 0 ;;
		--) break ;;
		-h|--help|-*) usage ;;
	esac
	shift
done

################################################################################
# Read local configuration
if [[ -f $CONF ]] ; then
	# shellcheck disable=SC1090
	. "$CONF"
fi

################################################################################
# Figure out git stuff
KDIR="$(realpath "${1:-${KDIR:-.}}")"
ORIGIN=$(cd "$KDIR"; git config --get remote.origin.url)
CACHE+="/$(sed -e 's|^.*://||; s|/|-|g' <<<"${ORIGIN}")"

################################################################################
# For debugging only
if [[ -n $TAGSONLY ]] ; then
	if [[ $TAGSONLY = major ]] ; then
		kernel_version_tags
	elif [[ $TAGSONLY = years ]] ; then
		kernel_git_tag_years
	else
		kernel_version_stable_tags "$TAGSONLY"
	fi

	exit 0
elif [[ -n $WCONLY ]] ; then
	info "kernel_wc only:" "$@"
	kernel_wc "$@"

	exit 0
elif [[ -n $COUNT ]] ; then
	info "count_kconfig:" "$COUNT"
	count_kconfig "$COUNT" "Kconfig" "$(get_kversion)"

	exit 0
fi

################################################################################
# Clone kernel repo unless the repo ends in $POSTFIX
# We'll be resetting things a lot, so we don't want to use an existing working tree
if [[ -n $CLONE ]] ; then
	if [[ -d $KDIR$POSTFIX ]] ; then
		warn "$KDIR$POSTFIX already exists. Assuming that is the one you want to use"
	else
		git clone --reference "$KDIR" "$ORIGIN" "$KDIR$POSTFIX"
	fi
	KDIR="$KDIR$POSTFIX"
fi

################################################################################
# Validate git dir and update if possible
if [[ -d $KDIR && -d $KDIR/.git && -d $KDIR/$ARCHDIR ]] ; then
	[[ $KDIR =~ $POSTFIX$ ]] || error "Kernel directory must end in \"$POSTFIX\" so that you don't use it for anything else.\n   Consider running the following to create an appropriate kernel checkout for this script:\n   \"$CMD --clone $(realpath "$KDIR")\""
	cd "$KDIR"
	if [[ -z $NOFETCH ]] && isinternet ; then
		info "Updating kernel git repo: $KDIR"
		git fetch origin && git reset --hard origin/master && git clean -f -d
	fi
else
	usage
fi

################################################################################
# Default names
TMPDIR="$KDIR-tmp"
info "All derived files to be stored in $TMPDIR"
mkdir -p "$TMPDIR"
mkdir -p "$CACHE"

KVERTIC="$TMPDIR/kvertic.txt"

cd "$KDIR"
################################################################################
kernel_kernel_per_year
[[ -n $NOGATHER ]] || gather_kernel_stats
[[ -n $NOHISTO ]] || kernel_histogram "$TMPDIR/kernel-loc.txt"

################################################################################
[[ -n $NOGRAPH ]] || generate_graph

