View Javadoc
1   package io.jawk.backend;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2026 MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import java.io.PrintStream;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Comparator;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import io.jawk.intermediate.Opcode;
33  
34  /**
35   * Snapshot of tuple and function execution statistics collected by an
36   * {@link AVM}.
37   */
38  public final class ProfilingReport {
39  
40  	private final List<Entry> tupleEntries;
41  	private final List<Entry> functionEntries;
42  
43  	/**
44  	 * Returns an empty profiling report.
45  	 *
46  	 * @return empty report
47  	 */
48  	public static ProfilingReport empty() {
49  		return new ProfilingReport(
50  				Collections.<Opcode, Accumulator>emptyMap(),
51  				Collections.<String, Accumulator>emptyMap());
52  	}
53  
54  	ProfilingReport(Map<Opcode, Accumulator> tupleStats, Map<String, Accumulator> functionStats) {
55  		this.tupleEntries = Collections.unmodifiableList(toTupleEntries(tupleStats));
56  		this.functionEntries = Collections.unmodifiableList(toFunctionEntries(functionStats));
57  	}
58  
59  	/**
60  	 * Returns tuple execution statistics sorted by descending total time.
61  	 *
62  	 * @return tuple execution entries
63  	 */
64  	public List<Entry> getTupleEntries() {
65  		return tupleEntries;
66  	}
67  
68  	/**
69  	 * Returns function execution statistics sorted by descending total time.
70  	 *
71  	 * @return function execution entries
72  	 */
73  	public List<Entry> getFunctionEntries() {
74  		return functionEntries;
75  	}
76  
77  	/**
78  	 * Prints this profiling report to the supplied stream.
79  	 *
80  	 * @param out destination stream
81  	 */
82  	public void print(PrintStream out) {
83  		out.println("Jawk profiling report");
84  		out.println();
85  		printSection(out, "Tuple execution", tupleEntries);
86  		out.println();
87  		printSection(out, "Function execution", functionEntries);
88  	}
89  
90  	private static void printSection(PrintStream out, String title, List<Entry> entries) {
91  		out.println(title + ":");
92  		if (entries.isEmpty()) {
93  			out.println("  (none)");
94  			return;
95  		}
96  		out.printf(Locale.ROOT, "  %-32s %12s %14s %14s%n", "Name", "Count", "Time (ms)", "Avg (ns)");
97  		for (Entry entry : entries) {
98  			out
99  					.printf(
100 							Locale.ROOT,
101 							"  %-32s %12d %14.3f %14.0f%n",
102 							entry.getName(),
103 							entry.getCount(),
104 							entry.getTotalNanos() / 1_000_000.0d,
105 							entry.getAverageNanos());
106 		}
107 	}
108 
109 	private static List<Entry> toTupleEntries(Map<Opcode, Accumulator> stats) {
110 		List<Entry> entries = new ArrayList<Entry>(stats.size());
111 		for (Map.Entry<Opcode, Accumulator> entry : stats.entrySet()) {
112 			entries.add(new Entry(entry.getKey().name(), entry.getValue().count, entry.getValue().totalNanos));
113 		}
114 		sort(entries);
115 		return entries;
116 	}
117 
118 	private static List<Entry> toFunctionEntries(Map<String, Accumulator> stats) {
119 		List<Entry> entries = new ArrayList<Entry>(stats.size());
120 		for (Map.Entry<String, Accumulator> entry : stats.entrySet()) {
121 			entries.add(new Entry(entry.getKey(), entry.getValue().count, entry.getValue().totalNanos));
122 		}
123 		sort(entries);
124 		return entries;
125 	}
126 
127 	private static void sort(List<Entry> entries) {
128 		Collections
129 				.sort(
130 						entries,
131 						Comparator
132 								.comparingLong(Entry::getTotalNanos)
133 								.reversed()
134 								.thenComparing(Comparator.comparingLong(Entry::getCount).reversed())
135 								.thenComparing(Entry::getName));
136 	}
137 
138 	static final class Accumulator {
139 		private long count;
140 		private long totalNanos;
141 
142 		void add(long elapsedNanos) {
143 			count++;
144 			totalNanos += elapsedNanos;
145 		}
146 	}
147 
148 	/**
149 	 * One profiling table row.
150 	 */
151 	public static final class Entry {
152 		private final String name;
153 		private final long count;
154 		private final long totalNanos;
155 
156 		private Entry(String name, long count, long totalNanos) {
157 			this.name = name;
158 			this.count = count;
159 			this.totalNanos = totalNanos;
160 		}
161 
162 		/**
163 		 * Returns the tuple type or function name.
164 		 *
165 		 * @return entry name
166 		 */
167 		public String getName() {
168 			return name;
169 		}
170 
171 		/**
172 		 * Returns the number of observed executions.
173 		 *
174 		 * @return execution count
175 		 */
176 		public long getCount() {
177 			return count;
178 		}
179 
180 		/**
181 		 * Returns the total elapsed time in nanoseconds.
182 		 *
183 		 * @return total elapsed nanoseconds
184 		 */
185 		public long getTotalNanos() {
186 			return totalNanos;
187 		}
188 
189 		/**
190 		 * Returns the average elapsed time in nanoseconds.
191 		 *
192 		 * @return average elapsed nanoseconds
193 		 */
194 		public double getAverageNanos() {
195 			return count == 0 ? 0.0d : (double) totalNanos / count;
196 		}
197 	}
198 }