| // Copyright 2022 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 | package com.google.devtools.build.lib.profiler; | 
 |  | 
 | import static java.util.Map.entry; | 
 |  | 
 | import com.google.common.annotations.VisibleForTesting; | 
 | import com.google.common.base.Preconditions; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.gson.stream.JsonWriter; | 
 | import java.io.IOException; | 
 | import java.time.Duration; | 
 | import java.util.Map; | 
 | import java.util.concurrent.TimeUnit; | 
 | import javax.annotation.Nullable; | 
 |  | 
 | /** | 
 |  * Used to inject a whole counter series (or even multiple) in one go in the JSON trace profile; | 
 |  * these could be used to represent CPU or memory usages over the course of an invocation (as | 
 |  * opposed to individual tasks such as executing an action). | 
 |  */ | 
 | final class CounterSeriesTraceData implements TraceData { | 
 |   @VisibleForTesting static final long PROCESS_ID = 1; | 
 |   private final Map<ProfilerTask, double[]> counterSeriesMap; | 
 |   private final Duration profileStart; | 
 |   private final Duration bucketDuration; | 
 |   private final int len; | 
 |   private final long threadId; | 
 |   private String displayName; | 
 |  | 
 |   @Nullable private String colorName; | 
 |  | 
 |   /** | 
 |    * If multiple series are passed: - they will be rendered as stacked chart; - we assume they all | 
 |    * have the same length; - display name and color are picked from the first profile task in the | 
 |    * map. However, colors the remaining series are picked arbitrarily by the Trace renderer. | 
 |    */ | 
 |   CounterSeriesTraceData( | 
 |       Map<ProfilerTask, double[]> counterSeriesMap, | 
 |       Duration profileStart, | 
 |       Duration bucketDuration) { | 
 |     Integer len = null; | 
 |     for (ProfilerTask profilerTask : counterSeriesMap.keySet()) { | 
 |       Preconditions.checkState(COUNTER_TASK_TO_SERIES_NAME.containsKey(profilerTask)); | 
 |       if (len == null) { | 
 |         len = counterSeriesMap.get(profilerTask).length; | 
 |  | 
 |         this.displayName = profilerTask.description; | 
 |  | 
 |         // Pick acceptable counter colors manually, unfortunately we have to pick from these | 
 |         // weird reserved names from | 
 |         // https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html | 
 |         this.colorName = COUNTER_TASK_TO_COLOR.get(profilerTask); | 
 |       } else { | 
 |         Preconditions.checkState(len.equals(counterSeriesMap.get(profilerTask).length)); | 
 |       } | 
 |     } | 
 |     this.len = len; | 
 |     this.threadId = Thread.currentThread().getId(); | 
 |     this.counterSeriesMap = counterSeriesMap; | 
 |     this.profileStart = profileStart; | 
 |     this.bucketDuration = bucketDuration; | 
 |  | 
 |   } | 
 |  | 
 |   // Pick acceptable counter colors manually, unfortunately we have to pick from these | 
 |   // weird reserved names from | 
 |   // https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html | 
 |   private static final ImmutableMap<ProfilerTask, String> COUNTER_TASK_TO_COLOR = | 
 |       ImmutableMap.ofEntries( | 
 |           entry(ProfilerTask.LOCAL_CPU_USAGE, "good"), | 
 |           entry(ProfilerTask.SYSTEM_CPU_USAGE, "rail_load"), | 
 |           entry(ProfilerTask.LOCAL_MEMORY_USAGE, "olive"), | 
 |           entry(ProfilerTask.SYSTEM_MEMORY_USAGE, "bad"), | 
 |           entry(ProfilerTask.SYSTEM_NETWORK_UP_USAGE, "rail_response"), | 
 |           entry(ProfilerTask.SYSTEM_NETWORK_DOWN_USAGE, "rail_response"), | 
 |           entry(ProfilerTask.WORKERS_MEMORY_USAGE, "rail_animation"), | 
 |           entry(ProfilerTask.SYSTEM_LOAD_AVERAGE, "generic_work"), | 
 |           entry(ProfilerTask.MEMORY_USAGE_ESTIMATION, "rail_idle"), | 
 |           entry(ProfilerTask.CPU_USAGE_ESTIMATION, "cq_build_attempt_passed"), | 
 |           entry(ProfilerTask.PRESSURE_STALL_IO, "rail_idle"), | 
 |           entry(ProfilerTask.PRESSURE_STALL_MEMORY, "rail_animation")); | 
 |  | 
 |   private static final ImmutableMap<ProfilerTask, String> COUNTER_TASK_TO_SERIES_NAME = | 
 |       ImmutableMap.ofEntries( | 
 |           entry(ProfilerTask.ACTION_COUNTS, "action"), | 
 |           entry(ProfilerTask.ACTION_CACHE_COUNTS, "local action cache"), | 
 |           entry(ProfilerTask.LOCAL_CPU_USAGE, "cpu"), | 
 |           entry(ProfilerTask.SYSTEM_CPU_USAGE, "system cpu"), | 
 |           entry(ProfilerTask.LOCAL_MEMORY_USAGE, "memory"), | 
 |           entry(ProfilerTask.SYSTEM_MEMORY_USAGE, "system memory"), | 
 |           entry(ProfilerTask.SYSTEM_NETWORK_UP_USAGE, "system network up (Mbps)"), | 
 |           entry(ProfilerTask.SYSTEM_NETWORK_DOWN_USAGE, "system network down (Mbps)"), | 
 |           entry(ProfilerTask.WORKERS_MEMORY_USAGE, "workers memory"), | 
 |           entry(ProfilerTask.SYSTEM_LOAD_AVERAGE, "load"), | 
 |           entry(ProfilerTask.MEMORY_USAGE_ESTIMATION, "estimated memory"), | 
 |           entry(ProfilerTask.CPU_USAGE_ESTIMATION, "estimated cpu"), | 
 |           entry(ProfilerTask.PRESSURE_STALL_IO, "i/o pressure"), | 
 |           entry(ProfilerTask.PRESSURE_STALL_MEMORY, "memory pressure")); | 
 |  | 
 |   @Override | 
 |   public void writeTraceData(JsonWriter jsonWriter, long profileStartTimeNanos) throws IOException { | 
 |     // See | 
 |     // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.msg3086636uq | 
 |     // for how counter series are represented in the Chrome Trace Event format. | 
 |     for (int i = 0; i < len; i++) { | 
 |       long timeNanos = profileStart.plus(bucketDuration.multipliedBy(i)).toNanos(); | 
 |       jsonWriter.setIndent("  "); | 
 |       jsonWriter.beginObject(); | 
 |       jsonWriter.setIndent(""); | 
 |       jsonWriter.name("name").value(displayName); | 
 |       jsonWriter.name("pid").value(PROCESS_ID); | 
 |       jsonWriter.name("tid").value(threadId); | 
 |       if (colorName != null) { | 
 |         jsonWriter.name("cname").value(colorName); | 
 |       } | 
 |       jsonWriter.name("ph").value("C"); | 
 |       jsonWriter.name("ts").value(TimeUnit.NANOSECONDS.toMicros(timeNanos - profileStartTimeNanos)); | 
 |       jsonWriter.name("args"); | 
 |  | 
 |       jsonWriter.beginObject(); | 
 |       for (ProfilerTask profilerTask : counterSeriesMap.keySet()) { | 
 |         double value = counterSeriesMap.get(profilerTask)[i]; | 
 |         // Skip counts equal to zero. They will show up as a thin line in the profile. | 
 |         if (Math.abs(value) > 0.00001) { | 
 |           jsonWriter.name(COUNTER_TASK_TO_SERIES_NAME.get(profilerTask)).value(value); | 
 |         } | 
 |       } | 
 |       jsonWriter.endObject(); | 
 |  | 
 |       jsonWriter.endObject(); | 
 |     } | 
 |   } | 
 | } |