diff --git a/config/menu/20-status.menu b/config/menu/20-status.menu
index 2bcf0d5e8..2e7c579e3 100644
--- a/config/menu/20-status.menu
+++ b/config/menu/20-status.menu
@@ -58,7 +58,7 @@
 				'title' => "$Lang::tr{'hardware graphs'}",
 				'enabled' => 1,
 			  };
-    $substatus->{'61.entropy'} = {
+    $substatus->{'62.entropy'} = {
 				'caption' => "$Lang::tr{'entropy'}",
 				'uri' => '/cgi-bin/entropy.cgi',
 				'title' => "$Lang::tr{'entropy graphs'}",
diff --git a/config/rootfiles/packages/squid-graph b/config/rootfiles/packages/squid-graph
new file mode 100644
index 000000000..499359cbd
--- /dev/null
+++ b/config/rootfiles/packages/squid-graph
@@ -0,0 +1,16 @@
+etc/fcron.cyclic/update-squid-graph
+srv/web/ipfire/cgi-bin/sgraph.cgi
+srv/web/ipfire/html/sgraph
+srv/web/ipfire/html/sgraph/logo.png
+usr/bin/squid-graph
+usr/local/bin/update-squid-graph.sh
+var/ipfire/addon-lang/squid-graph.de.pl
+var/ipfire/addon-lang/squid-graph.en.pl
+var/ipfire/addon-lang/squid-graph.es.pl
+var/ipfire/addon-lang/squid-graph.fr.pl
+var/ipfire/addon-lang/squid-graph.it.pl
+var/ipfire/addon-lang/squid-graph.nl.pl
+var/ipfire/addon-lang/squid-graph.pl.pl
+var/ipfire/addon-lang/squid-graph.ru.pl
+var/ipfire/addon-lang/squid-graph.tr.pl
+var/ipfire/menu.d/EX-squid-graph.menu
diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl
index 6dff5d781..12da3e2ab 100644
--- a/langs/de/cgi-bin/de.pl
+++ b/langs/de/cgi-bin/de.pl
@@ -1692,7 +1692,6 @@
 'no filter pass' => 'Legen Sie hier die Standardklassen fest durch die nicht-gefilterte Pakete gehen.',
 'no fritzdsl driver' => 'Kein Fritz!DSL-Treiber vorhanden. Bitte hochladen.',
 'no hardware random number generator' => 'Dieses System hat keine Entropiequelle.',
-'no information available' => 'Keine Informationen verfügbar.',
 'no log selected' => 'kein Protokoll ausgewählt',
 'no modem selected' => 'Kein Modem ausgewählt',
 'no set selected' => 'Es wurde kein Satz ausgewählt',
@@ -1934,7 +1933,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protokoll:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Diagramme zur Proxyauslastung',
 'proxy admin password' => 'Cache Administrator Passwort',
 'proxy cachemgr' => 'Cachemanager aktivieren',
 'proxy errmsg filedescriptors' => 'Ungültiger Anzahl Filedescriptoren',
@@ -2157,7 +2155,6 @@
 'ssnetwork status' => 'Netzwerk-Status',
 'sspasswords' => 'Passwörter',
 'ssport forwarding' => 'Port-Weiterleitung',
-'ssproxy graphs' => 'Proxy-Diagramme',
 'sssystem status' => 'System-Status',
 'sstraffic' => 'Net-Traffic',
 'sstraffic graphs' => 'Netzwerk-Diagramme',
diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl
index 4efff6e65..98694a7ab 100644
--- a/langs/en/cgi-bin/en.pl
+++ b/langs/en/cgi-bin/en.pl
@@ -1724,7 +1724,6 @@
 'no filter pass' => 'Enter the standard class for non-filtered packets.',
 'no fritzdsl driver' => 'No Fritz!DSL driver.  Please upload.',
 'no hardware random number generator' => 'This system has no source for entropy.',
-'no information available' => 'No information available.',
 'no log selected' => 'No log selected',
 'no modem selected' => 'No modem selected',
 'no set selected' => 'No set was selected',
@@ -1970,7 +1969,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protocol:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Proxy access graphs',
 'proxy admin password' => 'Cache administrator password',
 'proxy cachemgr' => 'Activate cachemanager',
 'proxy errmsg filedescriptors' => 'Wrong amount of filedescriptors',
@@ -2199,7 +2197,6 @@
 'ssnetwork status' => 'Network Status',
 'sspasswords' => 'Passwords',
 'ssport forwarding' => 'Port Forwarding',
-'ssproxy graphs' => 'Proxy Graphs',
 'sssystem status' => 'System Status',
 'sstraffic' => 'Net-Traffic',
 'sstraffic graphs' => 'Traffic Graphs',
diff --git a/langs/es/cgi-bin/es.pl b/langs/es/cgi-bin/es.pl
index ede7b661d..b1fb65e9c 100644
--- a/langs/es/cgi-bin/es.pl
+++ b/langs/es/cgi-bin/es.pl
@@ -1256,7 +1256,6 @@
 'no eciadsl synch.bin file' => 'No existe archi ECI ADSL synch.bin. Favor de cargarlo',
 'no filter pass' => 'Introduzca la clase estándar para paquetes no filtrados.',
 'no fritzdsl driver' => 'No hay drivers Fritz!DSL. Favor de cargarlos',
-'no information available' => 'No hay información disponible',
 'no log selected' => 'No ha seleccionado registro log',
 'no modem selected' => 'No ha seleccionado módem',
 'no set selected' => 'Ningún conjunto se ha seleccionado',
@@ -1437,7 +1436,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protocolo:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Gráficas de acceso al proxy',
 'proxy admin password' => 'Password Administrador de Caché',
 'proxy cachemgr' => 'Activar Cachemanager',
 'proxy errmsg filedescriptors' => 'Cantidad incorrecta de descriptores de archivos',
@@ -1647,7 +1645,6 @@
 'ssnetwork status' => 'Estado de la red',
 'sspasswords' => 'Contraseñas',
 'ssport forwarding' => 'Reenvío de puertos',
-'ssproxy graphs' => 'Gráficas de proxy',
 'sssystem status' => 'Status del sistema',
 'sstraffic' => 'Net-traffic',
 'sstraffic graphs' => 'Gráficas de tráfico',
diff --git a/langs/fr/cgi-bin/fr.pl b/langs/fr/cgi-bin/fr.pl
index e896c9b2a..8041db982 100644
--- a/langs/fr/cgi-bin/fr.pl
+++ b/langs/fr/cgi-bin/fr.pl
@@ -1247,7 +1247,6 @@
 'no eciadsl synch.bin file' => 'aucunfichier ECI ADSL synch.bin. Veuillez le charger.',
 'no filter pass' => 'Entrez la classe standard pour les paquets non filtrés.',
 'no fritzdsl driver' => 'Aucun pilote Fritz!DSL. Veuillez le charger.',
-'no information available' => 'Aucune information disponible.',
 'no log selected' => 'Aucun rapport sélectionné',
 'no modem selected' => 'Aucun modem sélectionné',
 'no set selected' => 'Aucun jeu sélectionné',
@@ -1443,7 +1442,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protocole',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Graphiques des accès proxy',
 'proxy admin password' => 'Mot de passe admnistrateur du Cache',
 'proxy cachemgr' => 'Activer le gestionnaire de cache',
 'proxy errmsg filedescriptors' => 'Mauvais montant de fichier descripteurs',
@@ -1652,7 +1650,6 @@
 'ssnetwork status' => 'Statut réseau',
 'sspasswords' => 'Mots de passe',
 'ssport forwarding' => 'Transfert de port',
-'ssproxy graphs' => 'Graphiques du proxy',
 'sssystem status' => 'Statut système',
 'sstraffic' => 'Trafic réseau',
 'sstraffic graphs' => 'Graphiques du trafic',
diff --git a/langs/it/cgi-bin/it.pl b/langs/it/cgi-bin/it.pl
index 02e047bb3..fc80a526d 100644
--- a/langs/it/cgi-bin/it.pl
+++ b/langs/it/cgi-bin/it.pl
@@ -1592,7 +1592,6 @@
 'no filter pass' => 'Enter the standard class for non-filtered packets.',
 'no fritzdsl driver' => 'No Fritz!DSL driver.  Please upload.',
 'no hardware random number generator' => 'This system has no source for entropy.',
-'no information available' => 'No information available.',
 'no log selected' => 'No log selected',
 'no modem selected' => 'No modem selected',
 'no set selected' => 'No set was selected',
@@ -1827,7 +1826,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protocolo',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Proxy access graphs',
 'proxy admin password' => 'Cache administrator password',
 'proxy cachemgr' => 'Attiva cachemanager',
 'proxy errmsg filedescriptors' => 'Wrong amount of filedescriptors',
@@ -2052,7 +2050,6 @@
 'ssnetwork status' => 'Network Status',
 'sspasswords' => 'Passwords',
 'ssport forwarding' => 'Port Forwarding',
-'ssproxy graphs' => 'Proxy Graphs',
 'sssystem status' => 'Stato del sistema',
 'sstraffic' => 'Traffico di rete',
 'sstraffic graphs' => 'Traffic Graphs',
diff --git a/langs/nl/cgi-bin/nl.pl b/langs/nl/cgi-bin/nl.pl
index 49c0cced6..97553962c 100644
--- a/langs/nl/cgi-bin/nl.pl
+++ b/langs/nl/cgi-bin/nl.pl
@@ -1555,7 +1555,6 @@
 'no filter pass' => 'Geef de standaard-klasse voor niet-gefilterde pakketten.',
 'no fritzdsl driver' => 'Geen Fritz!DSL driver. Bestand uploaden a.u.b.',
 'no hardware random number generator' => 'Dit systeem heeft geen mogelijkheden voor entropie.',
-'no information available' => 'Geen informatie beschikbaar.',
 'no log selected' => 'Geen log geselecteerd',
 'no modem selected' => 'Geen modem geselecteerd',
 'no set selected' => 'Geen set geselecteerd',
@@ -1780,7 +1779,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protocol:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Proxy toegangsgrafieken',
 'proxy admin password' => 'Cache beheerder wachtwoord',
 'proxy cachemgr' => 'Activeer cachebeheer',
 'proxy errmsg filedescriptors' => 'Verkeerd aantal bestandsindicators',
@@ -2000,7 +1998,6 @@
 'ssnetwork status' => 'Netwerkstatus',
 'sspasswords' => 'Wachtwoorden',
 'ssport forwarding' => 'Poort doorsturen',
-'ssproxy graphs' => 'Proxy grafieken',
 'sssystem status' => 'Systeemstatus',
 'sstraffic' => 'Netwerkgebruik',
 'sstraffic graphs' => 'Verkeersgrafieken',
diff --git a/langs/pl/cgi-bin/pl.pl b/langs/pl/cgi-bin/pl.pl
index e2f9da5c0..4788a8599 100644
--- a/langs/pl/cgi-bin/pl.pl
+++ b/langs/pl/cgi-bin/pl.pl
@@ -1255,7 +1255,6 @@
 'no eciadsl synch.bin file' => 'Brak pliku ECI ADSL synch.bin. Proszę o jego wgranie.',
 'no filter pass' => 'Proszę wprowadzić standardową klasę dla pakietów niefiltrowanych.',
 'no fritzdsl driver' => 'Brak sterownika Fritz!DSL.  Proszę o jego wgranie.',
-'no information available' => 'Brak dostępnych informacji.',
 'no log selected' => 'Nie wybrano logu',
 'no modem selected' => 'Nie wybrano modemu',
 'no set selected' => 'Nie wybrano ustawienia',
@@ -1449,7 +1448,6 @@
 'proto' => 'Proto',
 'protocol' => 'Protokół:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Wykresy dostępu do Proxy',
 'proxy admin password' => 'Hasło administratora Cache',
 'proxy cachemgr' => 'Aktywuj cachemanager',
 'proxy errmsg filedescriptors' => 'Niepoprawna wartość w polu filedescriptors',
@@ -1659,7 +1657,6 @@
 'ssnetwork status' => 'Stan sieci',
 'sspasswords' => 'Hasła',
 'ssport forwarding' => 'Przekierowanie portów',
-'ssproxy graphs' => 'Wykresy Proxy',
 'sssystem status' => 'Stan systemu',
 'sstraffic' => 'Net-Traffic',
 'sstraffic graphs' => 'Wykresy ruchu',
diff --git a/langs/ru/cgi-bin/ru.pl b/langs/ru/cgi-bin/ru.pl
index 4b0edb582..eefa3bc49 100644
--- a/langs/ru/cgi-bin/ru.pl
+++ b/langs/ru/cgi-bin/ru.pl
@@ -1247,7 +1247,6 @@
 'no eciadsl synch.bin file' => 'No ECI ADSL synch.bin file. Please upload.',
 'no filter pass' => 'Задайте стандартный класс для нефильтруемых пакетов.',
 'no fritzdsl driver' => 'No Fritz!DSL driver.  Please upload.',
-'no information available' => 'No information available.',
 'no log selected' => 'No log selected',
 'no modem selected' => 'No modem selected',
 'no set selected' => 'No set was selected',
@@ -1444,7 +1443,6 @@
 'proto' => 'Proto',
 'protocol' => 'Протокол:',
 'proxy' => 'Proxy',
-'proxy access graphs' => 'Proxy access graphs',
 'proxy admin password' => 'Cache administrator password',
 'proxy cachemgr' => 'Включить менеджер кэша',
 'proxy errmsg filedescriptors' => 'Wrong amount of filedescriptors',
@@ -1654,7 +1652,6 @@
 'ssnetwork status' => 'Статус сети',
 'sspasswords' => 'Пароли',
 'ssport forwarding' => 'Перенаправление портов',
-'ssproxy graphs' => 'Proxy Graphs',
 'sssystem status' => 'Статус системы',
 'sstraffic' => 'Сетевой трафик',
 'sstraffic graphs' => 'Traffic Graphs',
diff --git a/langs/tr/cgi-bin/tr.pl b/langs/tr/cgi-bin/tr.pl
index 9eb300006..42e615799 100644
--- a/langs/tr/cgi-bin/tr.pl
+++ b/langs/tr/cgi-bin/tr.pl
@@ -1660,7 +1660,6 @@
 'no filter pass' => 'Olmayan filtre paketleri için standart sınıfı girin.',
 'no fritzdsl driver' => 'Fritz!DSL sürücüsü yok. Lütfen yükleyin.',
 'no hardware random number generator' => 'Bu sistemin entropi için kaynağı yok.',
-'no information available' => 'Herhangi bir bilgi bulunmamaktadır.',
 'no log selected' => 'Günlük seçilmedi',
 'no modem selected' => 'Modem seçilmedi',
 'no set selected' => 'Hiçbir ayar seçilmedi',
@@ -1902,7 +1901,6 @@
 'proto' => 'Kural',
 'protocol' => 'Kural ',
 'proxy' => 'Vekil sunucu',
-'proxy access graphs' => 'Vekil sunucu erişim grafiği',
 'proxy admin password' => 'Önbellek yönetici parolası',
 'proxy cachemgr' => 'Önbellek yöneticisini aktifleştir',
 'proxy errmsg filedescriptors' => 'Dosya tanimlayıcı sayısı yanlış miktarda',
@@ -2131,7 +2129,6 @@
 'ssnetwork status' => 'Ağ Durumu',
 'sspasswords' => 'Parola',
 'ssport forwarding' => 'Bağlantı Noktası Yönlendirme',
-'ssproxy graphs' => 'Vekil Sunucu Grafikleri',
 'sssystem status' => 'Sistem Durumu',
 'sstraffic' => 'Ağ-Trafiği',
 'sstraffic graphs' => 'Trafik Grafiği',
diff --git a/lfs/squid-graph b/lfs/squid-graph
new file mode 100644
index 000000000..3d5147103
--- /dev/null
+++ b/lfs/squid-graph
@@ -0,0 +1,90 @@
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2007-2017  IPFire Team  <info@ipfire.org>                     #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+###############################################################################
+# Definitions
+###############################################################################
+
+include Config
+
+VER        = 3.2
+
+THISAPP    = squid-graph-$(VER)
+DIR_APP    = $(DIR_SRC)/$(THISAPP)
+TARGET     = $(DIR_INFO)/$(THISAPP)
+PROG       = squid-graph
+PAK_VER    = 1
+
+DEPS       = ""
+
+###############################################################################
+# Top-level Rules
+###############################################################################
+
+install : $(TARGET)
+
+check :
+
+download :
+
+md5 :
+
+dist:
+	@$(PAK)
+
+###############################################################################
+# Installation Details
+###############################################################################
+
+$(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects))
+	@$(PREBUILD)
+	@rm -rf $(DIR_APP) && mkdir $(DIR_APP) && cp -R $(DIR_SRC)/src/squid-graph/ $(DIR_APP)
+	cd $(DIR_APP)
+
+# Install GUI
+	install -v -m 755 $(DIR_APP)/squid-graph/sgraph.cgi \
+		/srv/web/ipfire/cgi-bin/sgraph.cgi
+
+# Create sgraph dir, install logo.png
+	-mkdir -p /srv/web/ipfire/html/sgraph
+	install -v -m 644 $(DIR_APP)/squid-graph/logo.png \
+		/srv/web/ipfire/html/sgraph/logo.png
+
+# Install main program
+	install -v -m 755 $(DIR_APP)/squid-graph/squid-graph \
+		/usr/bin/squid-graph
+
+# Install update script
+	install -v -m 755 $(DIR_APP)/squid-graph/update-squid-graph.sh \
+		/usr/local/bin/update-squid-graph.sh
+
+# Create symlink for five-minute-update-interval
+	ln -sf /usr/local/bin/update-squid-graph.sh /etc/fcron.cyclic/update-squid-graph
+
+# Install language files
+	install -v -m 004 $(DIR_APP)/squid-graph/squid-graph.*.pl \
+		/var/ipfire/addon-lang/
+
+# Install menu entry
+	install -v -m 644 $(DIR_APP)/squid-graph/EX-squid-graph.menu \
+		/var/ipfire/menu.d/EX-squid-graph.menu
+
+	@rm -rf $(DIR_APP)
+	@$(POSTBUILD)
diff --git a/make.sh b/make.sh
index f8a2e5b34..efcee24b3 100755
--- a/make.sh
+++ b/make.sh
@@ -877,6 +877,7 @@ buildipfire() {
   lfsmake2 perl-Net-IP
   lfsmake2 wio
   lfsmake2 iftop
+  lfsmake2 squid-graph
 }
 
 buildinstaller() {
diff --git a/src/paks/squid-graph/install.sh b/src/paks/squid-graph/install.sh
new file mode 100644
index 000000000..1e3b60c6e
--- /dev/null
+++ b/src/paks/squid-graph/install.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+############################################################################
+#                                                                          #
+# This file is part of the IPFire Firewall.                                #
+#                                                                          #
+# IPFire is free software; you can redistribute it and/or modify           #
+# it under the terms of the GNU General Public License as published by     #
+# the Free Software Foundation; either version 2 of the License, or        #
+# (at your option) any later version.                                      #
+#                                                                          #
+# IPFire is distributed in the hope that it will be useful,                #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
+# GNU General Public License for more details.                             #
+#                                                                          #
+# You should have received a copy of the GNU General Public License        #
+# along with IPFire; if not, write to the Free Software                    #
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA #
+#                                                                          #
+# Copyright (C) 2007 IPFire-Team <info@ipfire.org>.                        #
+#                                                                          #
+############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+
+extract_files
+
+# Update language-cache
+/usr/local/bin/update-lang-cache
+
+chown nobody.nobody /var/ipfire/menu.d/EX-squid-graph.menu
diff --git a/src/paks/squid-graph/uninstall.sh b/src/paks/squid-graph/uninstall.sh
new file mode 100644
index 000000000..dac570c05
--- /dev/null
+++ b/src/paks/squid-graph/uninstall.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+############################################################################
+#                                                                          #
+# This file is part of the IPFire Firewall.                                #
+#                                                                          #
+# IPFire is free software; you can redistribute it and/or modify           #
+# it under the terms of the GNU General Public License as published by     #
+# the Free Software Foundation; either version 2 of the License, or        #
+# (at your option) any later version.                                      #
+#                                                                          #
+# IPFire is distributed in the hope that it will be useful,                #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
+# GNU General Public License for more details.                             #
+#                                                                          #
+# You should have received a copy of the GNU General Public License        #
+# along with IPFire; if not, write to the Free Software                    #
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA #
+#                                                                          #
+# Copyright (C) 2007 IPFire-Team <info@ipfire.org>.                        #
+#                                                                          #
+############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+
+remove_files
+
+/usr/local/bin/update-lang-cache
diff --git a/src/paks/squid-graph/update.sh b/src/paks/squid-graph/update.sh
new file mode 100644
index 000000000..89c40d0d7
--- /dev/null
+++ b/src/paks/squid-graph/update.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+############################################################################
+#                                                                          #
+# This file is part of the IPFire Firewall.                                #
+#                                                                          #
+# IPFire is free software; you can redistribute it and/or modify           #
+# it under the terms of the GNU General Public License as published by     #
+# the Free Software Foundation; either version 2 of the License, or        #
+# (at your option) any later version.                                      #
+#                                                                          #
+# IPFire is distributed in the hope that it will be useful,                #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
+# GNU General Public License for more details.                             #
+#                                                                          #
+# You should have received a copy of the GNU General Public License        #
+# along with IPFire; if not, write to the Free Software                    #
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA #
+#                                                                          #
+# Copyright (C) 2007 IPFire-Team <info@ipfire.org>.                        #
+#                                                                          #
+############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+./uninstall.sh
+./install.sh
diff --git a/src/squid-graph/EX-squid-graph.menu b/src/squid-graph/EX-squid-graph.menu
new file mode 100644
index 000000000..a46bf6a79
--- /dev/null
+++ b/src/squid-graph/EX-squid-graph.menu
@@ -0,0 +1,6 @@
+    $substatus->{'61.squid-graph'} = {
+				'caption' => "$Lang::tr{'ssproxy graphs'}",
+				'uri' => '/cgi-bin/sgraph.cgi',
+				'title' => "$Lang::tr{'ssproxy graphs'}",
+				'enabled' => 1,
+				};
diff --git a/src/squid-graph/logo.png b/src/squid-graph/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e2fe57b703e2662ec3fd9b70b6cfb6de4425aadf
GIT binary patch
diff --git a/src/squid-graph/sgraph.cgi b/src/squid-graph/sgraph.cgi
new file mode 100644
index 000000000..ed25cd118
--- /dev/null
+++ b/src/squid-graph/sgraph.cgi
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2017  IPFire Team  <info@ipfire.org>                          #
+#                                                                             #
+# This program is free software: you can redistribute it and/or modify        #
+# it under the terms of the GNU General Public License as published by        #
+# the Free Software Foundation, either version 3 of the License, or           #
+# (at your option) any later version.                                         #
+#                                                                             #
+# This program is distributed in the hope that it will be useful,             #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
+# GNU General Public License for more details.                                #
+#                                                                             #
+# You should have received a copy of the GNU General Public License           #
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+use strict;
+
+# enable only the following on debugging purpose
+#use warnings;
+#use CGI::Carp 'fatalsToBrowser';
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/lang.pl";
+require "${General::swroot}/header.pl";
+
+my @graphs = ();
+
+&Header::showhttpheaders();
+
+my $dir = "/srv/web/ipfire/html/sgraph";
+my $sgraphdir = "/srv/web/ipfire/html/sgraph";
+
+&Header::openpage($Lang::tr{'ssproxy graphs'}, 1, '');
+
+&Header::openbigbox('100%', 'left');
+
+&Header::openbox('100%', 'left', $Lang::tr{'proxy access graphs'} . ":" );
+
+if (open(IPACHTML, "$sgraphdir/index.html"))
+{
+	my $skip = 1;
+	while (<IPACHTML>)
+	{
+		$skip = 1 if /^<HR>$/;
+		if ($skip)
+		{
+			$skip = 0 if /<H1>/;
+			next;
+		}
+		s/<IMG SRC=([^"'>]+)>/<img src='\/sgraph\/$1' alt='Graph' \/>/;
+		s/<HR>/<hr \/>/g;
+		s/<BR>/<br \/>/g;
+		s/<([^>]*)>/\L<$1>\E/g;
+		s/(size|align|border|color)=([^'"> ]+)/$1='$2'/g;
+		print;
+	}
+	close(IPACHTML);
+}
+else {
+	print $Lang::tr{'no information available'}; }
+
+&Header::closebox();
+
+&Header::closebigbox();
+
+&Header::closepage();
diff --git a/src/squid-graph/squid-graph b/src/squid-graph/squid-graph
new file mode 100644
index 000000000..7b96d867f
--- /dev/null
+++ b/src/squid-graph/squid-graph
@@ -0,0 +1,1280 @@
+#!/usr/bin/perl
+
+#####################################################################
+#
+# Squid Graph version 3.2
+# http://squid-graph.sourceforge.net
+#
+# This program is distributed under the GNU General Public License
+# See gpl.txt for full license information
+#
+#####################################################################
+
+use strict;
+use GD;
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/lang.pl";
+#require "${General::swroot}/header.pl";
+
+
+# Program version (DO NOT TOUCH THIS)
+my $VER = "3.2 release";
+
+# Enable console logging? (1 = on, 0 = off)
+my $CONSOLELOG = 0;
+
+# "Global" variables (DO NOT TOUCH THESE)
+my %config; # configuration details here
+my %color;  # colours configuration
+
+# Define graph colours [RRR,GGG,BBB]
+$color{'bg'}   = ['F5','F5','F5']; # graph background color
+$color{'fg'}   = ['00','00','00']; # graph foreground color
+$color{'gr'}   = ['33','33','99']; # graph total line color
+$color{'hit'}  = ['99','33','33']; # graph hit line color
+$color{'miss'} = ['33','99','33']; # graph miss line color
+$color{'ims'}  = ['99','33','99']; # graph ims hit line color
+$color{'hbg'}  = ['FF','FF','FF']; # html report background color
+
+############ DO NOT EDIT ANYTHING BELOW THIS LINE ##############
+
+# Forward subroutine declaration
+sub main();		# main program body
+sub error($);		# error(string: $msg);
+sub warning($);		# warning(string: $msg);
+sub console_log($);	# console_log(string: $msg);
+sub parse_argv();	# requires defined %config
+sub config_check();	# requires defined %config
+sub create_gd();	# returns $imgAccess, $graphAccess;
+sub write_img($$);	# write image to file
+sub plot($$\$\@\@\@\@);	# plot($type,$gdimg,@totalData,@hitData,@missData,$imsData);
+sub hr_units($);	# hr_units(int: $num);
+sub hr_bytes($);	# hr_bytes(int: $num);
+sub hr_digits($$);	# hr_digits(int: $num, char: $units);
+sub dec_truncate($$);	# dec_truncate($num,$decplaces);
+sub comma_sep($);	# comma_sep($num);
+sub help();		# help(string: $helpCmd);
+
+# Parse and check arguments
+parse_argv();
+if (config_check()) {
+	main();
+}
+else {
+	error("Configuration check failed.");
+}
+
+# Main subroutine
+sub main() {
+
+	#
+	# NOTES
+	#
+	# Graph stats: Total, Hit, Miss, IMS/Err
+	#		TCP, UDP
+	#		Access, Transfer
+	#
+
+	my ($have_tcp, $have_udp) = (0,0);
+	my $have_cumulative = 0;
+	my $have_transfer = 1;
+	my $logStart = 0;
+	my $title = "Squid Graph Logfile Analysis Report";
+	my $graphLength = 60*60*24; # Graph is 24 hours long
+	my $progStart = $^T;
+	my $progSpeed = 0;
+	my $duration = 0;
+	my $lineCounter = 0;
+	my $errorLines = 0;
+
+	# Statistics data
+	my @tcpAccessTotal;
+	my @tcpAccessHit;
+	my @tcpAccessMiss;
+	my @tcpAccessIMS;
+	my @tcpTransferTotal;
+	my @tcpTransferHit;
+	my @tcpTransferMiss;
+	my @tcpTransferIMS;
+	my @tcpTimeTotal;
+	my @tcpTimeHit;
+	my @tcpTimeMiss;
+	my @tcpTimeIMS;
+	my @udpAccessTotal;
+	my @udpAccessHit;
+	my @udpAccessMiss;
+	my @udpTransferTotal;
+	my @udpTransferHit;
+	my @udpTransferMiss;
+	my @udpTimeTotal;
+	my @udpTimeHit;
+	my @udpTimeMiss;
+
+	# Image objects
+	my ($tcpAccessImg, $tcpTransferImg, $tcpTimeImg);
+	my ($udpAccessImg, $udpTransferImg, $udpTimeImg);
+
+	# Stats counter
+	my $i = 0;
+
+	console_log("Squid-graph $VER OK. Program started.");
+	console_log("Graph domain is $graphLength seconds.");
+
+	# Report title specified
+	if (exists $config{'title'}) {
+		$title = $config{'title'};
+	}
+	console_log("Setting report title to \"$title\".");
+
+	# Determine if cumulative graps were configured
+	if (exists $config{'cumulative'}) {
+		console_log("Configured for cumulative curves.");
+		$have_cumulative = 1;
+	}
+	else {
+		console_log("Configured for default histograms.");
+	}
+
+	# Determine if TCP or UDP configured
+	if (exists $config{'tcp-only'}) {
+		console_log('Configured have_tcp.');
+		($have_tcp, $have_udp) = (1,0);
+	}
+	elsif (exists $config{'udp-only'}) {
+		console_log('Configured have_udp.');
+		($have_tcp, $have_udp) = (0,1);
+	}
+	else {
+		console_log('Configured have_tcp and have_udp.');
+		($have_tcp, $have_udp) = (1,1);
+	}
+
+	# Determine have_transfer
+	if (exists $config{'no-transfer-duration'}) {
+		console_log("Transfer duration graph(s) disabled.");
+		$have_transfer = 0;
+	}
+	elsif ($have_cumulative) {
+		console_log("Transfer duration graph(s) automatically disabled.");
+		$have_transfer = 0;
+	}
+	else {
+		console_log("Transfer duration graph(s) enabled.");
+	}
+
+	# Was --start or --end specified?
+	if (exists $config{'start'}) {
+		$logStart = $config{'start'};
+	}
+	elsif (exists $config{'end'}) {
+		$logStart = $config{'end'} - $graphLength;
+	}
+	else {
+		$logStart = time() - $graphLength;
+	}
+	console_log("Configured start time to $logStart.");
+
+	# Are we ready?
+	console_log("Reading STDIN for logfile input.");
+
+	foreach (<STDIN>) {
+		# Increment line counter
+		$lineCounter++;
+
+		# Activity log
+		#console_log("Read $lineCounter lines from STDIN.") if ($lineCounter%80000 == 0);
+
+		# Split the line
+		my @logParts = split(' ');
+
+		# Check if lines are erroneous, i.e. incorrect number of parts
+		if ($#logParts != 9) {
+			$errorLines++;
+			console_log("Invalid log data at line $lineCounter. Error #$errorLines.");
+		}
+
+		# Skip irrelevant lines
+		elsif (($logParts[0] >= $logStart) && ($i < 288)) {
+
+			# Gets the x value correct
+			while ($logStart + (300 * ($i + 1)) < $logParts[0]) { $i++; }
+
+			# Declare vars
+			my $isTCP = 0;
+			my $isUDP = 0;
+			my $isHit = 0;
+			my $isMiss = 0;
+
+			# Is this a hit or miss?
+			$isHit = 1 if ($logParts[3] =~ /HIT/);
+			$isMiss = 1 if ($logParts[3] =~ /MISS/);
+
+			# Protocol check
+			if ($logParts[3] =~ /TCP/) { $isTCP = 1 if ($have_tcp); }
+			elsif ($logParts[3] =~ /UDP/) { $isUDP = 1 if ($have_udp); }
+
+			# Unknown protocol? Can't be!
+			else {
+				$errorLines++;
+				console_log("Invalid log data at line $lineCounter. Error #$errorLines.");
+			}
+
+			# Collect data
+			if ($isTCP) {
+				$tcpAccessTotal[$i]++;
+				$tcpTransferTotal[$i] += $logParts[4];
+				$tcpTimeTotal[$i] += $logParts[1]/1000;
+				if ($isHit) {
+					$tcpAccessHit[$i]++;
+					$tcpTransferHit[$i] += $logParts[4];
+					$tcpTimeHit[$i] += $logParts[1]/1000;
+					if ($logParts[3] =~ /IMS/) {
+						$tcpAccessIMS[$i]++;
+						$tcpTransferIMS[$i] += $logParts[4];
+					}
+				}
+				elsif ($isMiss) {
+					$tcpAccessMiss[$i]++;
+					$tcpTransferMiss[$i] += $logParts[4];
+					$tcpTimeMiss[$i] += $logParts[1]/1000;
+				}
+			}
+			elsif ($isUDP) {
+				$udpAccessTotal[$i]++;
+				$udpTransferTotal[$i] += $logParts[4];
+				$udpTimeTotal[$i] += $logParts[1]/1000;
+				if ($isHit) {
+					$udpAccessHit[$i]++;
+					$udpTransferHit[$i] += $logParts[4];
+					$udpTimeHit[$i] += $logParts[1]/1000;
+				}
+				elsif ($isMiss) {
+					$udpAccessMiss[$i]++;
+					$udpTransferMiss[$i] += $logParts[4];
+					$udpTimeMiss[$i] += $logParts[1]/1000;
+				}
+			}
+
+			# Undefine vars
+			undef $isTCP;
+			undef $isUDP;
+			undef $isHit;
+			undef $isMiss;
+		}
+
+		undef @logParts;
+	}
+
+	# Calculate transfer duration averages
+	if ($have_transfer) {
+		# Activity log
+		console_log("Calculating averages for TCP/UDP transfer duration.");
+
+		# Start looping
+		my $i;
+		for ($i = 0; $i < 288; $i++) {
+			if ($have_tcp) {
+				$tcpTimeTotal[$i] = ($tcpAccessTotal[$i] == 0)?0:$tcpTimeTotal[$i]/$tcpAccessTotal[$i];
+				$tcpTimeHit[$i] = ($tcpAccessHit[$i] == 0)?0:$tcpTimeHit[$i]/$tcpAccessHit[$i];
+				$tcpTimeMiss[$i] = ($tcpAccessMiss[$i] == 0)?0:$tcpTimeMiss[$i]/$tcpAccessMiss[$i]; 
+			}
+			if ($have_udp) {
+				$udpTimeTotal[$i] = ($udpAccessTotal[$i] == 0)?0:$udpTimeTotal[$i]/$udpAccessTotal[$i];
+				$udpTimeHit[$i] = ($udpAccessHit[$i] == 0)?0:$udpTimeHit[$i]/$udpAccessHit[$i];
+				$udpTimeMiss[$i] = ($udpAccessMiss[$i] == 0)?0:$udpTimeMiss[$i]/$udpAccessMiss[$i];
+			}
+		}
+		undef $i;
+	}
+
+	# Cumulate the data
+	if ($have_cumulative) {
+		# Activity log
+		console_log("Cumulating log data for TCP/UDP graphs.");
+
+		# Start looping
+		my $i;
+		for ($i = 0; $i < 288; $i++) {
+			if ($have_tcp) {
+				$tcpAccessTotal[$i + 1] += $tcpAccessTotal[$i];
+				$tcpAccessHit[$i + 1] += $tcpAccessHit[$i];
+				$tcpAccessMiss[$i + 1] += $tcpAccessMiss[$i];
+				$tcpAccessIMS[$i + 1] += $tcpAccessIMS[$i];
+				$tcpTransferTotal[$i + 1] += $tcpTransferTotal[$i];
+				$tcpTransferHit[$i + 1] += $tcpTransferHit[$i];
+				$tcpTransferMiss[$i + 1] += $tcpTransferMiss[$i];
+				$tcpTransferIMS[$i + 1] += $tcpTransferIMS[$i];
+			}
+			if ($have_udp) {
+				$udpAccessTotal[$i + 1] += $udpAccessTotal[$i];
+				$udpAccessHit[$i + 1] += $udpAccessHit[$i];
+				$udpAccessMiss[$i + 1] += $udpAccessMiss[$i];
+				$udpTransferTotal[$i + 1] += $udpTransferTotal[$i];
+				$udpTransferHit[$i + 1] += $udpTransferHit[$i];
+				$udpTransferMiss[$i + 1] += $udpTransferMiss[$i];
+			}
+		}
+		undef $i;
+	}
+
+	# Done parsing, now calculate some stats
+	$duration = time() - $progStart;
+	$duration = 1 if ($duration <= 0);
+	$progSpeed = dec_truncate($lineCounter/$duration,2);
+
+	# Do some console logging
+	console_log("Done reading $lineCounter lines from logfile on STDIN. ($errorLines $Lang::tr{'errors'})");
+	console_log("Analysis duration is $duration seconds, $progSpeed lines/sec.");
+
+	# Create image objects
+	if ($have_tcp) {
+		console_log('Creating TCP image objects.');
+		$tcpAccessImg = create_gd();
+		$tcpTransferImg = create_gd();
+		$tcpTimeImg = create_gd() if ($have_transfer);
+	}
+	if ($have_udp) {
+		console_log('Creating UDP image objects.');
+		$udpAccessImg = create_gd();
+		$udpTransferImg = create_gd();
+		$udpTimeImg = create_gd() if ($have_transfer);
+	}
+
+	# Plot the graphs
+	my $c;
+	my @nullArr;
+	$c = 'C' if ($have_cumulative);
+	if ($have_tcp) {
+		console_log("Plotting graph of TCP accesses.");
+		plot("TA$c", $logStart, $tcpAccessImg,@tcpAccessTotal,@tcpAccessHit,@tcpAccessMiss,@tcpAccessIMS);
+		console_log("Plotting graph of TCP transfers.");
+		plot("TX$c", $logStart, $tcpTransferImg,@tcpTransferTotal,@tcpTransferHit,@tcpTransferMiss,@tcpAccessIMS);
+		if ($have_transfer) {
+			console_log("Plotting graph of TCP transfer duration.");
+			plot("TD", $logStart, $tcpTimeImg,@tcpTimeTotal,@tcpTimeHit,@tcpTimeMiss,@nullArr);
+		}
+	}
+	if ($have_udp) {
+		console_log("Plotting graph of UDP accesses.");
+		plot("UA$c", $logStart, $udpAccessImg,@udpAccessTotal,@udpAccessHit,@udpTransferMiss,@nullArr);
+		console_log("Plotting graph of UDP transfers.");
+		plot("UX$c", $logStart, $udpTransferImg,@udpTransferTotal,@udpTransferHit,@udpTransferMiss,@nullArr);
+		if ($have_transfer) {
+			console_log("Plotting graph of UDP transfer duration.");
+			plot("UD", $logStart, $udpTimeImg,@udpTimeTotal,@udpTimeHit,@udpTimeMiss,@nullArr);
+		}
+	}
+	undef @nullArr;
+	undef $c;
+
+	# Graph plotted! Now we save it
+	if ($have_tcp) {
+		write_img($tcpAccessImg,'tcp-access.png');
+		write_img($tcpTransferImg,'tcp-transfer.png');
+		write_img($tcpTimeImg,'tcp-duration.png') if ($have_transfer);
+	}
+	if ($have_udp) {
+		write_img($udpAccessImg,'udp-access.png');
+		write_img($udpTransferImg,'udp-transfer.png');
+		write_img($udpTimeImg,'udp-duration.png') if ($have_transfer);
+	}
+
+	# Time to gather additional statistics which are not on the graph :)
+	console_log("Gathering additional statistics.");
+
+	my $tcpAccessTotals = 0;
+	my $tcpAccessAverage = 0;
+
+	my $tcpAccessMissTotals = 0;
+	my $tcpAccessMissPercentage = 0;
+	my $tcpAccessMissAverage = 0;
+
+	my $tcpAccessHitTotals = 0;
+	my $tcpAccessHitPercentage = 0;
+	my $tcpAccessHitAverage = 0;
+
+	my $tcpAccessIMSTotals = 0;
+	my $tcpAccessIMSPercentage = 0;
+	my $tcpAccessIMSAverage = 0;
+
+	my $tcpTransferTotals = 0;
+	my $tcpTransferAverage = 0;
+
+	my $tcpTransferHitTotals = 0;
+	my $tcpTransferHitPercentage = 0;
+	my $tcpTransferHitAverage = 0;
+
+	my $tcpTransferMissTotals = 0;
+	my $tcpTransferMissPercentage = 0;
+	my $tcpTransferMissAverage = 0;
+
+	my $tcpTransferIMSTotals = 0;
+	my $tcpTransferIMSPercentage = 0;
+	my $tcpTransferIMSAverage = 0;
+
+	my $tcpTimeTotals = 0;
+	my $tcpTimeAverage = 0;
+	my $tcpTimeHitTotals = 0;
+	my $tcpTimeHitAverage = 0;
+	my $tcpTimeMissTotals = 0;
+	my $tcpTimeMissAverage = 0;
+
+	my $udpAccessTotals = 0;
+	my $udpAccessAverage = 0;
+
+	my $udpAccessMissTotals = 0;
+	my $udpAccessMissPercentage = 0;
+	my $udpAccessMissAverage = 0;
+
+	my $udpAccessHitTotals = 0;
+	my $udpAccessHitPercentage = 0;
+	my $udpAccessHitAverage = 0;
+
+	my $udpTransferTotals = 0;
+	my $udpTransferAverage = 0;
+
+	my $udpTransferMissTotals = 0;
+	my $udpTransferMissPercentage = 0;
+	my $udpTransferMissAverage = 0;
+
+	my $udpTransferHitTotals = 0;
+	my $udpTransferHitPercentage = 0;
+	my $udpTransferHitAverage = 0;
+
+	my $udpTimeTotals = 0;
+	my $udpTimeAverage = 0;
+	my $udpTimeHitTotals = 0;
+	my $udpTimeHitAverage = 0;
+	my $udpTimeMissTotals = 0;
+	my $udpTimeMissAverage = 0;
+
+
+	# Get the totals
+
+	if ($have_cumulative) {
+		if ($have_tcp) {
+			$tcpAccessTotals = $tcpAccessTotal[$#tcpAccessTotal];
+			$tcpAccessHitTotals = $tcpAccessHit[$#tcpAccessHit];
+			$tcpAccessMissTotals = $tcpAccessMiss[$#tcpAccessMiss];
+			$tcpAccessIMSTotals = $tcpAccessIMS[$#tcpAccessIMS];
+
+			$tcpTransferTotals = $tcpTransferTotal[$#tcpTransferTotal];
+			$tcpTransferHitTotals = $tcpTransferHit[$#tcpTransferHit];
+			$tcpTransferMissTotals = $tcpTransferMiss[$#tcpTransferMiss];
+			$tcpTransferIMSTotals = $tcpTransferIMS[$#tcpTransferIMS];
+		}
+		if ($have_udp) {
+			$udpAccessTotals = $udpAccessTotal[$#udpAccessTotal];
+			$udpAccessHitTotals = $udpAccessHit[$#udpAccessHit];
+			$udpAccessMissTotals = $udpAccessMiss[$#udpAccessMiss];
+
+			$udpTransferTotals = $udpTransferTotal[$#udpTransferTotal];
+			$udpTransferHitTotals = $udpTransferHit[$#udpTransferHit];
+			$udpTransferMissTotals = $udpTransferMiss[$#udpTransferMiss];
+		}
+
+	}
+	else {
+		my $i;
+		for ($i = 0; $i < 288; $i++) {
+			if ($have_tcp) {
+				$tcpAccessTotals += $tcpAccessTotal[$i];
+				$tcpAccessHitTotals += $tcpAccessHit[$i];
+				$tcpAccessMissTotals += $tcpAccessMiss[$i];
+				$tcpAccessIMSTotals += $tcpAccessIMS[$i];
+
+				$tcpTransferTotals += $tcpTransferTotal[$i];
+				$tcpTransferHitTotals += $tcpTransferHit[$i];
+				$tcpTransferMissTotals += $tcpTransferMiss[$i];
+				$tcpTransferIMSTotals += $tcpTransferIMS[$i];
+
+				if ($have_transfer) {
+					$tcpTimeTotals += $tcpTimeTotal[$i];
+					$tcpTimeHitTotals += $tcpTimeHit[$i];
+					$tcpTimeMissTotals += $tcpTimeMiss[$i];
+				}
+			}
+
+			if ($have_udp) {
+				$udpAccessTotals += $udpAccessTotal[$i];
+				$udpAccessHitTotals += $udpAccessHit[$i];
+				$udpAccessMissTotals += $udpAccessMiss[$i];
+
+				$udpTransferTotals += $udpTransferTotal[$i];
+				$udpTransferHitTotals += $udpTransferHit[$i];
+				$udpTransferMissTotals += $udpTransferMiss[$i];
+
+				if ($have_transfer) {
+					$udpTimeTotals += $udpTimeTotal[$i];
+					$udpTimeHitTotals += $udpTimeHit[$i];
+					$udpTimeMissTotals += $udpTimeMiss[$i];
+				}
+			}
+		}
+		undef $i;
+	}
+
+	# Calculate averages and percentages
+
+	sub percentage($$) {
+		my $val = shift;
+		my $tot = shift;
+		return dec_truncate(($tot == 0)?0:(($val/$tot) * 100),2);
+	};
+
+	if ($have_tcp) {
+			$tcpAccessAverage = dec_truncate($tcpAccessTotals/24,2);
+			$tcpAccessHitAverage = dec_truncate($tcpAccessHitTotals/24,2);
+			$tcpAccessMissAverage = dec_truncate($tcpAccessMissTotals/24,2);
+			$tcpAccessIMSAverage = dec_truncate($tcpAccessIMSTotals/24,2);
+
+			$tcpTransferAverage = hr_bytes($tcpTransferTotals/24);
+			$tcpTransferHitAverage = hr_bytes($tcpTransferHitTotals/24);
+			$tcpTransferMissAverage = hr_bytes($tcpTransferMissTotals/24);
+			$tcpTransferIMSAverage = hr_bytes($tcpTransferIMSTotals/24);
+
+			$tcpAccessHitPercentage = percentage($tcpAccessHitTotals,$tcpAccessTotals);
+			$tcpAccessMissPercentage = percentage($tcpAccessMissTotals,$tcpAccessTotals);
+
+			$tcpTransferHitPercentage = percentage($tcpTransferHitTotals,$tcpTransferTotals);
+			$tcpTransferMissPercentage = percentage($tcpTransferMissTotals,$tcpTransferTotals);
+
+			if ($have_transfer) {
+				$tcpTimeAverage = dec_truncate($tcpTimeTotals/288,2);
+				$tcpTimeHitAverage = dec_truncate($tcpTimeHitTotals/288,2);
+				$tcpTimeMissAverage = dec_truncate($tcpTimeMissTotals/288,2);
+			}
+	}
+
+	if ($have_udp) {
+			$udpAccessAverage = dec_truncate($udpAccessTotals/24,2);
+			$udpAccessHitAverage = dec_truncate($udpAccessHitTotals/24,2);
+			$udpAccessMissAverage = dec_truncate($udpAccessMissTotals/24,2);
+
+			$udpTransferAverage = hr_bytes($udpTransferTotals/24);
+			$udpTransferHitAverage = hr_bytes($udpTransferHitTotals/24);
+			$udpTransferMissAverage = hr_bytes($udpTransferMissTotals/24);
+
+			$udpAccessHitPercentage = percentage($udpAccessHitTotals,$udpAccessTotals);
+			$udpAccessMissPercentage = percentage($udpAccessMissTotals,$udpAccessTotals);
+
+			$udpTransferHitPercentage = percentage($udpTransferHitTotals,$udpTransferTotals);
+			$udpTransferMissPercentage = percentage($udpTransferMissTotals,$udpTransferTotals);
+
+			if ($have_transfer) {
+				$udpTimeAverage = dec_truncate($udpTimeTotals/288,2);
+				$udpTimeHitAverage = dec_truncate($udpTimeHitTotals/288,2);
+				$udpTimeMissAverage = dec_truncate($udpTimeMissTotals/288,2);
+			}
+	}
+
+	# Some tiny date/time conversions
+	my $progStartTime = localtime($progStart);
+	my $logStartTime = localtime($logStart);
+	my $logEndTime = localtime($logStart + 86400);
+
+	# Colours
+	my $bgcolor = "$color{'hbg'}[0]$color{'hbg'}[1]$color{'hbg'}[2]";
+	my $fgcolor = "$color{'fg'}[0]$color{'fg'}[1]$color{'fg'}[2]";
+	my $grcolor = "$color{'gr'}[0]$color{'gr'}[1]$color{'gr'}[2]";
+	my $hitcolor = "$color{'hit'}[0]$color{'hit'}[1]$color{'hit'}[2]";
+	my $misscolor = "$color{'miss'}[0]$color{'miss'}[1]$color{'miss'}[2]";
+	my $imscolor = "$color{'ims'}[0]$color{'ims'}[1]$color{'ims'}[2]" if ($have_tcp);
+
+	# Make some things more redable to the human eye
+	if ($have_tcp) {
+		$tcpAccessTotals = dec_truncate($tcpAccessTotals,2);
+		$tcpAccessHitTotals = dec_truncate($tcpAccessHitTotals,2);
+		$tcpAccessMissTotals = dec_truncate($tcpAccessMissTotals,2);
+		$tcpAccessIMSTotals = dec_truncate($tcpAccessIMSTotals,2);
+		$tcpTransferTotals = hr_bytes($tcpTransferTotals);
+		$tcpTransferHitTotals = hr_bytes($tcpTransferHitTotals);
+		$tcpTransferMissTotals = hr_bytes($tcpTransferMissTotals);
+		$tcpTransferIMSTotals = hr_bytes($tcpTransferIMSTotals);
+	}
+	if ($have_udp) {
+		$udpAccessTotals = dec_truncate($udpAccessTotals,2);
+		$udpAccessHitTotals = dec_truncate($udpAccessHitTotals,2);
+		$udpAccessMissTotals = dec_truncate($udpAccessMissTotals,2);
+		$udpTransferTotals = hr_bytes($udpTransferTotals);
+		$udpTransferHitTotals = hr_bytes($udpTransferHitTotals);
+		$udpTransferMissTotals = hr_bytes($udpTransferMissTotals);
+	}
+
+
+	console_log("Writing index.html file.");
+
+	open(IDX, ">$config{'output-dir'}/index.html") ||
+		error("Can't write to file $config{'output-dir'}/index.html. Check directory permissions?");
+
+	print IDX "<HTML>\n";
+	print IDX "<HEAD>\n";
+	print IDX "<TITLE>$title</TITLE>\n";
+	print IDX "</HEAD>\n";
+	print IDX "<BODY BGCOLOR=\"#$bgcolor\" TEXT=\"#$fgcolor\">\n";
+	print IDX "<H1>$title</H1>\n";
+	print IDX "<BR>\n";
+
+	print IDX "<TABLE BORDER=0>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'generated'}:</B></TD><TD style=padding-left:5px>$progStartTime</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'lines analyzed'}:</B></TD><TD style=padding-left:5px>$lineCounter $Lang::tr{'lines'} ($errorLines $Lang::tr{'errors'})</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'analysis duration'}:</B></TD><TD style=padding-left:5px>$duration $Lang::tr{'seconds'}</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'analysis speed'}:</B></TD><TD style=padding-left:5px>$progSpeed $Lang::tr{'lines sec'}</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'graph start'}:</B></TD><TD style=padding-left:5px>$logStartTime</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'graph end'}:</B></TD><TD style=padding-left:5px>$logEndTime</TD></TR>\n";
+	print IDX "<TR><TD><B>$Lang::tr{'graph domain'}:</B></TD><TD style=padding-left:5px>24 $Lang::tr{'hours'} ($Lang::tr{'24hseconds'})</TD></TR>\n";
+	print IDX "</TABLE>\n";
+
+	sub generate_html_row($$$) {
+		my $color = shift;
+		my $key = shift;
+		my $value = shift;
+		my $ret;
+
+		$ret = "<TR>";
+		$ret .= "<TD ALIGN=RIGHT><FONT SIZE=-1 COLOR=#$color><B>$key:</B></FONT></TD>";
+		$ret .= "<TD style=padding-left:5px ALIGN=LEFT><FONT SIZE=-1>$value</FONT></TD>";
+		$ret .= "</TR>\n";
+
+		undef $color;
+		undef $key;
+		undef $value;
+
+		return $ret;
+	}
+
+	if ($have_tcp) {
+		print IDX "<BR><HR><BR>\n";
+		print IDX "<H3>$Lang::tr{'graph of tcp accesses'}</H3>\n" if (!$have_cumulative);
+		print IDX "<H3>$Lang::tr{'cumulative graph of tcp accesses'}</H3>\n" if ($have_cumulative);
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX "<TR>\n";
+		print IDX "<TD><IMG SRC=tcp-access.png></TD>\n";
+		print IDX "<TD>\n";
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX generate_html_row($grcolor,"$Lang::tr{'total accesses'}",$tcpAccessTotals);
+		print IDX generate_html_row($grcolor,"$Lang::tr{'average accesses'}","$tcpAccessAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'total cache hits'}",$tcpAccessHitTotals);
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'average cache hits'}","$tcpAccessHitAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'percent cache hits'}","$tcpAccessHitPercentage %");
+		print IDX generate_html_row($imscolor,"$Lang::tr{'total cache ims hits'}",$tcpAccessIMSTotals);
+		print IDX generate_html_row($imscolor,"$Lang::tr{'average cache ims hits'}","$tcpAccessIMSAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'total cache misses'}",$tcpAccessMissTotals);
+		print IDX generate_html_row($misscolor,"$Lang::tr{'average cache misses'}","$tcpAccessMissAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'percent cache misses'}","$tcpAccessMissPercentage %");
+		print IDX "</TABLE>\n";
+		print IDX "</TD>\n";
+		print IDX "</TR>\n";
+		print IDX "</TABLE>\n";
+		print IDX "<H3>$Lang::tr{'graph of tcp transfers'}</H3>\n" if (!$have_cumulative);
+		print IDX "<H3>$Lang::tr{'cumulative graph of tcp transfers'}</H3>\n" if ($have_cumulative);
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX "<TR>\n";
+		print IDX "<TD><IMG SRC=tcp-transfer.png></TD>\n";
+		print IDX "<TD>\n";
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX generate_html_row($grcolor,"$Lang::tr{'total transfers'}",$tcpTransferTotals);
+		print IDX generate_html_row($grcolor,"$Lang::tr{'average transfers'}","$tcpTransferAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'total cache hits'}",$tcpTransferHitTotals);
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'average cache hits'}","$tcpTransferHitAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'percent cache hits'}","$tcpTransferHitPercentage %");
+		print IDX generate_html_row($imscolor,"$Lang::tr{'total cache ims hits'}",$tcpTransferIMSTotals);
+		print IDX generate_html_row($imscolor,"$Lang::tr{'average cache ims hits'}","$tcpTransferIMSAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'total cache misses'}",$tcpTransferMissTotals);
+		print IDX generate_html_row($misscolor,"$Lang::tr{'average cache misses'}","$tcpTransferMissAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'percent cache misses'}","$tcpTransferMissPercentage %");
+		print IDX "</TABLE>\n";
+		print IDX "</TD>\n";
+		print IDX "</TR>\n";
+		print IDX "</TABLE>\n";
+		if ($have_transfer) {
+			print IDX "<H3>$Lang::tr{'graph of average tcp transfer duration'}</H3>\n";
+			print IDX "<TABLE BORDER=0>\n";
+			print IDX "<TR>\n";
+			print IDX "<TD><IMG SRC=tcp-duration.png></TD>\n";
+			print IDX "<TD>\n";
+			print IDX "<TABLE BORDER=0>\n";
+			print IDX generate_html_row($grcolor,"$Lang::tr{'avg transfer duration'}","$tcpTimeAverage $Lang::tr{'seconds'}");
+			print IDX generate_html_row($hitcolor,"$Lang::tr{'avg cache hit duration'}","$tcpTimeHitAverage $Lang::tr{'seconds'}");
+			print IDX generate_html_row($misscolor,"$Lang::tr{'avg cache miss duration'}","$tcpTimeMissAverage $Lang::tr{'seconds'}");
+			print IDX "</TABLE>\n";
+			print IDX "</TD>\n";
+			print IDX "</TR>\n";
+			print IDX "</TABLE>\n";
+		}
+	}
+
+	if ($have_udp) {
+		print IDX "<BR><HR><BR>\n";
+		print IDX "<H3>$Lang::tr{'graph of udp accesses'}</H3>\n" if (!$have_cumulative);
+		print IDX "<H3>$Lang::tr{'cumulative graph of udp accesses'}</H3>\n" if ($have_cumulative);
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX "<TR>\n";
+		print IDX "<TD><IMG SRC=udp-access.png></TD>\n";
+		print IDX "<TD>\n";
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX generate_html_row($grcolor,"$Lang::tr{'total accesses'}",$udpAccessTotals);
+		print IDX generate_html_row($grcolor,"$Lang::tr{'average accesses'}","$udpAccessAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'total cache hits'}",$udpAccessHitTotals);
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'average cache hits'}","$udpAccessHitAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'percent cache hits'}","$udpAccessHitPercentage %");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'total cache misses'}",$udpAccessMissTotals);
+		print IDX generate_html_row($misscolor,"$Lang::tr{'average cache misses'}","$udpAccessMissAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'percent cache misses'}","$udpAccessMissPercentage %");
+		print IDX "</TABLE>\n";
+		print IDX "</TD>\n";
+		print IDX "</TR>\n";
+		print IDX "</TABLE>\n";
+		print IDX "<H3>$Lang::tr{'graph of udp transfers'}</H3>\n" if (!$have_cumulative);
+		print IDX "<H3>$Lang::tr{'cumulative graph of udp transfers'}</H3>\n" if ($have_cumulative);
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX "<TR>\n";
+		print IDX "<TD><IMG SRC=udp-transfer.png></TD>\n";
+		print IDX "<TD>\n";
+		print IDX "<TABLE BORDER=0>\n";
+		print IDX generate_html_row($grcolor,"$Lang::tr{'total transfers'}",$udpTransferTotals);
+		print IDX generate_html_row($grcolor,"$Lang::tr{'average transfers'}","$udpTransferAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'total cache hits'}",$udpTransferHitTotals);
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'average cache hits'}","$udpTransferHitAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($hitcolor,"$Lang::tr{'percent cache hits'}","$udpTransferHitPercentage %");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'total cache misses'}",$udpTransferMissTotals);
+		print IDX generate_html_row($misscolor,"$Lang::tr{'average cache misses'}","$udpTransferMissAverage $Lang::tr{'per hour'}");
+		print IDX generate_html_row($misscolor,"$Lang::tr{'percent cache misses'}","$udpTransferMissPercentage %");
+		print IDX "</TABLE>\n";
+		print IDX "</TD>\n";
+		print IDX "</TR>\n";
+		print IDX "</TABLE>\n";
+		if ($have_transfer) {
+			print IDX "<H3>$Lang::tr{'graph of average udp transfer duration'}</H3>\n";
+			print IDX "<TABLE BORDER=0>\n";
+			print IDX "<TR>\n";
+			print IDX "<TD><IMG SRC=udp-duration.png></TD>\n";
+			print IDX "<TD>\n";
+			print IDX "<TABLE BORDER=0>\n";
+			print IDX generate_html_row($grcolor,"$Lang::tr{'avg transfer duration'}","$udpTimeAverage $Lang::tr{'seconds'}");
+			print IDX generate_html_row($hitcolor,"$Lang::tr{'avg cache hit duration'}","$udpTimeHitAverage $Lang::tr{'seconds'}");
+			print IDX generate_html_row($misscolor,"$Lang::tr{'avg cache miss duration'}","$udpTimeMissAverage $Lang::tr{'seconds'}");
+			print IDX "</TABLE>\n";
+			print IDX "</TD>\n";
+			print IDX "</TR>\n";
+			print IDX "</TABLE>\n";
+		}
+	}
+
+	print IDX <<EOF;
+
+<BR>
+<HR>
+<BR>
+
+<A HREF="http://squid-graph.sourceforge.net/"><IMG SRC="logo.png" BORDER="0"></A>
+<BR>
+version $VER
+
+</BODY>
+
+</HTML>
+EOF
+	close(IDX);
+
+	console_log("Done.");
+	console_log("Remember to copy logo.png found in your Squid Graph images/ directory to $config{'output-dir'}!");
+
+}
+
+# Error output
+sub error($) {
+	my $LOG = shift;
+	die "ERROR: $LOG Exiting.\n";
+}
+
+# Warning output
+sub warning($) {
+	my $LOG = shift;
+	print STDERR "WARNING: $LOG\n";
+	undef $LOG;
+}
+
+# Just for console logging
+sub console_log($) {
+	if ($CONSOLELOG) {
+		my $TIME = localtime(time());
+		my $LOG = shift;
+		print "[$TIME] $LOG\n";
+		undef $LOG;
+	}
+}
+
+# Help
+sub help() {
+print <<EOF;
+
+Squid Graph $VER Help ($^O, perl $])
+
+Usage examples:
+  squid-graph [options] < logfile.log
+  cat logfile.log | squid-graph [options]
+  tail -n 10000 logfile.log | squid-graph [options]
+
+Command line options (options marked * are compulsary):
+
+    * --output-dir=output-dir (or -o=output-dir)
+        Specifies the directory which stores the output files.
+
+      --start=start-time (or -s=start-time)
+        Specifies the graph start time in seconds since 1970.
+        When not specified, 24 hours before the current time is
+        used as default.
+
+      --end=end-time (or -e=end-time)
+        Specifies the graph end time in seconds since 1970.
+        When not specified, the current time is used as default.
+
+      --title="report-title"
+        Specifies the report title. When not specified, "Squid
+        Graph Logfile Analysis Report" is used as default.
+
+      --tcp-only
+        Specifies that only TCP access and transfer graphs are
+        generated. When not specified, both TCP and UDP graphs
+        are generated.
+
+      --udp-only
+        Specifies that only UDP access and transfer graphs are
+        generated. When not specified, both TCP and UDP graphs
+        are generated.
+
+      --cumulative (or -c)
+        Enables generation of cumulative graphs instead of the
+        default histograms.
+
+      --no-transfer-duration (or -d)
+        Disables plotting of average transfer duration graph(s).
+
+      --no-console-log (or -n)
+        Disables logging of messages to console.
+
+      --help (or -h)
+        Displays this help message.
+
+For more info, please visit http://squid-graph.sourceforge.net/
+
+EOF
+exit;
+}
+
+# Parse command line arguments
+sub parse_argv() {
+
+	# no arguments?
+	if ($#ARGV == -1) {
+		&help;
+	}
+
+	# scan command line arguments
+	foreach (@ARGV) {
+		my @parms = split(/=/);
+
+		if (($parms[0] eq "--help") || ($parms[0] eq "-h")) {
+			help();
+		}
+		elsif (($parms[0] eq "--no-console-log") || ($parms[0] eq "-n")) {
+			$CONSOLELOG = 0;
+		}
+		elsif (($parms[0] eq "--output-dir") || ($parms[0] eq "-o")) {
+			if ($parms[1] eq "") {
+				error("Output directory cannot be blank.");
+			}
+			elsif (-e $parms[1]) {
+				$config{'output-dir'} = $parms[1];
+			}
+			else {
+				error("Output directory $parms[1] does not exist.");
+			}
+		}
+		elsif (($parms[0] eq "--start") || ($parms[0] eq "-s")) {
+			if ($parms[1] eq "") {
+				warning("Starting time cannot be blank. Using defaults.");
+			}
+			else {
+				$config{'start'} = $parms[1];
+			}
+		}
+		elsif (($parms[0] eq "--end") || ($parms[0] eq "-e")) {
+			if ($parms[1] eq "") {
+				warning("End time cannot be blank. Using defaults.");
+			}
+			else {
+				$config{'end'} = $parms[1];
+			}	
+		}
+		elsif ($parms[0] eq "--title") {
+			if ($parms[1] eq "") {
+				warning("Title cannot be blank. Using defaults.");
+			}
+			else {
+				$config{'title'} = $parms[1];
+			}
+		}
+		elsif ($parms[0] eq "--tcp-only") { $config{'tcp-only'} = 1; }
+		elsif ($parms[0] eq "--udp-only") { $config{'udp-only'} = 1; }
+		elsif (($parms[0] eq "--cumulative") || ($parms[0] eq "-c")) { $config{'cumulative'} = 1; }
+		elsif (($parms[0] eq "--no-transfer-duration") || ($parms[0] eq "-d")) { $config{'no-transfer-duration'} = 1; }
+		elsif (($parms[0] eq "--no-console-log") || ($parms[0] eq "-n")) { $CONSOLELOG = 0; }
+		else {
+			warning("Unknown argument $_ from command line.");
+		}
+
+		undef @parms;
+	}
+}
+
+# Checks configuration validity
+sub config_check() {
+	my $noerror = 1;
+
+	if ($config{'output-dir'} eq "") {
+		warning("Output directory not configured.");
+		$noerror = 0;
+	}
+	else {
+		# Remove trailing slash in output dir
+		my $tmp = $config{'output-dir'};
+		if (chop($tmp) eq "/") {
+			chop($config{'output-dir'});
+		}
+	}
+
+	if ((exists $config{'start'}) && (exists $config{'end'})) {
+		warning("Cannot specify both --start and --end values. Use either --start OR --end.");
+		$noerror = 0;
+	}
+
+	if ((exists $config{'udp-only'}) && (exists $config{'tcp-only'})) {
+		warning("Both --udp-only and --tcp-only specified. Are you nuts? That's the default!");
+		$noerror = 0;
+	}
+
+	if (($config{'output-dir'} =~ /^\/tmp/) ||
+		($config{'output-dir'} eq '.') ||
+		($config{'output-dir'} =~ /^\.\//) ||
+		($config{'output-dir'} =~ /^\/dev/)) {
+		warning("Are you sure you want to output your files to \"$config{'output-dir'}\"? Continuing anyway...");
+	}
+
+	return $noerror;
+}
+
+# Create blank GD Images
+sub create_gd() {
+	my $IMG;
+	my $width = 370;
+	my $height = 240;
+	
+	$IMG = new GD::Image($width,$height);
+	$IMG->interlaced('true');
+	$IMG->rectangle(0,0,$width-1,$height-1,
+		$IMG->colorAllocate(hex($color{'bg'}[0]),hex($color{'bg'}[1]),hex($color{'bg'}[2])));
+
+	undef $width;
+	undef $height;
+
+	return $IMG;
+}
+
+# Write images
+sub write_img($$) {
+	my $IMG = shift;
+	my $filename = shift;
+
+	console_log("Writing to file $config{'output-dir'}/$filename");
+	open(GD, ">$config{'output-dir'}/$filename") ||
+		error("Cannot write to file $config{'output-dir'}/$filename. Check directory permissions?");
+	binmode GD;
+	print GD $IMG->png();
+	close(GD);
+}
+
+sub plot($$\$\@\@\@\@) {
+	my $type = shift;
+	my $logStart = shift;
+	my $imgRef = shift;
+	my $totalRef = shift;
+	my $hitRef = shift;
+	my $missRef = shift;
+	my $imsRef = shift;
+	my ($isTCP, $isUDP) = (0,0);
+	my $isCum = 0;
+	my ($width, $height) = $$imgRef->getBounds();
+	my $font = gdSmallFont;
+	my $fontWidth = $font->width();
+	my $fontHeight = $font->height();
+
+	# Check type options
+	$isCum = 1 if ($type =~ /C/);
+	$isTCP = 1 if ($type =~ /T/);
+	$isUDP = 1 if ($type =~ /U/);
+
+	# Colour tables
+	my $fgcolor   = $$imgRef->colorAllocate(hex($color{'fg'}[0]),hex($color{'fg'}[1]),hex($color{'fg'}[2]));
+	my $grcolor   = $$imgRef->colorAllocate(hex($color{'gr'}[0]),hex($color{'gr'}[1]),hex($color{'gr'}[2]));
+	my $hitcolor  = $$imgRef->colorAllocate(hex($color{'hit'}[0]),hex($color{'hit'}[1]),hex($color{'hit'}[2]));
+	my $misscolor = $$imgRef->colorAllocate(hex($color{'miss'}[0]),hex($color{'miss'}[1]),hex($color{'miss'}[2]));
+	my $imscolor  = $$imgRef->colorAllocate(hex($color{'ims'}[0]),hex($color{'ims'}[1]),hex($color{'ims'}[2]));
+
+	# Dotted brush
+	$$imgRef->setStyle($fgcolor,gdTransparent,gdTransparent);
+
+	#
+	# NOTES
+	#
+	# graph area w/h = 288/200
+	# graph area l/t = 62/20
+	# graph area eff = 62,20,350,220
+	#
+
+	# Draw the border
+	$$imgRef->rectangle(0,0,$width-1,$height-1,$fgcolor);
+	
+	# Draw the title at the left side
+	my $title;
+	$title = "Cumulative " if ($isCum);
+	$title = "Average " if ($type =~ /D/);
+	$title .= "TCP " if ($isTCP);
+	$title .= "UDP " if ($isUDP);
+	$title .= "Accesses" if ($type =~ /A/);
+	$title .= "Transfers (bytes)" if ($type =~ /X/);
+	$title .= "Transfer Duration (secs)" if ($type =~ /D/);
+	my $titlewidth = ($fontWidth * length($title));
+	my $titlestart = ((($height - $titlewidth) / 2) + $titlewidth);
+	$$imgRef->stringUp($font,5,$titlestart,$title,$fgcolor);
+	undef $titlewidth;
+	undef $titlestart;
+	undef $title;
+
+	# Determine the maximal and per pixel size of the graph
+	my ($graphMax) = sort {$b <=> $a} @$totalRef;
+	if ((sort {$b <=> $a} @$hitRef)[0] > $graphMax) {
+		$graphMax = (sort {$b <=> $a} @$hitRef)[0];
+	}
+	if ((sort {$b <=> $a} @$missRef)[0] > $graphMax) {
+		$graphMax = (sort {$b <=> $a} @$missRef)[0];
+	}
+	# Over-estimate max by 0.5%
+	$graphMax = ($graphMax < 1)?1:$graphMax * 1.05;
+	my $dotSize = $graphMax / 200;
+
+	# Plot the graph
+	my $i;
+	my $lastTotalPos = 0;
+	my $lastHitPos = 0;
+	my $lastMissPos = 0;
+	my $lastIMSPos = 0;
+	for ($i = 0; $i < 288; $i++) {
+		my $totalPos = int($$totalRef[$i] / $dotSize);
+		my $hitPos = int($$hitRef[$i] / $dotSize);
+		my $missPos = int($$missRef[$i] / $dotSize);
+		my $imsPos;
+		$imsPos = int($$imsRef[$i] / $dotSize) if ($isTCP);
+
+		# Draw in sequence. Drawing later will make line appear "on-top"
+		$$imgRef->line($i + 62, 219 - $lastIMSPos, $i + 63, 219 - $imsPos, $imscolor) if ($isTCP);
+		$$imgRef->line($i + 62, 219 - $lastMissPos, $i + 63, 219 - $missPos, $misscolor);
+		$$imgRef->line($i + 62, 219 - $lastHitPos, $i + 63, 219 - $hitPos, $hitcolor);
+		$$imgRef->line($i + 62, 219 - $lastTotalPos, $i + 63, 219 - $totalPos, $grcolor);
+		
+		$lastTotalPos = $totalPos;
+		$lastHitPos = $hitPos;
+		$lastMissPos = $missPos;
+		$lastIMSPos = $imsPos if ($isTCP);
+		undef $totalPos;
+		undef $hitPos;
+		undef $missPos;
+		undef $imsPos;
+	}
+	undef $i;
+	undef $lastTotalPos;
+	undef $lastHitPos;
+	undef $lastMissPos;
+	undef $lastIMSPos;
+
+	# Draw the graph plotting area bounding boxes and quarter markings
+	$$imgRef->line(62,70,350,70,gdStyled);
+	$$imgRef->line(62,120,350,120,gdStyled);
+	$$imgRef->line(62,170,350,170,gdStyled);
+	$$imgRef->rectangle(62,20,350,220,$fgcolor);
+
+	# Label the vertical (Y) axis
+	my $Q = $graphMax/4;
+	my $Q1 = hr_digits($graphMax,hr_units($graphMax));
+	my $Q2 = hr_digits($Q * 3,hr_units($graphMax));
+	my $Q3 = hr_digits($Q * 2,hr_units($graphMax));
+	my $Q4 = hr_digits($Q * 1,hr_units($graphMax));
+
+	$$imgRef->string($font,58-(length($Q1)*$fontWidth),20-($fontHeight/2),"$Q1",$fgcolor);
+	$$imgRef->string($font,58-(length($Q2)*$fontWidth),70-($fontHeight/2),"$Q2",$fgcolor);
+	$$imgRef->string($font,58-(length($Q3)*$fontWidth),120-($fontHeight/2),"$Q3",$fgcolor);
+	$$imgRef->string($font,58-(length($Q4)*$fontWidth),170-($fontHeight/2),"$Q4",$fgcolor);
+
+	$$imgRef->line(59,20,62,20,$fgcolor);
+	$$imgRef->line(59,70,62,70,$fgcolor);
+	$$imgRef->line(59,120,62,120,$fgcolor);
+	$$imgRef->line(59,170,62,170,$fgcolor);
+
+	# Write down the max value
+	# The max was overestimated. We shall get it again.
+	my ($graphMax) = sort {$b <=> $a} @$totalRef;
+	if ((sort {$b <=> $a} @$hitRef)[0] > $graphMax) {
+		$graphMax = (sort {$b <=> $a} @$hitRef)[0];
+	}
+	if ((sort {$b <=> $a} @$missRef)[0] > $graphMax) {
+		$graphMax = (sort {$b <=> $a} @$missRef)[0];
+	}
+	$graphMax = dec_truncate($graphMax,1);
+	$$imgRef->string($font,350-(length("Max: $graphMax") * $fontWidth),(20-$fontHeight)/2+1,"Max: $graphMax",$fgcolor);
+
+	undef $Q;
+	undef $Q1;
+	undef $Q2;
+	undef $Q3;
+	undef $Q4;
+
+	undef $dotSize;	
+	undef $graphMax;
+
+	# Label the horizontal (X) axis
+	my $i = 0;
+	my $alt = 0;
+	for ($i = 0; $i < 288; $i++) {
+		my ($sec, $min, $hour) = localtime($logStart + (300 * $i));
+		if (($min > 57) || ($min < 3)) {
+			$$imgRef->line(63 + $i, 20, 63 + $i, 220, gdStyled);
+			$$imgRef->line(63 + $i, 220, 63 + $i, 223, $fgcolor);
+			if ($alt) {
+				$$imgRef->string($font,63+$i-((length($hour) * $fontWidth)/2),224,"$hour",$fgcolor);
+				$alt = 0;
+			}
+			else { $alt = 1; }
+		}
+		undef $sec;
+		undef $min;
+		undef $hour;
+
+	}
+
+	undef $i;
+	undef $alt;
+
+
+	# Undefine all the vars used
+	undef $type;
+	undef $logStart;
+	undef $imgRef;
+	undef $totalRef;
+	undef $hitRef;
+	undef $missRef;
+	undef $imsRef;
+	undef $isTCP;
+	undef $isUDP;
+	undef $width;
+	undef $height;
+	undef $fgcolor;
+	undef $grcolor;
+	undef $hitcolor;
+	undef $misscolor;
+	undef $imscolor;
+
+}
+
+
+sub dec_truncate($$) {
+	my $num = shift;
+	my $dp = shift;
+	if ($dp eq '') {
+		$dp = 1;
+	}
+	my $power = 10;
+	my $i = 0;
+	for ($i = 1; $i < $dp; $i++) {
+		$power = $power * 10;
+	}
+	return int($num * $power)/$power;
+}
+
+sub comma_sep($) {
+	my $num = shift;
+	my $len = length($num);
+	my @str = split('', "$num");
+	my $val;
+	my $i;
+	for ($i = $len - 1; $i >= 0; $i--) {
+		if ((($len - $i - 1)%3 == 0) && ($i > 0) && ($i < $len - 1)) {
+			$val = "$str[$i]\,$val"
+		}
+		else {
+			$val = "$str[$i]$val";
+		}
+	}
+	undef $i;
+	undef $len;
+	undef $num;
+	undef @str;
+	return $val;
+}
+
+sub hr_units($) {
+	my $num = shift;
+	if ($num >= 1000000000) {
+		return 'G';
+	}
+	elsif ($num >= 1000000) {
+		return 'M';
+	}
+	elsif ($num >= 1000) {
+		return 'K';
+	}
+	else {
+		return '';
+	}
+}
+
+# hr_digits(int: $num, char: $units);
+sub hr_digits($$) {
+	my $num = shift;
+	my $unit = shift;
+	my $val = dec_truncate($num,1);
+
+	$val = dec_truncate($num/1000000000,1) if ($unit eq 'G');
+	$val = dec_truncate($num/1000000,1) if ($unit eq 'M');
+	$val = dec_truncate($num/1000,1) if ($unit eq 'K');
+
+	return "$val$unit";
+}
+
+sub hr_bytes($) {
+	my $num = shift;
+	my $val;
+	if ($num > 1000000000) {
+		$val = dec_truncate($num/1000000000,1);
+		return "$val Gb";
+	}
+	elsif ($num > 1000000) {
+		$val = dec_truncate($num/1000000,1);
+		return "$val Mb";
+	}
+	elsif ($num > 1000) {
+		$val = dec_truncate($num/1000,1);
+		return "$val Kb";
+	}
+	else {
+		$val = dec_truncate($num,1);
+		return "$val bytes";
+	}
+}
+
+# Undefine "global" vars
+undef %config;
+undef %color;
+undef $CONSOLELOG;
+undef $VER;
diff --git a/src/squid-graph/squid-graph.de.pl b/src/squid-graph/squid-graph.de.pl
new file mode 100644
index 000000000..54d1d414d
--- /dev/null
+++ b/src/squid-graph/squid-graph.de.pl
@@ -0,0 +1,49 @@
+%tr = (
+%tr,
+
+'24hseconds' => '86400 Sekunden',
+'analysis duration' => 'Analyse - Dauer',
+'analysis speed' => 'Analyse - Geschwindigkeit',
+'average accesses' => 'Zugriffe - Durchschnitt',
+'average cache hits' => 'Cache-Treffer - Durchschnitt',
+'average cache ims hits' => 'Cache-IMS-Treffer - Durchschnitt',
+'average cache misses' => 'Cache verfehlt - Durchschnitt',
+'average transfers' => 'Transfers - Durchschnitt',
+'avg cache hit duration' => 'Cache-Dauer (Treffer)',
+'avg cache miss duration' => 'Cache-Dauer (verfehlt)',
+'avg transfer duration' => 'Transfer-Dauer',
+'cumulative graph of tcp accesses' => 'TCP-Zugriffe (Zusammenfassung)',
+'cumulative graph of tcp transfers' => 'TCP-Transfers (Zusammenfassung)',
+'cumulative graph of udp accesses' => 'UDP-Zugriffe (Zusammenfassung)',
+'cumulative graph of udp transfers' => 'UDP-Transfers (Zusammenfassung)',
+'errors' => 'Fehler',
+'generated' => 'Erstellt',
+'graph domain' => 'Diagramm - Zeitraum',
+'graph end' => 'Diagramm - Ende',
+'graph of average tcp transfer duration' => 'TCP-Transfer-Dauer (Durchschnitt)',
+'graph of average udp transfer duration' => 'UDP-Transfer-Dauer (Durchschnitt)',
+'graph of tcp accesses' => 'TCP-Zugriffe (alle 5 Minuten)',
+'graph of tcp transfers' => 'TCP-Transfers (alle 5 Minuten)',
+'graph of udp accesses' => 'UDP-Zugriffe (alle 5 Minuten)',
+'graph of udp transfers' => 'UDP-Transfers (alle 5 Minuten)',
+'graph start' => 'Diagramm - Start',
+'hours' => 'Stunden',
+'lines analyzed' => 'Analysierte Zeilen<br />(access.log)',
+'lines sec' => 'Zeilen/Sekunde',
+'lines' => 'Zeilen',
+'no information available' => 'Keine Informationen verfügbar.',
+'percent cache hits' => 'Cache-Treffer - Prozent',
+'percent cache misses' => 'Cache verfehlt - Prozent',
+'per hour' => 'pro Stunde',
+'proxy access graphs' => 'Diagramme zur Proxyauslastung',
+'seconds' => 'Sekunde(n)',
+'ssproxy graphs' => 'Proxy-Diagramme',
+'total accesses' => 'Zugriffe  - gesamt',
+'total cache hits' => 'Cache-Treffer - gesamt',
+'total cache ims hits' => 'Cache-IMS-Treffer - gesamt',
+'total cache misses' => 'Cache verfehlt - gesamt',
+'total transfers' => 'Transfers  - gesamt',
+
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.en.pl b/src/squid-graph/squid-graph.en.pl
new file mode 100644
index 000000000..385729352
--- /dev/null
+++ b/src/squid-graph/squid-graph.en.pl
@@ -0,0 +1,49 @@
+%tr = (
+%tr,
+
+'24hseconds' => '86400 seconds',
+'analysis duration' => 'Analysis Duration',
+'analysis speed' => 'Analysis Speed',
+'average accesses' => 'Average Accesses',
+'average cache hits' => 'Average Cache Hits',
+'average cache ims hits' => 'Average Cache IMS Hits',
+'average cache misses' => 'Average Cache Misses',
+'average transfers' => 'Average Transfers',
+'avg cache hit duration' => 'Avg. Cache Hit Duration',
+'avg cache miss duration' => 'Avg. Cache Miss Duration',
+'avg transfer duration' => 'Avg. Transfer Duration',
+'cumulative graph of tcp accesses' => 'TCP Accesses - cumulative',
+'cumulative graph of tcp transfers' => 'TCP Transfers - cumulative',
+'cumulative graph of udp accesses' => 'UDP Accesses - cumulative',
+'cumulative graph of udp transfers' => 'UDP Transfers - cumulative',
+'errors' => 'error(s)',
+'generated' => 'Generated',
+'graph domain' => 'Graph Domain',
+'graph end' => 'Graph End',
+'graph of average tcp transfer duration' => 'Average TCP Transfer Duration',
+'graph of average udp transfer duration' => 'Average UDP Transfer Duration',
+'graph of tcp accesses' => 'TCP Accesses (5 minute total)',
+'graph of tcp transfers' => 'TCP Transfers (5 minute total)',
+'graph of udp accesses' => 'UDP Accesses (5 minute total)',
+'graph of udp transfers' => 'UDP Transfers (5 minute total)',
+'graph start' => 'Graph Start',
+'hours' => 'hours',
+'lines analyzed' => 'Lines analyzed<br />(access.log)',
+'lines sec' => 'lines/sec',
+'lines' => 'lines',
+'no information available' => 'No information available.',
+'percent cache hits' => '% Cache Hits',
+'percent cache misses' => '% Cache Misses',
+'per hour' => 'per hour',
+'proxy access graphs' => 'Proxy access graphs',
+'seconds' => 'second(s)',
+'ssproxy graphs' => 'Proxy Graphs',
+'total accesses' => 'Total Accesses',
+'total cache hits' => 'Total Cache Hits',
+'total cache ims hits' => 'Total Cache IMS Hits',
+'total cache misses' => 'Total Cache Misses',
+'total transfers' => 'Total Transfers',
+
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.es.pl b/src/squid-graph/squid-graph.es.pl
new file mode 100644
index 000000000..a113e1203
--- /dev/null
+++ b/src/squid-graph/squid-graph.es.pl
@@ -0,0 +1,10 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'No hay información disponible',
+'proxy access graphs' => 'Gráficas de acceso al proxy',
+'ssproxy graphs' => 'Gráficas de proxy',
+
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.fr.pl b/src/squid-graph/squid-graph.fr.pl
new file mode 100644
index 000000000..a2c59f50d
--- /dev/null
+++ b/src/squid-graph/squid-graph.fr.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'Aucune information disponible.',
+'proxy access graphs' => 'Graphiques des accès proxy',
+'ssproxy graphs' => 'Graphiques du proxy',
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.it.pl b/src/squid-graph/squid-graph.it.pl
new file mode 100644
index 000000000..6d2a3a5a9
--- /dev/null
+++ b/src/squid-graph/squid-graph.it.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'No information available.',
+'proxy access graphs' => 'Proxy access graphs',
+'ssproxy graphs' => 'Proxy Graphs',
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.nl.pl b/src/squid-graph/squid-graph.nl.pl
new file mode 100644
index 000000000..b3a2105e0
--- /dev/null
+++ b/src/squid-graph/squid-graph.nl.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'Geen informatie beschikbaar.',
+'proxy access graphs' => 'Proxy toegangsgrafieken',
+'ssproxy graphs' => 'Proxy grafieken',
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.pl.pl b/src/squid-graph/squid-graph.pl.pl
new file mode 100644
index 000000000..254286f1a
--- /dev/null
+++ b/src/squid-graph/squid-graph.pl.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'Brak dostępnych informacji.',
+'proxy access graphs' => 'Wykresy dostępu do Proxy',
+'ssproxy graphs' => 'Wykresy Proxy',
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.ru.pl b/src/squid-graph/squid-graph.ru.pl
new file mode 100644
index 000000000..6d2a3a5a9
--- /dev/null
+++ b/src/squid-graph/squid-graph.ru.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'No information available.',
+'proxy access graphs' => 'Proxy access graphs',
+'ssproxy graphs' => 'Proxy Graphs',
+);
+
+#EOF
diff --git a/src/squid-graph/squid-graph.tr.pl b/src/squid-graph/squid-graph.tr.pl
new file mode 100644
index 000000000..139fa2aa1
--- /dev/null
+++ b/src/squid-graph/squid-graph.tr.pl
@@ -0,0 +1,9 @@
+%tr = ( 
+%tr,
+
+'no information available' => 'Herhangi bir bilgi bulunmamaktadır.',
+'proxy access graphs' => 'Vekil sunucu erişim grafiği',
+'ssproxy graphs' => 'Vekil Sunucu Grafikleri',
+);
+
+#EOF
diff --git a/src/squid-graph/update-squid-graph.sh b/src/squid-graph/update-squid-graph.sh
new file mode 100644
index 000000000..4251607f7
--- /dev/null
+++ b/src/squid-graph/update-squid-graph.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+###
+### Squid-Graphs
+###
+if [ -e "/var/log/squid/access.log" ]; then
+	/usr/bin/squid-graph --no-console-log --tcp-only --output-dir=/srv/web/ipfire/html/sgraph < /var/log/squid/access.log >/dev/null 2>&1;
+fi
