blob: 2643b48e9e7a3fd4c1af9e28962e4de3cbb2d73e [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
package org.eclipse.jifa.worker.support;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.vertx.core.Future;
import org.eclipse.jifa.common.aux.JifaException;
import org.eclipse.jifa.common.enums.FileType;
import org.eclipse.jifa.common.enums.ProgressState;
import org.eclipse.jifa.common.util.ErrorUtil;
import org.eclipse.jifa.common.util.FileUtil;
import org.eclipse.jifa.worker.support.heapdump.SnapshotContext;
import org.eclipse.mat.snapshot.SnapshotFactory;
import org.eclipse.mat.util.IProgressListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.eclipse.jifa.common.enums.FileType.HEAP_DUMP;
import static org.eclipse.jifa.common.util.Assertion.ASSERT;
import static org.eclipse.jifa.worker.support.heapdump.HeapDumpSupport.VOID_LISTENER;
public class Analyzer {
private static final Logger LOGGER = LoggerFactory.getLogger(Analyzer.class);
private Map<String, AnalysisProgressListener> listeners;
private Cache<String, Object> cache;
private Analyzer() {
listeners = new HashMap<>();
cache = CacheBuilder.newBuilder().build();
}
private static <T> T getOrBuild(String key, Builder<T> builder) {
T result = getInstance().getCacheValueIfPresent(key);
if (result != null) {
return result;
}
synchronized (key.intern()) {
result = getInstance().getCacheValueIfPresent(key);
if (result != null) {
return result;
}
try {
result = builder.build(key);
} catch (Throwable t) {
throw new JifaException(t);
}
getInstance().putCacheValue(key, result);
return result;
}
}
private static SnapshotContext getOrOpenSnapshotContext(String heapFile,
Map<String, String> option, IProgressListener listener) {
return getOrBuild(heapFile, key -> new SnapshotContext(
SnapshotFactory.openSnapshot(new File(FileSupport.filePath(HEAP_DUMP, heapFile)), option,
listener)));
}
public static SnapshotContext getOrOpenSnapshotContext(String heapFile) {
return getOrOpenSnapshotContext(heapFile, Collections.emptyMap(), VOID_LISTENER);
}
public static Analyzer getInstance() {
return Singleton.INSTANCE;
}
public boolean isFirstAnalysis(FileType fileType, String file) {
switch (fileType) {
case HEAP_DUMP:
return !new File(FileSupport.indexPath(fileType, file)).exists() &&
!new File(FileSupport.errorLogPath(fileType, file)).exists() &&
getFileListener(file) == null;
default:
throw new IllegalArgumentException(fileType.name());
}
}
public void analyze(Future<Void> future, FileType fileType, String fileName, Map<String, String> options) {
AnalysisProgressListener progressListener;
if (getCacheValueIfPresent(fileName) != null ||
new File(FileSupport.errorLogPath(fileType, fileName)).exists()) {
future.complete();
return;
}
progressListener = new AnalysisProgressListener();
boolean success = putFileListener(fileName, progressListener);
future.complete();
if (success) {
try {
switch (fileType) {
case HEAP_DUMP:
getOrOpenSnapshotContext(fileName, options, progressListener);
break;
default:
break;
}
} catch (Exception e) {
File log = new File(FileSupport.errorLogPath(fileType, fileName));
FileUtil.write(log, progressListener.log(), false);
FileUtil.write(log, ErrorUtil.toString(e), true);
} finally {
removeFileListener(fileName);
}
}
}
public void clean(FileType fileType, String fileName) {
clearCacheValue(fileName);
File errorLog = new File(FileSupport.errorLogPath(fileType, fileName));
if (errorLog.exists()) {
ASSERT.isTrue(errorLog.delete(), "Delete error log failed");
}
if (getFileListener(fileName) != null) {
return;
}
File index = new File(FileSupport.indexPath(fileType, fileName));
if (index.exists()) {
ASSERT.isTrue(index.delete(), "Delete index file failed");
}
}
public void release(String fileName) {
clearCacheValue(fileName);
}
public org.eclipse.jifa.common.vo.Progress pollProgress(FileType fileType, String fileName) {
AnalysisProgressListener progressListener = getFileListener(fileName);
if (progressListener == null) {
org.eclipse.jifa.common.vo.Progress progress = buildProgressIfFinished(fileType, fileName);
ASSERT.notNull(progress);
return progress;
} else {
org.eclipse.jifa.common.vo.Progress progress = new org.eclipse.jifa.common.vo.Progress();
progress.setState(ProgressState.IN_PROGRESS);
progress.setMessage(progressListener.log());
progress.setPercent(progressListener.percent());
return progress;
}
}
@SuppressWarnings("unchecked")
private synchronized <T> T getCacheValueIfPresent(String key) {
return (T) cache.getIfPresent(key);
}
private synchronized void putCacheValue(String key, Object value) {
cache.put(key, value);
LOGGER.info("Put cache: {}", key);
}
private synchronized void clearCacheValue(String key) {
Object value = cache.getIfPresent(key);
if (value instanceof SnapshotContext) {
SnapshotFactory.dispose(((SnapshotContext) value).getSnapshot());
}
cache.invalidate(key);
LOGGER.info("Clear cache: {}", key);
}
private synchronized AnalysisProgressListener getFileListener(String fileName) {
return listeners.get(fileName);
}
private synchronized boolean putFileListener(String fileName, AnalysisProgressListener listener) {
if (listeners.containsKey(fileName)) {
return false;
}
listeners.put(fileName, listener);
return true;
}
private synchronized void removeFileListener(String fileName) {
listeners.remove(fileName);
}
private org.eclipse.jifa.common.vo.Progress buildProgressIfFinished(FileType fileType, String fileName) {
if (getCacheValueIfPresent(fileName) != null) {
org.eclipse.jifa.common.vo.Progress result = new org.eclipse.jifa.common.vo.Progress();
result.setPercent(1);
result.setState(ProgressState.SUCCESS);
return result;
}
File failed = new File(FileSupport.errorLogPath(fileType, fileName));
if (failed.exists()) {
org.eclipse.jifa.common.vo.Progress result = new org.eclipse.jifa.common.vo.Progress();
result.setState(ProgressState.ERROR);
result.setMessage(FileUtil.content(failed));
return result;
}
return null;
}
interface Builder<T> {
T build(String key) throws Throwable;
}
private static class Singleton {
static Analyzer INSTANCE = new Analyzer();
}
}