[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
[0786]
[0787]
[0788]
[0789]
[0790]
[0791]
[0792]
[0793]
[0794]
[0795]
[0796]
[0797]
[0798]
[0799]
[0800]
[0801]
[0802]
[0803]
[0804]
[0805]
[0806]
[0807]
[0808]
[0809]
[0810]
[0811]
[0812]
[0813]
[0814]
[0815]
[0816]
[0817]
[0818]
[0819]
[0820]
[0821]
[0822]
[0823]
[0824]
[0825]
[0826]
[0827]
[0828]
[0829]
[0830]
[0831]
[0832]
[0833]
[0834]
[0835]
[0836]
[0837]
[0838]
[0839]
[0840]
[0841]
[0842]
[0843]
[0844]
[0845]
[0846]
[0847]
[0848]
[0849]
[0850]
[0851]
[0852]
[0853]
[0854]
[0855]
[0856]
[0857]
[0858]
[0859]
[0860]
[0861]
[0862]
[0863]
[0864]
[0865]
[0866]
[0867]
[0868]
[0869]
[0870]
[0871]
[0872]
[0873]
[0874]
[0875]
[0876]
[0877]
[0878]
[0879]
[0880]
[0881]
[0882]
[0883]
[0884]
[0885]
[0886]
[0887]
[0888]
[0889]
[0890]
[0891]
[0892]
[0893]
[0894]
[0895]
[0896]
[0897]
[0898]
[0899]
[0900]
[0901]
[0902]
[0903]
[0904]
[0905]
[0906]
[0907]
[0908]
[0909]
[0910]
[0911]
[0912]
[0913]
[0914]
[0915]
[0916]
[0917]
[0918]
[0919]
[0920]
[0921]
[0922]
[0923]
[0924]
[0925]
[0926]
[0927]
[0928]
[0929]
[0930]
[0931]
[0932]
[0933]
[0934]
[0935]
[0936]
[0937]
[0938]
[0939]
[0940]
[0941]
[0942]
[0943]
[0944]
[0945]
[0946]
[0947]
[0948]
[0949]
[0950]
[0951]
[0952]
[0953]
[0954]
[0955]
[0956]
[0957]
[0958]
[0959]
[0960]
[0961]
[0962]
[0963]
[0964]
[0965]
[0966]
[0967]
[0968]
[0969]
[0970]
[0971]
[0972]
[0973]
[0974]
[0975]
[0976]
[0977]
[0978]
[0979]
[0980]
[0981]
[0982]
[0983]
[0984]
[0985]
[0986]
[0987]
[0988]
[0989]
[0990]
[0991]
[0992]
[0993]
[0994]
[0995]
[0996]
[0997]
[0998]
[0999]
[1000]
[1001]
[1002]
[1003]
[1004]
[1005]
[1006]
[1007]
[1008]
[1009]
[1010]
[1011]
[1012]
[1013]
[1014]
[1015]
[1016]
[1017]
[1018]
[1019]
[1020]
[1021]
[1022]
[1023]
[1024]
[1025]
[1026]
[1027]
[1028]
[1029]
[1030]
[1031]
[1032]
[1033]
[1034]
[1035]
[1036]
[1037]
[1038]
[1039]
[1040]
[1041]
[1042]
[1043]
[1044]
[1045]
[1046]
[1047]
[1048]
[1049]
[1050]
[1051]
[1052]
[1053]
[1054]
[1055]
[1056]
[1057]
[1058]
[1059]
[1060]
[1061]
[1062]
[1063]
[1064]
[1065]
[1066]
[1067]
[1068]
[1069]
[1070]
[1071]
[1072]
[1073]
[1074]
[1075]
[1076]
[1077]
[1078]
[1079]
[1080]
[1081]
[1082]
[1083]
[1084]
[1085]
[1086]
[1087]
[1088]
[1089]
[1090]
[1091]
[1092]
[1093]
[1094]
[1095]
[1096]
[1097]
[1098]
[1099]
[1100]
[1101]
[1102]
[1103]
[1104]
[1105]
[1106]
[1107]
[1108]
[1109]
[1110]
[1111]
[1112]
[1113]
[1114]
[1115]
[1116]
[1117]
[1118]
[1119]
[1120]
[1121]
[1122]
[1123]
[1124]
[1125]
[1126]
[1127]
[1128]
[1129]
[1130]
[1131]
[1132]
[1133]
[1134]
[1135]
[1136]
[1137]
[1138]
[1139]
[1140]
[1141]
[1142]
[1143]
[1144]
[1145]
[1146]
[1147]
[1148]
[1149]
[1150]
[1151]
[1152]
[1153]
[1154]
[1155]
[1156]
[1157]
[1158]
[1159]
[1160]
[1161]
[1162]
[1163]
[1164]
[1165]
[1166]
[1167]
[1168]
[1169]
[1170]
[1171]
[1172]
[1173]
[1174]
[1175]
[1176]
[1177]
[1178]
[1179]
[1180]
[1181]
[1182]
[1183]
[1184]
[1185]
[1186]
[1187]
[1188]
[1189]
[1190]
[1191]
[1192]
[1193]
[1194]
[1195]
[1196]
[1197]
[1198]
[1199]
[1200]
[1201]
[1202]
[1203]
[1204]
[1205]
[1206]
[1207]
[1208]
[1209]
[1210]
[1211]
[1212]
[1213]
[1214]
[1215]
[1216]
[1217]
[1218]
[1219]
[1220]
[1221]
[1222]
[1223]
[1224]
[1225]
[1226]
[1227]
[1228]
[1229]
[1230]
[1231]
[1232]
[1233]
[1234]
[1235]
[1236]
[1237]
[1238]
[1239]
[1240]
[1241]
[1242]
[1243]
[1244]
[1245]
[1246]
[1247]
[1248]
[1249]
[1250]
[1251]
[1252]
[1253]
[1254]
[1255]
[1256]
[1257]
[1258]
[1259]
[1260]
[1261]
[1262]
[1263]
[1264]
[1265]
[1266]
[1267]
[1268]
[1269]
[1270]
[1271]
[1272]
[1273]
[1274]
[1275]
[1276]
[1277]
[1278]
[1279]
[1280]
[1281]
[1282]
[1283]
[1284]
[1285]
[1286]
[1287]
[1288]
[1289]
[1290]
[1291]
[1292]
[1293]
[1294]
[1295]
[1296]
[1297]
[1298]
[1299]
[1300]
[1301]
[1302]
[1303]
[1304]
[1305]
[1306]
[1307]
[1308]
[1309]
[1310]
[1311]
[1312]
[1313]
[1314]
[1315]
[1316]
[1317]
[1318]
[1319]
[1320]
[1321]
[1322]
[1323]
[1324]
[1325]
[1326]
[1327]
[1328]
[1329]
[1330]
[1331]
[1332]
[1333]
[1334]
[1335]
[1336]
[1337]
[1338]
[1339]
[1340]
[1341]
[1342]
[1343]
[1344]
[1345]
[1346]
[1347]
[1348]
[1349]
[1350]
[1351]
[1352]
[1353]
[1354]
[1355]
[1356]
[1357]
[1358]
[1359]
[1360]
[1361]
[1362]
[1363]
[1364]
[1365]
[1366]
[1367]
[1368]
[1369]
[1370]
[1371]
[1372]
[1373]
[1374]
[1375]
[1376]
[1377]
[1378]
[1379]
[1380]
[1381]
[1382]
[1383]
[1384]
[1385]
[1386]
[1387]
[1388]
[1389]
[1390]
[1391]
[1392]
[1393]
[1394]
[1395]
[1396]
[1397]
[1398]
[1399]
[1400]
[1401]
[1402]
[1403]
[1404]
[1405]
[1406]
[1407]
[1408]
[1409]
[1410]
[1411]
[1412]
[1413]
[1414]
[1415]
[1416]
[1417]
[1418]
[1419]
[1420]
[1421]
[1422]
[1423]
[1424]
[1425]
[1426]
[1427]
[1428]
[1429]
[1430]
[1431]
[1432]
[1433]
[1434]
[1435]
[1436]
[1437]
[1438]
[1439]
[1440]
[1441]
[1442]
[1443]
[1444]
[1445]
[1446]
[1447]
[1448]
[1449]
[1450]
[1451]
[1452]
[1453]
[1454]
[1455]
[1456]
[1457]
[1458]
[1459]
[1460]
[1461]
[1462]
[1463]
[1464]
[1465]
[1466]
[1467]
[1468]
[1469]
[1470]
[1471]
[1472]
[1473]
[1474]
[1475]
[1476]
[1477]
[1478]
[1479]
[1480]
[1481]
[1482]
[1483]
[1484]
[1485]
[1486]
[1487]
[1488]
[1489]
[1490]
[1491]
[1492]
[1493]
[1494]
[1495]
[1496]
[1497]
[1498]
[1499]
[1500]
[1501]
[1502]
[1503]
[1504]
[1505]
[1506]
[1507]
[1508]
[1509]
[1510]
[1511]
[1512]
[1513]
[1514]
[1515]
[1516]
[1517]
[1518]
[1519]
[1520]
[1521]
[1522]
[1523]
[1524]
[1525]
[1526]
[1527]
[1528]
[1529]
[1530]
[1531]
[1532]
[1533]
[1534]
[1535]
[1536]
[1537]
[1538]
[1539]
[1540]
[1541]
[1542]
[1543]
[1544]
[1545]
[1546]
[1547]
[1548]
[1549]
[1550]
[1551]
[1552]
[1553]
[1554]
[1555]
[1556]
[1557]
[1558]
[1559]
[1560]
[1561]
[1562]
[1563]
[1564]
[1565]
[1566]
[1567]
[1568]
[1569]
[1570]
[1571]
[1572]
[1573]
[1574]
[1575]
[1576]
[1577]
[1578]
[1579]
[1580]
[1581]
[1582]
[1583]
[1584]
[1585]
[1586]
[1587]
[1588]
[1589]
[1590]
[1591]
[1592]
[1593]
[1594]
[1595]
[1596]
[1597]
[1598]
[1599]
[1600]
[1601]
[1602]
[1603]
[1604]
[1605]
[1606]
[1607]
[1608]
[1609]
[1610]
[1611]
[1612]
[1613]
[1614]
[1615]
[1616]
[1617]
[1618]
[1619]
[1620]
[1621]
[1622]
[1623]
[1624]
[1625]
[1626]
[1627]
[1628]
[1629]
[1630]
[1631]
[1632]
[1633]
[1634]
[1635]
[1636]
[1637]
[1638]
[1639]
[1640]
[1641]
[1642]
[1643]
[1644]
[1645]
[1646]
[1647]
[1648]
[1649]
[1650]
[1651]
[1652]
[1653]
[1654]
[1655]
[1656]
[1657]
[1658]
[1659]
[1660]
[1661]
[1662]
[1663]
[1664]
[1665]
[1666]
[1667]
[1668]
[1669]
[1670]
[1671]
[1672]
[1673]
[1674]
[1675]
[1676]
[1677]
[1678]
[1679]
[1680]
[1681]
[1682]
[1683]
[1684]
[1685]
[1686]
[1687]
[1688]
[1689]
[1690]
[1691]
[1692]
[1693]
[1694]
[1695]
[1696]
[1697]
[1698]
[1699]
[1700]
[1701]
[1702]
[1703]
[1704]
[1705]
[1706]
[1707]
[1708]
[1709]
[1710]
[1711]
[1712]
[1713]
[1714]
[1715]
[1716]
[1717]
[1718]
[1719]
[1720]
[1721]
[1722]
[1723]
[1724]
[1725]
[1726]
[1727]
[1728]
[1729]
[1730]
[1731]
[1732]
[1733]
[1734]
[1735]
[1736]
[1737]
[1738]
[1739]
[1740]
[1741]
[1742]
[1743]
[1744]
[1745]
[1746]
[1747]
[1748]
[1749]
[1750]
[1751]
[1752]
[1753]
[1754]
[1755]
[1756]
[1757]
[1758]
[1759]
[1760]
[1761]
[1762]
[1763]
[1764]
[1765]
[1766]
[1767]
[1768]
[1769]
[1770]
[1771]
[1772]
[1773]
[1774]
[1775]
[1776]
[1777]
[1778]
[1779]
[1780]
[1781]
[1782]
[1783]
[1784]
[1785]
[1786]
[1787]
[1788]
[1789]
[1790]
[1791]
[1792]
[1793]
[1794]
[1795]
[1796]
[1797]
[1798]
[1799]
[1800]
[1801]
[1802]
[1803]
[1804]
[1805]
[1806]
[1807]
[1808]
[1809]
[1810]
[1811]
[1812]
[1813]
[1814]
[1815]
[1816]
[1817]
[1818]
[1819]
[1820]
[1821]
[1822]
[1823]
[1824]
[1825]
[1826]
[1827]
[1828]
[1829]
[1830]
[1831]
[1832]
[1833]
[1834]
[1835]
[1836]
[1837]
[1838]
[1839]
[1840]
[1841]
[1842]
[1843]
[1844]
[1845]
[1846]
[1847]
[1848]
[1849]
[1850]
[1851]
[1852]
[1853]
[1854]
[1855]
[1856]
[1857]
[1858]
[1859]
[1860]
[1861]
[1862]
[1863]
[1864]
[1865]
[1866]
[1867]
[1868]
[1869]
[1870]
[1871]
[1872]
[1873]
[1874]
[1875]
[1876]
[1877]
[1878]
[1879]
[1880]
[1881]
[1882]
[1883]
[1884]
[1885]
[1886]
[1887]
[1888]
[1889]
[1890]
[1891]
[1892]
[1893]
[1894]
[1895]
[1896]
[1897]
[1898]
[1899]
[1900]
[1901]
[1902]
[1903]
[1904]
[1905]
[1906]
[1907]
[1908]
[1909]
[1910]
[1911]
[1912]
[1913]
[1914]
[1915]
[1916]
[1917]
[1918]
[1919]
[1920]
[1921]
[1922]
[1923]
[1924]
[1925]
[1926]
[1927]
[1928]
[1929]
[1930]
[1931]
[1932]
[1933]
[1934]
[1935]
[1936]
[1937]
[1938]
[1939]
[1940]
[1941]
[1942]
[1943]
[1944]
[1945]
[1946]
[1947]
[1948]
[1949]
[1950]
[1951]
[1952]
[1953]
[1954]
[1955]
[1956]
[1957]
[1958]
[1959]
[1960]
[1961]
[1962]
[1963]
[1964]
[1965]
[1966]
[1967]
[1968]
[1969]
[1970]
[1971]
[1972]
[1973]
[1974]
[1975]
[1976]
[1977]
[1978]
[1979]
[1980]
[1981]
[1982]
[1983]
[1984]
[1985]
[1986]
[1987]
[1988]
[1989]
[1990]
[1991]
[1992]
[1993]
[1994]
[1995]
[1996]
[1997]
[1998]
[1999]
[2000]
[2001]
[2002]
[2003]
[2004]
[2005]
[2006]
[2007]
[2008]
[2009]
[2010]
[2011]
[2012]
[2013]
[2014]
[2015]
[2016]
[2017]
[2018]
[2019]
[2020]
[2021]
[2022]
[2023]
[2024]
[2025]
[2026]
[2027]
[2028]
[2029]
[2030]
[2031]
[2032]
[2033]
[2034]
[2035]
[2036]
[2037]
[2038]
[2039]
[2040]
[2041]
[2042]
[2043]
[2044]
[2045]
[2046]
[2047]
[2048]
[2049]
[2050]
[2051]
[2052]
[2053]
[2054]
[2055]
[2056]
[2057]
[2058]
[2059]
[2060]
[2061]
[2062]
[2063]
[2064]
[2065]
[2066]
[2067]
[2068]
[2069]
[2070]
[2071]
[2072]
[2073]
[2074]
[2075]
[2076]
[2077]
[2078]
[2079]
[2080]
[2081]
[2082]
[2083]
[2084]
[2085]
[2086]
[2087]
[2088]
[2089]
[2090]
[2091]
[2092]
[2093]
[2094]
[2095]
[2096]
[2097]
[2098]
[2099]
[2100]
[2101]
[2102]
[2103]
[2104]
[2105]
[2106]
[2107]
[2108]
[2109]
[2110]
[2111]
[2112]
[2113]
[2114]
[2115]
[2116]
[2117]
[2118]
[2119]
[2120]
[2121]
[2122]
[2123]
[2124]
[2125]
[2126]
[2127]
[2128]
[2129]
[2130]
[2131]
[2132]
[2133]
[2134]
[2135]
[2136]
[2137]
[2138]
[2139]
[2140]
[2141]
[2142]
[2143]
[2144]
[2145]
[2146]
[2147]
[2148]
[2149]
[2150]
[2151]
[2152]
[2153]
[2154]
[2155]
[2156]
[2157]
[2158]
[2159]
[2160]
[2161]
[2162]
[2163]
[2164]
[2165]
[2166]
[2167]
[2168]
[2169]
[2170]
[2171]
[2172]
[2173]
[2174]
[2175]
[2176]
[2177]
[2178]
[2179]
[2180]
[2181]
[2182]
[2183]
[2184]
[2185]
[2186]
[2187]
[2188]
[2189]
[2190]
[2191]
[2192]
[2193]
[2194]
[2195]
[2196]
[2197]
[2198]
[2199]
[2200]
[2201]
[2202]
[2203]
[2204]
[2205]
[2206]
[2207]
[2208]
[2209]
[2210]
[2211]
[2212]
[2213]
[2214]
[2215]
[2216]
[2217]
[2218]
[2219]
[2220]
[2221]
[2222]
[2223]
[2224]
[2225]
[2226]
[2227]
[2228]
[2229]
[2230]
[2231]
[2232]
[2233]
[2234]
[2235]
[2236]
[2237]
[2238]
[2239]
[2240]
[2241]
[2242]
[2243]
[2244]
[2245]
[2246]
[2247]
[2248]
[2249]
[2250]
[2251]
[2252]
[2253]
[2254]
[2255]
[2256]
[2257]
[2258]
[2259]
[2260]
[2261]
[2262]
[2263]
[2264]
[2265]
[2266]
[2267]
[2268]
[2269]
[2270]
[2271]
[2272]
[2273]
[2274]
[2275]
[2276]
[2277]
[2278]
[2279]
[2280]
[2281]
[2282]
[2283]
[2284]
[2285]
[2286]
[2287]
[2288]
[2289]
[2290]
[2291]
[2292]
[2293]
[2294]
[2295]
[2296]
[2297]
[2298]
[2299]
[2300]
[2301]
[2302]
[2303]
[2304]
[2305]
[2306]
[2307]
[2308]
[2309]
[2310]
[2311]
[2312]
[2313]
[2314]
[2315]
[2316]
[2317]
[2318]
[2319]
[2320]
[2321]
[2322]
[2323]
[2324]
[2325]
[2326]
[2327]
[2328]
[2329]
[2330]
[2331]
[2332]
[2333]
[2334]
[2335]
[2336]
[2337]
[2338]
[2339]
[2340]
[2341]
[2342]
[2343]
[2344]
[2345]
[2346]
[2347]
[2348]
[2349]
[2350]
[2351]
[2352]
[2353]
[2354]
[2355]
[2356]
[2357]
[2358]
[2359]
[2360]
[2361]
[2362]
[2363]
[2364]
[2365]
[2366]
[2367]
[2368]
[2369]
[2370]
[2371]
[2372]
[2373]
[2374]
[2375]
[2376]
[2377]
[2378]
[2379]
[2380]
[2381]
[2382]
[2383]
[2384]
[2385]
[2386]
[2387]
[2388]
[2389]
[2390]
[2391]
[2392]
[2393]
[2394]
[2395]
[2396]
[2397]
[2398]
[2399]
[2400]
[2401]
[2402]
[2403]
[2404]
[2405]
[2406]
[2407]
[2408]
[2409]
[2410]
[2411]
[2412]
[2413]
[2414]
[2415]
[2416]
[2417]
[2418]
[2419]
[2420]
[2421]
[2422]
[2423]
[2424]
[2425]
[2426]
[2427]
[2428]
[2429]
[2430]
[2431]
[2432]
[2433]
[2434]
[2435]
[2436]
[2437]
[2438]
[2439]
[2440]
[2441]
[2442]
[2443]
[2444]
[2445]
[2446]
[2447]
[2448]
[2449]
[2450]
[2451]
[2452]
[2453]
[2454]
[2455]
[2456]
[2457]
[2458]
[2459]
[2460]
[2461]
[2462]
[2463]
[2464]
[2465]
[2466]
[2467]
[2468]
[2469]
[2470]
[2471]
[2472]
[2473]
[2474]
[2475]
[2476]
[2477]
[2478]
[2479]
[2480]
[2481]
[2482]
[2483]
[2484]
[2485]
[2486]
[2487]
[2488]
[2489]
[2490]
[2491]
[2492]
[2493]
[2494]
[2495]
[2496]
[2497]
[2498]
[2499]
[2500]
[2501]
[2502]
[2503]
[2504]
[2505]
[2506]
[2507]
[2508]
[2509]
[2510]
[2511]
[2512]
[2513]
[2514]
[2515]
[2516]
[2517]
[2518]
[2519]
[2520]
[2521]
[2522]
[2523]
[2524]
[2525]
[2526]
[2527]
[2528]
[2529]
[2530]
[2531]
[2532]
[2533]
[2534]
[2535]
[2536]
[2537]
[2538]
[2539]
[2540]
[2541]
[2542]
[2543]
[2544]
[2545]
[2546]
[2547]
[2548]
[2549]
[2550]
[2551]
[2552]
[2553]
[2554]
[2555]
[2556]
[2557]
[2558]
[2559]
[2560]
[2561]
[2562]
[2563]
[2564]
[2565]
[2566]
[2567]
[2568]
[2569]
[2570]
[2571]
[2572]
[2573]
[2574]
[2575]
[2576]
[2577]
[2578]
[2579]
[2580]
[2581]
[2582]
[2583]
[2584]
[2585]
[2586]
[2587]
[2588]
[2589]
[2590]
[2591]
[2592]
[2593]
[2594]
[2595]
[2596]
[2597]
[2598]
[2599]
[2600]
[2601]
[2602]
[2603]
[2604]
[2605]
[2606]
[2607]
[2608]
[2609]
[2610]
[2611]
[2612]
[2613]
[2614]
[2615]
[2616]
[2617]
[2618]
[2619]
[2620]
[2621]
[2622]
[2623]
[2624]
[2625]
[2626]
[2627]
[2628]
[2629]
[2630]
[2631]
[2632]
[2633]
[2634]
[2635]
[2636]
[2637]
[2638]
[2639]
[2640]
[2641]
[2642]
[2643]
[2644]
[2645]
[2646]
[2647]
[2648]
[2649]
[2650]
[2651]
[2652]
[2653]
[2654]
[2655]
[2656]
[2657]
[2658]
[2659]
[2660]
[2661]
[2662]
[2663]
[2664]
[2665]
[2666]
[2667]
[2668]
[2669]
[2670]
[2671]
[2672]
[2673]
[2674]
[2675]
[2676]
[2677]
[2678]
[2679]
[2680]
[2681]
[2682]
[2683]
[2684]
[2685]
[2686]
[2687]
[2688]
[2689]
[2690]
[2691]
[2692]
[2693]
[2694]
[2695]
[2696]
[2697]
[2698]
[2699]
[2700]
[2701]
[2702]
[2703]
[2704]
[2705]
[2706]
[2707]
[2708]
[2709]
[2710]
[2711]
[2712]
[2713]
[2714]
[2715]
[2716]
[2717]
[2718]
[2719]
[2720]
[2721]
[2722]
[2723]
[2724]
[2725]
[2726]
[2727]
[2728]
[2729]
[2730]
[2731]
[2732]
[2733]
[2734]
[2735]
[2736]
[2737]
[2738]
[2739]
[2740]
[2741]
[2742]
[2743]
[2744]
[2745]
[2746]
[2747]
[2748]
[2749]
[2750]
[2751]
[2752]
[2753]
[2754]
[2755]
[2756]
[2757]
[2758]
[2759]
[2760]
[2761]
[2762]
[2763]
[2764]
[2765]
[2766]
[2767]
[2768]
[2769]
[2770]
[2771]
[2772]
[2773]
[2774]
[2775]
[2776]
[2777]
[2778]
[2779]
[2780]
[2781]
[2782]
[2783]
[2784]
[2785]
[2786]
[2787]
[2788]
[2789]
[2790]
[2791]
[2792]
[2793]
[2794]
[2795]
[2796]
[2797]
[2798]
[2799]
[2800]
[2801]
[2802]
[2803]
[2804]
[2805]
[2806]
[2807]
[2808]
[2809]
[2810]
[2811]
[2812]
[2813]
[2814]
[2815]
[2816]
[2817]
[2818]
[2819]
[2820]
[2821]
[2822]
[2823]
[2824]
[2825]
[2826]
[2827]
[2828]
[2829]
[2830]
[2831]
[2832]
[2833]
[2834]
[2835]
[2836]
[2837]
[2838]
[2839]
[2840]
[2841]
[2842]
[2843]
[2844]
[2845]
[2846]
[2847]
[2848]
[2849]
[2850]
[2851]
[2852]
[2853]
[2854]
[2855]
[2856]
[2857]
[2858]
[2859]
[2860]
[2861]
[2862]
[2863]
[2864]
[2865]
[2866]
[2867]
[2868]
[2869]
[2870]
[2871]
[2872]
[2873]
[2874]
[2875]
[2876]
[2877]
[2878]
[2879]
[2880]
[2881]
[2882]
[2883]
[2884]
[2885]
[2886]
[2887]
[2888]
[2889]
[2890]
[2891]
[2892]
[2893]
[2894]
[2895]
[2896]
[2897]
[2898]
[2899]
/*****************************************************************************/
/*
                                 Graph.c

This module accumlates and manages server activity data in a global section.

It also allows a(n admin) client to access that data, rendering it as a
time-based graphic, displaying requests and bytes transferred per minute, along
with associated means and peaks.  Events such as server exit (normal and error)
and startup are also indicated.

Prior to WASD v11.1 the activity graphic was an independently generated GIF
image and JavaScript was optional.  From that version the graphic is generated
using JavaScript and the HTML5 canvas API.  Client JavaScript is a requisite.


ACTIVITY STATISTICS NOTES
-------------------------

Server activity is gathered by accumlating all requests and bytes transmitted
on a per-minute basis, along with the request peak for that minute. These
statistics are kept in a two arrays of longwords, and one shorts for the peaks.
The arrays are sized on a per-day basis, 1440 long/short words per-day, maximum
28 days (although that's an arbitrary number I can't imagine anyone usefully
using this facility over a longer period - that becomes the provence of log
analysis tools).  The arrays are stored in a permananet global section allowing
these statistics, and the fact of startups and shutdowns, to be stored across
startups and shutdown.  The index into the array is based on the day of
activity (zero to whatever day it is), plus the hour and minute. Days are the
VMS absolute day (day number from start of epoch). The index into the array is
calculated using a modulas of the day of activity by the number of days in the
array (hence ranges from zero to the number of days in the array) multiplied by
the number of minutes in the day, plus the hour multiplied by sixty, plus the
minute.


ACTIVITY REPORT/PLOT NOTES
--------------------------

Activity statistics are reported/plotted from an absolute day and hour BACK
for a specified number of hours. That is, the date and hour specified is the
date and hour the report/plot ends, the start is calculated as a relative
offset backwards from that. Slightly curious, but the idea was for a quick
snapshot of activity up until the current time, so default behaviour (without
any parameters) is for the current hour, beginning at minute 00 and ending
at minute 59. If a duration is specified it is the number of hours prior to
current time, so a duration of 4 at time 11:15 ranges from 08:00 to 11:59!
If a time is specified then it is the period leading up to that, etc.


VERSION HISTORY
---------------
14-NOV-2020  MGD  migrate from ulong[2] to int64 for 64 bit quantities
28-APR-2018  MGD  refactor Admin..() AST delivery
21-APR-2018  MGD  JavaScript startUpdatePage() only when at current time
03-FEB-2018  MGD  bugfix; GraphActivityReport() number hours "%4u%2u%2u%2u+%u"
01-JAN-2017  MGD  push rendering from C20 (GIF) to C21 (HTML5 Canvas)
                    (while I did want to refine the functionality I didn't
                     want to completely reinvent this particular wheel and
                     so restricted changes to the graphic rendering code)
29-SEP-2009  MGD  activity ByteCount increased from long to quad word
                    (this ripples through the code something chronic!)
                  GraphActivityUpdate() is used to periodically update
                    network network traffic activity statistics and now
                    indexes statistics on current time rather than request
31-OCT-2007  MGD  provide a slash-delimitted 'max-requests' that scales the
                    Y axis (requests) allowing finer detail to be displayed
                  refine zooming
                  bugfix; X axis scaling for non-integral factors
21-SEP-2007  MGD  bugfix; GraphActivityReport() uninitialised 'cptr' before
                  use in processing '"form"-based query string'
11-JUL-2006  MGD  see 'CRAZY' note in GraphActivityReport()
06-JUL-2006  MGD  add request peak data (connections has been masquerading)
15-JUN-2005  MGD  make 'ByteTotal' a quad for better calculating 'ByteMean'
04-SEP-2004  MGD  adjustments for data stored in quadwords,
                  removed (ancient) JavaScript checks for (ancient) browsers
30-OCT-2003  MGD  bugfix; GraphActivityPlotBegin(), GraphActivityDataScan()
                  signed/unsigned issue masking out request value
02-APR-2003  MGD  bugfix; GraphActivityClearDay() again
15-OCT-2002  MGD  bugfix; GraphActivityClearDay()
18-MAY-2002  MGD  activity statistics stored in global section,
                  record/display startup events and peak requests
06-JAN-2002  MGD  refine instance support
05-AUG-2001  MGD  support module WATCHing,
                  bugfix; sscanf() from %d to %u
                  bugfix; provide "elbow-room" in activity data storage
09-MAY-2000  MGD  remove keep-alive paraphanelia
04-MAR-2000  MGD  use FaolToNet(), et.al.
07-JAN-1998  MGD  same bugfix (obviously wasn't); in GraphActivityClearDay()
                  (and a plethora of other annoyances/problems ...
                  bit of a holiday does you the world of good :^)
01-DEC-1997  MGD  bugfix; in GraphActivityClearDay()
18-OCT-1997  MGD  remove dependence on Unix time functions after irritating
                  experience related to VMS 7.1,
                  add JavaScript-driven descriptions to client-side map links
01-AUG-1997  MGD  new for v4.3 (hope my plotting functions are not too
                  brain-dead, I'm only a graphics novice, sigh!)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

/* VMS header files */
#include <libdef.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#include "wasd.h"

#define WASD_MODULE "GRAPH"

#define ACTIVITY_GRAPH_WIDTH      480
#define ACTIVITY_GRAPH_HEIGHT     200
#define ACTIVITY_GRAPH_ZOOM         5

/*********************/
/* functional macros */
/*********************/

/*
   Used with an absolute VMS day number.  Provide the day number relative to
   the start of activity data collection.  Will be in the range 0 ... maximum
   number of days in the activity data.  If the day specified occurs before
   the current day/hour it will be folded back into the data so it is up to
   the using code to ensure reading data from there is legitimate.
*/

#define ACTIVITY_DATA_IDX(absday,hour) \
   ((absday%ActivityNumberOfDays)*MINUTES_IN_DAY)+(hour*MINUTES_IN_HOUR)

/******************/
/* global storage */
/******************/

BOOL  GraphDebug = false;

int  ActivityNumberOfDays,
     ActivityTotalMinutes;

ACTIVITY_GBLSEC  *ActivityGblSecPtr;

int  GraphAbsDay,
     GraphicMaximumHeight = 1505,
     GraphicMaximumWidth = 1505;

ushort  GraphPrevDate;

char  ErrorGraphNotInit [] = "Activity statistics not initialized!",
      ErrorGraphPeriod [] = "Period problem.",
      ErrorGraphQuery [] = "Query not understood",
      ErrorGraphFuture [] = "Future history!",
      ErrorGraphHistory [] = "Too far back in history!";

/********************/
/* external storage */
/********************/

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  CliGblSecDelete,
             CliGblSecNoPerm;

extern int  ActivityGblSecVersion,
            GblPageCount,
            GblPagePermCount,
            GblSectionCount,
            GblSectionPermCount,
            InstanceEnvNumber,
            InstanceNodeCurrent,
            NetCurrentConnected,
            NetCurrentProcessing;

extern const int64  Delta01Sec;

extern int64  HttpdTime64;

extern ulong  GblSecPrvMask[];

extern ushort  HttpdTime7[];

extern char  ServerHostPort[];

extern CONFIG_STRUCT  Config;
extern HTTPD_PROCESS  HttpdProcess;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialize the per-minute activity statistics data arrays.
If only one instance can execute (from configuration) then allocate a block of
process-local dynamic memory and point to that.  If multiple instances create
and map a global section and point to that.
*/ 

GraphActivityGblSecInit ()

{
   static char  ReportActivityPages [] =
"%HTTPD-I-ACTIVITY, !AZ global section of !UL page(let)s\n",
                ReportActivityWarning [] =
"%HTTPD-W-ACTIVITY, error mapping !UL page(let)s (disabled)\n-!&M\n";

   /* global, allocate space, system, in page file, writable */
   static int CreFlags = SEC$M_GBL | SEC$M_EXPREG | SEC$M_SYSGBL |
                         SEC$M_PAGFIL | SEC$M_PERM | SEC$M_WRT;
   static int DelFlags = SEC$M_SYSGBL;
   /* system & owner full access, group and world no access */
   static ulong  ProtectionMask = 0xff00;
   /* it is recommended to map into any virtual address in the region (P0) */
   static ulong  InAddr [2] = { 0x200, 0x200 };

   int  status, attempt,
        BaseGblSecPages,
        BytesRequired,
        GblSecPages,
        PageCount;
   short  ShortLength;
   ulong  RetAddr [2];
   char  GblSecName [32];
   $DESCRIPTOR (GblSecNameDsc, GblSecName);

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityGblSecInit()");

   FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength,
                GBLSEC_NAME_FAO, HTTPD_NAME, ACTIVITY_GBLSEC_VERSION_NUMBER,
                InstanceEnvNumber, "ACTIVITY");
   GblSecNameDsc.dsc$w_length = ShortLength;

   if (CliGblSecDelete)
   {
      /* delete the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      return (status);
   }

   if (Config.cfMisc.ActivityNumberOfDays <= 0)
   { 
      ActivityTotalMinutes = 0;
      return;
   }
   ActivityNumberOfDays = ACTIVITY_DAYS;
   ActivityTotalMinutes = ActivityNumberOfDays * MINUTES_IN_DAY;

   GblSecPages = sizeof(ACTIVITY_GBLSEC) / 512;
   if (GblSecPages & 0x1ff) GblSecPages++;

   /* do not create a permanent global section */
   if (CliGblSecNoPerm) CreFlags &= ~SEC$M_PERM;

   for (attempt = 1; attempt <= 2; attempt++)
   {
      /* create and/or map the global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$crmpsc (&InAddr, &RetAddr, 0, CreFlags,
                           &GblSecNameDsc, 0, 0, 0, GblSecPages, 0,
                           ProtectionMask, GblSecPages);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      PageCount = (RetAddr[1]+1) - RetAddr[0] >> 9;
      ActivityGblSecPtr = (ACTIVITY_GBLSEC*)RetAddr[0];
      if (VMSnok (status) || status == SS$_CREATED) break;

      /* section already exists, break if 'same size' and version! */
      if (PageCount >= GblSecPages &&
          ActivityGblSecPtr->GblSecVersion == ActivityGblSecVersion)
         break;

      /* delete the current global section, have one more attempt */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      status = SS$_IDMISMATCH;
   }

   if (VMSnok (status))
   {
      /* just disable it */
      ActivityTotalMinutes = 0;
      FaoToStdout (ReportActivityWarning, GblSecPages, status);
      return (status);
   }

   if (status == SS$_CREATED)
   {
      /* first time it's been mapped */
      FaoToStdout (ReportActivityPages, "created", PageCount);
      InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY);
      memset (ActivityGblSecPtr, 0, PageCount * 512);
      ActivityGblSecPtr->GblSecVersion = ActivityGblSecVersion;
      sys$gettim (&ActivityGblSecPtr->StartTime64);
      lib$day (&ActivityGblSecPtr->StartAbsDay,
               &ActivityGblSecPtr->StartTime64,
               &ActivityGblSecPtr->StartMinute);
      /* adjust from ten milli-second to one minute units */
      ActivityGblSecPtr->StartMinute =
         ActivityGblSecPtr->StartAbsDay * MINUTES_IN_DAY +
         ActivityGblSecPtr->StartMinute / 6000;
      InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY);
   }
   else
      FaoToStdout (ReportActivityPages, "existing", PageCount);

   if (CliGblSecNoPerm)
   {
      GblSectionCount++;
      GblPageCount += PageCount;
   }
   else
   {
      GblSectionPermCount++;
      GblPagePermCount += PageCount;
   }

   return (status);
}

