From c53b6ffbb00f8a5cc09c1a8ae2c8cc2f8a1309f6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 2 Mar 2018 18:26:50 -0800 Subject: [PATCH] rework montagereview to load data faster and leave event and frame data in json objects --- web/skins/classic/views/js/montagereview.js | 169 ++++++++++++------ .../classic/views/js/montagereview.js.php | 119 +++++------- web/skins/classic/views/montagereview.php | 31 ++-- 3 files changed, 172 insertions(+), 147 deletions(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 826c814ca..148b0e50b 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -38,42 +38,95 @@ function evaluateLoadTimes() { else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1); else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1); else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1); - currentDisplayInterval = Math.min(Math.max(currentDisplayInterval, 30), 10000); // limit this from about 30fps to .1 fps + // limit this from about 40fps to .1 fps + currentDisplayInterval = Math.min(Math.max(currentDisplayInterval, 40), 10000); imageLoadTimesEvaluated=0; setSpeed(speedIndex); $('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + "."; } // end evaluateLoadTimes() +function getFrame( monId, time ) { + var Frame = null; + for ( var event_id in events ) { + // Search for the event matching this time. Would be more efficient if we had events indexed by monitor + Event = events[event_id]; + if ( Event.MonitorId != monId || Event.StartTimeSecs > time || Event.EndTimeSecs < time ) + continue; + + var duration = Event.EndTimeSecs - Event.StartTimeSecs; + var frame = parseInt((time - Event.StartTimeSecs)/(duration)*Object.keys(Event.FramesById).length)+1; + // Need to get frame by time, not some fun calc that assumes frames have the same mlength. + // Frames are not sorted. + for ( var frame_id in Event.FramesById ) { +if ( 0 ) { + if ( frame == 0 ) { +console.log("Found frame for time " + time ); +console.log(Frame); + Frame = Event.FramesById[frame_id]; + break; + } + frame --; + continue; +} + if ( + Event.FramesById[frame_id].TimeStampSecs == time + || ( + Event.FramesById[frame_id].TimeStampSecs < time + && ( + (!Event.FramesById[frame_id].NextTimeStampSecs) + || + (Event.FramesById[frame_id].NextTimeStampSecs > time) + ) + ) + ) { + Frame = Event.FramesById[frame_id]; + break; + } + } // end foreach frame in the event. + if ( ! Frame ) { +console.log("Difn't find frame for " + time ); + return null; + } + } // end foreach event + return Frame; +} + // time is seconds since epoch function getImageSource( monId, time ) { if ( liveMode == 1 ) { - var new_url = monitorImageObject[monId].src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) )); + var new_url = monitorImageObject[monId].src.replace( + /rand=\d+/i, + 'rand='+Math.floor((Math.random() * 1000000) ) + ); if ( auth_hash ) { // update auth hash new_url = new_url.replace(/auth=[a-z0-9]+/i, 'auth='+auth_hash); } return new_url; } + var Frame = getFrame(monId, time); + if ( Frame ) { + Event = events[Frame.EventId]; - for ( var i=0, eIdlength = eId.length; i < eIdlength; i++ ) { - // Search for the event matching this time. Would be more efficient if we had events indexed by monitor - if ( eMonId[i] == monId && time >= eStartSecs[i] && time <= eEndSecs[i] ) { - var duration = eEndSecs[i]-eStartSecs[i]; - var frame = parseInt((time - eStartSecs[i])/(duration)*eventFrames[i])+1; - var storage = Storage[eStorageId[i]]; - if ( storage.ServerId ) { - var server = Servers[storage.ServerId]; - if ( server ) { -//console.log( server.Hostname + " for event " + eId[i] ); - return location.protocol + '//' + server.Hostname + '/index.php?view=image&eid=' + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height; - } else { - console.log("No server found for " + storage.ServerId ); - } + var storage = Storage[Event.StorageId]; + if ( storage.ServerId ) { + var server = Servers[storage.ServerId]; + if ( server ) { + //console.log( server.Hostname + " for event " + eId[i] ); + return location.protocol + '//' + server.Hostname + + //'/cgi-bin/zms?mode=jpeg&replay=single&event=' + event_id + + //'&frame='+Frame.FrameId + +'/index.php?view=image&eid=' + event_id + '&fid='+Frame.FrameId + + "&width=" + monitorCanvasObj[monId].width + + "&height=" + monitorCanvasObj[monId].height; + } else { + console.log("No server found for " + storage.ServerId ); } - //console.log("No storage found for " + eStorageId[i] ); - return "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height; } - } // end for + //console.log("No storage found for " + eStorageId[i] ); + return '/index.php?view=image&eid=' + Frame.EventId + '&fid='+Frame.FrameId + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height; + return "/cgi-bin/zms?mode=jpeg&replay=single&event=" + Frame.EventId + '&frame='+Frame.FrameId + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height; + } // end found Frame return "no data"; } @@ -294,7 +347,7 @@ function drawGraph() { canvas.height = cHeight; - if ( eId.length == 0 ) { + if ( Object.keys(events).length == 0 ) { ctx.globalAlpha=1; ctx.font= "40px Georgia"; ctx.fillStyle="white"; @@ -308,24 +361,32 @@ function drawGraph() { // first fill in the bars for the events (not alarms) - for(var i=0; i0); i++) { - // Now put in scored frames (if any) - var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down - var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up - if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide - ctx.fillStyle=monitorColour[fMonId[i]]; - ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background - ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight); - } + ctx.clearRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker + ctx.fillRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight); + + for ( var frame_id in Event.FramesById ) { + var Frame = Event.FramesById[frame_id]; + if ( ! Frame.Score ) + continue; + + // Now put in scored frames (if any) + var x1=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down + var x2=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up + if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide + ctx.fillStyle=monitorColour[Event.MonitorId]; + ctx.globalAlpha = 0.4 + 0.6 * (1 - Frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background + ctx.fillRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight); + } // end foreach frame + } // end foreach Event + for(var i=0; i= eStartSecs[i] && currentTimeSecs <= eEndSecs[i] ) { - url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) )); - break; - } else if ( currentTimeSecs <= eStartSecs[i] ) { - if ( i ) { - // Didn't find an exact event, so go with the one before. - url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) )); - } - break; - } - } - if ( url ) { - createPopup(url, 'zmEvent', 'event', monitorWidth[monId], monitorHeight[monId]); + var Frame = getFrame( monId, currentTimeSecs ); + if ( Frame ) { + url="?view=event&eid=" + Frame.EventId + '&fid=' +Frame.FrameId; + createPopup(url, 'zmEvent', 'event', monitorWidth[monId], monitorHeight[monId]); } else { url="?view=watch&mid=" + monId.toString(); createPopup(url, 'zmWatch', 'watch', monitorWidth[monId], monitorHeight[monId] ); } - } + } // end if live/events } function zoom(monId,scale) { @@ -787,8 +836,6 @@ function changeDateTime(e) { // >>>>>>>>> Initialization that runs on window load by being at the bottom function initPage() { - canvas = $("timeline"); - ctx = canvas.getContext('2d'); for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) { var monId = monitorPtr[i]; if ( ! monId ) continue; @@ -804,7 +851,11 @@ function initPage() { loadImage2Monitor( monId, monitorImageURL[monId] ); } } - drawGraph(); + if ( !liveMode ) { + canvas = $("timeline"); + ctx = canvas.getContext('2d'); + drawGraph(); + } setSpeed(speedIndex); //setFit(fitMode); // will redraw //setLive(liveMode); // will redraw diff --git a/web/skins/classic/views/js/montagereview.js.php b/web/skins/classic/views/js/montagereview.js.php index 029eb92ff..1349ee512 100644 --- a/web/skins/classic/views/js/montagereview.js.php +++ b/web/skins/classic/views/js/montagereview.js.php @@ -9,10 +9,14 @@ echo $offset . '; // ' . floor($offset / 3600) . ' hours '; var currentScale=; var liveMode=; var fitMode=; -var currentSpeed=; // slider scale, which is only for replay and relative to real time + +// slider scale, which is only for replay and relative to real time +var currentSpeed=; var speedIndex=; -// will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms) +// will be set based on performance, this is the display interval in milliseconds +// for history, and fps for live, and dynamically determined (in ms) + var currentDisplayInterval=; var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live) var timerInterval; // milliseconds between interrupts @@ -21,12 +25,6 @@ var freeTimeLastIntervals=[]; // Percentage of current interval used in loadi var imageLoadTimesEvaluated=0; // running count var imageLoadTimesNeeded=15; // and how many we need var timeLabelsFractOfRow = 0.9; -var eMonId = []; -var eId = []; -var eStorageId = []; -var eStartSecs = []; -var eEndSecs = []; -var eventFrames = []; // this is going to presume all frames equal durationlength fetch( PDO::FETCH_ASSOC ) ) { + $event_id = $event['Id']; + $EventsById[$event_id] = $event; + } - $StartTimeSecs = strtotime($event['StartTime']); - $EndTimeSecs = strtotime($event['EndTime']); + $next_frames = array(); - if ( $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs; + if ( $result = dbQuery($frameSql) ) { + $next_frame = null; + while( $frame = $result->fetch(PDO::FETCH_ASSOC) ) { + $event_id = $frame['EventId']; + $event = &$EventsById[$event_id]; + + $frame['TimeStampSecs'] = $event['StartTimeSecs'] + $frame['Delta']; + if ( !isset($event['FramesById']) ) { + $event['FramesById'] = array(); + $frame['NextTimeStampSecs'] = 0; + } else { + $frame['NextTimeStampSecs'] = $next_frames[$frame['EventId']]['TimeStampSecs'];; + } + $event['FramesById'] += array( $frame['Id']=>$frame ); + $next_frames[$frame['EventId']] = $frame; + } + } + + echo "var events = {\n"; + foreach ( $EventsById as $event_id=>$event ) { + + $StartTimeSecs = $event['StartTimeSecs']; + $EndTimeSecs = $event['EndTimeSecs']; + + if ( $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs; if ( $maxTimeSecs < $EndTimeSecs ) $maxTimeSecs = $EndTimeSecs; - echo " -eMonId[$index]=" . $event['MonitorId'] . "; -eStorageId[$index]=".$event['StorageId'] . "; -eId[$index]=" . $event['Id'] . "; -eStartSecs[$index]=" . $StartTimeSecs . "; -eEndSecs[$index]=" . $EndTimeSecs . "; -eventFrames[$index]=" . $event['Frames'] . "; - "; + $event_json = json_encode($event, JSON_PRETTY_PRINT); + echo " $event_id : $event_json,\n"; $index = $index + 1; - if ( $event['MaxScore'] > 0 ) + if ( $event['MaxScore'] > 0 ) { + if ( $event['MaxScore'] > $maxScore ) + $maxScore = $event['MaxScore']; $anyAlarms = true; + } } +echo " };\n"; // if there is no data set the min/max to the passed in values if ( $index == 0 ) { @@ -93,60 +118,6 @@ eventFrames[$index]=" . $event['Frames'] . "; $maxTimeSecs = strtotime($maxTime); } - // If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments - // comparison in else governs how aggressively it consolidates - - echo " -var fMonId = []; -var fTimeFromSecs = []; -var fTimeToSecs = []; -var fScore = []; -"; -$maxScore=0; -$index=0; -$mId=-1; -$fromSecs=-1; -$toSecs=-1; -$maxScore=-1; - -if ( $anyAlarms && $result = dbQuery( $frameSql ) ) { - - while( $frame = $result->fetch( PDO::FETCH_ASSOC ) ) { - if ( $mId < 0 ) { - $mId = $frame['MonitorId']; - $fromSecs = $frame['TimeStampSecs']; - $toSecs = $frame['TimeStampSecs']; - $maxScore = $frame['Score']; - } else if ( $mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10 ) { - // dump this one start a new - $index++; - echo " - -fMonId[$index]= $mId; -fTimeFromSecs[$index]= $fromSecs; -fTimeToSecs[$index]= $toSecs; -fScore[$index]= $maxScore; -"; - $mId = $frame['MonitorId']; - $fromSecs = $frame['TimeStampSecs']; - $toSecs = $frame['TimeStampSecs']; - $maxScore = $frame['Score']; - } else { - // just add this one on - $toSecs = $frame['TimeStampSecs']; - if ( $maxScore < $frame['Score'] ) $maxScore = $frame['Score']; - } - } -} -if ( $mId > 0 ) { - echo " - fMonId[$index]= $mId; - fTimeFromSecs[$index]= $fromSecs; - fTimeToSecs[$index]= $toSecs; - fScore[$index]= $maxScore; -"; -} - echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms. } // end if initialmodeislive diff --git a/web/skins/classic/views/montagereview.php b/web/skins/classic/views/montagereview.php index d2d60b8e7..c5e5e3913 100644 --- a/web/skins/classic/views/montagereview.php +++ b/web/skins/classic/views/montagereview.php @@ -94,14 +94,14 @@ if (isset($_REQUEST['minTime']) && isset($_REQUEST['maxTime']) && count($display // if the bulk record has not been written - to be able to include more current frames reduce bulk frame sizes (event size can be large) // Note we round up just a bit on the end time as otherwise you get gaps, like 59.78 to 00 in the next second, which can give blank frames when moved through slowly. -$eventsSql = ' - SELECT E.Id,E.Name,E.StorageId,E.StartTime AS StartTime, - CASE WHEN E.EndTime IS NULL THEN (SELECT NOW()) ELSE E.EndTime END AS EndTime, - E.Length, - CASE WHEN E.Frames IS NULL THEN (SELECT COUNT(*) FROM Frames F WHERE F.EventId=E.Id) ELSE E.Frames END AS Frames, - E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId +$eventsSql = 'SELECT + E.Id,E.Name,E.StorageId, + E.StartTime AS StartTime,UNIX_TIMESTAMP(E.StartTime) AS StartTimeSecs, + CASE WHEN E.EndTime IS NULL THEN (SELECT NOW()) ELSE E.EndTime END AS EndTime, + UNIX_TIMESTAMP(EndTime) AS EndTimeSecs, + E.Length, E.Frames, E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId FROM Events AS E - WHERE NOT isnull(E.Frames) + WHERE 1 > 0 '; // select E.Id,E.Name,UNIX_TIMESTAMP(E.StartTime) as StartTimeSecs,UNIX_TIMESTAMP(max(DATE_ADD(E.StartTime, Interval Delta+0.5 Second))) as CalcEndTimeSecs, E.Length,max(F.FrameId) as Frames,E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId @@ -112,10 +112,9 @@ $eventsSql = ' // Note that the delta value seems more accurate than the time stamp for some reason. $frameSql = ' - SELECT E.Id AS eId, E.MonitorId, UNIX_TIMESTAMP(DATE_ADD(E.StartTime, Interval Delta Second)) AS TimeStampSecs, max(F.Score) AS Score - FROM Events AS E - INNER JOIN Frames AS F ON (F.EventId = E.Id) - WHERE F.Score>0 + SELECT Id, FrameId, EventId, TimeStamp, UNIX_TIMESTAMP(TimeStamp) AS TimeStampSecs, Score, Delta + FROM Frames + WHERE EventId IN (SELECT E.Id FROM Events AS E WHERE 1>0 '; // This program only calls itself with the time range involved -- it does all monitors (the user can see, in the called group) all the time @@ -183,7 +182,7 @@ if ( isset($_REQUEST['current']) ) $liveMode = 1; // default to live if ( isset($_REQUEST['live']) && $_REQUEST['live']=='0' ) - $liveMode=0; + $liveMode = 0; $initialDisplayInterval = 1000; if ( isset($_REQUEST['displayinterval']) ) @@ -196,10 +195,14 @@ if ( isset($minTime) && isset($maxTime) ) { $maxTimeSecs = strtotime($maxTime); Logger::Debug("Min/max time secs: $minTimeSecs $maxTimeSecs"); $eventsSql .= " AND EndTime > '" . $minTime . "' AND StartTime < '" . $maxTime . "'"; - $frameSql .= " AND TimeStamp > '" . $minTime . "' AND TimeStamp < '" . $maxTime . "'"; + $frameSql .= " AND EndTime > '" . $minTime . "' AND StartTime < '" . $maxTime . "'"; + $frameSql .= ") AND TimeStamp > '" . $minTime . "' AND TimeStamp < '" . $maxTime . "'"; } -$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC'; +#$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC'; +#$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC'; $eventsSql .= ' ORDER BY E.Id ASC'; +// DESC is intentional. We process them in reverse order so that we can point each frame to the next one in time. +$frameSql .= ' ORDER BY Id DESC'; $monitors = array(); foreach( $displayMonitors as $row ) {