From 46d4ba9cbc0a959c63cbd3ff3fc41c9028574bf5 Mon Sep 17 00:00:00 2001 From: Daniel Mitterdorfer Date: Tue, 30 Apr 2024 13:59:20 +0200 Subject: [PATCH] [Profiling] Round top-level values in TopN API (#108054) Typically double values are rounded in profiling APIs. However, we have missed `self_annual_co2_tons` and `self_annual_cost_usd` in the TopN functions API. With this commit we also round these two values according to the existing convention. Relates elastic/kibana#182001 --- .../action/GetTopNFunctionsResponse.java | 4 +- .../action/GetTopNFunctionsResponseTests.java | 95 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponse.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponse.java index a42e64546058c..4ee496dcb2870 100644 --- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponse.java +++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponse.java @@ -67,8 +67,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject(); builder.field("self_count", selfCount); builder.field("total_count", totalCount); - builder.field("self_annual_co2_tons", annualCo2Tons); - builder.field("self_annual_cost_usd", annualCostsUsd); + builder.field("self_annual_co2_tons").rawValue(NumberUtils.doubleToString(annualCo2Tons)); + builder.field("self_annual_cost_usd").rawValue(NumberUtils.doubleToString(annualCostsUsd)); builder.xContentList("topn", topNFunctions); builder.endObject(); return builder; diff --git a/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java new file mode 100644 index 0000000000000..ebb3d492b024c --- /dev/null +++ b/x-pack/plugin/profiling/src/test/java/org/elasticsearch/xpack/profiling/action/GetTopNFunctionsResponseTests.java @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.profiling.action; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentType; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent; + +public class GetTopNFunctionsResponseTests extends ESTestCase { + + public void testToXContent() throws IOException { + String fileID = "6tVKI4mSYDEJ-ABAIpYXcg"; + int frameType = 1; + boolean inline = false; + int addressOrLine = 23; + String functionName = "PyDict_GetItemWithError"; + String sourceFilename = "/build/python3.9-RNBry6/python3.9-3.9.2/Objects/dictobject.c"; + int sourceLine = 1456; + String exeFilename = "python3.9"; + + String frameGroupID = FrameGroupID.create(fileID, addressOrLine, exeFilename, sourceFilename, functionName); + + XContentType contentType = randomFrom(XContentType.values()); + + // tag::noformat + XContentBuilder expectedResponse = XContentFactory.contentBuilder(contentType) + .startObject() + .field("self_count", 1) + .field("total_count", 10) + .field("self_annual_co2_tons").rawValue("2.2000") + .field("self_annual_cost_usd").rawValue("12.0000") + .startArray("topn") + .startObject() + .field("id", frameGroupID) + .field("rank", 1) + .startObject("frame") + .field("frame_type", frameType) + .field("inline", inline) + .field("address_or_line", addressOrLine) + .field("function_name", functionName) + .field("file_name", sourceFilename) + .field("line_number", sourceLine) + .field("executable_file_name", exeFilename) + .endObject() + .field("sub_groups", Map.of("basket", 7L)) + .field("self_count", 1) + .field("total_count", 10) + .field("self_annual_co2_tons").rawValue("2.2000") + .field("total_annual_co2_tons").rawValue("22.0000") + .field("self_annual_costs_usd").rawValue("12.0000") + .field("total_annual_costs_usd").rawValue("120.0000") + .endObject() + .endArray() + .endObject(); + // end::noformat + + XContentBuilder actualResponse = XContentFactory.contentBuilder(contentType); + TopNFunction topNFunction = new TopNFunction( + frameGroupID, + 1, + frameType, + inline, + addressOrLine, + functionName, + sourceFilename, + sourceLine, + exeFilename, + 1, + 10, + 2.2d, + 22.0d, + 12.0d, + 120.0d, + Map.of("basket", 7L) + ); + GetTopNFunctionsResponse response = new GetTopNFunctionsResponse(1, 10, 2.2d, 12.0d, List.of(topNFunction)); + response.toXContent(actualResponse, ToXContent.EMPTY_PARAMS); + + assertToXContentEquivalent(BytesReference.bytes(expectedResponse), BytesReference.bytes(actualResponse), contentType); + } +}