/*****************************************************************************/
/*
Resets to zero the activity request and byte acumulators for all days between
the last day that was cleared and this day.  Relies on the calling routine(s)
to have locked the activity global section.
*/

void GraphActivityClearDay ()

{
   int  idx, status;
   ulong  AbsDay,
          Day;
   int64  Time64;
   ushort  Time7 [7];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityClearDay()");

   lib$day (&AbsDay, &HttpdTime64, 0);

   /* just return if there are no intervening days to be cleared */
   if (AbsDay == ActivityGblSecPtr->ClearAbsDay) return;

   Day = ActivityGblSecPtr->ClearAbsDay;
   if (!Day) Day = AbsDay;
   Day++;
   while (Day <= AbsDay)
   {
      idx = ACTIVITY_DATA_IDX (Day, 0);
      memset (&ActivityGblSecPtr->ByteCount64[idx], 0,
              MINUTES_IN_DAY * sizeof(int64));
      memset (&ActivityGblSecPtr->ConnectPeak[idx], 0,
              MINUTES_IN_DAY * sizeof(ushort));
      memset (&ActivityGblSecPtr->RequestCount[idx], 0,
              MINUTES_IN_DAY * sizeof(ulong));
      memset (&ActivityGblSecPtr->RequestPeak[idx], 0,
              MINUTES_IN_DAY * sizeof(ushort));
      Day++;
   }
   ActivityGblSecPtr->ClearAbsDay = AbsDay;

   /* check if the data buffer has "wrapped around" */
   if (AbsDay >= ActivityGblSecPtr->StartAbsDay + ActivityNumberOfDays)
   {
      /* yep, data is now available from a new start date at midnight */
      sys$gettim (&ActivityGblSecPtr->StartTime64);
      GraphActivityOffsetTime (-(ActivityTotalMinutes-MINUTES_IN_DAY),
                               &ActivityGblSecPtr->StartTime64,
                               &Time64);

      sys$numtim (&Time7, &Time64);
      Time7[3] = Time7[4] = Time7[5] = Time7[6] = 0;

      status = lib$cvt_vectim (&Time7, &ActivityGblSecPtr->StartTime64);
      if (VMSnok(status))
         ErrorExitVmsStatus (status, "lib$cvt_vectim()", FI_LI);

      lib$day (&ActivityGblSecPtr->StartAbsDay,
               &ActivityGblSecPtr->StartTime64, 0);

      ActivityGblSecPtr->StartMinute = ActivityGblSecPtr->StartAbsDay *
                                       MINUTES_IN_DAY;
   }
}

