private List<GridEvent> getNextSlice(long elapsed) { List<GridEvent> slice = new ArrayList<GridEvent>(); if (elapsed <= 0) return slice; this.totalElapsed += elapsed; log.trace("getNextSlice, prevElapsed={}, totalElapsed={}", prevElapsed, totalElapsed); if (prevElapsed >= totalElapsed) { log.warn( "No slice possible with (prevElapsed={}, totalElapsed={})", prevElapsed, totalElapsed); return slice; } long start = prevElapsed; long end = totalElapsed; SortedMap<Long, List<Event>> eventSlice = timeline.getEvents(start, end); if (!eventSlice.isEmpty()) { // We only move the start of the window up when we find an event. This done because the // database might // have gaps if the incoming events cannot be processed in real-time. In that case, we don't // want to // miss any events if they come late. this.prevElapsed = totalElapsed; log.trace("Timeline has {} offset buckets", timeline.getNumOffsets()); log.info( "Requested slice where {}<=t<{} and got " + eventSlice.size() + " buckets", start, end); } if (!eventSlice.isEmpty()) { for (Long offset : eventSlice.keySet()) { log.trace("Got offset bucket {}", offset); if (offset >= totalElapsed) { log.warn( "Timeline returned grid events outside the requested frame: {}>{}", offset, totalElapsed); break; } List<Event> events = eventSlice.get(offset); synchronized (events) { if (events.isEmpty()) { log.trace("Got empty offset bucket for offset {}", offset); } for (Event event : events) { if (event instanceof GridEvent) { GridEvent gridEvent = (GridEvent) event; log.trace("Got grid event {}", gridEvent); slice.add(gridEvent); } else if (event instanceof SnapshotEvent) { SnapshotEvent gridEvent = (SnapshotEvent) event; log.trace("Got snapshot event {}", gridEvent); } else { log.trace("Got unrecognized event {}", event); } } } } } return slice; }
@Override public void run() { while (true) { switch (playState) { case BUFFERING: bufferToNextPosition(); break; case PLAYING: Date currDate = new Date(); long elapsed = (int) ((currDate.getTime() - lastSliceRequestDate.getTime()) * playSpeed); this.lastSliceRequestDate = currDate; // Check if we've been truncated, and move forward if necessary if (totalElapsed < timeline.getFirstOffset()) { log.info( "Elapsed time ({}) occurs before the current timeline ({})", totalElapsed, timeline.getFirstOffset()); long position = elapsed; boolean noMatch = true; while (noMatch) { if (position != timeline.getFirstOffset()) { position = timeline.getFirstOffset(); try { Thread.sleep(500); } catch (InterruptedException e) { // Ignore } } else { noMatch = false; } } log.info("Will buffer to new position: {}", position); bufferAtPosition(position); break; } // Update actors and build a usage map Map<String, Integer> slotsUsedByUser = new HashMap<String, Integer>(); Iterator<? extends Actor> i = getJobActors().values().iterator(); while (i.hasNext()) { Actor actor = i.next(); if (actor instanceof JobActor) { JobActor jobActor = (JobActor) actor; if (jobActor.defunct) { removeJobActor(jobActor.name, jobActor); } else { int slots = 1; if (jobActor.queued) { // If a job is queued then it is represented by a single sprite, so we need the // actual number of slots GridJob job = state.getJobByFullId(jobActor.name); if (job != null) { slots = job.getSlots(); } } if (jobActor.getName().contains(":")) { // Parse a jobId like this: 1275988.2828-4000:1 try { Pattern p = Pattern.compile("(\\d+)\\.(\\d+)-(\\d+):(\\d+)"); Matcher m = p.matcher(jobActor.getName()); if (m.matches()) { int start = Integer.parseInt(m.group(2)); int end = Integer.parseInt(m.group(3)); int interval = Integer.parseInt(m.group(4)); slots = (end - start) / interval; } } catch (Exception e) { log.error("Error parsing jobId: " + jobActor.getName(), e); } } else if (jobActor.getName().contains(",")) { // Parse a jobId like this: 2968157.205,211 try { Pattern p = Pattern.compile("(\\d+)\\.(\\d+),(\\d+)"); Matcher m = p.matcher(jobActor.getName()); if (m.matches()) { int first = Integer.parseInt(m.group(2)); int second = Integer.parseInt(m.group(3)); // There are two jobs listed here, so we require twice the number of slots slots *= 2; } } catch (Exception e) { log.error("Error parsing jobId: " + jobActor.getName(), e); } } String user = jobActor.getUsername(); if (!slotsUsedByUser.containsKey(user)) { slotsUsedByUser.put(user, 0); } if (!jobActor.queued) { slotsUsedByUser.put(user, slotsUsedByUser.get(user) + slots); } } } } legend.retain(slotsUsedByUser); updateState(elapsed); break; case READY: break; case PAUSED: break; case END: return; default: log.error("Invalid play state: " + playState); break; } try { Thread.sleep(50); } catch (InterruptedException e) { // Ignore } } }
private void bufferToNextPosition() { log.info("Buffering to next position: {}", nextStartingPosition); int i = 0; Snapshot reqSnapshot = null; Snapshot prevSnapshot = null; for (Snapshot snapshot : timeline.getSnapshots()) { long offset = timeline.getOffset(snapshot.getSamplingTime()); log.debug("Snapshot {} has offset {}", i, offset); if (offset >= nextStartingPosition) { if (prevSnapshot == null) { reqSnapshot = snapshot; } else { reqSnapshot = prevSnapshot; } break; } prevSnapshot = snapshot; i++; } if (reqSnapshot == null) { reqSnapshot = prevSnapshot; if (reqSnapshot == null) { log.error("Could not find snapshot for offset {}", nextStartingPosition); setPlayState(PlayState.PAUSED); return; } } // Initialize the state at the closest possible snapshot log.info( "Init with snapshot with offset {}", timeline.getOffset(reqSnapshot.getSamplingTime())); this.state = new GridState(reqSnapshot, "runState"); initState(); // Apply all events between the closet snapshot and the desired starting position this.totalElapsed = timeline.getOffset((reqSnapshot.getSamplingTime())) + 1; // This must be set before calling updateState for the first time after changing the position // (i.e. totalElapsed) this.prevElapsed = totalElapsed; long elapsed = nextStartingPosition - totalElapsed; if (elapsed < 0) { log.warn( "Negative time elapsed. Normalizing to nextStartingPosition={}", nextStartingPosition); totalElapsed = nextStartingPosition; elapsed = nextStartingPosition; } log.info("Buffering elapsed: {}", elapsed); this.tweenChanges = false; updateState(elapsed); this.tweenChanges = true; // Get ready to start playing this.lastSliceRequestDate = new Date(); if (totalElapsed != nextStartingPosition) { totalElapsed = nextStartingPosition; } setPlayState(PlayState.READY); log.info("Buffered at totalElapsed={}", totalElapsed); }