001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.util;
020
021import org.HdrHistogram.Histogram;
022
023import java.io.*;
024import java.util.Map;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.stream.Collectors;
027
028public class Metrics implements Loggeable {
029    private Histogram template;
030    private Map<String,Histogram> metrics = new ConcurrentHashMap<>();
031    private double conversion = 1;
032
033    public Metrics(Histogram template) {
034        super();
035        this.template = template;
036        if (template != null && template.getStartTimeStamp() == Long.MAX_VALUE) {
037            template.setStartTimeStamp(System.currentTimeMillis());
038        }
039    }
040
041    public Map<String, Histogram> metrics() {
042        return metrics.entrySet()
043          .stream()
044          .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().copy()));
045    }
046
047    public Map<String, Histogram> metrics(String prefix) {
048        return metrics.entrySet()
049          .stream()
050          .filter(e -> e.getKey().startsWith(prefix))
051          .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().copy()));
052    }
053
054    public void record(String name, long elapsed) {
055        Histogram h = getHistogram(name);
056        long l = Math.min(elapsed, h.getHighestTrackableValue());
057        if (l > 0)
058            h.recordValue(l);
059    }
060
061    private Histogram getHistogram(String p) {
062        Histogram h = metrics.get(p);
063        if (h == null) {
064            Histogram hist = new Histogram(template);
065            hist.setTag(p);
066            metrics.putIfAbsent(p, hist);
067            h = metrics.get(p);
068        }
069        return h;
070    }
071
072    public void dump(PrintStream ps, String indent) {
073        metrics.entrySet()
074          .stream()
075          .sorted(Map.Entry.comparingByKey())
076          .forEach(e -> dumpPercentiles(ps, indent, e.getKey(), e.getValue().copy()));
077    }
078
079    private void dumpPercentiles (PrintStream ps, String indent, String key, Histogram h) {
080          ps.printf("%s%s min=%.7f, max=%.7f, mean=%.7f stddev=%.7f P50=%.7f, P90=%.7f, P99=%.7f, P99.9=%.7f, P99.99=%.7f tot=%d size=%d%n",
081          indent,
082          key,
083          h.getMinValue()/conversion,
084          h.getMaxValue()/conversion,
085          h.getMean()/conversion,
086          h.getStdDeviation()/conversion,
087          h.getValueAtPercentile(50.0)/conversion,                    
088          h.getValueAtPercentile(90.0)/conversion,
089          h.getValueAtPercentile(99.0)/conversion,
090          h.getValueAtPercentile(99.9)/conversion,
091          h.getValueAtPercentile(99.99)/conversion,
092          h.getTotalCount(),
093          h.getEstimatedFootprintInBytes()
094        );
095    }
096
097    private void dumpHistogram(File dir, String key, Histogram h) {
098        try (FileOutputStream fos = new FileOutputStream(new File(dir, key + ".hgrm"))) {
099            h.outputPercentileDistribution(new PrintStream(fos), 1.0);
100        } catch (IOException e) {
101            e.printStackTrace();
102        }
103    }
104
105    public void dumpHistograms(File dir, String prefix) {
106        metrics.entrySet()
107          .stream()
108          .sorted(Map.Entry.comparingByKey())
109          .forEach(e -> dumpHistogram(dir, prefix + e.getKey(), e.getValue().copy()));
110    }
111
112    /**
113     * @param conversion
114     *            This is used to divide the percentile values while dumping. 
115     *            If you are using nano seconds to record and want to display the numbers in millis then conversion can be set to 1000000.
116     *            By default conversion is set to 1.
117     */
118    public void setConversion(double conversion) {
119        this.conversion = conversion;
120    }
121}