Browse code

minor revision

Gandolf authored on 07/06/2021 21:30:56
Showing 5 changed files
... ...
@@ -24,16 +24,21 @@
24 24
 #    along with this program.  If not, see http://www.gnu.org/license.
25 25
 #
26 26
 # Revision History
27
-#   * v10 released 15 Sep 2015 by J L Owrey
27
+#   * v11 released 24 Jul 2018 by J L Owrey
28 28
 #
29
+# Example of rrdtool command line executed by this program:
30
+#
31
+#   rrdtool create radmonData.rrd --step 30 DS:CPM:GAUGE:60:U:U
32
+#   DS:SvperHr:GAUGE:60:U:U RRA:LAST:0.5:1:2880 RRA:LAST:0.5:30:35520
33
+#
34
+
29 35
 import os
30
-import time
31 36
 import subprocess
32 37
 
33 38
     ### DEFINE FILE LOCATIONS ###
34 39
 
35 40
 _USER = os.environ['USER']
36
-_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER  # the file that stores the data
41
+_RRD_FILE = "/home/%s/database/radmonData.rrd" % _USER  # rrd database file
37 42
 _RRD_SIZE_IN_DAYS = 370 # days
38 43
 _1YR_RRA_STEPS_PER_DAY = 96
39 44
 _DATABASE_UPDATE_INTERVAL = 30
... ...
@@ -51,7 +56,8 @@ def createRrdFile():
51 56
      ## Calculate database size
52 57
  
53 58
     heartBeat = 2 * _DATABASE_UPDATE_INTERVAL
54
-    rra1yrNumPDP =  int(round(86400 / (_1YR_RRA_STEPS_PER_DAY * _DATABASE_UPDATE_INTERVAL)))
59
+    rra1yrNumPDP =  int(round(86400 / (_1YR_RRA_STEPS_PER_DAY * \
60
+                        _DATABASE_UPDATE_INTERVAL)))
55 61
     rrd24hrNumRows = int(round(86400 / _DATABASE_UPDATE_INTERVAL))
56 62
     rrd1yearNumRows = _1YR_RRA_STEPS_PER_DAY * _RRD_SIZE_IN_DAYS
57 63
        
... ...
@@ -69,8 +75,7 @@ def createRrdFile():
69 75
         subprocess.check_output(strCmd, stderr=subprocess.STDOUT, \
70 76
                                 shell=True)
71 77
     except subprocess.CalledProcessError, exError:
72
-        print "%s rrdtool create failed: %s" % \
73
-                            (getTimeStamp(), exError.output)
78
+        print "rrdtool create failed: %s" % (exError.output)
74 79
         return False
75 80
     return True
76 81
 ##end def
... ...
@@ -4,12 +4,14 @@
4 4
 # something linke http://radiationMonitor.domain.com depending on
5 5
 # whether your local network uses a domain name server.
6 6
 #
7
-MONITOR_URL="{your monitor url}"
8 7
 
9 8
 APP_PATH="/home/$USER/bin"
10 9
 LOG_PATH="/home/$USER/log"
11 10
 
12
-PROCESS_ID="$(ps x | awk '/[r]admonAgent.py/{print $1}')"
11
+AGENT_NAME="[r]admonAgent.py"
12
+SOURCE_URL="{your radiation monitor url}"
13
+
14
+PROCESS_ID="$(ps x | awk -v a=$AGENT_NAME '$7 ~ a {print $1}')"
13 15
 
14 16
 if [ -n "$PROCESS_ID" ]; then
15 17
   if [ "$1" != "-q" ]; then
... ...
@@ -18,5 +20,10 @@ if [ -n "$PROCESS_ID" ]; then
18 20
 else
19 21
   printf "starting up radmon agent\n"
20 22
   cd $APP_PATH
21
-  ./radmonAgent.py -t 10 -u $MONITOR_URL >> $LOG_PATH/radmonAgent.log 2>&1 &
23
+  if [ "$1" != "" ]; then
24
+    ./$AGENT_NAME $1 -u $SOURCE_URL
25
+  else
26
+    ./$AGENT_NAME -u $SOURCE_URL >> \
27
+      $LOG_PATH/radmonAgent.log 2>&1 &
28
+  fi
22 29
 fi
... ...
@@ -1,7 +1,9 @@
1 1
 #!/bin/bash
