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.jpos.iso.ISOUtil;
022
023import java.io.PrintStream;
024import java.util.Collection;
025import java.util.Iterator;
026import java.util.LinkedHashMap;
027
028/**
029 * Simple Profiler
030 * @author Alejandro P. Revilla
031 * @author David D. Bergert
032 * @version $Id$
033 */
034public class Profiler implements Loggeable {
035    long start, partial;
036    LinkedHashMap<String, Entry> events;
037    /** Conversion factor from nanoseconds to milliseconds. */
038    public static final int TO_MILLIS = 1000000;
039
040    /** Default constructor. */
041    public Profiler () {
042        super();
043        reset();
044    }
045    /**
046     * reset timers
047     */
048    public void reset() {
049        start = partial = System.nanoTime();
050        events = new LinkedHashMap<>();
051    }
052    /**
053     * mark checkpoint
054     * @param detail checkpoint information
055     */
056    @SuppressWarnings("unchecked")
057    public synchronized void checkPoint (String detail) {
058        long now = System.nanoTime();
059        Entry e = new Entry();
060        e.setDurationInNanos(now - partial);
061        e.setTotalDurationInNanos(now - start);
062        if (events.containsKey(detail)) {
063            for (int i=1; ;i++) {
064                String d = detail + "-" + i;
065                if (!events.containsKey (d)) {
066                    detail = d;
067                    break;
068                }
069            }
070        }
071        e.setEventName(detail);
072        events.put (detail, e);
073        partial = now;
074    }
075    /**
076     * Returns the total elapsed time since the last reset.
077     * @return total elapsed time since last reset
078     */
079    public long getElapsed() {
080        return System.nanoTime() - start;
081    }
082    /** Returns total elapsed time in milliseconds since the last reset.
083     * @return elapsed time in milliseconds
084     */
085    public long getElapsedInMillis() {
086        return getElapsed() / TO_MILLIS;
087    }
088    /**
089     * Returns the partial elapsed time since the last checkpoint.
090     * @return partial elapsed time since last reset
091     */
092    public long getPartial() {
093        return System.nanoTime() - partial;
094    }
095    /** Returns partial elapsed time in milliseconds since the last checkpoint.
096     * @return partial elapsed time in milliseconds
097     */
098    public long getPartialInMillis() {
099        return getPartial() / TO_MILLIS;
100    }
101    /** Dumps profiler results to the given print stream.
102     * @param p the output stream
103     * @param indent indent prefix
104     */
105    public void dump (PrintStream p, String indent) {
106        String inner = indent + "  ";
107        if (!events.containsKey("end"))
108            checkPoint ("end");
109        Collection c = events.values();
110        Iterator iter = c.iterator();
111        p.println (indent + "<profiler>");
112        while (iter.hasNext()) 
113            p.println (inner + ISOUtil.normalize(iter.next().toString()));
114        p.println (indent + "</profiler>");
115    }
116    /** Returns all profiler events collected since the last reset.
117     * @return ordered map of event name to Entry
118     */
119    public LinkedHashMap<String, Entry> getEvents() {
120        return events;
121    }
122    /** Returns the profiler entry for the given event name.
123     * @param eventName the event name
124     * @return the corresponding Entry, or {@code null} if not found
125     */
126    public Entry getEntry(String eventName) {
127         return events.get(eventName);
128    }
129    /** Removes the "end" checkpoint so profiling can continue. */
130    public void reenable() {
131        events.remove("end");
132    }
133    /** A single timed checkpoint entry recorded by the Profiler. */
134    public static class Entry  {
135        String  eventName;
136        long    duration;
137        long    totalDuration;          
138        /** Default constructor — initialises all fields to empty/zero. */
139        public Entry() {
140           eventName     = "";
141           duration      = 0L;
142           totalDuration = 0L;        
143        }
144        /** Sets the event name for this entry.
145         * @param myEvent the event label
146         */
147        public void setEventName (String myEvent) {
148            this.eventName = myEvent;
149        }
150        /** Returns the event name.
151         * @return event name
152         */
153        public String getEventName () {
154            return eventName;
155        }    
156        /** Sets the entry duration in nanoseconds.
157         * @param duration duration in nanoseconds
158         */
159        public void setDurationInNanos (long duration) {
160            this.duration = duration;
161        }
162        /** Returns the entry duration in milliseconds.
163         * @return duration in milliseconds
164         */
165        public long getDuration () {
166            return duration / TO_MILLIS;
167        }
168        /** Returns the raw entry duration in nanoseconds.
169         * @return duration in nanoseconds
170         */
171        public long getDurationInNanos() {
172            return duration;
173        }
174        /** Sets the total elapsed duration in nanoseconds.
175         * @param totalDuration total duration in nanoseconds
176         */
177        public void setTotalDurationInNanos (long totalDuration) {
178            this.totalDuration = totalDuration;
179        }
180        /** Returns the total elapsed duration in milliseconds.
181         * @return total duration in milliseconds
182         */
183        public long getTotalDuration () {
184            return totalDuration / TO_MILLIS;
185        }
186        /** Returns the raw total elapsed duration in nanoseconds.
187         * @return total duration in nanoseconds
188         */
189        public long getTotalDurationInNanos () {
190            return totalDuration;
191        }
192        public String toString()  {
193            StringBuilder sb = new StringBuilder (eventName);
194            sb.append (" [");
195            sb.append (getDuration());
196            sb.append ('.');
197            sb.append (duration % TO_MILLIS / 100000);
198            sb.append ('/');
199            sb.append (getTotalDuration ());
200            sb.append ('.');
201            sb.append (totalDuration % TO_MILLIS / 100000);
202            sb.append (']');
203            return sb.toString();
204        }            
205    }
206}