Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.codeforces.inmemo</groupId>
<artifactId>inmemo</artifactId>
<version>2.0.1-SNAPSHOT</version>
<version>2.0.2-SNAPSHOT</version>
<packaging>jar</packaging>
<name>inmemo</name>
<distributionManagement>
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/com/codeforces/inmemo/Index.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,30 @@ long findCount(Object value, Matcher<T> predicate) {
return internalFindCount((V) value, predicate);
}

@Nullable
BucketStats getBucketStats() {
if (unique) {
return null;
}

assert map != null;

long bucketCount = 0;
long totalBucketSize = 0;
int maxBucketSize = 0;

for (TLongObjectMap<T> bucket : map.values()) {
int bucketSize = bucket.size();
bucketCount++;
totalBucketSize += bucketSize;
if (bucketSize > maxBucketSize) {
maxBucketSize = bucketSize;
}
}

return new BucketStats(bucketCount, totalBucketSize, maxBucketSize);
}

/**
* Helper interface to emergency query database if object is not found in memory.
*/
Expand All @@ -277,4 +301,36 @@ public interface EmergencyDatabaseHelper<V> {
*/
Object[] getEmergencyQueryFields(@Nullable V indexValue);
}

static final class BucketStats {
private final long bucketCount;
private final long totalBucketSize;
private final int maxBucketSize;

private BucketStats(long bucketCount, long totalBucketSize, int maxBucketSize) {
this.bucketCount = bucketCount;
this.totalBucketSize = totalBucketSize;
this.maxBucketSize = maxBucketSize;
}

long getBucketCount() {
return bucketCount;
}

long getTotalBucketSize() {
return totalBucketSize;
}

int getMaxBucketSize() {
return maxBucketSize;
}

double getAverageBucketSize() {
if (bucketCount == 0) {
return 0.0;
}

return (double) totalBucketSize / bucketCount;
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/codeforces/inmemo/Inmemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public final class Inmemo {
private static final ConcurrentMap<ClassPair, BeanCopier> beanCopiers = new ConcurrentHashMap<>();
private static volatile boolean debug;
private static final Set<Class<?>> noSizeSupportClasses = new HashSet<>();
private static final Set<String> noJournalSupportTableClassNames
= Collections.newSetFromMap(new ConcurrentHashMap<>());

private Inmemo() {
// No operations.
Expand Down Expand Up @@ -92,10 +94,55 @@ public static void unsetSizeSupport(@Nonnull Class<?> clazz) {
noSizeSupportClasses.add(clazz);
}

/**
* Disables journal support for a table class. The first call for a class
* must be made before createTable; repeated calls are always no-op.
*
* @param clazz Table item class.
*/
public static void unsetJournalSupport(@Nonnull Class<?> clazz) {
String tableClassName = ReflectionUtil.getTableClassName(clazz);
tablesLock.lock();
try {
if (noJournalSupportTableClassNames.contains(tableClassName)) {
return;
}
if (tables.containsKey(tableClassName)) {
throw new IllegalStateException("Inmemo.unsetJournalSupport(clazz) must be called"
+ " before Inmemo.createTable [clazz=" + tableClassName + "].");
}
noJournalSupportTableClassNames.add(tableClassName);
} finally {
tablesLock.unlock();
}
}

static Set<Class<?>> getNoSizeSupportClasses() {
return noSizeSupportClasses;
}

static boolean isJournalSupportUnset(@Nonnull Class<?> clazz) {
return noJournalSupportTableClassNames.contains(ReflectionUtil.getTableClassName(clazz));
}

static <T extends HasId> void putTableForUnsetJournalSupportTestOnly(Class<T> clazz) {
tablesLock.lock();
try {
tables.put(ReflectionUtil.getTableClassName(clazz), new Table<>(clazz, "id", null));
} finally {
tablesLock.unlock();
}
}

static <T extends HasId> void removeTableForUnsetJournalSupportTestOnly(Class<T> clazz) {
tablesLock.lock();
try {
tables.remove(ReflectionUtil.getTableClassName(clazz));
} finally {
tablesLock.unlock();
}
}

/**
* Creates new table, if there is already table for compatible class then doing nothing.
*
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/com/codeforces/inmemo/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -71,6 +72,14 @@ public static void setJournalsDir(File journalsDir) {
Table.journalsDir = journalsDir;
}

static File getJournalsDirForTesting() {
return journalsDir;
}

static void setJournalsDirForTesting(File journalsDir) {
Table.journalsDir = journalsDir;
}

Table(Class<T> clazz, String indicatorField, Inmemo.Filter<T> rowFilter) {
this.clazz = clazz;
if (indicatorField.contains("@")) {
Expand All @@ -84,6 +93,11 @@ public static void setJournalsDir(File journalsDir) {
clazzSpec = ReflectionUtil.getTableClassSpec(clazz);
ids = Inmemo.getNoSizeSupportClasses().contains(clazz) ? null : new TLongHashSet();
this.rowFilter = rowFilter;
if (Inmemo.isJournalSupportUnset(clazz)) {
journal = null;
useJournal = false;
deleteStaleJournalFileQuietly();
}
}

void createUpdater(Object initialIndicatorValue) {
Expand Down Expand Up @@ -294,6 +308,34 @@ List<T> findAndUpdateByEmergencyQueryFields(Object[] fields) {
return tableUpdater.findAndUpdateByEmergencyQueryFields(fields);
}

void logBucketStats() {
lock.lock();
try {
for (Index<T, ?> index : indices.values()) {
Index.BucketStats bucketStats = index.getBucketStats();
if (bucketStats == null) {
continue;
}

logger.info("Inmemo bucket stats [table="
+ ReflectionUtil.getTableClassName(clazz)
+ ", index="
+ index.getName()
+ ", buckets="
+ bucketStats.getBucketCount()
+ ", totalBucketSize="
+ bucketStats.getTotalBucketSize()
+ ", avgBucketSize="
+ String.format(Locale.US, "%.2f", bucketStats.getAverageBucketSize())
+ ", maxBucketSize="
+ bucketStats.getMaxBucketSize()
+ "].");
}
} finally {
lock.unlock();
}
}

void deleteJournal() throws IOException {
File journalFile = new File(journalsDir, getInmemoFilename());
if (journalFile.isFile()) {
Expand All @@ -305,6 +347,17 @@ void deleteJournal() throws IOException {
}
}

private void deleteStaleJournalFileQuietly() {
try {
deleteJournal();
logger.info("Journal support is disabled for table '" + clazz.getSimpleName()
+ "', stale journal file (if any) has been deleted.");
} catch (IOException | RuntimeException e) {
logger.error("Journal support is disabled for table '" + clazz.getSimpleName()
+ "', but failed to delete stale journal file.", e);
}
}

private String getInmemoFilename() {
return clazz.getSimpleName() + ".inmemo";
}
Expand Down
Loading
Loading