1 /* 2 * Copyright 2019 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.perfmark.tracewriter; 18 19 import com.google.gson.annotations.SerializedName; 20 import io.perfmark.impl.Mark; 21 import java.util.AbstractMap; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.Collections; 25 import java.util.LinkedHashMap; 26 import java.util.List; 27 import java.util.ListIterator; 28 import java.util.Map; 29 import java.util.Set; 30 import javax.annotation.CheckReturnValue; 31 import javax.annotation.Nullable; 32 33 @CheckReturnValue 34 final class TraceEvent implements Cloneable { 35 TraceEvent()36 private TraceEvent() {} 37 38 static final TraceEvent EVENT = new TraceEvent(); 39 40 @SerializedName("ph") 41 @SuppressWarnings("unused") 42 private String phase; 43 44 @SerializedName("name") 45 @SuppressWarnings("unused") 46 private String name; 47 48 @Nullable 49 @SerializedName("cat") 50 @SuppressWarnings("unused") 51 private String categories; 52 53 @Nullable 54 @SerializedName("ts") 55 @SuppressWarnings("unused") 56 private Double traceClockMicros; 57 58 @Nullable 59 @SerializedName("pid") 60 @SuppressWarnings("unused") 61 private Long pid; 62 63 @SerializedName("tid") 64 @Nullable 65 @SuppressWarnings("unused") 66 private Long tid; 67 68 @Nullable 69 @SerializedName("id") 70 @SuppressWarnings("unused") 71 private Long id; 72 73 @Nullable 74 @SerializedName("args") 75 @SuppressWarnings("unused") 76 private TagMap args = null; 77 78 @Nullable 79 @SerializedName("cname") 80 @SuppressWarnings("unused") 81 private String colorName = null; 82 name(String name)83 TraceEvent name(String name) { 84 if (name == null) { 85 throw new NullPointerException("name"); 86 } 87 TraceEvent other = clone(); 88 other.name = name; 89 return other; 90 } 91 categories(String... categories)92 TraceEvent categories(String... categories) { 93 if (categories == null) { 94 throw new NullPointerException("categories"); 95 } 96 return categories(Arrays.asList(categories)); 97 } 98 categories(List<String> categories)99 TraceEvent categories(List<String> categories) { 100 if (categories == null) { 101 throw new NullPointerException("categories"); 102 } 103 TraceEvent other = clone(); 104 if (!categories.isEmpty()) { 105 StringBuilder sb = new StringBuilder(); 106 ListIterator<String> it = categories.listIterator(); 107 sb.append(it.next()); 108 while (it.hasNext()) { 109 String next = it.next(); 110 if (next == null) { 111 throw new NullPointerException("next null at " + (it.nextIndex() - 1)); 112 } 113 sb.append(',').append(next); 114 } 115 other.categories = sb.toString(); 116 } else { 117 other.categories = null; 118 } 119 return other; 120 } 121 traceClockNanos(long traceClockNanos)122 strictfp TraceEvent traceClockNanos(long traceClockNanos) { 123 TraceEvent other = clone(); 124 other.traceClockMicros = traceClockNanos / 1000.0; 125 return other; 126 } 127 phase(String phase)128 TraceEvent phase(String phase) { 129 if (phase == null) { 130 throw new NullPointerException("phase"); 131 } 132 TraceEvent other = clone(); 133 other.phase = phase; 134 return other; 135 } 136 tid(long tid)137 TraceEvent tid(long tid) { 138 TraceEvent other = clone(); 139 other.tid = tid; 140 return other; 141 } 142 pid(long pid)143 TraceEvent pid(long pid) { 144 TraceEvent other = clone(); 145 other.pid = pid; 146 return other; 147 } 148 id(long id)149 TraceEvent id(long id) { 150 TraceEvent other = clone(); 151 other.id = id; 152 return other; 153 } 154 155 /** 156 * Note This should only be used for tags, as the map size is used to determine the arg names in 157 * TraceEventWriter. This will overwrite any existing args. 158 * 159 * @param tagMap the args to use. 160 * @return this 161 */ args(TagMap tagMap)162 TraceEvent args(TagMap tagMap) { 163 if (tagMap == null) { 164 throw new NullPointerException("tagMap"); 165 } 166 TraceEvent other = clone(); 167 other.args = tagMap; 168 return other; 169 } 170 args()171 TagMap args() { 172 if (args == null) { 173 return TagMap.EMPTY; 174 } else { 175 return args; 176 } 177 } 178 179 @Override clone()180 protected TraceEvent clone() { 181 try { 182 return (TraceEvent) super.clone(); 183 } catch (CloneNotSupportedException e) { 184 throw new RuntimeException(e); 185 } 186 } 187 188 static final class TagMap extends AbstractMap<String, Object> { 189 190 static final TagMap EMPTY = 191 new TagMap(Collections.<Entry<String, ?>>emptyList(), Collections.emptyList()); 192 193 private final List<Entry<String, ?>> keyedValues; 194 private final List<?> unkeyedValues; 195 TagMap(List<Entry<String, ?>> keyedValues, List<?> unkeyedValues)196 private TagMap(List<Entry<String, ?>> keyedValues, List<?> unkeyedValues) { 197 this.keyedValues = keyedValues; 198 this.unkeyedValues = unkeyedValues; 199 } 200 withUnkeyed(@ullable String tagName, long tagId)201 TagMap withUnkeyed(@Nullable String tagName, long tagId) { 202 List<Object> unkeyedValues = null; 203 if (tagName != null && !Mark.NO_TAG_NAME.equals(tagName)) { 204 unkeyedValues = new ArrayList<>(this.unkeyedValues); 205 unkeyedValues.add(tagName); 206 } 207 if (tagId != Mark.NO_TAG_ID) { 208 unkeyedValues = unkeyedValues != null ? unkeyedValues : new ArrayList<>(this.unkeyedValues); 209 unkeyedValues.add(tagId); 210 } 211 if (unkeyedValues != null) { 212 return new TagMap(keyedValues, Collections.unmodifiableList(unkeyedValues)); 213 } else { 214 return new TagMap(keyedValues, this.unkeyedValues); 215 } 216 } 217 withKeyed(@ullable String tagName, Object tagValue)218 TagMap withKeyed(@Nullable String tagName, Object tagValue) { 219 List<Entry<String, ?>> keyedValues = new ArrayList<>(this.keyedValues); 220 keyedValues.add(new SimpleImmutableEntry<>(String.valueOf(tagName), tagValue)); 221 return new TagMap(Collections.unmodifiableList(keyedValues), unkeyedValues); 222 } 223 withKeyed(@ullable String tagName, long tagValue0, long tagValue1)224 TagMap withKeyed(@Nullable String tagName, long tagValue0, long tagValue1) { 225 List<Entry<String, ?>> keyedValues = new ArrayList<>(this.keyedValues); 226 keyedValues.add( 227 new SimpleImmutableEntry<>(String.valueOf(tagName), tagValue0 + ":" + tagValue1)); 228 return new TagMap(Collections.unmodifiableList(keyedValues), unkeyedValues); 229 } 230 231 @Override entrySet()232 public Set<Entry<String, Object>> entrySet() { 233 List<Entry<String, ?>> pairs = new ArrayList<>(keyedValues.size() + unkeyedValues.size()); 234 pairs.addAll(keyedValues); 235 for (Object value : unkeyedValues) { 236 if (value instanceof Long) { 237 pairs.add(new SimpleImmutableEntry<>("id", value)); 238 } else if (value instanceof String) { 239 pairs.add(new SimpleImmutableEntry<>("tag", value)); 240 } else { 241 pairs.add(new SimpleImmutableEntry<>("tag", String.valueOf(value))); 242 } 243 } 244 245 Map<String, Object> ret = new LinkedHashMap<>(); 246 addEntry: 247 for (Entry<String, ?> kv : pairs) { 248 String name = kv.getKey(); 249 Object value = kv.getValue(); 250 String derivedName = name; 251 int usages = 0; 252 while (true) { 253 if (!ret.containsKey(derivedName)) { 254 ret.put(derivedName, value); 255 continue addEntry; 256 } 257 if (ret.get(derivedName).equals(value)) { 258 continue addEntry; 259 } 260 usages++; 261 derivedName = name + " (" + usages + ')'; 262 } 263 } 264 return Collections.unmodifiableSet(ret.entrySet()); 265 } 266 } 267 } 268