/*****************************************************************************/
/*
Adjusts per-minute activity statistics.
Relies on the calling routine to lock the activity global section.

1) Called as a final update by RequestEnd2() when concluding each request.

2) Called periodically by HttpdSupervisor() to update the network transfered
bytes in the activity accumulator for the minute.
*/

void GraphActivityUpdate
(
REQUEST_STRUCT *rqptr,
BOOL FinalUpdate
)
{
   int  idx;
   long  Day;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityUpdate()");

   if (!ActivityTotalMinutes) return;

   /* save the overhead of the lib$day() call if the date has not changed! */
   if (HttpdTime7[2] != GraphPrevDate)
   {
      GraphActivityClearDay ();
      lib$day (&GraphAbsDay, &HttpdTime64, 0);
      GraphPrevDate = HttpdTime7[2];
   }

   Day = GraphAbsDay % ActivityNumberOfDays;
   idx = Day * MINUTES_IN_DAY +
         HttpdTime7[3] * MINUTES_IN_HOUR +
         HttpdTime7[4];

   if (FinalUpdate)
   {
      ActivityGblSecPtr->RequestCount[idx]++;

      if (NetCurrentConnected > ActivityGblSecPtr->ConnectPeak[idx])
         ActivityGblSecPtr->ConnectPeak[idx] = NetCurrentConnected;

      if (NetCurrentProcessing > ActivityGblSecPtr->RequestPeak[idx])
         ActivityGblSecPtr->RequestPeak[idx] = NetCurrentProcessing;
   }

   ActivityGblSecPtr->ByteCount64[idx] += rqptr->NetIoPtr->BytesTallyRx64;
   ActivityGblSecPtr->ByteCount64[idx] += rqptr->NetIoPtr->BytesTallyTx64;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!UL-!UL=!SL !UL !UL !UL !@SQ[!UL] !UL[!UL] !UL[!UL]",
                 ActivityGblSecPtr->StartAbsDay, GraphAbsDay,
                 ActivityGblSecPtr->StartAbsDay - GraphAbsDay,
                 rqptr->rqTime.BeginTime7[3],
                 rqptr->rqTime.BeginTime7[4], Day,
                 &ActivityGblSecPtr->ByteCount64[idx], idx,
                 ActivityGblSecPtr->RequestCount[idx] & ACTIVITY_MASK, idx,
                 ActivityGblSecPtr->ConnectPeak[idx], idx);
}

/*****************************************************************************/
/*
Place a high-order bit indicating some server event (startup, shutdown, restart
or error-induced exit) into request storage for the specific second
*/

void GraphActivityEvent (ulong EventBit)

{
   int  idx;
   long  Day;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "GraphActivityEvent() !&X", EventBit);

   if (!ActivityTotalMinutes) return;

   /* avoid using a mutex just in case it's the cause of the error! */
   if (EventBit != ACTIVITY_EXIT_ERROR)
      InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY);

   /* save the overhead of the lib$day() call if the date has not changed! */
   if (HttpdTime7[2] != GraphPrevDate)
   {
      GraphActivityClearDay ();
      lib$day (&GraphAbsDay, &HttpdTime64, 0);
      GraphPrevDate = HttpdTime7[2];
   }

   Day = GraphAbsDay % ActivityNumberOfDays;
   idx = Day * MINUTES_IN_DAY +
         HttpdTime7[3] * MINUTES_IN_HOUR +
         HttpdTime7[4];
   ActivityGblSecPtr->RequestCount[idx] |= EventBit;

   if (EventBit != ACTIVITY_EXIT_ERROR)
      InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY);
}

/*****************************************************************************/
/*
Scan the specified range in the activity statistics finding the peak-per-
minute and total requests and bytes. Returns the number of minutes in the
specified range, -1 for an error. 'StartAbsDay' is the VMS absolute day,
and 'Hour' the hour of the day (0..23) for the start of the scan.
'NumberOfHours' specifies the duration of the scan. 'MinuteGranularity', if
greater than one, specifies that the simple mean of the data for that period
of minutes is to be used as peak values (totals are raw!).  This function
relies on calling functions to ensure the time and range makes sense!
*/