2
-# Stop the radmon agent process and clean up environment.
2
+# Stop the radmon agent process.
3 3
 
4
-PROCESS_ID="$(ps x | awk '/[r]admonAgent.py/{print $1}')"
4
+AGENT_NAME="[r]admonAgent.py"
5
+
6
+PROCESS_ID="$(ps x | awk -v a=$AGENT_NAME '$7 ~ a {print $1}')"
5 7
 
6 8
 if [ -n "$PROCESS_ID" ]; then
7 9
   printf "killing radmon agent [%s]\n" $PROCESS_ID
8 10
old mode 100644
9 11
new mode 100755
... ...
@@ -56,10 +56,10 @@ img.chart {
56 56
     text-align: left;
57 57
     padding: 10px;
58 58
 }
59
-span.chartNav {
59
+span.chartSelectors {
60 60
     margin: auto;
61 61
 }
62
-ul.chartNav {
62
+ul.selectorElement {
63 63
     list-style-type: none;
64 64
     margin: 10px;
65 65
     padding: 0;
... ...
@@ -67,20 +67,25 @@ ul.chartNav {
67 67
     background-color: #bbb;
68 68
     text-align: center;
69 69
 }
70
-li.chartNav {
70
+li.selectorElement {
71 71
     display: inline-block;
72 72
     font: bold 18px arial, sans-serif;
73 73
     color: black;
74 74
 }
75
-text.chartNav:hover {
75
+span.selectorElement:hover {
76 76
     background-color: #333;
77 77
     cursor: pointer;
78 78
     color: white;
79 79
 }
80
-text.chartNav {
80
+span.selectorElement {
81 81
     display: inline-block;
82 82
     padding: 8px 12px;
83 83
 }
84
+#iframe_a {
85
+    border:none;
86
+    width:100%;
87
+    height:450px;
88
+}
84 89
 </style>
85 90
 </head>
86 91
 
... ...
@@ -94,9 +99,9 @@ DIY Radiation Monitor</a></h2>
94 99
 <h4>Albany, Oregon</h4>
95 100
 
96 101
 <div class="datetime">
97
-<text id="date"></text>
102
+<span id="date"></span>
98 103
 &nbsp;&nbsp;
99
-<text id="time"></text>
104
+<span id="time"></span>
100 105
 </div>
101 106
 
102 107
 <div class="rowContainer">
... ...
@@ -108,9 +113,9 @@ Counts per second:<br>
108 113
 uSv per hour:
109 114
 </div>
110 115
 <div class="dataItems">
111
-<text id="cpm"></text><br>
112
-<text id="cps"></text><br>
113
-<text id="uSvPerHr"></text>
116
+<span id="cpm"></span><br>
117
+<span id="cps"></span><br>
118
+<span id="uSvPerHr"></span>
114 119
 </div>
115 120
 </div>
116 121
 
... ...
@@ -121,32 +126,57 @@ Status:<br>
121 126
 Mode:
122 127
 </div>
123 128
 <div class="dataItems">
124
-<text id="status"></text><br>
125
-<text id="mode"></text>
129
+<span id="status"></span><br>
130
+<span id="mode"></span>
126 131
 </div>
127 132
 </div>
128 133
 </div>
129 134
 
130
-<span class="chartNav">
131
-<ul class="chartNav">
132
-<li class="chartNav">Select charts:</li>
133
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(1)">
134
-24 hours</text></li>
135
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(2)">
136
-4 weeks</text></li>
137
-<li class="chartNav"><text class="chartNav" onclick="setChartPeriod(3)">
138
-12 months</text></li>
135
+<span class="chartSelectors">
136
+<ul class="selectorElement">
137
+<li class="selectorElement">Select charts:</li>
138
+<li class="selectorElement"><span class="selectorElement"
139
+ onclick="setChartPeriod(1)">
140
+24 hours</span></li>
141
+<li class="selectorElement"><span class="selectorElement"
142
+ onclick="setChartPeriod(2)">
143
+4 weeks</span></li>
144
+<li class="selectorElement"><span class="selectorElement"
145
+ onclick="setChartPeriod(3)">12 months</span></li>
146
+<li id="customSelector" class="selectorElement" style="visibility:hidden;">
147
+<span  class="selectorElement"
148
+ onclick="setChartPeriod(0)">Custom...</span></li>
139 149
 </ul>
140 150
 </span>
151
+
152
+<div class="rowContainer" id="customChartsContainer" style="display:none;">
153
+<div class="currentDataCell">
154
+<form id="fmDateSelector" action="radmon.php" method="post"
155
+ target="iframe_a">
156
+<label for="beginDate">Begin Date: </label>
157
+<input id="beginDate" name="beginDate" type="date" value="mm/dd/yyyy" />
158
+<label for="endDate">End Date: </label>
159
+<input id="endDate" name="endDate" type="date" value="mm/dd/yyyy" />
160
+<br><br>
161
+<input type="button" onclick="getCustomCharts()" value="Get Charts">
162
+</form>
163
+<span id="errorMsg"></span><br>
164
+<iframe id="iframe_a" name="iframe_a"></iframe>
165
+</div>
166
+</div>
167
+
141 168
 <br>
142 169
 
170
+
171
+
172
+<div class="rowContainer" id="stockChartsContainer">
143 173
 <div class="chartContainer">
144 174
 <img class="chart" id="cpmChart">
145 175
 </div>
146
-
147 176
 <div class="chartContainer">
148 177
 <img class="chart" id="uSvChart">
149 178
 </div>
179
+</div>
150 180
 
151 181
 <div class="notes">
152 182
 <b>NOTES:</b>
... ...
@@ -166,10 +196,10 @@ Radiation Dose Chart</a> by Randall Monroe.</li>
166 196
 <br>
167 197
 
168 198
 <script>
169
-
199
+"use strick";
170 200
 /* Global constants */
171 201
 
172
-var radmonDataUrl = "dynamic/radmonOutputData.js";
202
+var radmonDataUrl = "dynamic/radmonData.js";
173 203
 
174 204
 /* Global DOM objects */
175 205
 
... ...
@@ -177,7 +207,7 @@ var radmonDataUrl = "dynamic/radmonOutputData.js";
177 207
 var cpmChart_g = document.getElementById("cpmChart");
178 208
 var uSvChart_g = document.getElementById("uSvChart");
179 209
 
180
-// Text elements
210
+// Document elements
181 211
 var date_e = document.getElementById("date");    
182 212
 var time_e = document.getElementById("time"); 
183 213
 var cpm_e = document.getElementById("cpm");    
... ...
@@ -186,16 +216,25 @@ var uSvPerHr_e = document.getElementById("uSvPerHr");
186 216
 var mode_e = document.getElementById("mode");    
187 217
 var status_e = document.getElementById("status");    
188 218
 
219
+// Custom charts document elements
220
+var customChartsContainer = document.getElementById("customChartsContainer");
221
+var stockChartsContainer = document.getElementById("stockChartsContainer");
222
+var fmDateSelector = document.getElementById("fmDateSelector");
223
+var errorMsg = document.getElementById("errorMsg");
224
+var customSelector = document.getElementById("customSelector");
225
+
226
+
189 227
 /* Global objects */
190 228
 
191 229
 var httpRequest = new XMLHttpRequest();
192 230
 
193 231
 /* Global variables */
194 232
 
195
-var graphPeriod;
196
-
233
+var chartPeriod = 1;
197 234
 
198 235
 function main() {
236
+    httpRequest.timeout = 3000;
237
+
199 238
     /* Register call back function to process http requests */
200 239
     httpRequest.onreadystatechange = function() {
201 240
         if (httpRequest.readyState == 4 && httpRequest.status == 200) {
... ...
@@ -208,30 +247,38 @@ function main() {
208 247
     httpRequest.ontimeout = function(e) {
209 248
         displayOfflineStatus();
210 249
     };
211
-
250
+    
251
+    initializeDateSelector();
212 252
     getRadmonData();
213
-    graphPeriod = 1;
214
-    getRadmonGraphs();
215
-    setInterval(getRadmonData, 5000);
216
-    setInterval(getRadmonGraphs, 300000);
253
+    getRadmonCharts();
254
+    setInterval(getRadmonData, 2000);
255
+    setInterval(getRadmoncharts, 300000);
217 256
 }
218 257
 
219 258
 function getRadmonData() {
220
-    httpRequest.open("GET", radmonDataUrl, true);
221
-    httpRequest.timeout = 3000;
259
+    httpRequest.open("POST", radmonDataUrl, true);
222 260
     httpRequest.send();
223 261
 }
224 262
 
225 263
 function setChartPeriod(n) {
226
-    graphPeriod = n;
227
-    getRadmonGraphs();   
264
+    /* Show custom charts if custom charts selected, otherwise show
265
+       stock charts. */
266
+    chartPeriod = n;
267
+    if (n == 0) {
268
+        customChartsContainer.style.display = "block";
269
+        stockChartsContainer.style.display = "none";
270
+    } else {
271
+        customChartsContainer.style.display = "none";
272
+        stockChartsContainer.style.display = "block";
273
+        getRadmonCharts();   
274
+    }
228 275
 }
229 276
 
230
-function getRadmonGraphs() {
277
+function getRadmonCharts() {
231 278
     var d = new Date;
232 279
     var pfx;
233 280
 
234
-    switch(graphPeriod) {
281
+    switch(chartPeriod) {
235 282
         case 1:
236 283
             pfx = "24hr_";
237 284
             break;
... ...
@@ -250,6 +297,12 @@ function displayData(dataItem) {
250 297
     var timeStamp, date, time, hourminute;
251 298
     var localDateObj,localTimeZone;
252 299
 
300
+    // Custom charts can only be generated by the local server, i.e,
301
+    // with access to rrdtool database.
302
+    if (dataItem.serverMode == "primary") {
303
+        customSelector.style.visibility = "visible";
304
+    }
305
+
253 306
     timeStamp = dataItem.date;
254 307
     date = timeStamp.split(" ")[0];
255 308
     time = timeStamp.split(" ")[1];
... ...
@@ -259,11 +312,11 @@ function displayData(dataItem) {
259 312
     
260 313
     date_e.innerHTML = date;    
261 314
     time_e.innerHTML = hourminute +
262
-        "  <small>(GMT+" + localTimeZone + ")</small>";    
315
+        "  <small>(UTC-" + localTimeZone + ")</small>";    
263 316
     cpm_e.innerHTML = dataItem.CPM;    
264 317
     cps_e.innerHTML = dataItem.CPS;    
265 318
     uSvPerHr_e.innerHTML = dataItem.uSvPerHr;    
266
-    mode_e.innerHTML = dataItem.Mode;    
319
+    mode_e.innerHTML = dataItem.mode;    
267 320
 
268 321
     status_e.innerHTML = dataItem.status;    
269 322
     if (dataItem.status == "online") {
... ...
@@ -277,7 +330,7 @@ function displayOfflineStatus() {
277 330
     var d = new Date();
278 331
     localTimeZone = d.getTimezoneOffset() / 60;
279 332
     date_e.innerHTML = (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();    
280
-    time_e.innerHTML = d.getHours() + ":" + d.getMinutes() + "  <small>(GMT+" + 
333
+    time_e.innerHTML = d.getHours() + ":" + d.getMinutes() + "  <small>(UTC-" + 
281 334
                        localTimeZone + ")</small>";
282 335
     cpm_e.innerHTML = "";    
283 336
     cps_e.innerHTML = "";    
... ...
@@ -287,6 +340,22 @@ function displayOfflineStatus() {
287 340
     status_e.style.color = "red";
288 341
 }
289 342
 
343
+function initializeDateSelector() {
344
+    var d = new Date();
345
+
346
+    var dEnd = new Date(d.getFullYear(),
347
+               d.getMonth(), d.getDate() - 0);
348
+
349
+    var dBegin = new Date(d.getFullYear(),
350
+               d.getMonth(), d.getDate() - 1);
351
+
352
+    document.getElementById("beginDate").valueAsDate = dBegin;
353
+    document.getElementById("endDate").valueAsDate = dEnd;
354
+}
355
+
356
+function getCustomCharts() {
357
+    fmDateSelector.submit();
358
+}
290 359
 </script>
291 360
 
292 361
 </body>
293 362
new file mode 100644
... ...
@@ -0,0 +1,204 @@
1
+<html>
2
+<!-- Courtsey ruler
3
+12345678901234567890123456789012345678901234567890123456789012345678901234567890
4
+-->
5
+<head>
6
+<style>
7
+p {
8
+    font: 14px ariel, sans serif;
9
+}
10
+#errorMsg {
11
+    font:bold 18px arial,sans-serif;
12
+    color:red;
13
+    text-align:center;
14
+}
15
+.chartContainer {
16
+    padding: 2px;
17
+}
18
+img.chart {
19
+    width:100%;
20
+}
21
+</style>
22
+</head>
23
+<body>
24
+
25
+<?php
26
+/*
27
+ Script: radmon.php
28
+
29
+ Description: This scripts generates on the server charts showing
30
+ radmon data spanning the period supplied by the user.  The script
31
+ does the following:
32
+    - converts user supplied dates to  epoch time
33
+    - gets the times of the first and last data point in the round
34
+      robin database (RRD)
35
+    - from above validates user supplied begin and end dates
36
+    - creates charts of the specified period
37
+
38
+ Copyright 2020 Jeff Owrey
39
+    This program is free software: you can redistribute it and/or modify
40
+    it under the terms of the GNU General Public License as published by
41
+    the Free Software Foundation, either version 3 of the License, or
42
+    (at your option) any later version.
43
+
44
+    This program is distributed in the hope that it will be useful,
45
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
46
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
47
+    GNU General Public License for more details.
48
+
49
+    You should have received a copy of the GNU General Public License
50
+    along with this program.  If not, see http://www.gnu.org/license.
51
+
52
+ Revision History
53
+   * v20 released 18 Jan 2020 by J L Owrey; first release
54
+*/
55
+
56
+# Define global constants
57
+
58
+# round robin database file
59
+define("_RRD_FILE", str_replace("public_html/radmon/radmon.php",
60
+                                "database/radmonData.rrd",
61
+                                $_SERVER["SCRIPT_FILENAME"]));
62
+# charts html directory
63
+define("_CHART_DIRECTORY", str_replace("radmon.php",
64
+                                       "dynamic/",
65
+                                       $_SERVER["SCRIPT_FILENAME"]));
66
+# standard chart width in pixels
67
+define("_CHART_WIDTH", 600);
68
+# standard chart height in pixels
69
+define("_CHART_HEIGHT", 150);
70
+# debug mode
71
+define("_DEBUG", false);
72
+
73
+# Set error handling modes.
74
+error_reporting(E_ALL);
75
+
76
+# Get user supplied chart begin and end dates.
77
+$beginDate = $_POST["beginDate"];
78
+$endDate =  $_POST["endDate"];
79
+
80
+# Convert the user supplied dates to epoch time stamps.
81
+$beginDateEp = strtotime($beginDate);
82
+$endDateEp = strtotime($endDate);
83
+
84
+# Get the time stamp of the earliest data point in the RRD file.
85
+$cmd = sprintf("rrdtool first %s --rraindex 1", _RRD_FILE);
86
+$firstDP = shell_exec($cmd);
87
+
88
+# Get the time stamp of the latest data point in the RRD file.
89
+$cmd = sprintf("rrdtool last %s", _RRD_FILE);
90
+$lastDP = shell_exec($cmd);
91
+
92
+# Determine validity of user supplied dates.  User supplied begin
93
+# date must be less than user supplied end date.  Furthermore both
94
+# dates must be within the range of dates stored in the RRD.
95
+if ($beginDateEp > $endDateEp) {
96
+    echo "<p id=\"errorMsg\">" .
97
+         "End date must be after begin date.</p>";
98
+} elseif ($beginDateEp < $firstDP || $endDateEp > $lastDP) {
99
+    echo "<p id=\"errorMsg\">" .
100
+          "Date range must be between " .
101
+          date('m / d / Y', $firstDP) . " and " . 
102
+          date('m / d / Y', $lastDP) . ".</p>";
103
+} else {
104
+    # Generate charts from validated user supplied dates.
105
+    if (_DEBUG) {
106
+        echo "<p>Date range: " . $beginDateEp . " thru " .
107
+              $endDateEp . "</p>";
108
+    }
109
+    createChart('custom_cpm', 'CPM', 'counts\ per\ minute', 
110
+                'CPM', $beginDateEp, $endDateEp,
111
+                 0, 0, 2, false);
112
+    createChart('custom_svperhr', 'SvperHr', 'Sv\ per\ hour', 
113
+                'Sv/Hr', $beginDateEp, $endDateEp,
114
+                 0, 0, 2, false);
115
+    # Send html commands to client browser.
116
+    echo "<div class=\"chartContainer\">" .
117
+         "<img class=\"chart\" src=\"dynamic/custom_cpm.png\">" .
118
+         "</div>";
119
+    echo "<div class=\"chartContainer\">" .
120
+         "<img class=\"chart\" src=\"dynamic/custom_svperhr.png\">" .
121
+         "</div>";
122
+}
123
+
124
+function createChart($chartFile, $dataItem, $label, $title, $begin,
125
+                     $end, $lower, $upper, $addTrend, $autoScale) {
126
+    /*
127
+    Uses rrdtool to create a chart of specified aredn node data item.
128
+    Parameters:
129
+       fileName - name of the created chart file
130
+       dataItem - data item to be charted
131
+       label - string containing a label for the item to be charted
132
+       title - string containing a title for the chart
133
+       begin - beginning time of the chart data
134
+       end   - ending time of the data to be charted
135
+       lower - lower bound for chart ordinate #NOT USED
136
+       upper - upper bound for chart ordinate #NOT USED
137
+       addTrend - 0, show only chart data
138
+                  1, show only a trend line
139
+                  2, show a trend line and the chart data
140
+       autoScale - if True, then use vertical axis auto scaling
141
+           (lower and upper parameters are ignored), otherwise use
142
+           lower and upper parameters to set vertical axis scale
143
+    Returns: True if successful, False otherwise
144
+    */
145
+
146
+    # Define path on server to chart files.
147
+    $chartPath = _CHART_DIRECTORY . $chartFile . ".png";
148
+
149
+    # Format the rrdtool chart command.
150
+
151
+    # Set chart file name, start time, end time, height, and width.
152
+    $cmdfmt = "rrdtool graph %s -a PNG -s %s -e %s -w %s -h %s ";
153
+    $cmd = sprintf($cmdfmt, $chartPath, $begin, $end, _CHART_WIDTH,
154
+                   _CHART_HEIGHT);
155
+    $cmdfmt = "-l %s -u %s -r ";
156
+
157
+    # Set upper and lower ordinate bounds.
158
+    if ($lower < $upper) {
159
+        $cmd .= sprintf($cmdfmt, $lower, $upper);
160
+    } elseif ($autoScale) {
161
+        $cmd .= "-A ";
162
+    }
163
+    $cmd .= "-Y ";
164
+
165
+    # Set the chart ordinate label and chart title. 
166
+    $cmdfmt = "-v %s -t %s ";
167
+    $cmd .= sprintf($cmdfmt, $label, $title);
168
+   
169
+    # Define moving average window width.
170
+    $trendWindow = floor(($end - $begin) / 12);
171
+        
172
+    # Show the data, or a moving average trend line over
173
+    # the data, or both.
174
+    $cmdfmt = "DEF:dSeries=%s:%s:LAST ";
175
+    $cmd .= sprintf($cmdfmt, _RRD_FILE, $dataItem);
176
+    if ($addTrend == 0) {
177
+        $cmd .= "LINE1:dSeries#0400ff ";
178
+    } elseif ($addTrend == 1) {
179
+        $cmdfmt = "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 ";
180
+        $cmd .= sprintf($cmdfmt, $trendWindow);
181
+    } elseif ($addTrend == 2) {
182
+        $cmd .= "LINE1:dSeries#0400ff ";
183
+        $cmdfmt = "CDEF:smoothed=dSeries,%s,TREND LINE2:smoothed#006600 ";
184
+        $cmd .=  sprintf($cmdfmt, $trendWindow);
185
+    }
186
+     
187
+    # Execute the formatted rrdtool command in the shell. The rrdtool
188
+    # command will complete execution before the html image tags get
189
+    # sent to the browser.  This assures that the charts are available
190
+    # when the client browser executes the html code that loads the
191
+    # charts into the document displayed by the client browser.
192
+    if (_DEBUG) {
193
+        echo "<p>chart command:<br>" . $cmd . "</p>";
194
+    }
195
+    $result = shell_exec($cmd . " 2>&1");
196
+    if (_DEBUG) {
197
+        echo "<p>result:<br>" . $result . "</p>";
198
+    }
199
+}
200
+
201
+?>
202
+
203
+</body>
204
+</html>