package act.metric;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import act.app.App;
import org.osgl.$;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.IO;
import org.osgl.util.S;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* A simple implementation of {@link MetricStore}
*/
public class SimpleMetricStore implements MetricStore, Serializable {
private transient static final Logger defLogger = LogManager.get("metric.default");
private static final long serialVersionUID = 7357409264403928225L;
private ConcurrentMap<String, AtomicLong> counters = new ConcurrentHashMap<String, AtomicLong>();
private ConcurrentMap<String, AtomicLong> timers = new ConcurrentHashMap<String, AtomicLong>();
private transient SimpleMetricPlugin plugin;
private transient FileSynchronizer synchronizer;
private transient boolean dataSync = true;
public SimpleMetricStore(SimpleMetricPlugin plugin) {
this.plugin = $.notNull(plugin);
synchronizer = new FileSynchronizer();
SimpleMetricStore persisted = synchronizer.read();
if (null != persisted) {
counters = persisted.counters;
timers = persisted.timers;
}
}
@Override
public void countOnce(String name) {
E.illegalArgumentIf(S.blank(name), "");
countOnce_(name);
}
private void countOnce_(String name) {
AtomicLong al = counters.get(name);
if (null == al) {
counters.putIfAbsent(name, new AtomicLong());
al = counters.get(name);
}
al.incrementAndGet();
name = getParent(name);
if (S.notBlank(name)) {
countOnce_(name);
}
}
public void enableDataSync(boolean enabled) {
dataSync = enabled;
}
@Override
public void onTimerStart(String name) {
logger(name).trace("Timer[%s] started", name);
}
@Override
public void onTimerStop(Timer timer) {
String name = timer.name();
long ns = timer.ns();
logger(name).trace("Timer[%s] stopped. Time elapsed: %sns", name, ns);
onTimerStop_(name, ns);
}
private void onTimerStop_(String name, long ns) {
AtomicLong al = timers.get(name);
if (null == al) {
timers.putIfAbsent(name, new AtomicLong());
al = timers.get(name);
}
al.addAndGet(ns);
name = getParent(name);
if (S.notBlank(name)) {
onTimerStop_(name, ns);
}
}
@Override
public Long count(String name) {
AtomicLong al = counters.get(name);
return null == al ? null : al.get();
}
@Override
public Long ns(String name) {
AtomicLong al = counters.get(name);
return null == al ? null : al.get();
}
@Override
public List<MetricInfo> counters() {
Set<MetricInfo> set = new TreeSet<MetricInfo>();
for (Map.Entry<String, AtomicLong> entry : counters.entrySet()) {
set.add(new MetricInfo(entry.getKey(), entry.getValue().get()));
}
return C.list(set);
}
@Override
public List<MetricInfo> timers() {
Set<MetricInfo> set = C.newSet();
for (Map.Entry<String, AtomicLong> entry : timers.entrySet()) {
set.add(new MetricInfo(entry.getKey(), entry.getValue().get(), counters.get(entry.getKey()).get()));
}
return C.list(set);
}
@Override
public void clear() {
timers.clear();
counters.clear();
}
public void takeSnapshot() {
if (dataSync) {
synchronizer.write(this);
}
}
private Logger logger(String name) {
Logger logger = plugin.logger(name);
return null == logger ? defLogger : logger;
}
private String getParent(String name) {
return S.beforeLast(name, ":");
}
private static class FileSynchronizer {
private static final String FILE_NAME = ".act.metric";
private boolean ioError = false;
void write(SimpleMetricStore store) {
if (ioError) {
return;
}
ObjectOutputStream oos = null;
try {
File file = new File(FILE_NAME);
oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(store);
} catch (IOException e) {
ioError = true;
throw E.ioException(e);
} finally {
IO.close(oos);
}
}
SimpleMetricStore read() {
File file = new File(FILE_NAME);
if (file.exists() && file.canRead()) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
SimpleMetricStore store = $.cast(ois.readObject());
return store;
} catch (IOException e) {
ioError = true;
App.LOGGER.error(e, "Error reading simple metric store persisted file:%s. Will reset this file", file.getAbsolutePath());
if (!file.delete()) {
file.deleteOnExit();
}
return null;
} catch (ClassNotFoundException e) {
throw E.unexpected(e);
} finally {
IO.close(ois);
}
} else {
return null;
}
}
}
}