int GraphActivityDataScan
(
int StartAbsDay,
int StartHour,
int NumberOfHours,
int MinuteGranularity,
uint *PeakConnectPtr,
uint *PeakPerMinRequestPtr,
uint *PeakRequestPtr,
int64 *PeakBytePtr,
int64 *TotalRequestsPtr,
int64 *TotalBytesPtr
)
{
   int  cnt, idx, mcnt;
   uint  AbsDay,
         ConnectPeak,
         NumberOfMinutes,
         PeakConnect,
         PeakPerMinRequest,
         PeakRequest,
         RequestCount,
         RequestPeak,
         RequestValue;
   int64  ByteCount64,
          ByteValue64,
          PeakBytes64,
          SignScratch64,
          TotalBytes64,
          TotalRequests64;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "GraphActivityDataScan() !UL !UL !UL !UL !&X !&X",
                 StartAbsDay, StartHour, NumberOfHours, MinuteGranularity,
                 TotalRequestsPtr, TotalBytesPtr);

   if (!ActivityTotalMinutes) return (-1);

   if (NumberOfHours <= 0 || NumberOfHours > ACTIVITY_DAYS * 24) return (-1);

   InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY);

   /* ensure any days' data between the last request and now are cleared */
   GraphActivityClearDay ();

   NumberOfMinutes = NumberOfHours * MINUTES_IN_HOUR;

   idx = ACTIVITY_DATA_IDX (StartAbsDay, StartHour);

   PeakConnect = PeakPerMinRequest = PeakRequest = 0;
   PeakBytes64 = TotalBytes64 = TotalRequests64 = 0;

   for (mcnt = 0; mcnt < NumberOfMinutes; mcnt += MinuteGranularity)
   {
      if (idx >= ActivityTotalMinutes) idx = 0;
      if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL", mcnt, idx);

      if (MinuteGranularity == 1)
      {
         ByteCount64 = ActivityGblSecPtr->ByteCount64[idx];
         TotalBytes64 += ByteCount64;
         RequestCount = ActivityGblSecPtr->RequestCount[idx] & ACTIVITY_MASK;
         TotalRequests64 += RequestCount;
         ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx];
         RequestPeak = ActivityGblSecPtr->RequestPeak[idx];
         idx++;
      }
      else
      {
         /* find the peak or average of the minute counts */
         RequestCount = ConnectPeak = RequestPeak = 0;
         ByteCount64 = 0;
         for (cnt = 0; cnt < MinuteGranularity; cnt++)
         {
            ByteValue64 = ActivityGblSecPtr->ByteCount64[idx];
            TotalBytes64 += ByteValue64;
            if (ByteValue64 > ByteCount64) ByteCount64 = ByteValue64;
            RequestValue = ActivityGblSecPtr->RequestCount[idx];
            RequestValue = (ulong)RequestValue & ACTIVITY_MASK;
            TotalRequests64 += RequestValue;
            if (RequestValue > RequestCount) RequestCount = RequestValue;
            if (ActivityGblSecPtr->ConnectPeak[idx] > ConnectPeak)
               ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx];
            if (ActivityGblSecPtr->RequestPeak[idx] > RequestPeak)
               RequestPeak = ActivityGblSecPtr->RequestPeak[idx];

            idx++;
         }
         if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
            WatchThis (WATCHALL, WATCH_MOD__OTHER,
                       "!@SQ !UL", &ByteCount64, RequestCount);
      }

      if (ByteCount64 > PeakBytes64) PeakBytes64 = ByteCount64;
      if (RequestCount > PeakPerMinRequest) PeakPerMinRequest = RequestCount;
      if (ConnectPeak > PeakConnect) PeakConnect = ConnectPeak;
      if (RequestPeak > PeakRequest) PeakRequest = RequestPeak;
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!@SQ !@SQ !@SQ !UL !UL !UL",
                  &TotalBytes64, &TotalRequests64, &PeakBytes64,
                  PeakPerMinRequest, PeakRequest, PeakConnect);

   if (PeakBytePtr) *PeakBytePtr = PeakBytes64;
   if (PeakConnectPtr) *PeakConnectPtr = PeakConnect;
   if (PeakPerMinRequestPtr) *PeakPerMinRequestPtr = PeakPerMinRequest;
   if (PeakRequestPtr) *PeakRequestPtr = PeakRequest;
   if (TotalBytesPtr) *TotalBytesPtr = TotalBytes64;
   if (TotalRequestsPtr) *TotalRequestsPtr = TotalRequests64;

   return (NumberOfMinutes);
}

/*****************************************************************************/
/*
Round the request and byte maxima up to the next whole digit in the range
(e.g. 8745 to 9000, 320 to 400)
*/

void GraphActivityMaxima
(
uint *PeakRequestPtr,
uint *MaxRequestPtr,
int64 *MaxBytePtr
)
{
   uint  MaxRequests,
         PeakRequests;
   int64  MaxBytes64,
          MaxBytesScratch64;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityMaxima()");

   PeakRequests = MaxRequests = 0;
   if (PeakRequestPtr) PeakRequests = *PeakRequestPtr;
   if (MaxRequestPtr) MaxRequests = *MaxRequestPtr;
   if (!MaxBytePtr)
   {
      MaxBytePtr = &MaxBytesScratch64;
      MaxBytesScratch64 = 0;
   }

   if (PeakRequests < 10)
      PeakRequests = 10;
   else
   if (PeakRequests < 100)
      PeakRequests = ((PeakRequests / 10) * 10) + 10;
   else
   if (PeakRequests < 1000)
      PeakRequests = ((PeakRequests / 100) * 100) + 100;
   else
   if (PeakRequests < 10000)
      PeakRequests = ((PeakRequests / 1000) * 1000) + 1000;

   if (MaxRequests < 10)
      MaxRequests = 10;
   else
   if (MaxRequests < 100)
      MaxRequests = ((MaxRequests / 10) * 10) + 10;
   else
   if (MaxRequests < 1000)
      MaxRequests = ((MaxRequests / 100) * 100) + 100;
   else
   if (MaxRequests < 10000)
      MaxRequests = ((MaxRequests / 1000) * 1000) + 1000;
   else
   if (MaxRequests < 100000)
      MaxRequests = ((MaxRequests / 10000) * 10000) + 10000;
   else
   if (MaxRequests < 1000000)
      MaxRequests = ((MaxRequests / 100000) * 100000) + 100000;

   MaxBytes64 = *MaxBytePtr;

   if (MaxBytes64 < 100)
      *MaxBytePtr = ((MaxBytes64 / 10) * 10) + 10;
   else
   if (MaxBytes64 < 1000)
      *MaxBytePtr = ((MaxBytes64 / 100) * 100) + 100;
   else
   if (MaxBytes64 < 10000)
      *MaxBytePtr = ((MaxBytes64 / 1000) * 1000) + 1000;
   else
   if (MaxBytes64 < 100000)
      *MaxBytePtr = ((MaxBytes64 / 10000) * 10000) + 10000;
   else
   if (MaxBytes64 < 1000000)
      *MaxBytePtr = ((MaxBytes64 / 100000) * 100000) + 100000;
   else
   if (MaxBytes64 < 10000000)
      *MaxBytePtr = ((MaxBytes64 / 1000000) * 1000000) + 1000000;
   else
   if (MaxBytes64 < 100000000)
      *MaxBytePtr = ((MaxBytes64 / 10000000) * 10000000) + 10000000;
   else
   if (MaxBytes64 < 1000000000)
      *MaxBytePtr = ((MaxBytes64 / 100000000) * 100000000) + 100000000;

   if (MaxRequestPtr) *MaxRequestPtr = MaxRequests;
   if (PeakRequestPtr) *PeakRequestPtr = PeakRequests;
}

/*****************************************************************************/
/*
Set the offset binary time to the base binary time plus or minus the number of
minutes specified.
*/

void GraphActivityOffsetTime
(
int NumberOfMinutes,
int64 *BaseTime64Ptr,
int64 *OffsetTime64Ptr
)
{
   int64  DeltaTime64,
          Seconds64;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "GraphActivityOffsetTime() !SL", NumberOfMinutes);

   if (!NumberOfMinutes)
      Seconds64 = 1;
   else
   if (NumberOfMinutes > 0)
      Seconds64 = NumberOfMinutes * MINUTES_IN_HOUR;
   else
      Seconds64 = -NumberOfMinutes * MINUTES_IN_HOUR;

   DeltaTime64 = Seconds64 * TIME64_ONE_SEC;

   if (NumberOfMinutes >= 0)
      *OffsetTime64Ptr = *BaseTime64Ptr + DeltaTime64;
   else
      *OffsetTime64Ptr = *BaseTime64Ptr - DeltaTime64;
}

/*****************************************************************************/
/*
Return the absolute hour for the supplied time.
*/

ulong GraphActivityAbsHour (int64 *Time64Ptr)

{
   ulong  day, millisec;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityAbsHour()");

   lib$day (&day, Time64Ptr, &millisec);
   return ((day * 24) + ((millisec / 360000)) % 60);
}

/*****************************************************************************/
/*
Return the absolute minute for the supplied time.
*/

ulong GraphActivityAbsMinute (int64 *Time64Ptr)

{
   ulong  day, millisec;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityAbsHour()");

   lib$day (&day, Time64Ptr, &millisec);
   return ((day * 24 * 60) + (millisec / 6000));
}

/*****************************************************************************/
/*
Return a pointer to a string containing a description of the period.
*/

char* GraphActivityPeriod
(
int64 *StartTime64,
int64 *EndTime64
)
{
   static char  MultipleDaysFao [] = "!17&W to !17&W";
   static char  WithinOneDayFao [] = "!17&W to !2ZL:!2ZL";
   static char  PeriodBuffer [64];

   int  status;
   ushort  EndTime7 [7],
           StartTime7 [7];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityPeriod()");

   status = sys$numtim (StartTime7, StartTime64);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   status = sys$numtim (EndTime7, EndTime64);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   if (StartTime7[0] != EndTime7[0] ||
       StartTime7[1] != EndTime7[1] ||
       StartTime7[2] != EndTime7[2])
      status = FaoToBuffer (PeriodBuffer, sizeof(PeriodBuffer), NULL, 
                            MultipleDaysFao, StartTime64, EndTime64);
   else
      status = FaoToBuffer (PeriodBuffer, sizeof(PeriodBuffer), NULL, 
                            WithinOneDayFao, StartTime64,
                            EndTime7[3], EndTime7[4]);
   if (VMSnok (status)) ErrorNoticed (NULL, status, NULL, FI_LI);

   return (PeriodBuffer);
}

/*****************************************************************************/
/*
Generate the HTML activity report page.  The supplied query string specifies
the END TIME for the the display, NOT THE START.  The period actually
specifies for what duration prior to the specified end time the display should
be!
*/ 

void GraphActivityReport (REQUEST_STRUCT  *rqptr)

