Mon, 12 Aug 2019 18:30:40 +0300
8223147: JFR Backport
8199712: Flight Recorder
8203346: JFR: Inconsistent signature of jfr_add_string_constant
8195817: JFR.stop should require name of recording
8195818: JFR.start should increase autogenerated name by one
8195819: Remove recording=x from jcmd JFR.check output
8203921: JFR thread sampling is missing fixes from JDK-8194552
8203929: Limit amount of data for JFR.dump
8203664: JFR start failure after AppCDS archive created with JFR StartFlightRecording
8003209: JFR events for network utilization
8207392: [PPC64] Implement JFR profiling
8202835: jfr/event/os/TestSystemProcess.java fails on missing events
Summary: Backport JFR from JDK11. Initial integration
Reviewed-by: neugens
apetushkov@9858 | 1 | package build.tools.jfr; |
apetushkov@9858 | 2 | |
apetushkov@9858 | 3 | import java.io.BufferedOutputStream; |
apetushkov@9858 | 4 | import java.io.File; |
apetushkov@9858 | 5 | import java.io.FileNotFoundException; |
apetushkov@9858 | 6 | import java.io.FileOutputStream; |
apetushkov@9858 | 7 | import java.io.IOException; |
apetushkov@9858 | 8 | import java.io.PrintStream; |
apetushkov@9858 | 9 | import java.util.ArrayList; |
apetushkov@9858 | 10 | import java.util.Arrays; |
apetushkov@9858 | 11 | import java.util.HashMap; |
apetushkov@9858 | 12 | import java.util.LinkedHashMap; |
apetushkov@9858 | 13 | import java.util.List; |
apetushkov@9858 | 14 | import java.util.Map; |
apetushkov@9858 | 15 | import java.util.StringJoiner; |
apetushkov@9858 | 16 | import java.util.function.Predicate; |
apetushkov@9858 | 17 | |
apetushkov@9858 | 18 | import javax.xml.XMLConstants; |
apetushkov@9858 | 19 | import javax.xml.parsers.ParserConfigurationException; |
apetushkov@9858 | 20 | import javax.xml.parsers.SAXParser; |
apetushkov@9858 | 21 | import javax.xml.parsers.SAXParserFactory; |
apetushkov@9858 | 22 | import javax.xml.validation.SchemaFactory; |
apetushkov@9858 | 23 | |
apetushkov@9858 | 24 | import org.xml.sax.Attributes; |
apetushkov@9858 | 25 | import org.xml.sax.SAXException; |
apetushkov@9858 | 26 | import org.xml.sax.SAXParseException; |
apetushkov@9858 | 27 | import org.xml.sax.helpers.DefaultHandler; |
apetushkov@9858 | 28 | |
apetushkov@9858 | 29 | public class GenerateJfrFiles { |
apetushkov@9858 | 30 | |
apetushkov@9858 | 31 | public static void main(String... args) throws Exception { |
apetushkov@9858 | 32 | if (args.length != 3) { |
apetushkov@9858 | 33 | System.err.println("Incorrect number of command line arguments."); |
apetushkov@9858 | 34 | System.err.println("Usage:"); |
apetushkov@9858 | 35 | System.err.println("java GenerateJfrFiles[.java] <path-to-metadata.xml> <path-to-metadata.xsd> <output-directory>"); |
apetushkov@9858 | 36 | System.exit(1); |
apetushkov@9858 | 37 | } |
apetushkov@9858 | 38 | try { |
apetushkov@9858 | 39 | File metadataXml = new File(args[0]); |
apetushkov@9858 | 40 | File metadataSchema = new File(args[1]); |
apetushkov@9858 | 41 | File outputDirectory = new File(args[2]); |
apetushkov@9858 | 42 | |
apetushkov@9858 | 43 | Metadata metadata = new Metadata(metadataXml, metadataSchema); |
apetushkov@9858 | 44 | metadata.verify(); |
apetushkov@9858 | 45 | metadata.wireUpTypes(); |
apetushkov@9858 | 46 | |
apetushkov@9858 | 47 | printJfrPeriodicHpp(metadata, outputDirectory); |
apetushkov@9858 | 48 | printJfrEventIdsHpp(metadata, outputDirectory); |
apetushkov@9858 | 49 | printJfrEventControlHpp(metadata, outputDirectory); |
apetushkov@9858 | 50 | printJfrTypesHpp(metadata, outputDirectory); |
apetushkov@9858 | 51 | printJfrEventClassesHpp(metadata, outputDirectory); |
apetushkov@9858 | 52 | |
apetushkov@9858 | 53 | } catch (Exception e) { |
apetushkov@9858 | 54 | e.printStackTrace(); |
apetushkov@9858 | 55 | System.exit(1); |
apetushkov@9858 | 56 | } |
apetushkov@9858 | 57 | } |
apetushkov@9858 | 58 | |
apetushkov@9858 | 59 | static class XmlType { |
apetushkov@9858 | 60 | final String fieldType; |
apetushkov@9858 | 61 | final String parameterType; |
apetushkov@9858 | 62 | XmlType(String fieldType, String parameterType) { |
apetushkov@9858 | 63 | this.fieldType = fieldType; |
apetushkov@9858 | 64 | this.parameterType = parameterType; |
apetushkov@9858 | 65 | } |
apetushkov@9858 | 66 | } |
apetushkov@9858 | 67 | |
apetushkov@9858 | 68 | static class TypeElement { |
apetushkov@9858 | 69 | List<FieldElement> fields = new ArrayList<>(); |
apetushkov@9858 | 70 | String name; |
apetushkov@9858 | 71 | String fieldType; |
apetushkov@9858 | 72 | String parameterType; |
apetushkov@9858 | 73 | boolean supportStruct; |
apetushkov@9858 | 74 | } |
apetushkov@9858 | 75 | |
apetushkov@9858 | 76 | static class Metadata { |
apetushkov@9858 | 77 | final Map<String, TypeElement> types = new LinkedHashMap<>(); |
apetushkov@9858 | 78 | final Map<String, XmlType> xmlTypes = new HashMap<>(); |
apetushkov@9858 | 79 | Metadata(File metadataXml, File metadataSchema) throws ParserConfigurationException, SAXException, FileNotFoundException, IOException { |
apetushkov@9858 | 80 | SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
apetushkov@9858 | 81 | SAXParserFactory factory = SAXParserFactory.newInstance(); |
apetushkov@9858 | 82 | factory.setSchema(schemaFactory.newSchema(metadataSchema)); |
apetushkov@9858 | 83 | SAXParser sp = factory.newSAXParser(); |
apetushkov@9858 | 84 | sp.parse(metadataXml, new MetadataHandler(this)); |
apetushkov@9858 | 85 | } |
apetushkov@9858 | 86 | |
apetushkov@9858 | 87 | List<EventElement> getEvents() { |
apetushkov@9858 | 88 | return getList(t -> t.getClass() == EventElement.class); |
apetushkov@9858 | 89 | } |
apetushkov@9858 | 90 | |
apetushkov@9858 | 91 | List<TypeElement> getEventsAndStructs() { |
apetushkov@9858 | 92 | return getList(t -> t.getClass() == EventElement.class || t.supportStruct); |
apetushkov@9858 | 93 | } |
apetushkov@9858 | 94 | |
apetushkov@9858 | 95 | List<TypeElement> getTypesAndStructs() { |
apetushkov@9858 | 96 | return getList(t -> t.getClass() == TypeElement.class || t.supportStruct); |
apetushkov@9858 | 97 | } |
apetushkov@9858 | 98 | |
apetushkov@9858 | 99 | @SuppressWarnings("unchecked") |
apetushkov@9858 | 100 | <T> List<T> getList(Predicate<? super TypeElement> pred) { |
apetushkov@9858 | 101 | List<T> result = new ArrayList<>(types.size()); |
apetushkov@9858 | 102 | for (TypeElement t : types.values()) { |
apetushkov@9858 | 103 | if (pred.test(t)) { |
apetushkov@9858 | 104 | result.add((T) t); |
apetushkov@9858 | 105 | } |
apetushkov@9858 | 106 | } |
apetushkov@9858 | 107 | return result; |
apetushkov@9858 | 108 | } |
apetushkov@9858 | 109 | |
apetushkov@9858 | 110 | List<EventElement> getPeriodicEvents() { |
apetushkov@9858 | 111 | return getList(t -> t.getClass() == EventElement.class && ((EventElement) t).periodic); |
apetushkov@9858 | 112 | } |
apetushkov@9858 | 113 | |
apetushkov@9858 | 114 | List<TypeElement> getNonEventsAndNonStructs() { |
apetushkov@9858 | 115 | return getList(t -> t.getClass() != EventElement.class && !t.supportStruct); |
apetushkov@9858 | 116 | } |
apetushkov@9858 | 117 | |
apetushkov@9858 | 118 | List<TypeElement> getTypes() { |
apetushkov@9858 | 119 | return getList(t -> t.getClass() == TypeElement.class && !t.supportStruct); |
apetushkov@9858 | 120 | } |
apetushkov@9858 | 121 | |
apetushkov@9858 | 122 | List<TypeElement> getStructs() { |
apetushkov@9858 | 123 | return getList(t -> t.getClass() == TypeElement.class && t.supportStruct); |
apetushkov@9858 | 124 | } |
apetushkov@9858 | 125 | |
apetushkov@9858 | 126 | void verify() { |
apetushkov@9858 | 127 | for (TypeElement t : types.values()) { |
apetushkov@9858 | 128 | for (FieldElement f : t.fields) { |
apetushkov@9858 | 129 | if (!xmlTypes.containsKey(f.typeName)) { // ignore primitives |
apetushkov@9858 | 130 | if (!types.containsKey(f.typeName)) { |
apetushkov@9858 | 131 | throw new IllegalStateException("Could not find definition of type '" + f.typeName + "' used by " + t.name + "#" + f.name); |
apetushkov@9858 | 132 | } |
apetushkov@9858 | 133 | } |
apetushkov@9858 | 134 | } |
apetushkov@9858 | 135 | } |
apetushkov@9858 | 136 | } |
apetushkov@9858 | 137 | |
apetushkov@9858 | 138 | void wireUpTypes() { |
apetushkov@9858 | 139 | for (TypeElement t : types.values()) { |
apetushkov@9858 | 140 | for (FieldElement f : t.fields) { |
apetushkov@9858 | 141 | TypeElement type = types.get(f.typeName); |
apetushkov@9858 | 142 | if (f.struct) { |
apetushkov@9858 | 143 | type.supportStruct = true; |
apetushkov@9858 | 144 | } |
apetushkov@9858 | 145 | f.type = type; |
apetushkov@9858 | 146 | } |
apetushkov@9858 | 147 | } |
apetushkov@9858 | 148 | } |
apetushkov@9858 | 149 | } |
apetushkov@9858 | 150 | |
apetushkov@9858 | 151 | static class EventElement extends TypeElement { |
apetushkov@9858 | 152 | String representation; |
apetushkov@9858 | 153 | boolean thread; |
apetushkov@9858 | 154 | boolean stackTrace; |
apetushkov@9858 | 155 | boolean startTime; |
apetushkov@9858 | 156 | boolean periodic; |
apetushkov@9858 | 157 | boolean cutoff; |
apetushkov@9858 | 158 | } |
apetushkov@9858 | 159 | |
apetushkov@9858 | 160 | static class FieldElement { |
apetushkov@9858 | 161 | final Metadata metadata; |
apetushkov@9858 | 162 | TypeElement type; |
apetushkov@9858 | 163 | String name; |
apetushkov@9858 | 164 | String typeName; |
apetushkov@9858 | 165 | boolean struct; |
apetushkov@9858 | 166 | |
apetushkov@9858 | 167 | FieldElement(Metadata metadata) { |
apetushkov@9858 | 168 | this.metadata = metadata; |
apetushkov@9858 | 169 | } |
apetushkov@9858 | 170 | |
apetushkov@9858 | 171 | String getParameterType() { |
apetushkov@9858 | 172 | if (struct) { |
apetushkov@9858 | 173 | return "const JfrStruct" + typeName + "&"; |
apetushkov@9858 | 174 | } |
apetushkov@9858 | 175 | XmlType xmlType = metadata.xmlTypes.get(typeName); |
apetushkov@9858 | 176 | if (xmlType != null) { |
apetushkov@9858 | 177 | return xmlType.parameterType; |
apetushkov@9858 | 178 | } |
apetushkov@9858 | 179 | return type != null ? "u8" : typeName; |
apetushkov@9858 | 180 | } |
apetushkov@9858 | 181 | |
apetushkov@9858 | 182 | String getParameterName() { |
apetushkov@9858 | 183 | return struct ? "value" : "new_value"; |
apetushkov@9858 | 184 | } |
apetushkov@9858 | 185 | |
apetushkov@9858 | 186 | String getFieldType() { |
apetushkov@9858 | 187 | if (struct) { |
apetushkov@9858 | 188 | return "JfrStruct" + typeName; |
apetushkov@9858 | 189 | } |
apetushkov@9858 | 190 | XmlType xmlType = metadata.xmlTypes.get(typeName); |
apetushkov@9858 | 191 | if (xmlType != null) { |
apetushkov@9858 | 192 | return xmlType.fieldType; |
apetushkov@9858 | 193 | } |
apetushkov@9858 | 194 | return type != null ? "u8" : typeName; |
apetushkov@9858 | 195 | } |
apetushkov@9858 | 196 | } |
apetushkov@9858 | 197 | |
apetushkov@9858 | 198 | static class MetadataHandler extends DefaultHandler { |
apetushkov@9858 | 199 | final Metadata metadata; |
apetushkov@9858 | 200 | FieldElement currentField; |
apetushkov@9858 | 201 | TypeElement currentType; |
apetushkov@9858 | 202 | MetadataHandler(Metadata metadata) { |
apetushkov@9858 | 203 | this.metadata = metadata; |
apetushkov@9858 | 204 | } |
apetushkov@9858 | 205 | @Override |
apetushkov@9858 | 206 | public void error(SAXParseException e) throws SAXException { |
apetushkov@9858 | 207 | throw e; |
apetushkov@9858 | 208 | } |
apetushkov@9858 | 209 | @Override |
apetushkov@9858 | 210 | public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
apetushkov@9858 | 211 | switch (qName) { |
apetushkov@9858 | 212 | case "XmlType": |
apetushkov@9858 | 213 | String name = attributes.getValue("name"); |
apetushkov@9858 | 214 | String parameterType = attributes.getValue("parameterType"); |
apetushkov@9858 | 215 | String fieldType = attributes.getValue("fieldType"); |
apetushkov@9858 | 216 | metadata.xmlTypes.put(name, new XmlType(fieldType, parameterType)); |
apetushkov@9858 | 217 | break; |
apetushkov@9858 | 218 | case "Type": |
apetushkov@9858 | 219 | currentType = new TypeElement(); |
apetushkov@9858 | 220 | currentType.name = attributes.getValue("name"); |
apetushkov@9858 | 221 | break; |
apetushkov@9858 | 222 | case "Event": |
apetushkov@9858 | 223 | EventElement eventtType = new EventElement(); |
apetushkov@9858 | 224 | eventtType.name = attributes.getValue("name"); |
apetushkov@9858 | 225 | eventtType.thread = getBoolean(attributes, "thread", false); |
apetushkov@9858 | 226 | eventtType.stackTrace = getBoolean(attributes, "stackTrace", false); |
apetushkov@9858 | 227 | eventtType.startTime = getBoolean(attributes, "startTime", true); |
apetushkov@9858 | 228 | eventtType.periodic = attributes.getValue("period") != null; |
apetushkov@9858 | 229 | eventtType.cutoff = getBoolean(attributes, "cutoff", false); |
apetushkov@9858 | 230 | currentType = eventtType; |
apetushkov@9858 | 231 | break; |
apetushkov@9858 | 232 | case "Field": |
apetushkov@9858 | 233 | currentField = new FieldElement(metadata); |
apetushkov@9858 | 234 | currentField.struct = getBoolean(attributes, "struct", false); |
apetushkov@9858 | 235 | currentField.name = attributes.getValue("name"); |
apetushkov@9858 | 236 | currentField.typeName = attributes.getValue("type"); |
apetushkov@9858 | 237 | break; |
apetushkov@9858 | 238 | } |
apetushkov@9858 | 239 | } |
apetushkov@9858 | 240 | |
apetushkov@9858 | 241 | private boolean getBoolean(Attributes attributes, String name, boolean defaultValue) { |
apetushkov@9858 | 242 | String value = attributes.getValue(name); |
apetushkov@9858 | 243 | return value == null ? defaultValue : Boolean.valueOf(value); |
apetushkov@9858 | 244 | } |
apetushkov@9858 | 245 | |
apetushkov@9858 | 246 | @Override |
apetushkov@9858 | 247 | public void endElement(String uri, String localName, String qName) { |
apetushkov@9858 | 248 | switch (qName) { |
apetushkov@9858 | 249 | case "Type": |
apetushkov@9858 | 250 | case "Event": |
apetushkov@9858 | 251 | metadata.types.put(currentType.name, currentType); |
apetushkov@9858 | 252 | currentType = null; |
apetushkov@9858 | 253 | break; |
apetushkov@9858 | 254 | case "Field": |
apetushkov@9858 | 255 | currentType.fields.add(currentField); |
apetushkov@9858 | 256 | currentField = null; |
apetushkov@9858 | 257 | break; |
apetushkov@9858 | 258 | } |
apetushkov@9858 | 259 | } |
apetushkov@9858 | 260 | } |
apetushkov@9858 | 261 | |
apetushkov@9858 | 262 | static class Printer implements AutoCloseable { |
apetushkov@9858 | 263 | final PrintStream out; |
apetushkov@9858 | 264 | Printer(File outputDirectory, String filename) throws FileNotFoundException { |
apetushkov@9858 | 265 | out = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File(outputDirectory, filename)))); |
apetushkov@9858 | 266 | write("/* AUTOMATICALLY GENERATED FILE - DO NOT EDIT */"); |
apetushkov@9858 | 267 | write(""); |
apetushkov@9858 | 268 | } |
apetushkov@9858 | 269 | |
apetushkov@9858 | 270 | void write(String text) { |
apetushkov@9858 | 271 | out.print(text); |
apetushkov@9858 | 272 | out.print("\n"); // Don't use Windows line endings |
apetushkov@9858 | 273 | } |
apetushkov@9858 | 274 | |
apetushkov@9858 | 275 | @Override |
apetushkov@9858 | 276 | public void close() throws Exception { |
apetushkov@9858 | 277 | out.close(); |
apetushkov@9858 | 278 | } |
apetushkov@9858 | 279 | } |
apetushkov@9858 | 280 | |
apetushkov@9858 | 281 | private static void printJfrPeriodicHpp(Metadata metadata, File outputDirectory) throws Exception { |
apetushkov@9858 | 282 | try (Printer out = new Printer(outputDirectory, "jfrPeriodic.hpp")) { |
apetushkov@9858 | 283 | out.write("#ifndef JFRFILES_JFRPERIODICEVENTSET_HPP"); |
apetushkov@9858 | 284 | out.write("#define JFRFILES_JFRPERIODICEVENTSET_HPP"); |
apetushkov@9858 | 285 | out.write(""); |
apetushkov@9858 | 286 | out.write("#include \"utilities/macros.hpp\""); |
apetushkov@9858 | 287 | out.write("#if INCLUDE_JFR"); |
apetushkov@9858 | 288 | out.write("#include \"jfrfiles/jfrEventIds.hpp\""); |
apetushkov@9858 | 289 | out.write("#include \"memory/allocation.hpp\""); |
apetushkov@9858 | 290 | out.write(""); |
apetushkov@9858 | 291 | out.write("class JfrPeriodicEventSet : public AllStatic {"); |
apetushkov@9858 | 292 | out.write(" public:"); |
apetushkov@9858 | 293 | out.write(" static void requestEvent(JfrEventId id) {"); |
apetushkov@9858 | 294 | out.write(" switch(id) {"); |
apetushkov@9858 | 295 | out.write(" "); |
apetushkov@9858 | 296 | for (EventElement e : metadata.getPeriodicEvents()) { |
apetushkov@9858 | 297 | out.write(" case Jfr" + e.name + "Event:"); |
apetushkov@9858 | 298 | out.write(" request" + e.name + "();"); |
apetushkov@9858 | 299 | out.write(" break;"); |
apetushkov@9858 | 300 | out.write(" "); |
apetushkov@9858 | 301 | } |
apetushkov@9858 | 302 | out.write(" default:"); |
apetushkov@9858 | 303 | out.write(" break;"); |
apetushkov@9858 | 304 | out.write(" }"); |
apetushkov@9858 | 305 | out.write(" }"); |
apetushkov@9858 | 306 | out.write(""); |
apetushkov@9858 | 307 | out.write(" private:"); |
apetushkov@9858 | 308 | out.write(""); |
apetushkov@9858 | 309 | for (EventElement e : metadata.getPeriodicEvents()) { |
apetushkov@9858 | 310 | out.write(" static void request" + e.name + "(void);"); |
apetushkov@9858 | 311 | out.write(""); |
apetushkov@9858 | 312 | } |
apetushkov@9858 | 313 | out.write("};"); |
apetushkov@9858 | 314 | out.write(""); |
apetushkov@9858 | 315 | out.write("#endif // INCLUDE_JFR"); |
apetushkov@9858 | 316 | out.write("#endif // JFRFILES_JFRPERIODICEVENTSET_HPP"); |
apetushkov@9858 | 317 | } |
apetushkov@9858 | 318 | } |
apetushkov@9858 | 319 | |
apetushkov@9858 | 320 | private static void printJfrEventControlHpp(Metadata metadata, File outputDirectory) throws Exception { |
apetushkov@9858 | 321 | try (Printer out = new Printer(outputDirectory, "jfrEventControl.hpp")) { |
apetushkov@9858 | 322 | out.write("#ifndef JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); |
apetushkov@9858 | 323 | out.write("#define JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); |
apetushkov@9858 | 324 | out.write(""); |
apetushkov@9858 | 325 | out.write("#include \"utilities/macros.hpp\""); |
apetushkov@9858 | 326 | out.write("#if INCLUDE_JFR"); |
apetushkov@9858 | 327 | out.write("#include \"jfrfiles/jfrEventIds.hpp\""); |
apetushkov@9858 | 328 | out.write(""); |
apetushkov@9858 | 329 | out.write("/**"); |
apetushkov@9858 | 330 | out.write(" * Event setting. We add some padding so we can use our"); |
apetushkov@9858 | 331 | out.write(" * event IDs as indexes into this."); |
apetushkov@9858 | 332 | out.write(" */"); |
apetushkov@9858 | 333 | out.write(""); |
apetushkov@9858 | 334 | out.write("struct jfrNativeEventSetting {"); |
apetushkov@9858 | 335 | out.write(" jlong threshold_ticks;"); |
apetushkov@9858 | 336 | out.write(" jlong cutoff_ticks;"); |
apetushkov@9858 | 337 | out.write(" u1 stacktrace;"); |
apetushkov@9858 | 338 | out.write(" u1 enabled;"); |
apetushkov@9858 | 339 | out.write(" u1 pad[6]; // Because GCC on linux ia32 at least tries to pack this."); |
apetushkov@9858 | 340 | out.write("};"); |
apetushkov@9858 | 341 | out.write(""); |
apetushkov@9858 | 342 | out.write("union JfrNativeSettings {"); |
apetushkov@9858 | 343 | out.write(" // Array version."); |
apetushkov@9858 | 344 | out.write(" jfrNativeEventSetting bits[MaxJfrEventId];"); |
apetushkov@9858 | 345 | out.write(" // Then, to make it easy to debug,"); |
apetushkov@9858 | 346 | out.write(" // add named struct members also."); |
apetushkov@9858 | 347 | out.write(" struct {"); |
apetushkov@9858 | 348 | out.write(" jfrNativeEventSetting pad[NUM_RESERVED_EVENTS];"); |
apetushkov@9858 | 349 | for (TypeElement t : metadata.getEventsAndStructs()) { |
apetushkov@9858 | 350 | out.write(" jfrNativeEventSetting " + t.name + ";"); |
apetushkov@9858 | 351 | } |
apetushkov@9858 | 352 | out.write(" } ev;"); |
apetushkov@9858 | 353 | out.write("};"); |
apetushkov@9858 | 354 | out.write(""); |
apetushkov@9858 | 355 | out.write("#endif // INCLUDE_JFR"); |
apetushkov@9858 | 356 | out.write("#endif // JFRFILES_JFR_NATIVE_EVENTSETTING_HPP"); |
apetushkov@9858 | 357 | } |
apetushkov@9858 | 358 | } |
apetushkov@9858 | 359 | |
apetushkov@9858 | 360 | private static void printJfrEventIdsHpp(Metadata metadata, File outputDirectory) throws Exception { |
apetushkov@9858 | 361 | try (Printer out = new Printer(outputDirectory, "jfrEventIds.hpp")) { |
apetushkov@9858 | 362 | out.write("#ifndef JFRFILES_JFREVENTIDS_HPP"); |
apetushkov@9858 | 363 | out.write("#define JFRFILES_JFREVENTIDS_HPP"); |
apetushkov@9858 | 364 | out.write(""); |
apetushkov@9858 | 365 | out.write("#include \"utilities/macros.hpp\""); |
apetushkov@9858 | 366 | out.write("#if INCLUDE_JFR"); |
apetushkov@9858 | 367 | out.write("#include \"jfrfiles/jfrTypes.hpp\""); |
apetushkov@9858 | 368 | out.write(""); |
apetushkov@9858 | 369 | out.write("/**"); |
apetushkov@9858 | 370 | out.write(" * Enum of the event types in the JVM"); |
apetushkov@9858 | 371 | out.write(" */"); |
apetushkov@9858 | 372 | out.write("enum JfrEventId {"); |
apetushkov@9858 | 373 | out.write(" _jfreventbase = (NUM_RESERVED_EVENTS-1), // Make sure we start at right index."); |
apetushkov@9858 | 374 | out.write(" "); |
apetushkov@9858 | 375 | out.write(" // Events -> enum entry"); |
apetushkov@9858 | 376 | for (TypeElement t : metadata.getEventsAndStructs()) { |
apetushkov@9858 | 377 | out.write(" Jfr" + t.name + "Event,"); |
apetushkov@9858 | 378 | } |
apetushkov@9858 | 379 | out.write(""); |
apetushkov@9858 | 380 | out.write(" MaxJfrEventId"); |
apetushkov@9858 | 381 | out.write("};"); |
apetushkov@9858 | 382 | out.write(""); |
apetushkov@9858 | 383 | out.write("/**"); |
apetushkov@9858 | 384 | out.write(" * Struct types in the JVM"); |
apetushkov@9858 | 385 | out.write(" */"); |
apetushkov@9858 | 386 | out.write("enum JfrStructId {"); |
apetushkov@9858 | 387 | for (TypeElement t : metadata.getNonEventsAndNonStructs()) { |
apetushkov@9858 | 388 | out.write(" Jfr" + t.name + "Struct,"); |
apetushkov@9858 | 389 | } |
apetushkov@9858 | 390 | for (TypeElement t : metadata.getEventsAndStructs()) { |
apetushkov@9858 | 391 | out.write(" Jfr" + t.name + "Struct,"); |
apetushkov@9858 | 392 | } |
apetushkov@9858 | 393 | out.write(""); |
apetushkov@9858 | 394 | out.write(" MaxJfrStructId"); |
apetushkov@9858 | 395 | out.write("};"); |
apetushkov@9858 | 396 | out.write(""); |
apetushkov@9858 | 397 | out.write("typedef enum JfrEventId JfrEventId;"); |
apetushkov@9858 | 398 | out.write("typedef enum JfrStructId JfrStructId;"); |
apetushkov@9858 | 399 | out.write(""); |
apetushkov@9858 | 400 | out.write("#endif // INCLUDE_JFR"); |
apetushkov@9858 | 401 | out.write("#endif // JFRFILES_JFREVENTIDS_HPP"); |
apetushkov@9858 | 402 | } |
apetushkov@9858 | 403 | } |
apetushkov@9858 | 404 | |
apetushkov@9858 | 405 | private static void printJfrTypesHpp(Metadata metadata, File outputDirectory) throws Exception { |
apetushkov@9858 | 406 | List<String> knownTypes = Arrays.asList(new String[] {"Thread", "StackTrace", "Class", "StackFrame"}); |
apetushkov@9858 | 407 | try (Printer out = new Printer(outputDirectory, "jfrTypes.hpp")) { |
apetushkov@9858 | 408 | out.write("#ifndef JFRFILES_JFRTYPES_HPP"); |
apetushkov@9858 | 409 | out.write("#define JFRFILES_JFRTYPES_HPP"); |
apetushkov@9858 | 410 | out.write(""); |
apetushkov@9858 | 411 | out.write("#include \"utilities/macros.hpp\""); |
apetushkov@9858 | 412 | out.write("#if INCLUDE_JFR"); |
apetushkov@9858 | 413 | out.write(""); |
apetushkov@9858 | 414 | out.write("enum JfrTypeId {"); |
apetushkov@9858 | 415 | out.write(" TYPE_NONE = 0,"); |
apetushkov@9858 | 416 | out.write(" TYPE_CLASS = 20,"); |
apetushkov@9858 | 417 | out.write(" TYPE_STRING = 21,"); |
apetushkov@9858 | 418 | out.write(" TYPE_THREAD = 22,"); |
apetushkov@9858 | 419 | out.write(" TYPE_STACKTRACE = 23,"); |
apetushkov@9858 | 420 | out.write(" TYPE_BYTES = 24,"); |
apetushkov@9858 | 421 | out.write(" TYPE_EPOCHMILLIS = 25,"); |
apetushkov@9858 | 422 | out.write(" TYPE_MILLIS = 26,"); |
apetushkov@9858 | 423 | out.write(" TYPE_NANOS = 27,"); |
apetushkov@9858 | 424 | out.write(" TYPE_TICKS = 28,"); |
apetushkov@9858 | 425 | out.write(" TYPE_ADDRESS = 29,"); |
apetushkov@9858 | 426 | out.write(" TYPE_PERCENTAGE = 30,"); |
apetushkov@9858 | 427 | out.write(" TYPE_DUMMY,"); |
apetushkov@9858 | 428 | out.write(" TYPE_DUMMY_1,"); |
apetushkov@9858 | 429 | for (TypeElement type : metadata.getTypes()) { |
apetushkov@9858 | 430 | if (!knownTypes.contains(type.name)) { |
apetushkov@9858 | 431 | out.write(" TYPE_" + type.name.toUpperCase() + ","); |
apetushkov@9858 | 432 | } |
apetushkov@9858 | 433 | } |
apetushkov@9858 | 434 | out.write(""); |
apetushkov@9858 | 435 | out.write(" NUM_JFR_TYPES,"); |
apetushkov@9858 | 436 | out.write(" TYPES_END = 255"); |
apetushkov@9858 | 437 | out.write("};"); |
apetushkov@9858 | 438 | out.write(""); |
apetushkov@9858 | 439 | out.write("enum ReservedEvent {"); |
apetushkov@9858 | 440 | out.write(" EVENT_METADATA,"); |
apetushkov@9858 | 441 | out.write(" EVENT_CHECKPOINT,"); |
apetushkov@9858 | 442 | out.write(" EVENT_BUFFERLOST,"); |
apetushkov@9858 | 443 | out.write(" NUM_RESERVED_EVENTS = TYPES_END"); |
apetushkov@9858 | 444 | out.write("};"); |
apetushkov@9858 | 445 | out.write(""); |
apetushkov@9858 | 446 | out.write("#endif // INCLUDE_JFR"); |
apetushkov@9858 | 447 | out.write("#endif // JFRFILES_JFRTYPES_HPP"); |
apetushkov@9858 | 448 | }; |
apetushkov@9858 | 449 | } |
apetushkov@9858 | 450 | |
apetushkov@9858 | 451 | private static void printJfrEventClassesHpp(Metadata metadata, File outputDirectory) throws Exception { |
apetushkov@9858 | 452 | try (Printer out = new Printer(outputDirectory, "jfrEventClasses.hpp")) { |
apetushkov@9858 | 453 | out.write("#ifndef JFRFILES_JFREVENTCLASSES_HPP"); |
apetushkov@9858 | 454 | out.write("#define JFRFILES_JFREVENTCLASSES_HPP"); |
apetushkov@9858 | 455 | out.write(""); |
apetushkov@9858 | 456 | out.write("#include \"oops/klass.hpp\""); |
apetushkov@9858 | 457 | out.write("#include \"jfrfiles/jfrTypes.hpp\""); |
apetushkov@9858 | 458 | out.write("#include \"jfr/utilities/jfrTypes.hpp\""); |
apetushkov@9858 | 459 | out.write("#include \"utilities/macros.hpp\""); |
apetushkov@9858 | 460 | out.write("#include \"utilities/ticks.hpp\""); |
apetushkov@9858 | 461 | out.write("#if INCLUDE_JFR"); |
apetushkov@9858 | 462 | out.write("#include \"jfr/recorder/service/jfrEvent.hpp\""); |
apetushkov@9858 | 463 | out.write("/*"); |
apetushkov@9858 | 464 | out.write(" * Each event class has an assert member function verify() which is invoked"); |
apetushkov@9858 | 465 | out.write(" * just before the engine writes the event and its fields to the data stream."); |
apetushkov@9858 | 466 | out.write(" * The purpose of verify() is to ensure that all fields in the event are initialized"); |
apetushkov@9858 | 467 | out.write(" * and set before attempting to commit."); |
apetushkov@9858 | 468 | out.write(" *"); |
apetushkov@9858 | 469 | out.write(" * We enforce this requirement because events are generally stack allocated and therefore"); |
apetushkov@9858 | 470 | out.write(" * *not* initialized to default values. This prevents us from inadvertently committing"); |
apetushkov@9858 | 471 | out.write(" * uninitialized values to the data stream."); |
apetushkov@9858 | 472 | out.write(" *"); |
apetushkov@9858 | 473 | out.write(" * The assert message contains both the index (zero based) as well as the name of the field."); |
apetushkov@9858 | 474 | out.write(" */"); |
apetushkov@9858 | 475 | out.write(""); |
apetushkov@9858 | 476 | printTypes(out, metadata, false); |
apetushkov@9858 | 477 | out.write(""); |
apetushkov@9858 | 478 | out.write(""); |
apetushkov@9858 | 479 | out.write("#else // !INCLUDE_JFR"); |
apetushkov@9858 | 480 | out.write(""); |
apetushkov@9858 | 481 | out.write("class JfrEvent {"); |
apetushkov@9858 | 482 | out.write(" public:"); |
apetushkov@9858 | 483 | out.write(" JfrEvent() {}"); |
apetushkov@9858 | 484 | out.write(" void set_starttime(const Ticks&) const {}"); |
apetushkov@9858 | 485 | out.write(" void set_endtime(const Ticks&) const {}"); |
apetushkov@9858 | 486 | out.write(" bool should_commit() const { return false; }"); |
apetushkov@9858 | 487 | out.write(" static bool is_enabled() { return false; }"); |
apetushkov@9858 | 488 | out.write(" void commit() {}"); |
apetushkov@9858 | 489 | out.write("};"); |
apetushkov@9858 | 490 | out.write(""); |
apetushkov@9858 | 491 | printTypes(out, metadata, true); |
apetushkov@9858 | 492 | out.write(""); |
apetushkov@9858 | 493 | out.write(""); |
apetushkov@9858 | 494 | out.write("#endif // INCLUDE_JFR"); |
apetushkov@9858 | 495 | out.write("#endif // JFRFILES_JFREVENTCLASSES_HPP"); |
apetushkov@9858 | 496 | } |
apetushkov@9858 | 497 | } |
apetushkov@9858 | 498 | |
apetushkov@9858 | 499 | private static void printTypes(Printer out, Metadata metadata, boolean empty) { |
apetushkov@9858 | 500 | for (TypeElement t : metadata.getStructs()) { |
apetushkov@9858 | 501 | if (empty) { |
apetushkov@9858 | 502 | out.write(""); |
apetushkov@9858 | 503 | printEmptyType(out, t); |
apetushkov@9858 | 504 | } else { |
apetushkov@9858 | 505 | printType(out, t); |
apetushkov@9858 | 506 | } |
apetushkov@9858 | 507 | out.write(""); |
apetushkov@9858 | 508 | } |
apetushkov@9858 | 509 | for (EventElement e : metadata.getEvents()) { |
apetushkov@9858 | 510 | if (empty) { |
apetushkov@9858 | 511 | printEmptyEvent(out, e); |
apetushkov@9858 | 512 | } else { |
apetushkov@9858 | 513 | printEvent(out, e); |
apetushkov@9858 | 514 | } |
apetushkov@9858 | 515 | out.write(""); |
apetushkov@9858 | 516 | } |
apetushkov@9858 | 517 | } |
apetushkov@9858 | 518 | |
apetushkov@9858 | 519 | private static void printEmptyEvent(Printer out, EventElement event) { |
apetushkov@9858 | 520 | out.write("class Event" + event.name + " : public JfrEvent"); |
apetushkov@9858 | 521 | out.write("{"); |
apetushkov@9858 | 522 | out.write(" public:"); |
apetushkov@9858 | 523 | out.write(" Event" + event.name + "(EventStartTime ignore=TIMED) {}"); |
apetushkov@9858 | 524 | if (event.startTime) { |
apetushkov@9858 | 525 | StringJoiner sj = new StringJoiner(",\n "); |
apetushkov@9858 | 526 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 527 | sj.add(f.getParameterType()); |
apetushkov@9858 | 528 | } |
apetushkov@9858 | 529 | out.write(" Event" + event.name + "("); |
apetushkov@9858 | 530 | out.write(" " + sj.toString() + ") { }"); |
apetushkov@9858 | 531 | } |
apetushkov@9858 | 532 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 533 | out.write(" void set_" + f.name + "(" + f.getParameterType() + ") { }"); |
apetushkov@9858 | 534 | } |
apetushkov@9858 | 535 | out.write("};"); |
apetushkov@9858 | 536 | } |
apetushkov@9858 | 537 | |
apetushkov@9858 | 538 | private static void printEmptyType(Printer out, TypeElement t) { |
apetushkov@9858 | 539 | out.write("struct JfrStruct" + t.name); |
apetushkov@9858 | 540 | out.write("{"); |
apetushkov@9858 | 541 | out.write(" public:"); |
apetushkov@9858 | 542 | for (FieldElement f : t.fields) { |
apetushkov@9858 | 543 | out.write(" void set_" + f.name + "(" + f.getParameterType() + ") { }"); |
apetushkov@9858 | 544 | } |
apetushkov@9858 | 545 | out.write("};"); |
apetushkov@9858 | 546 | } |
apetushkov@9858 | 547 | |
apetushkov@9858 | 548 | private static void printType(Printer out, TypeElement t) { |
apetushkov@9858 | 549 | out.write("struct JfrStruct" + t.name); |
apetushkov@9858 | 550 | out.write("{"); |
apetushkov@9858 | 551 | out.write(" private:"); |
apetushkov@9858 | 552 | for (FieldElement f : t.fields) { |
apetushkov@9858 | 553 | printField(out, f); |
apetushkov@9858 | 554 | } |
apetushkov@9858 | 555 | out.write(""); |
apetushkov@9858 | 556 | out.write(" public:"); |
apetushkov@9858 | 557 | for (FieldElement f : t.fields) { |
apetushkov@9858 | 558 | printTypeSetter(out, f); |
apetushkov@9858 | 559 | } |
apetushkov@9858 | 560 | out.write(""); |
apetushkov@9858 | 561 | printWriteData(out, t.fields); |
apetushkov@9858 | 562 | out.write("};"); |
apetushkov@9858 | 563 | out.write(""); |
apetushkov@9858 | 564 | } |
apetushkov@9858 | 565 | |
apetushkov@9858 | 566 | private static void printEvent(Printer out, EventElement event) { |
apetushkov@9858 | 567 | out.write("class Event" + event.name + " : public JfrEvent<Event" + event.name + ">"); |
apetushkov@9858 | 568 | out.write("{"); |
apetushkov@9858 | 569 | out.write(" private:"); |
apetushkov@9858 | 570 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 571 | printField(out, f); |
apetushkov@9858 | 572 | } |
apetushkov@9858 | 573 | out.write(""); |
apetushkov@9858 | 574 | out.write(" public:"); |
apetushkov@9858 | 575 | out.write(" static const bool hasThread = " + event.thread + ";"); |
apetushkov@9858 | 576 | out.write(" static const bool hasStackTrace = " + event.stackTrace + ";"); |
apetushkov@9858 | 577 | out.write(" static const bool isInstant = " + !event.startTime + ";"); |
apetushkov@9858 | 578 | out.write(" static const bool hasCutoff = " + event.cutoff + ";"); |
apetushkov@9858 | 579 | out.write(" static const bool isRequestable = " + event.periodic + ";"); |
apetushkov@9858 | 580 | out.write(" static const JfrEventId eventId = Jfr" + event.name + "Event;"); |
apetushkov@9858 | 581 | out.write(""); |
apetushkov@9858 | 582 | out.write(" Event" + event.name + "(EventStartTime timing=TIMED) : JfrEvent<Event" + event.name + ">(timing) {}"); |
apetushkov@9858 | 583 | out.write(""); |
apetushkov@9858 | 584 | int index = 0; |
apetushkov@9858 | 585 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 586 | out.write(" void set_" + f.name + "(" + f.getParameterType() + " " + f.getParameterName() + ") {"); |
apetushkov@9858 | 587 | out.write(" this->_" + f.name + " = " + f.getParameterName() + ";"); |
apetushkov@9858 | 588 | out.write(" DEBUG_ONLY(set_field_bit(" + index++ + "));"); |
apetushkov@9858 | 589 | out.write(" }"); |
apetushkov@9858 | 590 | } |
apetushkov@9858 | 591 | out.write(""); |
apetushkov@9858 | 592 | printWriteData(out, event.fields); |
apetushkov@9858 | 593 | out.write(""); |
apetushkov@9858 | 594 | out.write(" using JfrEvent<Event" + event.name + ">::commit; // else commit() is hidden by overloaded versions in this class"); |
apetushkov@9858 | 595 | printConstructor2(out, event); |
apetushkov@9858 | 596 | printCommitMethod(out, event); |
apetushkov@9858 | 597 | printVerify(out, event.fields); |
apetushkov@9858 | 598 | out.write("};"); |
apetushkov@9858 | 599 | } |
apetushkov@9858 | 600 | |
apetushkov@9858 | 601 | private static void printWriteData(Printer out, List<FieldElement> fields) { |
apetushkov@9858 | 602 | out.write(" template <typename Writer>"); |
apetushkov@9858 | 603 | out.write(" void writeData(Writer& w) {"); |
apetushkov@9858 | 604 | for (FieldElement field : fields) { |
apetushkov@9858 | 605 | if (field.struct) { |
apetushkov@9858 | 606 | out.write(" _" + field.name + ".writeData(w);"); |
apetushkov@9858 | 607 | } else { |
apetushkov@9858 | 608 | out.write(" w.write(_" + field.name + ");"); |
apetushkov@9858 | 609 | } |
apetushkov@9858 | 610 | } |
apetushkov@9858 | 611 | out.write(" }"); |
apetushkov@9858 | 612 | } |
apetushkov@9858 | 613 | |
apetushkov@9858 | 614 | private static void printTypeSetter(Printer out, FieldElement field) { |
apetushkov@9858 | 615 | out.write(" void set_" + field.name + "(" + field.getParameterType() + " new_value) { this->_" + field.name + " = new_value; }"); |
apetushkov@9858 | 616 | } |
apetushkov@9858 | 617 | |
apetushkov@9858 | 618 | private static void printVerify(Printer out, List<FieldElement> fields) { |
apetushkov@9858 | 619 | out.write(""); |
apetushkov@9858 | 620 | out.write("#ifdef ASSERT"); |
apetushkov@9858 | 621 | out.write(" void verify() const {"); |
apetushkov@9858 | 622 | int index = 0; |
apetushkov@9858 | 623 | for (FieldElement f : fields) { |
apetushkov@9858 | 624 | out.write(" assert(verify_field_bit(" + index++ + "), \"Attempting to write an uninitialized event field: " + f.name + "\");"); |
apetushkov@9858 | 625 | } |
apetushkov@9858 | 626 | out.write(" }"); |
apetushkov@9858 | 627 | out.write("#endif"); |
apetushkov@9858 | 628 | } |
apetushkov@9858 | 629 | |
apetushkov@9858 | 630 | private static void printCommitMethod(Printer out, EventElement event) { |
apetushkov@9858 | 631 | if (event.startTime) { |
apetushkov@9858 | 632 | StringJoiner sj = new StringJoiner(",\n "); |
apetushkov@9858 | 633 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 634 | sj.add(f.getParameterType() + " " + f.name); |
apetushkov@9858 | 635 | } |
apetushkov@9858 | 636 | out.write(""); |
apetushkov@9858 | 637 | out.write(" void commit(" + sj.toString() + ") {"); |
apetushkov@9858 | 638 | out.write(" if (should_commit()) {"); |
apetushkov@9858 | 639 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 640 | out.write(" set_" + f.name + "(" + f.name + ");"); |
apetushkov@9858 | 641 | } |
apetushkov@9858 | 642 | out.write(" commit();"); |
apetushkov@9858 | 643 | out.write(" }"); |
apetushkov@9858 | 644 | out.write(" }"); |
apetushkov@9858 | 645 | } |
apetushkov@9858 | 646 | out.write(""); |
apetushkov@9858 | 647 | StringJoiner sj = new StringJoiner(",\n "); |
apetushkov@9858 | 648 | if (event.startTime) { |
apetushkov@9858 | 649 | sj.add("const Ticks& startTicks"); |
apetushkov@9858 | 650 | sj.add("const Ticks& endTicks"); |
apetushkov@9858 | 651 | } |
apetushkov@9858 | 652 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 653 | sj.add(f.getParameterType() + " " + f.name); |
apetushkov@9858 | 654 | } |
apetushkov@9858 | 655 | out.write(" static void commit(" + sj.toString() + ") {"); |
apetushkov@9858 | 656 | out.write(" Event" + event.name + " me(UNTIMED);"); |
apetushkov@9858 | 657 | out.write(""); |
apetushkov@9858 | 658 | out.write(" if (me.should_commit()) {"); |
apetushkov@9858 | 659 | if (event.startTime) { |
apetushkov@9858 | 660 | out.write(" me.set_starttime(startTicks);"); |
apetushkov@9858 | 661 | out.write(" me.set_endtime(endTicks);"); |
apetushkov@9858 | 662 | } |
apetushkov@9858 | 663 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 664 | out.write(" me.set_" + f.name + "(" + f.name + ");"); |
apetushkov@9858 | 665 | } |
apetushkov@9858 | 666 | out.write(" me.commit();"); |
apetushkov@9858 | 667 | out.write(" }"); |
apetushkov@9858 | 668 | out.write(" }"); |
apetushkov@9858 | 669 | } |
apetushkov@9858 | 670 | |
apetushkov@9858 | 671 | private static void printConstructor2(Printer out, EventElement event) { |
apetushkov@9858 | 672 | if (!event.startTime) { |
apetushkov@9858 | 673 | out.write(""); |
apetushkov@9858 | 674 | out.write(""); |
apetushkov@9858 | 675 | } |
apetushkov@9858 | 676 | if (event.startTime) { |
apetushkov@9858 | 677 | out.write(""); |
apetushkov@9858 | 678 | out.write(" Event" + event.name + "("); |
apetushkov@9858 | 679 | StringJoiner sj = new StringJoiner(",\n "); |
apetushkov@9858 | 680 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 681 | sj.add(f.getParameterType() + " " + f.name); |
apetushkov@9858 | 682 | } |
apetushkov@9858 | 683 | out.write(" " + sj.toString() + ") : JfrEvent<Event" + event.name + ">(TIMED) {"); |
apetushkov@9858 | 684 | out.write(" if (should_commit()) {"); |
apetushkov@9858 | 685 | for (FieldElement f : event.fields) { |
apetushkov@9858 | 686 | out.write(" set_" + f.name + "(" + f.name + ");"); |
apetushkov@9858 | 687 | } |
apetushkov@9858 | 688 | out.write(" }"); |
apetushkov@9858 | 689 | out.write(" }"); |
apetushkov@9858 | 690 | } |
apetushkov@9858 | 691 | } |
apetushkov@9858 | 692 | |
apetushkov@9858 | 693 | private static void printField(Printer out, FieldElement field) { |
apetushkov@9858 | 694 | out.write(" " + field.getFieldType() + " _" + field.name + ";"); |
apetushkov@9858 | 695 | } |
apetushkov@9858 | 696 | } |