/**
* This software is licensed to you under the Apache License, Version 2.0 (the
* "Apache License").
*
* LinkedIn's contributions are made under the Apache License. If you contribute
* to the Software, the contributions will be deemed to have been made under the
* Apache License, unless you expressly indicate otherwise. Please do not make any
* contributions that would be inconsistent with the Apache License.
*
* You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, this software
* distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache
* License for the specific language governing permissions and limitations for the
* software governed under the Apache License.
*
* © 2012 LinkedIn Corp. All Rights Reserved.
*/
package com.senseidb.indexing.activity.time;
import com.senseidb.metrics.MetricFactory;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.senseidb.indexing.activity.ActivityPersistenceFactory.AggregatesMetadata;
import com.senseidb.indexing.activity.time.TimeAggregatedActivityValues.IntValueHolder;
import com.senseidb.metrics.MetricsConstants;
import com.yammer.metrics.core.MetricName;
import com.yammer.metrics.core.Timer;
/**
* Operates on top of TimeAggregatedActivityValues.
* It is executed every 30 seceonds.
* Updates the activity values for the time aggregates based on the current time. For example is the activity 5m count is 10,
* and one of the activity updates came into the system more than 5 mins ago,
* it will substract the stale activity value from ten and assign the result to the 5m time aggregated count
* @author vzhabiuk
*
*/
public class AggregatesUpdateJob implements Runnable {
private final static Logger logger = Logger.getLogger(AggregatesUpdateJob.class);
protected ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
private final TimeAggregatedActivityValues timeAggregatedActivityValues;
private final AggregatesMetadata aggregatesMetadata;
private int currentCount;
private final Timer timer = MetricFactory.newTimer(new MetricName(MetricsConstants.Domain,
"timer",
"updateJob-time",
"agregatesUpdateJob"),
TimeUnit.MILLISECONDS,
TimeUnit.SECONDS);
public AggregatesUpdateJob(TimeAggregatedActivityValues timeAggregatedActivityValues, AggregatesMetadata aggregatesMetadata) {
this.timeAggregatedActivityValues = timeAggregatedActivityValues;
this.aggregatesMetadata = aggregatesMetadata;
}
public void start() {
executorService.scheduleAtFixedRate(this, 30, 30, TimeUnit.SECONDS);
}
public void stop() {
executorService.shutdown();
}
public void awaitTermination() {
try {
executorService.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized void run() {
try {
timer.time(new Callable<Void>() {
@Override
public Void call() throws Exception {
runUpdateJob();
return null;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void runUpdateJob() {
int currentTime = Clock.getCurrentTimeInMinutes();
if (currentTime <= aggregatesMetadata.getLastUpdatedTime()) {
return;
}
currentCount = 0;;
for (int i = 0; i <= timeAggregatedActivityValues.maxIndex; i++) {
synchronized (timeAggregatedActivityValues.timeActivities.getLock(i)) {
if (!timeAggregatedActivityValues.timeActivities.isSet(i)) {
continue;
}
IntContainer activities = timeAggregatedActivityValues.timeActivities.getActivities(i);
IntContainer times = timeAggregatedActivityValues.timeActivities.getTimes(i);
int[] updateTempValues = new int[timeAggregatedActivityValues.intActivityValues.length];
updateActivityValues(timeAggregatedActivityValues.intActivityValues, activities, times, currentTime, i, updateTempValues);
}
}
aggregatesMetadata.updateTime(currentTime);
logger.info("Finished the AggregatesUpdateJob. Updated " + currentCount + " records");
}
private final void updateActivityValues(IntValueHolder[] intActivityValues, IntContainer activities, IntContainer times, int currentTime, int index, int[] updateTempValues) {
int minimumAggregateIndex = 0;
for (int activityIndex = 0; activityIndex < activities.getSize(); activityIndex++) {
//the activity is current. As they are sorted in the ascending order, we can stop now
if (times.size() != activities.size()) {
throw new IllegalStateException("activities.size = " + activities.getSize() + ", times.size() = " + times.size());
}
if (times.size() == 0) {
continue;
}
if (currentTime - times.get(activityIndex) < intActivityValues[intActivityValues.length - 1].timeInMinutes) {
break;
}
for (int aggregateIndex = intActivityValues.length - 1; aggregateIndex >= minimumAggregateIndex; aggregateIndex--) {
IntValueHolder intValueHolder = intActivityValues[aggregateIndex];
int currentElapsedTime = currentTime - times.get(activityIndex);
//activity is current
if (currentElapsedTime < intValueHolder.timeInMinutes) {
minimumAggregateIndex = aggregateIndex + 1;
break;
}
int previousElapsedTime = aggregatesMetadata.getLastUpdatedTime() - times.get(activityIndex);
//activity is not current against the current time, but was current for the previous run
if (currentElapsedTime >= intValueHolder.timeInMinutes && previousElapsedTime < intValueHolder.timeInMinutes) {
int activityValue = activities.get(activityIndex);
if (activityValue != 0) {
updateTempValues[aggregateIndex] += activityValue;
currentCount++;
}
}
}
}
for (int i = 0; i < updateTempValues.length; i++) {
int updateValue = updateTempValues[i];
if (updateValue != 0) {
synchronized (intActivityValues[i].activityIntValues.getFieldValues()) {
intActivityValues[i].activityIntValues.update(index, updateValue > 0 ? String.valueOf(-updateValue) : "+" + String.valueOf(updateValue));
}
updateTempValues[i] = 0;
}
}
//remove outdated activities
while (true) {
if (times.size() == 0) {
break;
}
int time = times.peekFirst();
int elapsedTime = currentTime - time;
if (elapsedTime >= intActivityValues[0].timeInMinutes) {
times.removeFirst();
activities.removeFirst();
if (times.size() == 0) {
timeAggregatedActivityValues.timeActivities.reset(index);
}
} else {
break;
}
}
}
}