{
   static char *MonthName [] =
      { "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
            "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

   static char  ProblemNoted [] =
"<div style=\"color:red;font-size:200%;\">\
Problem noted.  Check server process log.</div>\n";

   static char  ResponseFao [] =
"!AZ\
<html>\n\
<head>\n\
!AZ\
!AZ\
!&@\
<style type=\"text/css\">\n\
#activity { position:relative; z-index:1; }\n\
#region { display:none; position:fixed; z-index:2; \
background-color:whiteSmoke; color:black; \
border:1px dotted lightGray; border-radius:3px; \
padding:3px 10px 3px 10px; }\n\
</style>\n\
<script>\n\
<!!--\n\
\
var activityUrl = null;\n\
var errorOccured = false;\n\
var needsUpdating = false;\n\
var updateSeconds = !UL;\n\
var windowInFocus = true;\n\
\
function updatePage() {\n\
   if (windowInFocus)\n\
      location.replace(activityUrl);\n\
   else\n\
      needsUpdating = true;\n\
}\n\
\
function windowFocus() {\n\
   if (needsUpdating && !!errorOccured) location.replace(activityUrl);\n\
   windowInFocus = true;\n\
}\n\
\
function startUpdatePage(Url,updateSeconds) {\n\
   activityUrl = Url;\n\
   if (!!errorOccured) setTimeout(\"updatePage()\",updateSeconds*1000);\n\
}\n\
\
function suppressError() {\n\
   errorOccured = true;\n\
   return true;\n\
}\n\
\
var activityCanvas = null;\n\
var activityRegion = null;\n\
var plotClickArray = [];\n\
!AZ\
\
function plotMouseOut(e) {\n\
   activityRegion.style.display = \'none\';\n\
}\n\
\
function plotMouseMove(e) {\n\
   var acvs, entry, offsetX, offsetY, rwidth, x, xoff, y;\n\
   activityRegion.style.display = \'block\';\n\
   acvs = activityCanvas;\n\
   offsetX = 0; offsetY = 0;\n\
   if (acvs.offsetParent) {\n\
      do {\n\
         offsetX += acvs.offsetLeft;\n\
         offsetY += acvs.offsetTop;\n\
      } while ((acvs = acvs.offsetParent));\n\
   }\n\
   x = e.pageX - offsetX - window.pageXOffset;\n\
   y = e.pageY - offsetY - window.pageYOffset;\n\
   for (var idx = 0; idx < plotClickArray.length; idx++) {\n\
      var entry = plotClickArray[idx];\n\
      if (x > entry[0] && y > entry[1] && x < entry[2] && y < entry[3]) break;\n\
   }\n\
   if (idx < plotClickArray.length)\n\
      activityRegion.innerHTML = entry[5];\n\
   else\n\
      activityRegion.innerHTML = \'<i>no data available</i>\';\n\
   rwidth = activityRegion.offsetWidth;\n\
   if (e.pageX > window.innerWidth + window.pageXOffset - rwidth - 30)\n\
      xoff = -(activityRegion.offsetWidth + 10);\n\
   else\n\
      xoff = +10;\n\
   activityRegion.style.left = (e.pageX - window.pageXOffset + xoff) + \'px\';\n\
   activityRegion.style.top = (e.pageY - window.pageYOffset + 10) + \'px\';\n\
}\n\
\
function plotMouseClick(e) {\n\
   var acvs, entry, offsetX, offsetY, x, y;\n\
   offsetX = 0; offsetY = 0;\n\
   acvs = activityCanvas;\n\
   if (acvs.offsetParent) {\n\
      do {\n\
         offsetX += acvs.offsetLeft;\n\
         offsetY += acvs.offsetTop;\n\
      } while ((acvs = acvs.offsetParent));\n\
   }\n\
   x = e.pageX - offsetX - window.pageXOffset;\n\
   y = e.pageY - offsetY - window.pageYOffset;\n\
   for (var idx = 0; idx < plotClickArray.length; idx++) {\n\
      entry = plotClickArray[idx];\n\
      if (x > entry[0] && y > entry[1] &&  x < entry[2] && y < entry[3])\n\
         location.replace(entry[4]);\n\
   }\n\
}\n\
\
function plotFillStyle(ctx,stroke,colour) {\n\
   if (stroke) { ctx.stroke(); ctx.beginPath(); }\n\
   ctx.fillStyle=colour;\n\
}\n\
\
function plotStrokeStyle(ctx,stroke,colour) {\n\
   if (stroke) { ctx.stroke(); ctx.beginPath(); }\n\
   ctx.strokeStyle=colour;\n\
}\n\
\
function plotStrokeWidth(ctx,stroke,width) {\n\
   if (stroke) { ctx.stroke(); ctx.beginPath(); }\n\
   ctx.lineWidth=width;\n\
}\n\
\
function plotXaxis(ctx,width,grads,y1,y2) {\n\
   var xf = 0;\n\
   var gf = width / (grads + 0.001);\n\
   for (var x = 0; x < width-1; x = Math.round(xf += gf)) {\n\
      ctx.moveTo(x,y1); ctx.lineTo(x,y2);\n\
   }\n\
}\n\
\
function plotYaxis(ctx,height,grads,x1,x2) {\n\
   var yf = 0;\n\
   var gf = height / (grads + 0.001);\n\
   for (var y = 0; y < height-1; y = Math.round(yf += gf)) {\n\
      ctx.moveTo(x1,y); ctx.lineTo(x2,y);\n\
   }\n\
}\n\
\
function plotActivity() {\n\
   activityCanvas = document.getElementById(\'activity\');\n\
   activityCanvas.addEventListener(\'click\',plotMouseClick,false);\n\
   activityCanvas.addEventListener(\'mousemove\',plotMouseMove,false);\n\
   activityCanvas.addEventListener(\'mouseout\',plotMouseOut,false);\n\
   activityRegion = document.getElementById(\'region\');\n\
   var ctx = activityCanvas.getContext(\"2d\");\n\
   // set origin to cartesian\n\
   ctx.translate(0,!UL);\n\
   ctx.scale(1,-1);\n\
!AZ\
}\n\
\
//-->\n\
</script>\n\
<title>WASD !AZ ... Server Activity</title>\n\
</head>\n\
\
<body onload=\"plotActivity();\"\
 onerror=\"suppressError()\"\
 onfocus=\"windowFocus()\"\
 onblur=\"windowInFocus = false\"\
!AZ\n\
<div class=\"wasd\">\n\
<h2><nobr>WASD !AZ</nobr></h2>\n\
<h3>Server Activity!&@</h3>\n\
!20&W\n\
\
<p><table class=\"rghtlft\">\n\
\
<tr>\
<td class=\"targht\">!UL</td>\
<td>&nbsp;</td>\
<td class=\"tacnt\" colspan=\"3\"></td>\
<td>&nbsp;</td>\
<td class=\"talft\">!UL!AZ</td>\
</tr>\n\
\
<tr>\
<td class=\"targht\" style=\"vertical-align:middle;\">!AZ</td>\
<td>&nbsp;</td>\
<td colspan=\"3\">\
<canvas id=\"activity\" width=\"!UL\" height=\"!UL\"></canvas>\
<div id=\"region\">(there's been a problem!!)</div>\
</td>\
<td>&nbsp;</td>\
<td class=\"talft\" style=\"vertical-align:middle;\">!AZ</td>\
</tr>\n\
\
<tr>\
<td class=\"targht\">!UL</td>\
<td>&nbsp;</td>\
<td class=\"talft\">&nbsp;&nbsp;<font size=\"-2\">!UL-!AZ !2ZL:!2ZL</font></td>\
<td class=\"tacnt\" style=\"font-family:monospace;\">!&@!&@</td>\
<td class=\"targht\"><font size=\"-2\">!UL-!AZ !2ZL:!2ZL</font>&nbsp;&nbsp;</td>\
<td>&nbsp;</td>\
<td class=\"talft\">!UL</td>\
</tr>\n\
\
</table>\n\
\
<p><table class=\"rghtlft\">\n\
<tr><th>Period:</th><td>!AZ &nbsp;(!UL hour!%s)</td></tr>\n\
<tr><th>Connections:</th><td>!&L peak!&@</td></tr>\n\
<tr><th>Requests:</th><td>!&,@SQ total; \
&nbsp;!&L max; &nbsp;!&L peak!&@</td></tr>\n\
<tr><th>Bytes:</th><td>!&,@SQ total; \
&nbsp;!&,@SQ max</td></tr>\n\
<tr><th></th>\
<td style=\"font-size:80%\">(Data available from !17&W)</td>\
</tr>\n\
</table>\n\
!AZ!AZ";

   static char  EndPageFao [] =
"<noscript>\
<div style=\"color:red;font-size:200%;\">Requires JavaScript!!</div>\
</noscript>\n\
</div>\n\
</body>\n\
</html>\n";

   static char  MultipleDaysFao [] = "!17&W to !17&W";
   static char  UniquifierFao [] = "+!2ZL!2ZL!2ZL!2ZL!2ZL";
   static char  WithinOneDayFao [] = "!17&W to !2ZL:!2ZL";

   BOOL  CurrentStats,
         HourSupplied;
   int  cnt, scnt, status,
        AdminInstanceCount,
        AtX,
        Bytes,
        ColumnWidth,
        Count,
        Day,
        GraphHeight,
        GraphWidth,
        Hour,
        IncrementMinutes,
        MapSections,
        MinuteGranularity,
        Minutes,
        Month,
        NodeInstanceCount,
        NumberOfDays,
        NumberOfHours,
        NumberOfMinutes,
        PeriodHours,
        UpdateSeconds,
        Year;
   int64  DeltaTime64,
          Time64,
          MaxBytes64,
          PeakBytes64,
          TotalBytes64,
          TotalRequests64;
   ushort  Length;
   ushort  Time7 [7];
   ulong  AbsDay,
          MaxPeak,
          MaxRequests,
          MaxYRequests,
          Minute,
          PeakConnect,
          PeakPerMinRequests,
          PeakRequests,
          Second;
   ulong  *vecptr;
   ulong  FaoVector [64];
   char  *cptr, *sptr, *zptr,
         *BytesPtr,
         *EndPtr,
         *HoursPtr,
         *NodeInstancePtr,
         *StartPtr;
   char  OfString [32],
         RefreshString [32],
         Uniquifier [16];
   GRAPH_STRUCT  *grptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "GraphActivityReport() !&Z",
                 rqptr->rqHeader.QueryStringPtr);

   if (!ActivityTotalMinutes)
   {
      rqptr->rqResponse.HttpStatus = 500;
      ErrorGeneral (rqptr, ErrorGraphNotInit, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   /* allocate heap memory for the graph structure */
   grptr = (GRAPH_STRUCT*)VmGetHeap (rqptr, sizeof(GRAPH_STRUCT));

   grptr->RequestPtr = rqptr;

   GraphWidth = ACTIVITY_GRAPH_WIDTH;
   GraphHeight = ACTIVITY_GRAPH_HEIGHT;

   /**************************/
   /* parse the query string */
   /**************************/

   HourSupplied = false;
   Year = Month = Day = Hour = MaxYRequests = NumberOfHours = 0;
   if (rqptr->rqHeader.QueryStringLength)
   {
      cptr = rqptr->rqHeader.QueryStringPtr;
      if (isdigit(*cptr))
         NumberOfHours = atoi(cptr); 
      else
      {
         while (*cptr)
         {
            if (MATCH3 (cptr, "of="))
            {
               cptr += 3;
               scnt = sscanf (cptr, "%4u%2u%2u%2u/%u",
                              &Year, &Month, &Day, &Hour, &MaxYRequests);
               if (scnt != 5)
                  scnt = Year = Month = Day = Hour = MaxYRequests = 0;
               if (!scnt)
               {
                  scnt = sscanf (cptr, "%4u%2u%2u%2u+%u",
                                 &Year, &Month, &Day, &Hour, &NumberOfHours);
                  if (scnt != 5) scnt = Year = Month = Day = Hour = 0;
               }
               if (scnt != 5)
                  scnt = Year = Month = Day = Hour = MaxYRequests = 0;
               if (!scnt)
               {
                  scnt = sscanf (cptr, "%4u%2u%2u%2u",
                                 &Year, &Month, &Day, &Hour);
                  if (scnt != 4) scnt = Year = Month = Day = Hour = 0;
               }
               if (scnt)
                  HourSupplied = true;
               else
                  NumberOfHours = atoi(cptr); 
            }
            else
            if (MATCH3 (cptr, "dm="))
            {
               cptr += 3;
               scnt = sscanf (cptr, "%ux%u/%u",
                              &GraphWidth, &GraphHeight, &MaxYRequests);
               if (scnt != 3)
                  scnt = GraphWidth = GraphHeight = MaxYRequests = 0;
               if (!scnt)
               {
                  scnt = sscanf (cptr, "%ux%u", &GraphWidth, &GraphHeight);
                  if (scnt != 2)
                     scnt = GraphWidth = GraphHeight = MaxYRequests = 0;
               }
            }
            else
            if (MATCH3 (cptr, "yr="))
               Year = atoi(cptr+3);
            else
            if (MATCH3 (cptr, "mn="))
               Month = atoi(cptr+3);
            else
            if (MATCH3 (cptr, "dy="))
               Day = atoi(cptr+3);
            else
            if (MATCH3 (cptr, "hr="))
            {
               Hour = atoi(cptr+3);
               HourSupplied = true;
            }
            else
            if (MATCH3 (cptr, "du="))
               NumberOfHours = atoi(cptr+3);
            else
            if (MATCH3 (cptr, "xy="))
            {
               cptr += 3;
               GraphWidth = atoi(cptr);
               while (*cptr && isdigit(*cptr)) cptr++;
               while (*cptr && !isdigit(*cptr)) cptr++;
               GraphHeight = atoi(cptr);
            }
            else
            if (!MATCH8 (cptr, "refresh="))
            {
               rqptr->rqResponse.HttpStatus = 400;
               ErrorGeneral (rqptr, ErrorGraphQuery, FI_LI);
               AdminEnd (rqptr);
               return;
            }
            while (*cptr && *cptr != '&') cptr++;
            if (*cptr) cptr++;
         }
      }
   }

   if (MaxYRequests < 0) MaxYRequests = 0;

   if (rqptr->rqHeader.RefererPtr) MaxYRequests = 0;

   for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++)
      if (GraphWidth == cnt * (ACTIVITY_GRAPH_WIDTH / 2)) break;
   if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphWidth = 0;
   for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++)
      if (GraphHeight == cnt * (ACTIVITY_GRAPH_HEIGHT / 2)) break;
   if (cnt >= ACTIVITY_GRAPH_ZOOM+2) GraphHeight = 0;
   if (!GraphWidth || !GraphHeight)
   {
      GraphWidth = ACTIVITY_GRAPH_WIDTH;
      GraphHeight = ACTIVITY_GRAPH_HEIGHT;
   }
   GraphWidth += 2;
   GraphHeight += 2;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!UL/!UL/!UL:!UL !UL !ULx!UL/!UL",
                 Year, Month, Day, Hour,
                 NumberOfHours, GraphWidth, GraphHeight,
                 MaxYRequests);

   /***********/
   /* process */
   /***********/

   grptr->FirstDataAbsHour =
      GraphActivityAbsHour (&ActivityGblSecPtr->StartTime64);

   lib$day (&grptr->CurrentAbsDay, &HttpdTime64, 0);
   grptr->CurrentAbsHour = GraphActivityAbsHour(&HttpdTime64);
   grptr->CurrentAbsMinute = GraphActivityAbsMinute(&HttpdTime64);
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL !UL",
                 grptr->CurrentAbsDay, grptr->CurrentAbsHour,
                 grptr->CurrentAbsMinute);

   /* defaults for any time components not supplied */
   if (!Year) Year = HttpdTime7[0];
   if (!Month) Month = HttpdTime7[1];
   if (!Day) Day = HttpdTime7[2];
   if (!Hour && !HourSupplied) Hour = HttpdTime7[3];
   if (!NumberOfHours) NumberOfHours = 1;

   /* make it a multiple of 4 (which divides the graph up nicely :^) */
   if (NumberOfHours > 2) while (NumberOfHours % 4) NumberOfHours++;
   NumberOfMinutes = NumberOfHours * MINUTES_IN_HOUR;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!UL/!UL/!UL:!UL !UL !ULx!UL/!UL !UL",
                 Year, Month, Day, Hour,
                 NumberOfHours, GraphWidth, GraphHeight,
                 MaxYRequests,  NumberOfMinutes);

   grptr->EndTime7[0] = Year;
   grptr->EndTime7[1] = Month;
   grptr->EndTime7[2] = Day;
   grptr->EndTime7[3] = Hour;
   /* always ends after the 59th minute! */
   grptr->EndTime7[4] = 59;
   grptr->EndTime7[5] = grptr->EndTime7[6] = 0;

   if (VMSnok (status = lib$cvt_vectim (&grptr->EndTime7, &grptr->EndTime64)))
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   /* get the start time as the number of minutes before the end time */
   GraphActivityOffsetTime (-(NumberOfMinutes-1),
                            &grptr->EndTime64,
                            &grptr->StartTime64);
   if (grptr->StartTime64 == -1)
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   status = lib$day (&grptr->StartAbsDay, &grptr->StartTime64, 0);
   if (VMSnok (status))
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, ErrorGraphPeriod, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   sys$numtim (&grptr->StartTime7, &grptr->StartTime64);
   grptr->StartHour = grptr->StartTime7[3];

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL !UL !UL",
                 grptr->CurrentAbsDay,  grptr->StartAbsDay,
                 HttpdTime7[3], grptr->StartHour);

   if (grptr->CurrentAbsDay - grptr->StartAbsDay > ActivityNumberOfDays)
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, ErrorGraphHistory, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (grptr->StartAbsDay > grptr->CurrentAbsDay ||
       (grptr->StartAbsDay == grptr->CurrentAbsDay &&
        grptr->StartHour > HttpdTime7[3]))
   {
      rqptr->rqResponse.HttpStatus = 400;
      ErrorGeneral (rqptr, ErrorGraphFuture, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   DeltaTime64 = HttpdTime64 - grptr->EndTime64;
   if (DeltaTime64 <= 0)
      CurrentStats = false;
   else
      CurrentStats = TRUE;

   grptr->StartMinute = grptr->StartAbsDay * MINUTES_IN_DAY +
                        grptr->StartHour * MINUTES_IN_HOUR;
   grptr->EndMinute = grptr->StartMinute +
                      (NumberOfHours * MINUTES_IN_HOUR) - 1;
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL",
                 grptr->StartMinute, grptr->EndMinute);

   /***********************/
   /* make some estimates */
   /***********************/

   /* width of the bar graph */
   ColumnWidth = (GraphWidth-2) / (NumberOfMinutes * 2);
   if (!ColumnWidth) ColumnWidth = 1;

   /* calculate simple mean for this number of minutes when duration large */
   MinuteGranularity =
      (int)(1.0 / ((float)(GraphWidth-2) / ((float)NumberOfMinutes * 2.0)));
   if (!MinuteGranularity) MinuteGranularity = 1;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL",
                 ColumnWidth, MinuteGranularity);

   GraphActivityDataScan (grptr->StartAbsDay, grptr->StartHour, NumberOfHours,
                          MinuteGranularity,
                          &PeakConnect, &PeakPerMinRequests, &PeakRequests,
                          &PeakBytes64, &TotalRequests64, &TotalBytes64);

   MaxBytes64 = PeakBytes64;
   MaxRequests = PeakPerMinRequests;
   MaxPeak = PeakRequests;
   GraphActivityMaxima (&MaxPeak, &MaxRequests, &MaxBytes64);

   if (!MaxYRequests) MaxYRequests = MaxRequests;
   if (MaxYRequests) MaxYRequests--;
   GraphActivityMaxima (0, &MaxYRequests, 0);

   if (MaxBytes64 < 1000)
   {
      Bytes = MaxBytes64;
      BytesPtr = "";
   }
   else
   if (MaxBytes64 < 1000000)
   {
      Bytes = (int)((float)MaxBytes64 / 1000.0);
      BytesPtr = " K";
   }
   else
   if (MaxBytes64 < 1000000000)
   {
      Bytes = (int)((float)MaxBytes64 / 1000000.0);
      BytesPtr = " M";
   }
   else
   {
      Bytes = (int)((float)MaxBytes64 / 1000000000.0);
      BytesPtr = " G";
   }

   /* set these in the graphics structure */
   grptr->Width = GraphWidth;
   grptr->Height = GraphHeight;
   grptr->Year = Year;
   grptr->Month = Month;
   grptr->Day = Day;
   grptr->Hour = Hour;
   grptr->MaxRequests = MaxRequests;
   grptr->MaxYRequests = MaxYRequests;
   grptr->NumberOfHours = NumberOfHours;
   grptr->NumberOfMinutes = NumberOfMinutes;
   grptr->MaxBytes64 = MaxBytes64;
   grptr->PeakBytes64 = PeakBytes64;

   /**********************/
   /* generate HTML page */
   /**********************/

   if (NumberOfHours <= 4)
   {
      UpdateSeconds = 60 - HttpdTime7[5]; 
      if (UpdateSeconds < 45) UpdateSeconds += 60;
   }
   else
   if (NumberOfHours <= 8)
      UpdateSeconds = 120 - HttpdTime7[5]; 
   else
   if (NumberOfHours <= 24)
      UpdateSeconds = 300 - HttpdTime7[5]; 
   else
      UpdateSeconds = 3600 - HttpdTime7[5]; 
   /* six seconds before the new minute */
   UpdateSeconds -= 5;

   vecptr = FaoVector;
   *vecptr++ = HttpdTime7[1];
   *vecptr++ = HttpdTime7[2];
   *vecptr++ = HttpdTime7[3];
   *vecptr++ = HttpdTime7[4];
   *vecptr++ = HttpdTime7[5];
   status = FaolToBuffer (Uniquifier, sizeof(Uniquifier), NULL,
                          UniquifierFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   NodeInstanceCount = InstanceLockList (INSTANCE_NODE, "\n", &NodeInstancePtr);
   AdminInstanceCount = AdminMenuInstanceCount (rqptr, NodeInstancePtr);

   vecptr = FaoVector;

   *vecptr++ = WASD_DOCTYPE;
   *vecptr++ = HtmlMetaInfo (rqptr, NULL);
   *vecptr++ = AdminWasdCss();
   if (rqptr->rqPathSet.StyleSheetPtr)
   {
      *vecptr++ = "<link rel=\"stylesheet\" type=\"text/css\" href=\"!AZ\">\n";
      *vecptr++ = rqptr->rqPathSet.StyleSheetPtr;
   }
   else
      *vecptr++ = "";

   *vecptr++ = UpdateSeconds;

   /* canvas plotting JavaScript */
   *vecptr++ = GraphActivityClick (grptr);
   *vecptr++ = GraphHeight;
   *vecptr++ = GraphActivityPlot (grptr);

   /* title */
   *vecptr++ = ServerHostPort;

   /* bit of a kludge ;^) skip over the "<body" */
   *vecptr++ = ADMIN_BODY_TAG + 5;
   *vecptr++ = ServerHostPort;
   if (InstanceNodeCurrent > 1)
   {
      if (rqptr->ServicePtr->AdminService)
         *vecptr++ = "<span class=\"abttn bttn200 dbttn\">&minus;</span>";
      else
         *vecptr++ = "&nbsp;&nbsp; (!AZ)";
      *vecptr++ = HttpdProcess.PrcNam;
   }
   else
      *vecptr++ = "";

   *vecptr++ = &rqptr->rqTime.BeginTime64;

   *vecptr++ = MaxYRequests;
   *vecptr++ = Bytes;
   *vecptr++ = BytesPtr;
   *vecptr++ =
"<font color=\"#0000ff\">Requests</font><br>\
<font size=\"-1\" color=\"#ff0000\">(mean)</font><br>\
<font size=\"-3\">per-minute</font>";            
   *vecptr++ = GraphWidth;
   *vecptr++ = GraphHeight;
   *vecptr++ =
"<font color=\"#00bbbb\">Bytes</font><br>\
<font size=\"-1\" color=\"#ff66ff\">(mean)</font><br>\
<font size=\"-3\">per-minute</font>";
   *vecptr++ = 0;

   *vecptr++ = grptr->StartTime7[2];
   *vecptr++ = MonthName[grptr->StartTime7[1]];
   *vecptr++ = grptr->StartTime7[3];
   *vecptr++ = grptr->StartTime7[4];

   OfString[0] = RefreshString[0] = '\0';
   if (rqptr->rqHeader.QueryStringLength)
   {
      zptr = (sptr = OfString) + sizeof(OfString)-1;
      cptr = rqptr->rqHeader.QueryStringPtr;
      while (*cptr && *cptr != '&' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      if (MATCH4 (cptr, "&dm="))
      {
         cptr++;
         while (*cptr && *cptr != '&') cptr++;
      }
      if (*cptr)
      {
         zptr = (sptr = RefreshString) + sizeof(RefreshString)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
      }
   }

   for (cnt = 2; cnt < ACTIVITY_GRAPH_ZOOM+2; cnt++)
   {
      if (GraphWidth-2 != cnt * (ACTIVITY_GRAPH_WIDTH / 2)) continue;

      if (cnt > 2)
      {
         *vecptr++ =
"<a class=\"abttn bttn200\" href=\"!AZ?!AZ&dm=!ULx!UL/!UL!AZ\" \
onclick=\"location.replace(this.href);return false\">\
&minus;</a>";
         *vecptr++ = ADMIN_REPORT_ACTIVITY;
         *vecptr++ = OfString;
         *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_WIDTH / 2);
         *vecptr++ = (cnt - 1) * (ACTIVITY_GRAPH_HEIGHT / 2);
         *vecptr++ = MaxYRequests;
         *vecptr++ = RefreshString;
      }
      else
         *vecptr++ = "<span class=\"abttn bttn200 dbttn\">&minus;</span>";

      if (cnt < ACTIVITY_GRAPH_ZOOM+1)
      {
         *vecptr++ =
"<a class=\"abttn bttn200\" href=\"!AZ?!AZ&dm=!ULx!UL/!UL!AZ\" \
onclick=\"location.replace(this.href);return false\">\
&plus;</a>";
         *vecptr++ = ADMIN_REPORT_ACTIVITY;
         *vecptr++ = OfString;
         *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_WIDTH / 2);
         *vecptr++ = (cnt + 1) * (ACTIVITY_GRAPH_HEIGHT / 2);
         *vecptr++ = MaxYRequests;
         *vecptr++ = RefreshString;
      }
      else
         *vecptr++ = "<span class=\"abttn bttn200 dbttn\">&plus;</span>";

      break;
   }
   if (cnt >= ACTIVITY_GRAPH_ZOOM+2)
   {
      *vecptr++ = "";
      *vecptr++ = "";
   }

   *vecptr++ = grptr->EndTime7[2];
   *vecptr++ = MonthName[grptr->EndTime7[1]];
   *vecptr++ = grptr->EndTime7[3];
   *vecptr++ = grptr->EndTime7[4];

   *vecptr++ = 0;

   *vecptr++ = GraphActivityPeriod (&grptr->StartTime64, &grptr->EndTime64);
   *vecptr++ = NumberOfHours;
   *vecptr++ = PeakConnect;
   if (CurrentStats)
   {
      *vecptr++ = "; &nbsp;!&L current";
      *vecptr++ = NetCurrentConnected;
   }
   else
      *vecptr++ = "";
   *vecptr++ = &TotalRequests64;
   *vecptr++ = PeakPerMinRequests;
   *vecptr++ = PeakRequests;
   if (CurrentStats)
   {
      *vecptr++ = "; &nbsp;!&L current";
      *vecptr++ = NetCurrentProcessing;
   }
   else
      *vecptr++ = "";
   *vecptr++ = &TotalBytes64;
   *vecptr++ = &PeakBytes64;

   *vecptr++ = &ActivityGblSecPtr->StartTime64;

   *vecptr++ = grptr->ProblemCount ? ProblemNoted : "";

   *vecptr++ = AdminRefresh();

   status = FaolToNet (rqptr, ResponseFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   /************/
   /* end page */
   /************/

   status = FaolToNet (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Generate a JavaScript array of regions on the activity graph for moving around
the available data by clicking on that region.  Each entry is itself an array
with five elements; [0]..[3] the x,y and x,y defining the region, [4] the URL
to be loaded on click, and [5] a description of what that region represents
that 'tooltip' when the user hovers over the activity graph.  The array is used
by JavaScript functions activityPlotClick() and activityPlotMove() above. 
Returns a pointer to a string containing JavaScript code to create the array.

This functionality replicates the use of an image map used on earlier,
GIF-based activity graphics. 

For multiple hour reports the upper and lower sections have distinct functions.
The middle 50% of the upper section allows the same end time (most commonly the
current hour) to be examined over twice the current period.  The left 25%
allows the previous period to be viewed (if such data exists), and for
non-current reports the right 25% allows the next period to be viewed.  The
lower half can be divided into sections representing hours or days depending on
the period of the current report.  This allows that period to be viewed in
greater detail.  For single hour reports this section is not mapped.  Refresh
interval is NOT propagated for this periods as this navigation is intended for
looking back and forth, not for monitoring the current.

+-----------------+-------------------------------+-----------------+
|                 |                               |                 |
|   prev period   |    next in size of period     |   next period   |
|                 |                               |                 |
+-----------------+---+-----------------------+---+-----------------+
|                     |                       |                     |
|   prev period size  |   prev period size    |        etc.         |
|                     |                       |                     |
+---------------------+-----------------------+---------------------+
*/

char* GraphActivityClick (GRAPH_STRUCT *grptr)

{
   /* this initialises an array element with 5 elements of its own */
   static char  ClickFao [] = "plotClickArray[!UL] = \
[!UL,!UL,!UL,!UL,\
\'!AZ?of=!4ZL!2ZL!2ZL!2ZL+!UL&dm=!ULx!UL/!UL\',\
\'!UL hour!%s !AZ\'];\n";

   int  count, status,
        ArrayCount,
        AtX,
        GraphHeight,
        GraphWidth,
        MapSections,
        NumberOfDays,
        NumberOfHours,
        MaxYRequests,
        PeriodHours;
   int64  EndTime64,
          StartTime64;
   ushort  Time7 [7];
   ulong  *vecptr;
   ulong  FaoVector [24];
   char  *cptr, *sptr, *zptr;
   REQUEST_STRUCT  *rqptr;
   STR_DSC  *clkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityClick()");

   rqptr = grptr->RequestPtr;

   GraphHeight = grptr->Height;
   GraphWidth = grptr->Width;
   MaxYRequests = grptr->MaxYRequests;
   NumberOfHours = grptr->NumberOfHours;
   NumberOfDays = NumberOfHours / 24;

   ArrayCount = 0;

   clkptr = grptr->ClickPtr = StrDscBegin (rqptr, NULL, GRAPH_CLICK_SIZE); 
   /* grow the allocated memory by this amount when required */
   STR_DSC_REALLOC (clkptr, GRAPH_CLICK_SIZE);

   if (NumberOfHours > 1)
   {
      /*******************************/
      /* half period (across-bottom) */
      /*******************************/

      PeriodHours = 0;
      switch (NumberOfHours)
      {
         case 2 : { PeriodHours = 1; break; }
         case 4 : { PeriodHours = 2; break; }
         case 12 : { PeriodHours = 4; break; }
         case 24 : { PeriodHours = 12; break; }
         case 72 : { PeriodHours = 24; break; }
         case 168 : { PeriodHours = 72; break; }
         case 672 : { PeriodHours = 168; break; }
         default : PeriodHours = NumberOfHours / 2;
      }
      if (!PeriodHours) PeriodHours = 1;

      MapSections = NumberOfHours / PeriodHours;
      StartTime64 = grptr->StartTime64;

      count = 0;
      while (count < MapSections)
      {
         AtX = count++ * GraphWidth / MapSections;

         /* given the start time calculate the end time */
         GraphActivityOffsetTime ((PeriodHours*MINUTES_IN_HOUR)-1,
                                  &StartTime64, &EndTime64);

         if (GraphActivityAbsHour(&StartTime64) >= grptr->FirstDataAbsHour &&
             GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour)
         {             
            sys$numtim (&Time7, &EndTime64);

            vecptr = FaoVector;

            *vecptr++ = ArrayCount++;
            *vecptr++ = AtX;
            *vecptr++ = GraphHeight / 2;
            *vecptr++ = AtX + (GraphWidth / MapSections);
            *vecptr++ = GraphHeight;
            *vecptr++ = ADMIN_REPORT_ACTIVITY;
            *vecptr++ = Time7[0];
            *vecptr++ = Time7[1];
            *vecptr++ = Time7[2];
            *vecptr++ = Time7[3];
            *vecptr++ = PeriodHours;
            *vecptr++ = GraphWidth-2;
            *vecptr++ = GraphHeight-2;
            *vecptr++ = MaxYRequests;
            *vecptr++ = PeriodHours;
            *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64);

            status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector);
            if (VMSnok (status))
            {
               grptr->ProblemCount++;
               ErrorNoticed (rqptr, status, NULL, FI_LI);
            }
         }

         /* next start time */
         GraphActivityOffsetTime (PeriodHours*MINUTES_IN_HOUR,
                                  &StartTime64, &StartTime64);
      }
   }

   /**************************/
   /* less recent (top-left) */
   /**************************/

   GraphActivityOffsetTime (-(NumberOfHours*MINUTES_IN_HOUR),
                            &grptr->StartTime64, &StartTime64);

   GraphActivityOffsetTime ((NumberOfHours*MINUTES_IN_HOUR)-1,
                            &StartTime64, &EndTime64);

   if (GraphActivityAbsHour(&StartTime64) >= grptr->FirstDataAbsHour)
   {
      sys$numtim (&Time7, &EndTime64);

      vecptr = FaoVector;

      *vecptr++ = ArrayCount++;
      *vecptr++ = 0;
      *vecptr++ = 0;
      *vecptr++ = GraphWidth / 4;
      *vecptr++ = GraphHeight / 2;
      *vecptr++ = ADMIN_REPORT_ACTIVITY;
      *vecptr++ = Time7[0];
      *vecptr++ = Time7[1];
      *vecptr++ = Time7[2];
      *vecptr++ = Time7[3];
      *vecptr++ = NumberOfHours;
      *vecptr++ = GraphWidth-2;
      *vecptr++ = GraphHeight-2;
      *vecptr++ = MaxYRequests;
      *vecptr++ = NumberOfHours;
      *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64);

      status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector);
      if (VMSnok (status))
      {
         grptr->ProblemCount++;
         ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   if (NumberOfDays < ActivityNumberOfDays)
   {
      /******************************/
      /* double period (top-centre) */
      /******************************/

      switch (NumberOfHours)
      {
         case 1 : { PeriodHours = 2; break; }
         case 2 : { PeriodHours = 4; break; }
         case 4 : { PeriodHours = 12; break; }
         case 12 : { PeriodHours = 24; break; }
         case 24 : { PeriodHours = 72; break; }
         case 72 : { PeriodHours = 168; break; }
         case 168 : { PeriodHours = 672; break; }
         default : PeriodHours = NumberOfHours * 2;
      }
      if (PeriodHours > ActivityNumberOfDays * 24)
          PeriodHours = ActivityNumberOfDays * 24;

      GraphActivityOffsetTime (-((PeriodHours/2)*MINUTES_IN_HOUR),
                               &grptr->StartTime64, &StartTime64);

      for (;;)
      {
         GraphActivityOffsetTime ((PeriodHours*MINUTES_IN_HOUR)-1,
                                  &StartTime64, &EndTime64);
         /* break if it's not later than the last data available */
         if (GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour) break;

         /* otherwise move earlier by one hour */
         GraphActivityOffsetTime (-MINUTES_IN_HOUR, &StartTime64, &StartTime64);
      }

      sys$numtim (&Time7, &EndTime64);

      vecptr = FaoVector;

      *vecptr++ = ArrayCount++;
      *vecptr++ = GraphWidth / 4;
      *vecptr++ = 0;
      *vecptr++ = GraphWidth / 2 + GraphWidth / 4;
      *vecptr++ = GraphHeight / 2;
      *vecptr++ = ADMIN_REPORT_ACTIVITY;
      *vecptr++ = Time7[0];
      *vecptr++ = Time7[1];
      *vecptr++ = Time7[2];
      *vecptr++ = Time7[3];
      *vecptr++ = PeriodHours;
      *vecptr++ = GraphWidth-2;
      *vecptr++ = GraphHeight-2;
      *vecptr++ = MaxYRequests;
      *vecptr++ = PeriodHours;
      *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64);

      status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector);
      if (VMSnok (status))
      {
         grptr->ProblemCount++;
         ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   /***************************/
   /* more recent (top-right) */
   /***************************/

   GraphActivityOffsetTime (1, &grptr->EndTime64, &StartTime64);

   GraphActivityOffsetTime ((NumberOfHours*MINUTES_IN_HOUR)-1,
                            &StartTime64, &EndTime64);

   if (GraphActivityAbsHour(&EndTime64) <= grptr->CurrentAbsHour)
   {
      sys$numtim (&Time7, &EndTime64);

      vecptr = FaoVector;

      *vecptr++ = ArrayCount++;
      *vecptr++ = GraphWidth / 2 + GraphWidth / 4;
      *vecptr++ = 0;
      *vecptr++ = GraphWidth;
      *vecptr++ = GraphHeight / 2;
      *vecptr++ = ADMIN_REPORT_ACTIVITY;
      *vecptr++ = Time7[0];
      *vecptr++ = Time7[1];
      *vecptr++ = Time7[2];
      *vecptr++ = Time7[3];
      *vecptr++ = NumberOfHours;
      *vecptr++ = GraphWidth-2;
      *vecptr++ = GraphHeight-2;
      *vecptr++ = MaxYRequests;
      *vecptr++ = NumberOfHours;
      *vecptr++ = GraphActivityPeriod (&StartTime64, &EndTime64);

      status = FaolToBuffer (clkptr, -1, NULL, ClickFao, &FaoVector);
      if (VMSnok (status))
      {
         grptr->ProblemCount++;
         ErrorNoticed (rqptr, status, NULL, FI_LI);
      }
   }

   FaoToBuffer (clkptr, -1, NULL, "// !UL/!UL bytes\n",
                STR_DSC_LEN(clkptr), STR_DSC_SIZE(clkptr));

   return (STR_DSC_PTR(clkptr));
}

