-
Notifications
You must be signed in to change notification settings - Fork 208
OpenTelemetry Pre-aggregated metrics #2439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
f0959e6
Add placeholder
heyams e707111
Rename
heyams c4e5474
Start mapping pre-agg metrics to azure monitor metrics
heyams fd58580
Add preagg metrics extractor
heyams b47c27c
Fix spotless
heyams 4fcda82
Merge branch 'main' into heya/pre-aggregated-metrics
heyams 341b432
Use stdout
heyams 684b7ea
Update test and refactor
heyams a3b8fdd
Fix test
heyams e314498
Fix lgtm
heyams 58e50bd
Perf buckets
trask d37d258
Merge branch 'perfbuckets' into heya/pre-aggregated-metrics
heyams d448696
Hack httpservermetric onEnd
heyams 308b5e1
Fix spotless
heyams 0be0aa6
Shade HttpClientMetrics
heyams 17794bf
Fix conversion
heyams 18156e6
Disable performanceBucket in unit test
heyams b38b24f
Validate pre-agg in smoke test
heyams ecb893c
Remove debug logs
heyams 3901f3d
Fix typo
heyams 659c2b2
Remove commented out code
heyams 02a4440
Fix rpc
heyams e7849db
Fix ci tests
heyams fa44655
Refactor
heyams c028ac8
Add a new smoke test
heyams 07b42e0
Clean up smoke test
heyams 6defb03
Add perfromanceBucket to http.client.duration and http.server.duratio…
heyams 4655620
Revert httpclient smoke test
heyams 8d1abbd
Fix preaggregated metrics smoke test
heyams d0b9c7d
Delete a todo that has been resolved
heyams 93474a0
Delete stdout
heyams 8f667aa
Delete unused dependencies and ivar
heyams 5a598ca
Verify rpc pre-aggregated metrics
heyams cb2f9c6
Generate rpcClientMetrics to check its metric content
heyams ec5616a
Fix NPE and fix perfBucket is not part of rpc metrics attributes
heyams d7f9d05
Merge branch 'main' into heya/pre-aggregated-metrics
heyams 8936114
Merge branch 'main' into heya/pre-aggregated-metrics
heyams ea60a0a
Send rpc/http.client as dependencies/duration and rpc/http.server as …
heyams 354c24e
Stdout http.server.duration metric
heyams 73ac883
Add target to httpClientMetrics' attributes
heyams 650f81c
Add taret to rpcClientMetrics
heyams aad7c54
Add target to rpcclientmetrics
heyams 54faf3e
Fix smoke test
heyams 1be4807
Fix rpc smoke test
heyams 5251d59
Remove stdout suppressWarning
heyams ba69e1c
Debug 'ai.user.userAgent'
heyams 3e2bdb8
Remove perf bucket and add isSynthetic
heyams c8d4f0c
Address comments
heyams a0270bf
Fix compilation errors
heyams 9185a53
Remove unused imports
heyams 769a1e7
Add MS_ProcessedByMetricExtractors to request/dependency not pre-agg …
heyams 346179c
Remove an assert
heyams 9c223f6
Rename a method
heyams 4fcee8a
Fix tests
heyams f6974b0
Fix test
heyams 872d821
Fix ci tests
heyams 0e3a638
Fix spotless
heyams a6f2e99
Reimplement the logic for adding _MS.ProcessedByMetricExtractors
heyams 50d7984
Remove isPreAggregated attribute
heyams 8c4ce32
Fix spotless
heyams f41348b
Merge branch 'main' into heya/pre-aggregated-metrics
heyams 278b526
Address comments
heyams b84e94b
Fix smoke tests
heyams f63b8f5
Fix smoke tests
heyams 2fef285
Fix tests
heyams bd12ff8
Fix tests
heyams e0a6763
Fix
heyams f06077e
Debug synthetic
heyams b9d6669
Fix tests
heyams 7802806
Fix userAgent otel key and more tests
heyams 6075e3f
Fix more tests
heyams 25384f3
Fix more tests
heyams cd38814
Fix one more test
heyams 2d45b25
Fix pre-agg metrics' custom dimensions
heyams 9cac0e8
Revert change and add a todo
heyams 2f8e95f
Fix pre-agg metrics were failing to be ingested into mdm
heyams 29459ea
Fix tests
heyams 120fa3a
Remove a todo
heyams 8978dec
Remove debug logs
heyams 9890725
Merge branch 'main' into heya/pre-aggregated-metrics
heyams 79b0a08
Address comments
heyams ab331d7
Add appinsights code comment
heyams 8ab3154
Exclude return statement
heyams e216ed4
Use containsExactly
heyams 68bf945
Verify _MS.ProcessedByMetricExtractors
heyams 111c773
Fix wrong import
heyams 998e042
Fix a compilation error
heyams 24ad4c7
Turn baseextractor into a util class
heyams 6d46468
Address comments
heyams 12878c8
Merge branch 'main' into heya/pre-aggregated-metrics
heyams ad88fc4
Fix tests
heyams 22cdd01
Address more comments
heyams b916cbf
Fix tests
heyams a1ee8e3
Use attrbuteKey constants
heyams 8b9601f
Address comments
heyams a77fb40
Merge branch 'main' into heya/pre-aggregated-metrics
heyams 81efed1
Fix a typo
heyams d61b7d9
Fix one more test
heyams 1855abd
Remove MetricView and turn extractors into util class
heyams 53f145a
Apply the same to rpcservermetrics
heyams 0db361d
Rename
heyams 1e7fb05
Comments
heyams 9f77259
Use 'Http' for dependency type
heyams File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...n/java/io/opentelemetry/instrumentation/api/instrumenter/BootstrapSemanticAttributes.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* | ||
| * ApplicationInsights-Java | ||
| * Copyright (c) Microsoft Corporation | ||
| * All rights reserved. | ||
| * | ||
| * MIT License | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
| * software and associated documentation files (the ""Software""), to deal in the Software | ||
| * without restriction, including without limitation the rights to use, copy, modify, merge, | ||
| * publish, distribute, sublicense, and/or sell copies of the Software, and to permit | ||
| * persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| * The above copyright notice and this permission notice shall be included in all copies or | ||
| * substantial portions of the Software. | ||
| * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
| * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | ||
| * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | ||
| * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
| * DEALINGS IN THE SOFTWARE. | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.api.instrumenter; | ||
|
|
||
| import static io.opentelemetry.api.common.AttributeKey.booleanKey; | ||
| import static io.opentelemetry.api.common.AttributeKey.stringKey; | ||
|
|
||
| import io.opentelemetry.api.common.AttributeKey; | ||
|
|
||
| public final class BootstrapSemanticAttributes { | ||
|
|
||
| public static final AttributeKey<Boolean> IS_SYNTHETIC = | ||
| booleanKey("applicationinsights.internal.is_synthetic"); | ||
| public static final AttributeKey<String> TARGET = | ||
| stringKey("applicationinsights.internal.target"); | ||
| public static final AttributeKey<Boolean> IS_PRE_AGGREGATED = | ||
| booleanKey("applicationinsights.internal.is_pre_aggregated"); | ||
|
|
||
| private BootstrapSemanticAttributes() {} | ||
| } |
38 changes: 38 additions & 0 deletions
38
...bootstrap/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/UserAgents.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* | ||
| * ApplicationInsights-Java | ||
| * Copyright (c) Microsoft Corporation | ||
| * All rights reserved. | ||
| * | ||
| * MIT License | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
| * software and associated documentation files (the ""Software""), to deal in the Software | ||
| * without restriction, including without limitation the rights to use, copy, modify, merge, | ||
| * publish, distribute, sublicense, and/or sell copies of the Software, and to permit | ||
| * persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| * The above copyright notice and this permission notice shall be included in all copies or | ||
| * substantial portions of the Software. | ||
| * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
| * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | ||
| * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | ||
| * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
| * DEALINGS IN THE SOFTWARE. | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.api.instrumenter; | ||
|
|
||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; | ||
|
|
||
| public final class UserAgents { | ||
|
|
||
| public static boolean isBot(Attributes endAttributes, Attributes startAttributes) { | ||
| String userAgent = endAttributes.get(SemanticAttributes.HTTP_USER_AGENT); | ||
| if (userAgent == null) { | ||
| userAgent = startAttributes.get(SemanticAttributes.HTTP_USER_AGENT); | ||
| } | ||
| return userAgent != null && userAgent.contains("AlwaysOn"); | ||
| } | ||
|
|
||
| private UserAgents() {} | ||
| } |
281 changes: 281 additions & 0 deletions
281
...c/main/java/io/opentelemetry/instrumentation/api/instrumenter/http/HttpClientMetrics.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,281 @@ | ||
| /* | ||
| * ApplicationInsights-Java | ||
| * Copyright (c) Microsoft Corporation | ||
| * All rights reserved. | ||
| * | ||
| * MIT License | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
| * software and associated documentation files (the ""Software""), to deal in the Software | ||
| * without restriction, including without limitation the rights to use, copy, modify, merge, | ||
| * publish, distribute, sublicense, and/or sell copies of the Software, and to permit | ||
| * persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| * The above copyright notice and this permission notice shall be included in all copies or | ||
| * substantial portions of the Software. | ||
| * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
| * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | ||
| * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | ||
| * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
| * DEALINGS IN THE SOFTWARE. | ||
| */ | ||
|
|
||
| package io.opentelemetry.instrumentation.api.instrumenter.http; | ||
|
|
||
| import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_PRE_AGGREGATED; | ||
| import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.IS_SYNTHETIC; | ||
| import static io.opentelemetry.instrumentation.api.instrumenter.BootstrapSemanticAttributes.TARGET; | ||
| import static java.util.logging.Level.FINE; | ||
|
|
||
| import com.google.auto.value.AutoValue; | ||
| import io.opentelemetry.api.common.AttributeKey; | ||
| import io.opentelemetry.api.common.Attributes; | ||
| import io.opentelemetry.api.metrics.DoubleHistogram; | ||
| import io.opentelemetry.api.metrics.LongHistogram; | ||
| import io.opentelemetry.api.metrics.Meter; | ||
| import io.opentelemetry.api.trace.Span; | ||
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.ContextKey; | ||
| import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; | ||
| import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; | ||
| import io.opentelemetry.instrumentation.api.instrumenter.UserAgents; | ||
| import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.logging.Logger; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
| * {@link OperationListener} which keeps track of <a | ||
| * href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/http-metrics.md#http-client">HTTP | ||
| * client metrics</a>. | ||
| */ | ||
| public final class HttpClientMetrics implements OperationListener { | ||
|
|
||
| private static final double NANOS_PER_MS = TimeUnit.MILLISECONDS.toNanos(1); | ||
|
|
||
| private static final ContextKey<State> HTTP_CLIENT_REQUEST_METRICS_STATE = | ||
| ContextKey.named("http-client-request-metrics-state"); | ||
|
|
||
| private static final Logger logger = Logger.getLogger(HttpClientMetrics.class.getName()); | ||
|
|
||
| /** | ||
| * Returns a {@link OperationMetrics} which can be used to enable recording of {@link | ||
| * HttpClientMetrics} on an {@link | ||
| * io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder}. | ||
| */ | ||
| public static OperationMetrics get() { | ||
| return HttpClientMetrics::new; | ||
| } | ||
|
|
||
| private final DoubleHistogram duration; | ||
| private final LongHistogram requestSize; | ||
| private final LongHistogram responseSize; | ||
|
|
||
| private HttpClientMetrics(Meter meter) { | ||
| duration = | ||
| meter | ||
| .histogramBuilder("http.client.duration") | ||
| .setUnit("ms") | ||
| .setDescription("The duration of the outbound HTTP request") | ||
| .build(); | ||
| requestSize = | ||
| meter | ||
| .histogramBuilder("http.client.request.size") | ||
| .setUnit("By") | ||
| .setDescription("The size of HTTP request messages") | ||
| .ofLongs() | ||
| .build(); | ||
| responseSize = | ||
| meter | ||
| .histogramBuilder("http.client.response.size") | ||
| .setUnit("By") | ||
| .setDescription("The size of HTTP response messages") | ||
| .ofLongs() | ||
| .build(); | ||
| } | ||
|
|
||
| @Override | ||
| public Context onStart(Context context, Attributes startAttributes, long startNanos) { | ||
| return context.with( | ||
| HTTP_CLIENT_REQUEST_METRICS_STATE, | ||
| new AutoValue_HttpClientMetrics_State(startAttributes, startNanos)); | ||
| } | ||
|
|
||
| @Override | ||
| public void onEnd(Context context, Attributes endAttributes, long endNanos) { | ||
| State state = context.get(HTTP_CLIENT_REQUEST_METRICS_STATE); | ||
| if (state == null) { | ||
| logger.log( | ||
| FINE, | ||
| "No state present when ending context {0}. Cannot record HTTP request metrics.", | ||
| context); | ||
| return; | ||
| } | ||
| Attributes durationAndSizeAttributes = | ||
| TemporaryMetricsView.applyClientDurationAndSizeView(state.startAttributes(), endAttributes); | ||
|
|
||
| // START APPLICATION INSIGHTS CODE | ||
|
|
||
| // this is needed for detecting telemetry signals that will trigger pre-aggregated metrics via | ||
| // auto instrumentations | ||
| Span.fromContext(context).setAttribute(IS_PRE_AGGREGATED, true); | ||
|
|
||
| Attributes durationAttributes = | ||
| durationAndSizeAttributes.toBuilder() | ||
| .put(IS_SYNTHETIC, UserAgents.isBot(endAttributes, state.startAttributes())) | ||
| .put(TARGET, getTargetForHttpClientSpan(durationAndSizeAttributes)) | ||
| .build(); | ||
|
|
||
| // END APPLICATION INSIGHTS CODE | ||
heyams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| this.duration.record( | ||
| (endNanos - state.startTimeNanos()) / NANOS_PER_MS, durationAttributes, context); | ||
|
|
||
| Long requestLength = | ||
| getAttribute( | ||
| SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH, endAttributes, state.startAttributes()); | ||
| if (requestLength != null) { | ||
| requestSize.record(requestLength, durationAndSizeAttributes); | ||
| } | ||
| Long responseLength = | ||
| getAttribute( | ||
| SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, | ||
| endAttributes, | ||
| state.startAttributes()); | ||
| if (responseLength != null) { | ||
| responseSize.record(responseLength, durationAndSizeAttributes); | ||
| } | ||
| } | ||
|
|
||
| // this is copied from SpanDataMapper | ||
| private static String getTargetForHttpClientSpan(Attributes attributes) { | ||
| // from the spec, at least one of the following sets of attributes is required: | ||
| // * http.url | ||
| // * http.scheme, http.host, http.target | ||
| // * http.scheme, net.peer.name, net.peer.port, http.target | ||
| // * http.scheme, net.peer.ip, net.peer.port, http.target | ||
| String target = getTargetFromPeerService(attributes); | ||
| if (target != null) { | ||
| return target; | ||
| } | ||
| // note http.host includes the port (at least when non-default) | ||
| target = attributes.get(SemanticAttributes.HTTP_HOST); | ||
| if (target != null) { | ||
| String scheme = attributes.get(SemanticAttributes.HTTP_SCHEME); | ||
| if ("http".equals(scheme)) { | ||
| if (target.endsWith(":80")) { | ||
| target = target.substring(0, target.length() - 3); | ||
heyams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } else if ("https".equals(scheme)) { | ||
| if (target.endsWith(":443")) { | ||
| target = target.substring(0, target.length() - 4); | ||
heyams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| return target; | ||
| } | ||
| String url = attributes.get(SemanticAttributes.HTTP_URL); | ||
| if (url != null) { | ||
| target = getTargetFromUrl(url); | ||
| if (target != null) { | ||
| return target; | ||
| } | ||
| } | ||
| String scheme = attributes.get(SemanticAttributes.HTTP_SCHEME); | ||
| int defaultPort; | ||
| if ("http".equals(scheme)) { | ||
| defaultPort = 80; | ||
| } else if ("https".equals(scheme)) { | ||
| defaultPort = 443; | ||
| } else { | ||
| defaultPort = 0; | ||
| } | ||
| target = getTargetFromNetAttributes(attributes, defaultPort); | ||
| if (target != null) { | ||
| return target; | ||
| } | ||
| // this should not happen, just a failsafe | ||
| return "Http"; | ||
| } | ||
|
|
||
| // this is copied from SpanDataMapper | ||
| @Nullable | ||
| private static String getTargetFromPeerService(Attributes attributes) { | ||
| // do not append port to peer.service | ||
| return attributes.get(SemanticAttributes.PEER_SERVICE); | ||
| } | ||
|
|
||
| // this is copied from SpanDataMapper | ||
| @Nullable | ||
| private static String getTargetFromNetAttributes(Attributes attributes, int defaultPort) { | ||
| String target = getHostFromNetAttributes(attributes); | ||
| if (target == null) { | ||
| return null; | ||
| } | ||
| // append net.peer.port to target | ||
| Long port = attributes.get(SemanticAttributes.NET_PEER_PORT); | ||
| if (port != null && port != defaultPort) { | ||
| return target + ":" + port; | ||
| } | ||
| return target; | ||
| } | ||
|
|
||
| // this is copied from SpanDataMapper | ||
| @Nullable | ||
| private static String getHostFromNetAttributes(Attributes attributes) { | ||
| String host = attributes.get(SemanticAttributes.NET_PEER_NAME); | ||
| if (host != null) { | ||
| return host; | ||
| } | ||
| return attributes.get(SemanticAttributes.NET_PEER_IP); | ||
| } | ||
|
|
||
| // this is copied from SpanDataMapper | ||
| @Nullable | ||
| private static String getTargetFromUrl(String url) { | ||
| int schemeEndIndex = url.indexOf(':'); | ||
| if (schemeEndIndex == -1) { | ||
heyams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // not a valid url | ||
| return null; | ||
| } | ||
|
|
||
| int len = url.length(); | ||
| if (schemeEndIndex + 2 < len | ||
heyams marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| && url.charAt(schemeEndIndex + 1) == '/' | ||
| && url.charAt(schemeEndIndex + 2) == '/') { | ||
| // has authority component | ||
| // look for | ||
| // '/' - start of path | ||
| // '?' or end of string - empty path | ||
| int index; | ||
| for (index = schemeEndIndex + 3; index < len; index++) { | ||
| char c = url.charAt(index); | ||
| if (c == '/' || c == '?' || c == '#') { | ||
| break; | ||
| } | ||
| } | ||
| String target = url.substring(schemeEndIndex + 3, index); | ||
| return target.isEmpty() ? null : target; | ||
| } else { | ||
| // has no authority | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Nullable | ||
| private static <T> T getAttribute(AttributeKey<T> key, Attributes... attributesList) { | ||
| for (Attributes attributes : attributesList) { | ||
| T value = attributes.get(key); | ||
| if (value != null) { | ||
| return value; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @AutoValue | ||
| abstract static class State { | ||
|
|
||
| abstract Attributes startAttributes(); | ||
|
|
||
| abstract long startTimeNanos(); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.