/*****************************************************************************/
/*
Return a pointer to a string containing the JavaScript code used to plot the
activity graph.
*/

char* GraphActivityPlot (GRAPH_STRUCT *grptr)

{
   static int  ColourAxis = COLOUR_BLACK,
               ColourByte = COLOUR_CYAN,
               ColourNoData = COLOUR_GREY,
               ColourRequest = COLOUR_BLUE,
               ColourByteMean = COLOUR_MAGENTA,
               ColourConnectPeak = COLOUR_DBLUE,
               ColourRequestMean = COLOUR_RED,
               ColourRequestPeak = COLOUR_DWHITE,
               ColourDelPrc = COLOUR_BLACK,
               ColourExit = COLOUR_GREY,
               ColourExitError = COLOUR_RED,
               ColourStartup = COLOUR_GREEN;

   BOOL  EventStartup,
         EventExit,
         EventExitError,
         EventDelPrc;

   int  cnt, idx, mcnt, status, what,
        AbsDay,
        AtX,
        AxisBytes,
        AxisRequests,
        ByteHeight,
        ByteMean,
        ColumnWidth,
        Day,
        GraphHeight,
        GraphWidth,
        Hour,
        MinuteGranularity,
        Month,
        NumberOfHours,
        NumberOfDays,
        NumberOfMinutes,
        ConnectPeakHeight,
        PrevAtX,
        PrevByteMean,
        PrevRequestMean,
        RequestCount,
        RequestHeight,
        RequestMean,
        RequestPeakHeight,
        RequestTotal,
        RequestValue,
        SampleCount,
        Year;
   int64  Time64,
          ByteCount64,
          ByteTotal64,
          ByteValue64,
          MaxBytes64,
          PeakBytes64,
          SignScratch64;
   ulong  AbsActivityDay,
          ConnectPeak,
          DeltaDays,
          DeltaHours,
          DeltaMinutes,
          MaxConnect,
          MaxRequests,
          MaxYRequests,
          PeakConnect,
          PeakRequests,
          RequestPeak,
          Minute;
   ulong  FaoVector [32];
   float  AtXfloat,
          AtXfactor,
          ByteFactor,
          ByteMeanFloat,
          RequestFactor;
   char  *cptr;
   char  Uniquifier [16];
   REQUEST_STRUCT  *rqptr;
   STR_DSC  *pltptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "GraphActivityPlot()");

   rqptr = (REQUEST_STRUCT*)grptr->RequestPtr;

   /* retrieve these from the graphics structure */
   GraphWidth = grptr->Width;
   GraphHeight = grptr->Height;
   Year = grptr->Year;
   Month = grptr->Month;
   Day = grptr->Day;
   Hour = grptr->Hour;
   MaxRequests = grptr->MaxRequests;
   MaxYRequests = grptr->MaxYRequests;
   NumberOfHours = grptr->NumberOfHours;
   NumberOfMinutes = grptr->NumberOfMinutes;
   MaxBytes64 = grptr->MaxBytes64;
   PeakBytes64 = grptr->PeakBytes64;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "!UL/!UL/!UL:!UL !UL !UL !@SQ !ULx!UL/!UL",
                 Year, Month, Day, Hour, NumberOfHours,
                 MaxRequests, &MaxBytes64,
                 GraphWidth, GraphHeight, MaxYRequests);

   pltptr = grptr->PlotPtr = StrDscBegin (rqptr, NULL, GRAPH_PLOT_SIZE); 
   /* grow the allocated memory by this amount when required */
   STR_DSC_REALLOC (pltptr, GRAPH_PLOT_SIZE);

   PrevByteMean = PrevRequestMean = -1;

   /***********************/
   /* make some estimates */
   /***********************/

   /* width of the bar graph */
   ColumnWidth = (GraphWidth-2) / (NumberOfMinutes * 2);
   if (!ColumnWidth) ColumnWidth = 1;

   /* calculate simple mean for this number of minutes when duration large */
   MinuteGranularity =
      (int)(1.0 / ((float)(GraphWidth-2) / ((float)NumberOfMinutes * 2.0)));
   if (!MinuteGranularity) MinuteGranularity = 1;
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL",
                 ColumnWidth, MinuteGranularity);

   if (MaxRequests)
      RequestFactor = (float)(GraphHeight-2) / (float)MaxYRequests;
   else
      RequestFactor = 1.0;

   /* ratio for calculating byte bar graph height */
   if (MaxBytes64)
      ByteFactor = (float)(GraphHeight-2) / (float)MaxBytes64;
   else
      ByteFactor = 1.0;

   AtXfactor = (float)(GraphWidth-2) /
               (float)(NumberOfMinutes * 2) *
               (float)MinuteGranularity;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
   {
      char  String [32];
      sprintf (String, "%f %f", RequestFactor, ByteFactor);
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!AZ", String);
   }

   /**************/
   /* plot graph */
   /**************/

   InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY);

   /* repeated passes to minimise number of colour stroke/fill changes */
   for (what = 1; what <= 7; what++)
   {
      switch (what)
      {
         case 1 : FaoToBuffer (pltptr, -1, NULL, "   // events\n"); break;
         case 2 : FaoToBuffer (pltptr, -1, NULL, "   // requests\n"); break;
         case 3 : FaoToBuffer (pltptr, -1, NULL, "   // peak requests\n"); break;
         case 4 : FaoToBuffer (pltptr, -1, NULL, "   // bytes\n"); break;
         case 5 : FaoToBuffer (pltptr, -1, NULL, "   // peak bytes\n"); break;
         case 6 : FaoToBuffer (pltptr, -1, NULL, "   // mean requests\n"); break;
         case 7 : FaoToBuffer (pltptr, -1, NULL, "   // mean bytes\n"); break;
         default : FaoToBuffer (pltptr, -1, NULL, "   // woops!!\n");
      }

      ByteTotal64 = 0;
      RequestTotal = SampleCount = 0;
      AtXfloat = 1.0;
      Minute = grptr->StartMinute;
      idx = ACTIVITY_DATA_IDX (grptr->StartAbsDay, grptr->StartHour);

      while (Minute <= grptr->EndMinute)
      {
         if (idx >= ActivityTotalMinutes) idx = 0;

         AtX = (int)AtXfloat;

         if (Minute < ActivityGblSecPtr->StartMinute)
         {
            GraphDrawBlock (grptr, AtX, 1, AtX+ColumnWidth+ColumnWidth-1,
                            GraphHeight-1, ColourNoData);
            PrevAtX = AtX;
            AtXfloat += AtXfactor + AtXfactor;
            idx += MinuteGranularity;
            Minute += MinuteGranularity;
            continue;
         }

         if (Minute > grptr->CurrentAbsMinute) break;

         Minute += MinuteGranularity;
         EventDelPrc = EventExit = EventExitError = EventStartup = false;

         if (MinuteGranularity == 1)
         {
            ByteCount64 = ActivityGblSecPtr->ByteCount64[idx];
            ByteTotal64 += ByteCount64;
            ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx];
            RequestValue = ActivityGblSecPtr->RequestCount[idx];
            RequestCount = (ulong)RequestValue & ACTIVITY_MASK;
            RequestTotal += RequestCount;
            RequestPeak = ActivityGblSecPtr->RequestPeak[idx];
            if (RequestValue & ACTIVITY_DELPRC) EventDelPrc = true;
            if (RequestValue & ACTIVITY_EXIT) EventExit = true;
            if (RequestValue & ACTIVITY_EXIT_ERROR) EventExitError = true;
            if (RequestValue & ACTIVITY_STARTUP) EventStartup = true;
            idx++;
         }
         else
         {
            /* find the peak of the minute counts */
            RequestCount = ConnectPeak = RequestPeak = 0;
            ByteCount64 = 0;
            for (cnt = 0; cnt < MinuteGranularity; cnt++)
            {
               ByteValue64 = ActivityGblSecPtr->ByteCount64[idx];
               if (ByteValue64 > ByteCount64) ByteCount64 = ByteValue64;
               RequestValue = ActivityGblSecPtr->RequestCount[idx];
               if (RequestValue & ACTIVITY_DELPRC) EventDelPrc = true;
               if (RequestValue & ACTIVITY_EXIT) EventExit = true;
               if (RequestValue & ACTIVITY_EXIT_ERROR) EventExitError = true;
               if (RequestValue & ACTIVITY_STARTUP) EventStartup = true;
               RequestValue = (ulong)RequestValue & ACTIVITY_MASK;
               if (RequestValue > RequestCount) RequestCount = RequestValue;
               if (ActivityGblSecPtr->ConnectPeak[idx] > ConnectPeak)
                  ConnectPeak = ActivityGblSecPtr->ConnectPeak[idx];
               if (ActivityGblSecPtr->RequestPeak[idx] > RequestPeak)
                  RequestPeak = ActivityGblSecPtr->RequestPeak[idx];
               idx++;
            }
            RequestTotal += RequestCount;
            ByteTotal64 += ByteCount64;
         }

         /* calculate means */
         SampleCount++;
         RequestMean = (int)((float)(RequestTotal / SampleCount) * RequestFactor);

         /* breaking it down step-wise makes it easier (for me) to understand */
         ByteMeanFloat = (float)ByteTotal64;
         ByteMeanFloat /= (float)SampleCount;
         ByteMeanFloat *= ByteFactor;
         ByteMean = (int)ByteMeanFloat;

         /* limit the range (just in case) */
         if (ConnectPeak > MaxRequests) ConnectPeak = MaxRequests;
         if (RequestCount > MaxRequests) RequestCount = MaxRequests;
         if (RequestPeak > MaxRequests) RequestPeak = MaxRequests;
         if (ByteCount64 > MaxBytes64) ByteCount64 = MaxBytes64;

         ConnectPeakHeight = (int)((float)ConnectPeak * RequestFactor);
         RequestHeight = (int)((float)RequestCount * RequestFactor);
         RequestPeakHeight = (int)((float)RequestPeak * RequestFactor);
         ByteHeight = (int)((float)ByteCount64 * ByteFactor);

         if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
            WatchThis (WATCHALL, WATCH_MOD__OTHER,
                       "!UL !@SQ !UL !UL !UL !UL !UL",
                       RequestTotal, ByteTotal64,
                       ConnectPeakHeight, RequestHeight, ByteHeight,
                       RequestMean, ByteMean);

         switch (what)
         {
            case 1 :
            {
               /* event display order; error exit, shutdown, restart, startup */
               if (EventExitError)
                  GraphDrawBlock (grptr, AtX, 1,
                                  AtX, GraphHeight-1,
                                  ColourExitError);
               else
               if (EventDelPrc)
                  GraphDrawBlock (grptr, AtX, 1,
                                  AtX, GraphHeight-1,
                                  ColourDelPrc);
               else
               if (EventExit)
                  GraphDrawBlock (grptr, AtX, 1,
                                  AtX, GraphHeight-1,
                                  ColourExit);

               if (EventStartup)
                  GraphDrawBlock (grptr, AtX+ColumnWidth, 1,
                                  AtX+ColumnWidth, GraphHeight-1,
                                  ColourStartup);

               break;
            }

            case 2 :
            {
               /* instantaneous request value */
               GraphDrawBlock (grptr, AtX, 1,
                               AtX+ColumnWidth-1, RequestHeight,
                               ColourRequest);
               break;
            }

            case 3 :
            {
               /* peak request value */
               if (RequestPeakHeight < RequestHeight)
                  GraphDrawLine (grptr, AtX, RequestPeakHeight,
                                 AtX+ColumnWidth, RequestPeakHeight,
                                 ColourRequestPeak, 2);
               break;
            }

            case 4 :
            {
               /* instantaneous byte value */
               GraphDrawBlock (grptr, AtX+ColumnWidth, 1,
                               AtX+ColumnWidth+ColumnWidth-1,
                               ByteHeight, ColourByte);
               break;
            }

            case 5 :
            {
               /* peak connect value */
               if (ConnectPeakHeight < RequestHeight)
                  GraphDrawLine (grptr, AtX+ColumnWidth, ConnectPeakHeight,
                                 AtX+ColumnWidth+ColumnWidth, ConnectPeakHeight,
                                 ColourConnectPeak, 1);
               break;
            }

            case 6 :
            {
               /* average request value */
               if (PrevRequestMean >= 0)
                  GraphDrawLine (grptr, PrevAtX+ColumnWidth, PrevRequestMean,
                                 AtX+ColumnWidth, RequestMean,
                                 ColourRequestMean, 1);
               PrevRequestMean = RequestMean;
               PrevAtX = AtX;
               break;
            }

            case 7 :
            {
               /* average byte value */
               if (PrevByteMean >= 0)
                  GraphDrawLine (grptr,
                                 PrevAtX+ColumnWidth+ColumnWidth,
                                    PrevByteMean,
                                 AtX+ColumnWidth+ColumnWidth, ByteMean,
                                 ColourByteMean, 1);
               PrevByteMean = ByteMean;
               PrevAtX = AtX;
               break;
            }

            default :
            {
               grptr->ProblemCount++;
               ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI);
            }
         }

         AtXfloat += AtXfactor + AtXfactor;
      }

      switch (what)
      {
         case 6 :
         {
            /* average request value */
            if (PrevRequestMean >= 0)
               GraphDrawLine (grptr, PrevAtX+ColumnWidth, PrevRequestMean,
                              AtX+ColumnWidth, RequestMean,
                              ColourRequestMean, 1);
            break;
         }
         case 7 :
         {
            /* average byte value */
            if (PrevByteMean >= 0)
               GraphDrawLine (grptr,
                              PrevAtX+ColumnWidth+ColumnWidth, PrevByteMean,
                              AtX+ColumnWidth+ColumnWidth, ByteMean,
                              ColourByteMean, 1);
            break;
         }
      }
   }

   InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY);

   if (Minute >= grptr->CurrentAbsMinute)
      GraphDrawBlock (grptr, AtX, 1, GraphWidth-1, GraphHeight-1, ColourNoData);

   /****************/
   /* finish graph */
   /****************/

   if (MaxBytes64 < 100)
      AxisBytes = MaxBytes64 / 10;
   else
   if (MaxBytes64 < 1000)
      AxisBytes = MaxBytes64 / 100;
   else
   if (MaxBytes64 < 10000)
      AxisBytes = MaxBytes64 / 1000;
   else
   if (MaxBytes64 < 100000)
      AxisBytes = MaxBytes64 / 10000;
   else
   if (MaxBytes64 < 1000000)
      AxisBytes = MaxBytes64 / 100000;
   else
   if (MaxBytes64 < 10000000)
      AxisBytes = MaxBytes64 / 1000000;
   else
   if (MaxBytes64 < 100000000)
      AxisBytes = MaxBytes64 / 10000000;
   else
      AxisBytes = MaxBytes64 / 100000000;

   if (MaxRequests < 10)
      AxisRequests = 1;
   else
   if (MaxRequests < 100)
      AxisRequests = (((MaxRequests - 1) / 10) + 1);
   else
   if (MaxRequests < 1000)
      AxisRequests = (((MaxRequests - 1) / 100) + 1);
   else
   if (MaxRequests < 10000)
      AxisRequests = (((MaxRequests - 1) / 1000) + 1);
   else
   if (MaxRequests < 100000)
      AxisRequests = (((MaxRequests - 1) / 10000) + 1);
   else
   if (MaxRequests < 1000000)
      AxisRequests = (((MaxRequests - 1) / 100000) + 1);
   else
      AxisRequests = (((MaxRequests - 1) / 1000000) + 1);

   FaoToBuffer (pltptr, -1, NULL, "   // decorate axes\n");

   if (AxisRequests == 1)
      GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT,
                          AxisRequests*10, 7, ColourAxis);
   GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests*2, 7, ColourAxis);
   GraphGraduateYAxis (grptr, GRAPH_YAXIS_LEFT, AxisRequests, 10, ColourAxis);

   GraphGraduateYAxis (grptr, GRAPH_YAXIS_RIGHT, AxisBytes*2, 7, ColourAxis);
   GraphGraduateYAxis (grptr, GRAPH_YAXIS_RIGHT, AxisBytes, 10, ColourAxis);

   if (NumberOfHours == 1)
   {
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 60, 4, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 12, 7, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM, 2, 10, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 60, 4, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 12, 7, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP, 2, 10, ColourAxis);
   }
   else
   if (NumberOfHours < 24)
   {
      if (NumberOfHours <= 8)
      {
         GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                             NumberOfHours*12, 4, ColourAxis);
         GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                             NumberOfHours*12, 4, ColourAxis);
      }
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                          NumberOfHours*2, 7, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                          NumberOfHours, 10, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                          NumberOfHours*2, 7, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                          NumberOfHours, 10, ColourAxis);
   }
   else
   {
      NumberOfDays = NumberOfHours / 24;
      if (NumberOfDays <= 3)
      {
         GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                             NumberOfDays*24, 4, ColourAxis);
         GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                             NumberOfDays*24, 4, ColourAxis);
      }
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                          NumberOfDays*4, 6, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_BOTTOM,
                          NumberOfDays, 8, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                          NumberOfDays*4, 6, ColourAxis);
      GraphGraduateXAxis (grptr, GRAPH_XAXIS_TOP,
                          NumberOfDays, 8, ColourAxis);
   }

   GraphDrawBorder (grptr, ColourAxis, 1);

   if (grptr->Stroke)
   {
      /* coup de grace */
      FaoToBuffer (pltptr, -1, NULL, "   ctx.stroke();\n");
      grptr->Stroke = false;
   }

   FaoToBuffer (pltptr, -1, NULL, "   // !UL/!UL bytes\n",
                STR_DSC_LEN(pltptr), STR_DSC_SIZE(pltptr));

   if (Minute >= grptr->CurrentAbsMinute)
      FaoToBuffer (pltptr, -1, NULL,
"   startUpdatePage(\'!AZ?of=!UL&dm=!ULx!UL\',updateSeconds);\n",
                   ADMIN_REPORT_ACTIVITY,
                   NumberOfHours,
                   GraphWidth-2,
                   GraphHeight-2);

   return (STR_DSC_PTR(pltptr));
}

/*****************************************************************************/
/*
Set the canvas rectangle fill color.
Tracks current setting and only changes as required.
*/

void GraphSetFillColour
(
GRAPH_STRUCT *grptr,
int colour
)
{
   /*********/
   /* begin */
   /*********/

   if (GraphDebug) fprintf (stdout, "GraphSetFillColour() %d\n", colour);

   if (grptr->FillColour == colour) return;

   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   plotFillStyle(ctx,!AZ,\'!AZ\');\n",
                grptr->Stroke ? "true" : "false",
                GraphColorString(colour));

   grptr->Stroke = false;
   grptr->FillColour = colour;
}

/*****************************************************************************/
/*
Set the canvas line colour.
Tracks current setting and only changes as required.
*/

void GraphSetStrokeColour
(
GRAPH_STRUCT *grptr,
int colour
)
{
   /*********/
   /* begin */
   /*********/

   if (GraphDebug) fprintf (stdout, "GraphSetStrokeColour() %d\n", colour);

   if (grptr->StrokeColour == colour) return;

   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   plotStrokeStyle(ctx,!AZ,\'!AZ\');\n",
                grptr->Stroke ? "true" : "false",
                GraphColorString(colour));

   grptr->Stroke = false;
   grptr->StrokeColour = colour;
}

/*****************************************************************************/
/*
Set the canvas line width.
Tracks current setting and only changes as required.
*/

void GraphSetStrokeWidth
(
GRAPH_STRUCT *grptr,
int width
)
{
   /*********/
   /* begin */
   /*********/

   if (GraphDebug) fprintf (stdout, "GraphSetStrokeWidth() %d\n", width);

   if (grptr->StrokeWidth == width) return;

   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   plotStrokeWidth(ctx,!AZ,\'!UL\');\n",
                grptr->Stroke ? "true" : "false", width);

   grptr->Stroke = false;
   grptr->StrokeWidth = width;
}

/*****************************************************************************/
/*
Return a pointer to the string equivalent of the integer colour.
*/

char* GraphColorString (int Colour)

{
   /*********/
   /* begin */
   /*********/

   if (GraphDebug) fprintf (stdout, "GraphColorString() %d\n", Colour);

   switch (Colour)
   {
      case COLOUR_BLACK : return (COLOUR_S_BLACK);
      case COLOUR_RED : return (COLOUR_S_RED);
      case COLOUR_GREEN : return (COLOUR_S_GREEN);
      case COLOUR_BLUE : return (COLOUR_S_BLUE);
      case COLOUR_YELLOW : return (COLOUR_S_YELLOW);
      case COLOUR_MAGENTA : return (COLOUR_S_MAGENTA);
      case COLOUR_CYAN : return (COLOUR_S_CYAN);
      case COLOUR_WHITE : return (COLOUR_S_WHITE);
      case COLOUR_GREY : return (COLOUR_S_GREY);
      case COLOUR_DRED : return (COLOUR_S_DRED);
      case COLOUR_DGREEN : return (COLOUR_S_DGREEN);
      case COLOUR_DBLUE : return (COLOUR_S_DBLUE);
      case COLOUR_DYELLOW : return (COLOUR_S_DYELLOW);
      case COLOUR_DMAGENTA : return (COLOUR_S_DMAGENTA);
      case COLOUR_DCYAN : return (COLOUR_S_DCYAN);
      case COLOUR_DWHITE : return (COLOUR_S_DWHITE);
      return ("[error]");
   }
}

/*****************************************************************************/
/*
Draws a straight line from (x1,y1) to (x2,y2) of the specified colour and
width.
*/

void GraphDrawLine
(
GRAPH_STRUCT *grptr,
int x1,
int y1,
int x2,
int y2,
int colour,
int width
)
{
   BOOL  SlopePos;
   int  thy, x, xinc, y, yinc;
   float  Slope, yf;

   /*********/
   /* begin */
   /*********/

   if (GraphDebug)
      fprintf (stdout, "GraphDrawLine() %d,%d %d,%d %d %d\n",
               x1, y1, x2, y2, colour, width);

   if (width <= 0) width = 1;
   if (width >= grptr->Width) width = grptr->Width - 1;

   if (x1 < 0) x1 = 0;
   if (x1 >= grptr->Width) x1 = grptr->Width - 1;
   if (x2 < 0) x2 = 0;
   if (x2 >= grptr->Width) x2 = grptr->Width - 1;
   if (y1 < 0) y1 = 0;
   if (y1 >= grptr->Height) y1 = grptr->Height - 1;
   if (y2 < 0) y2 = 0;
   if (y2 >= grptr->Height) y2 = grptr->Height - 1;

   if (x1 > x2)
   {
      x = x1;
      x1 = x2;
      x2 = x;
      y = y1;
      y1 = y2;
      y2 = y;
   }

   GraphSetStrokeWidth (grptr, width);
   GraphSetStrokeColour (grptr, colour);
   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   ctx.moveTo(!UL,!UL);ctx.lineTo(!UL,!UL);\n",
                x1, y1, x2, y2);
   grptr->Stroke = true;
}

/*****************************************************************************/
/*
Draw a rectangle with opposite corners defined by (x1,y1) and (x2,y2) filled
with the specified colour.
*/

void GraphDrawBlock
(
GRAPH_STRUCT *grptr,
int x1,
int y1,
int x2,
int y2,
int colour
)
{
   int  x, y;

   /*********/
   /* begin */
   /*********/

   if (GraphDebug)
      fprintf (stdout, "GraphDrawBlock() %d,%d %d,%d %d\n",
               x1, y1, x2, y2, colour);

   if (x1 < 0) x1 = 0;
   if (x1 >= grptr->Width) x1 = grptr->Width - 1;
   if (x2 < 0) x2 = 0;
   if (x2 >= grptr->Width) x2 = grptr->Width - 1;
   if (y1 < 0) y1 = 0;
   if (y1 >= grptr->Height) y1 = grptr->Height - 1;
   if (y2 < 0) y2 = 0;
   if (y2 >= grptr->Height) y2 = grptr->Height - 1;

   if (x1 > x2)
   {
      x = x1;
      x1 = x2;
      x2 = x;
   }
   if (y1 > y2)
   {
      y = y1;
      y1 = y2;
      y2 = y;
   }

   GraphSetFillColour (grptr, colour);
   GraphSetStrokeColour (grptr, colour);
   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   ctx.fillRect(!UL,!UL,!UL,!UL);\n",
                x1, y1, x2-x1+1, y2-y1+1);
   grptr->Stroke = true;
}

/*****************************************************************************/
/*
Draw graduations along either the top or bottom X axis.
*/

void GraphGraduateXAxis
(
GRAPH_STRUCT *grptr,
int TopOrBottom,
int Graduations,
int length,
int colour
)
{
   int  x, FromY, ToY;

   /*********/
   /* begin */
   /*********/

   if (GraphDebug)
      fprintf (stdout, "GraphGraduateXAxis() %d %d %d\n",
               TopOrBottom, Graduations, colour);

   if (TopOrBottom == GRAPH_XAXIS_BOTTOM)
   {
      FromY = 0;
      ToY = length - 1;
   }
   else
   {
      FromY = grptr->Height - length;
      ToY = grptr->Height - 1;
   }

   GraphSetStrokeColour (grptr, colour);
   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   plotXaxis(ctx,!UL,!UL,!UL,!UL);\n",
                grptr->Width, Graduations, FromY, ToY);
   grptr->Stroke = true;
}

/*****************************************************************************/
/*
Draw graduations along either the left or right Y axis.
*/

void GraphGraduateYAxis
(
GRAPH_STRUCT *grptr,
int LeftOrRight,
int Graduations,
int length,
int colour
)
{
   int  y, FromX, ToX;

   /*********/
   /* begin */
   /*********/

   if (GraphDebug)
      fprintf (stdout, "GraphGraduateYAxis() %d %d %d\n",
               LeftOrRight, Graduations, colour);

   if (LeftOrRight == GRAPH_YAXIS_LEFT)
   {
      FromX = 0;
      ToX = length - 1;
   }
   else
   {
      FromX = grptr->Width - length;
      ToX = grptr->Width - 1;
   }

   GraphSetStrokeColour (grptr, colour);
   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   plotYaxis(ctx,!UL,!UL,!UL,!UL);\n",
                grptr->Height, Graduations, FromX, ToX);
   grptr->Stroke = true;
}

/*****************************************************************************/
/*
Place a border of the specified colour and width around the entire graphic.
*/

void GraphDrawBorder
(
GRAPH_STRUCT *grptr,
int colour,
int width
)
{
   int  x, y, cnt;
   unsigned char  *bptr;

   /*********/
   /* begin */
   /*********/

   if (GraphDebug) fprintf (stdout, "GraphDrawBorder() %d %d\n", colour, width);

   GraphSetStrokeWidth (grptr, width);
   GraphSetStrokeColour (grptr, colour);
   FaoToBuffer (grptr->PlotPtr, -1, NULL,
                "   ctx.rect(0,0,!UL,!UL);\n", grptr->Width, grptr->Height);
   grptr->Stroke = true;
}

/*****************************************************************************/