diff --git a/api/swagger.yml b/api/swagger.yml index 87163987f6d..0686bb56c76 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1138,6 +1138,33 @@ components: description: The id of the created metarange type: string + StatsEvent: + type: object + properties: + class: + description: stats event class (e.g. "s3_gateway", "openapi_request", "experimental-feature", "ui-event") + type: string + name: + description: stats event name (e.g. "put_object", "create_repository", "") + type: string + count: + description: number of events of the class and name + type: integer + required: + - class + - name + - count + + StatsEventsList: + type: object + required: + - events + properties: + events: + type: array + items: + $ref: "#/components/schemas/StatsEvent" + paths: /setup_comm_prefs: post: @@ -3783,3 +3810,29 @@ paths: $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/ServerError" + + /statistics: + post: + tags: + - statistics + operationId: postStatsEvents + summary: post stats events, this endpoint is meant for internal use only + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/StatsEventsList" + responses: + 204: + description: reported successfully + 400: + description: bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + $ref: "#/components/responses/Unauthorized" + default: + $ref: "#/components/responses/ServerError" diff --git a/clients/java/README.md b/clients/java/README.md index 5b3ae1890da..38f33e06ae2 100644 --- a/clients/java/README.md +++ b/clients/java/README.md @@ -215,6 +215,7 @@ Class | Method | HTTP request | Description *RetentionApi* | [**setGarbageCollectionRules**](docs/RetentionApi.md#setGarbageCollectionRules) | **POST** /repositories/{repository}/gc/rules | *StagingApi* | [**getPhysicalAddress**](docs/StagingApi.md#getPhysicalAddress) | **GET** /repositories/{repository}/branches/{branch}/staging/backing | get a physical address and a return token to write object to underlying storage *StagingApi* | [**linkPhysicalAddress**](docs/StagingApi.md#linkPhysicalAddress) | **PUT** /repositories/{repository}/branches/{branch}/staging/backing | associate staging on this physical address with a path +*StatisticsApi* | [**postStatsEvents**](docs/StatisticsApi.md#postStatsEvents) | **POST** /statistics | post stats events, this endpoint is meant for internal use only *TagsApi* | [**createTag**](docs/TagsApi.md#createTag) | **POST** /repositories/{repository}/tags | create tag *TagsApi* | [**deleteTag**](docs/TagsApi.md#deleteTag) | **DELETE** /repositories/{repository}/tags/{tag} | delete tag *TagsApi* | [**getTag**](docs/TagsApi.md#getTag) | **GET** /repositories/{repository}/tags/{tag} | get tag @@ -288,6 +289,8 @@ Class | Method | HTTP request | Description - [StagingLocation](docs/StagingLocation.md) - [StagingMetadata](docs/StagingMetadata.md) - [Statement](docs/Statement.md) + - [StatsEvent](docs/StatsEvent.md) + - [StatsEventsList](docs/StatsEventsList.md) - [StorageConfig](docs/StorageConfig.md) - [StorageURI](docs/StorageURI.md) - [TagCreation](docs/TagCreation.md) diff --git a/clients/java/api/openapi.yaml b/clients/java/api/openapi.yaml index d8d9f052d10..73c2a5f0de5 100644 --- a/clients/java/api/openapi.yaml +++ b/clients/java/api/openapi.yaml @@ -4719,6 +4719,41 @@ paths: tags: - templates x-accepts: application/json + /statistics: + post: + operationId: postStatsEvents + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StatsEventsList' + required: true + responses: + "204": + description: reported successfully + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: bad request + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Unauthorized + default: + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Internal Server Error + summary: post stats events, this endpoint is meant for internal use only + tags: + - statistics + x-contentType: application/json + x-accepts: application/json components: parameters: PaginationPrefix: @@ -6285,6 +6320,44 @@ components: description: The id of the created metarange type: string type: object + StatsEvent: + example: + name: name + count: 0 + class: class + properties: + class: + description: stats event class (e.g. "s3_gateway", "openapi_request", "experimental-feature", + "ui-event") + type: string + name: + description: stats event name (e.g. "put_object", "create_repository", "") + type: string + count: + description: number of events of the class and name + type: integer + required: + - class + - count + - name + type: object + StatsEventsList: + example: + events: + - name: name + count: 0 + class: class + - name: name + count: 0 + class: class + properties: + events: + items: + $ref: '#/components/schemas/StatsEvent' + type: array + required: + - events + type: object inline_object: properties: content: diff --git a/clients/java/docs/StatisticsApi.md b/clients/java/docs/StatisticsApi.md new file mode 100644 index 00000000000..f976d3ccc7d --- /dev/null +++ b/clients/java/docs/StatisticsApi.md @@ -0,0 +1,93 @@ +# StatisticsApi + +All URIs are relative to *http://localhost/api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**postStatsEvents**](StatisticsApi.md#postStatsEvents) | **POST** /statistics | post stats events, this endpoint is meant for internal use only + + + +# **postStatsEvents** +> postStatsEvents(statsEventsList) + +post stats events, this endpoint is meant for internal use only + +### Example +```java +// Import classes: +import io.lakefs.clients.api.ApiClient; +import io.lakefs.clients.api.ApiException; +import io.lakefs.clients.api.Configuration; +import io.lakefs.clients.api.auth.*; +import io.lakefs.clients.api.models.*; +import io.lakefs.clients.api.StatisticsApi; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath("http://localhost/api/v1"); + + // Configure HTTP basic authorization: basic_auth + HttpBasicAuth basic_auth = (HttpBasicAuth) defaultClient.getAuthentication("basic_auth"); + basic_auth.setUsername("YOUR USERNAME"); + basic_auth.setPassword("YOUR PASSWORD"); + + // Configure API key authorization: cookie_auth + ApiKeyAuth cookie_auth = (ApiKeyAuth) defaultClient.getAuthentication("cookie_auth"); + cookie_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //cookie_auth.setApiKeyPrefix("Token"); + + // Configure HTTP bearer authorization: jwt_token + HttpBearerAuth jwt_token = (HttpBearerAuth) defaultClient.getAuthentication("jwt_token"); + jwt_token.setBearerToken("BEARER TOKEN"); + + // Configure API key authorization: oidc_auth + ApiKeyAuth oidc_auth = (ApiKeyAuth) defaultClient.getAuthentication("oidc_auth"); + oidc_auth.setApiKey("YOUR API KEY"); + // Uncomment the following line to set a prefix for the API key, e.g. "Token" (defaults to null) + //oidc_auth.setApiKeyPrefix("Token"); + + StatisticsApi apiInstance = new StatisticsApi(defaultClient); + StatsEventsList statsEventsList = new StatsEventsList(); // StatsEventsList | + try { + apiInstance.postStatsEvents(statsEventsList); + } catch (ApiException e) { + System.err.println("Exception when calling StatisticsApi#postStatsEvents"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **statsEventsList** | [**StatsEventsList**](StatsEventsList.md)| | + +### Return type + +null (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [jwt_token](../README.md#jwt_token), [oidc_auth](../README.md#oidc_auth) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | reported successfully | - | +**400** | bad request | - | +**401** | Unauthorized | - | +**0** | Internal Server Error | - | + diff --git a/clients/java/docs/StatsEvent.md b/clients/java/docs/StatsEvent.md new file mode 100644 index 00000000000..c1de16909c5 --- /dev/null +++ b/clients/java/docs/StatsEvent.md @@ -0,0 +1,15 @@ + + +# StatsEvent + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**propertyClass** | **String** | stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\") | +**name** | **String** | stats event name (e.g. \"put_object\", \"create_repository\", \"<experimental-feature-name>\") | +**count** | **Integer** | number of events of the class and name | + + + diff --git a/clients/java/docs/StatsEventsList.md b/clients/java/docs/StatsEventsList.md new file mode 100644 index 00000000000..331b12f6df6 --- /dev/null +++ b/clients/java/docs/StatsEventsList.md @@ -0,0 +1,13 @@ + + +# StatsEventsList + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**events** | [**List<StatsEvent>**](StatsEvent.md) | | + + + diff --git a/clients/java/src/main/java/io/lakefs/clients/api/StatisticsApi.java b/clients/java/src/main/java/io/lakefs/clients/api/StatisticsApi.java new file mode 100644 index 00000000000..d7c26b9a90f --- /dev/null +++ b/clients/java/src/main/java/io/lakefs/clients/api/StatisticsApi.java @@ -0,0 +1,177 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api; + +import io.lakefs.clients.api.ApiCallback; +import io.lakefs.clients.api.ApiClient; +import io.lakefs.clients.api.ApiException; +import io.lakefs.clients.api.ApiResponse; +import io.lakefs.clients.api.Configuration; +import io.lakefs.clients.api.Pair; +import io.lakefs.clients.api.ProgressRequestBody; +import io.lakefs.clients.api.ProgressResponseBody; + +import com.google.gson.reflect.TypeToken; + +import java.io.IOException; + + +import io.lakefs.clients.api.model.Error; +import io.lakefs.clients.api.model.StatsEventsList; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class StatisticsApi { + private ApiClient localVarApiClient; + + public StatisticsApi() { + this(Configuration.getDefaultApiClient()); + } + + public StatisticsApi(ApiClient apiClient) { + this.localVarApiClient = apiClient; + } + + public ApiClient getApiClient() { + return localVarApiClient; + } + + public void setApiClient(ApiClient apiClient) { + this.localVarApiClient = apiClient; + } + + /** + * Build call for postStatsEvents + * @param statsEventsList (required) + * @param _callback Callback for upload/download progress + * @return Call to execute + * @throws ApiException If fail to serialize the request body object + * @http.response.details + + + + + + +
Status Code Description Response Headers
204 reported successfully -
400 bad request -
401 Unauthorized -
0 Internal Server Error -
+ */ + public okhttp3.Call postStatsEventsCall(StatsEventsList statsEventsList, final ApiCallback _callback) throws ApiException { + Object localVarPostBody = statsEventsList; + + // create path and map variables + String localVarPath = "/statistics"; + + List localVarQueryParams = new ArrayList(); + List localVarCollectionQueryParams = new ArrayList(); + Map localVarHeaderParams = new HashMap(); + Map localVarCookieParams = new HashMap(); + Map localVarFormParams = new HashMap(); + + final String[] localVarAccepts = { + "application/json" + }; + final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts); + if (localVarAccept != null) { + localVarHeaderParams.put("Accept", localVarAccept); + } + + final String[] localVarContentTypes = { + "application/json" + }; + final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes); + localVarHeaderParams.put("Content-Type", localVarContentType); + + String[] localVarAuthNames = new String[] { "basic_auth", "cookie_auth", "jwt_token", "oidc_auth" }; + return localVarApiClient.buildCall(localVarPath, "POST", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback); + } + + @SuppressWarnings("rawtypes") + private okhttp3.Call postStatsEventsValidateBeforeCall(StatsEventsList statsEventsList, final ApiCallback _callback) throws ApiException { + + // verify the required parameter 'statsEventsList' is set + if (statsEventsList == null) { + throw new ApiException("Missing the required parameter 'statsEventsList' when calling postStatsEvents(Async)"); + } + + + okhttp3.Call localVarCall = postStatsEventsCall(statsEventsList, _callback); + return localVarCall; + + } + + /** + * post stats events, this endpoint is meant for internal use only + * + * @param statsEventsList (required) + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + +
Status Code Description Response Headers
204 reported successfully -
400 bad request -
401 Unauthorized -
0 Internal Server Error -
+ */ + public void postStatsEvents(StatsEventsList statsEventsList) throws ApiException { + postStatsEventsWithHttpInfo(statsEventsList); + } + + /** + * post stats events, this endpoint is meant for internal use only + * + * @param statsEventsList (required) + * @return ApiResponse<Void> + * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body + * @http.response.details + + + + + + +
Status Code Description Response Headers
204 reported successfully -
400 bad request -
401 Unauthorized -
0 Internal Server Error -
+ */ + public ApiResponse postStatsEventsWithHttpInfo(StatsEventsList statsEventsList) throws ApiException { + okhttp3.Call localVarCall = postStatsEventsValidateBeforeCall(statsEventsList, null); + return localVarApiClient.execute(localVarCall); + } + + /** + * post stats events, this endpoint is meant for internal use only (asynchronously) + * + * @param statsEventsList (required) + * @param _callback The callback to be executed when the API call finishes + * @return The request call + * @throws ApiException If fail to process the API call, e.g. serializing the request body object + * @http.response.details + + + + + + +
Status Code Description Response Headers
204 reported successfully -
400 bad request -
401 Unauthorized -
0 Internal Server Error -
+ */ + public okhttp3.Call postStatsEventsAsync(StatsEventsList statsEventsList, final ApiCallback _callback) throws ApiException { + + okhttp3.Call localVarCall = postStatsEventsValidateBeforeCall(statsEventsList, _callback); + localVarApiClient.executeAsync(localVarCall, _callback); + return localVarCall; + } +} diff --git a/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEvent.java b/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEvent.java new file mode 100644 index 00000000000..e5a581c62c9 --- /dev/null +++ b/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEvent.java @@ -0,0 +1,156 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; + +/** + * StatsEvent + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class StatsEvent { + public static final String SERIALIZED_NAME_PROPERTY_CLASS = "class"; + @SerializedName(SERIALIZED_NAME_PROPERTY_CLASS) + private String propertyClass; + + public static final String SERIALIZED_NAME_NAME = "name"; + @SerializedName(SERIALIZED_NAME_NAME) + private String name; + + public static final String SERIALIZED_NAME_COUNT = "count"; + @SerializedName(SERIALIZED_NAME_COUNT) + private Integer count; + + + public StatsEvent propertyClass(String propertyClass) { + + this.propertyClass = propertyClass; + return this; + } + + /** + * stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\") + * @return propertyClass + **/ + @javax.annotation.Nonnull + @ApiModelProperty(required = true, value = "stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\")") + + public String getPropertyClass() { + return propertyClass; + } + + + public void setPropertyClass(String propertyClass) { + this.propertyClass = propertyClass; + } + + + public StatsEvent name(String name) { + + this.name = name; + return this; + } + + /** + * stats event name (e.g. \"put_object\", \"create_repository\", \"<experimental-feature-name>\") + * @return name + **/ + @javax.annotation.Nonnull + @ApiModelProperty(required = true, value = "stats event name (e.g. \"put_object\", \"create_repository\", \"\")") + + public String getName() { + return name; + } + + + public void setName(String name) { + this.name = name; + } + + + public StatsEvent count(Integer count) { + + this.count = count; + return this; + } + + /** + * number of events of the class and name + * @return count + **/ + @javax.annotation.Nonnull + @ApiModelProperty(required = true, value = "number of events of the class and name") + + public Integer getCount() { + return count; + } + + + public void setCount(Integer count) { + this.count = count; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StatsEvent statsEvent = (StatsEvent) o; + return Objects.equals(this.propertyClass, statsEvent.propertyClass) && + Objects.equals(this.name, statsEvent.name) && + Objects.equals(this.count, statsEvent.count); + } + + @Override + public int hashCode() { + return Objects.hash(propertyClass, name, count); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StatsEvent {\n"); + sb.append(" propertyClass: ").append(toIndentedString(propertyClass)).append("\n"); + sb.append(" name: ").append(toIndentedString(name)).append("\n"); + sb.append(" count: ").append(toIndentedString(count)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEventsList.java b/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEventsList.java new file mode 100644 index 00000000000..2f8619b09f3 --- /dev/null +++ b/clients/java/src/main/java/io/lakefs/clients/api/model/StatsEventsList.java @@ -0,0 +1,106 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api.model; + +import java.util.Objects; +import java.util.Arrays; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.lakefs.clients.api.model.StatsEvent; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * StatsEventsList + */ +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen") +public class StatsEventsList { + public static final String SERIALIZED_NAME_EVENTS = "events"; + @SerializedName(SERIALIZED_NAME_EVENTS) + private List events = new ArrayList(); + + + public StatsEventsList events(List events) { + + this.events = events; + return this; + } + + public StatsEventsList addEventsItem(StatsEvent eventsItem) { + this.events.add(eventsItem); + return this; + } + + /** + * Get events + * @return events + **/ + @javax.annotation.Nonnull + @ApiModelProperty(required = true, value = "") + + public List getEvents() { + return events; + } + + + public void setEvents(List events) { + this.events = events; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StatsEventsList statsEventsList = (StatsEventsList) o; + return Objects.equals(this.events, statsEventsList.events); + } + + @Override + public int hashCode() { + return Objects.hash(events); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class StatsEventsList {\n"); + sb.append(" events: ").append(toIndentedString(events)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces + * (except the first line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + +} + diff --git a/clients/java/src/test/java/io/lakefs/clients/api/StatisticsApiTest.java b/clients/java/src/test/java/io/lakefs/clients/api/StatisticsApiTest.java new file mode 100644 index 00000000000..d5628a0b809 --- /dev/null +++ b/clients/java/src/test/java/io/lakefs/clients/api/StatisticsApiTest.java @@ -0,0 +1,51 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api; + +import io.lakefs.clients.api.ApiException; +import io.lakefs.clients.api.model.Error; +import io.lakefs.clients.api.model.StatsEventsList; +import org.junit.Test; +import org.junit.Ignore; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * API tests for StatisticsApi + */ +@Ignore +public class StatisticsApiTest { + + private final StatisticsApi api = new StatisticsApi(); + + + /** + * post stats events, this endpoint is meant for internal use only + * + * + * + * @throws ApiException + * if the Api call fails + */ + @Test + public void postStatsEventsTest() throws ApiException { + StatsEventsList statsEventsList = null; + api.postStatsEvents(statsEventsList); + // TODO: test validations + } + +} diff --git a/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventTest.java b/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventTest.java new file mode 100644 index 00000000000..3e8ce547650 --- /dev/null +++ b/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventTest.java @@ -0,0 +1,67 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Model tests for StatsEvent + */ +public class StatsEventTest { + private final StatsEvent model = new StatsEvent(); + + /** + * Model tests for StatsEvent + */ + @Test + public void testStatsEvent() { + // TODO: test StatsEvent + } + + /** + * Test the property 'propertyClass' + */ + @Test + public void propertyClassTest() { + // TODO: test propertyClass + } + + /** + * Test the property 'name' + */ + @Test + public void nameTest() { + // TODO: test name + } + + /** + * Test the property 'count' + */ + @Test + public void countTest() { + // TODO: test count + } + +} diff --git a/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventsListTest.java b/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventsListTest.java new file mode 100644 index 00000000000..b3b59d0fdff --- /dev/null +++ b/clients/java/src/test/java/io/lakefs/clients/api/model/StatsEventsListTest.java @@ -0,0 +1,54 @@ +/* + * lakeFS API + * lakeFS HTTP API + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +package io.lakefs.clients.api.model; + +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.lakefs.clients.api.model.StatsEvent; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * Model tests for StatsEventsList + */ +public class StatsEventsListTest { + private final StatsEventsList model = new StatsEventsList(); + + /** + * Model tests for StatsEventsList + */ + @Test + public void testStatsEventsList() { + // TODO: test StatsEventsList + } + + /** + * Test the property 'events' + */ + @Test + public void eventsTest() { + // TODO: test events + } + +} diff --git a/clients/python/.openapi-generator/FILES b/clients/python/.openapi-generator/FILES index 653e3e5c28d..17868dcd004 100644 --- a/clients/python/.openapi-generator/FILES +++ b/clients/python/.openapi-generator/FILES @@ -79,6 +79,9 @@ docs/StagingApi.md docs/StagingLocation.md docs/StagingMetadata.md docs/Statement.md +docs/StatisticsApi.md +docs/StatsEvent.md +docs/StatsEventsList.md docs/StorageConfig.md docs/StorageURI.md docs/TagCreation.md @@ -105,6 +108,7 @@ lakefs_client/api/refs_api.py lakefs_client/api/repositories_api.py lakefs_client/api/retention_api.py lakefs_client/api/staging_api.py +lakefs_client/api/statistics_api.py lakefs_client/api/tags_api.py lakefs_client/api/templates_api.py lakefs_client/api_client.py @@ -177,6 +181,8 @@ lakefs_client/model/stage_range_creation.py lakefs_client/model/staging_location.py lakefs_client/model/staging_metadata.py lakefs_client/model/statement.py +lakefs_client/model/stats_event.py +lakefs_client/model/stats_events_list.py lakefs_client/model/storage_config.py lakefs_client/model/storage_uri.py lakefs_client/model/tag_creation.py @@ -272,6 +278,9 @@ test/test_staging_api.py test/test_staging_location.py test/test_staging_metadata.py test/test_statement.py +test/test_statistics_api.py +test/test_stats_event.py +test/test_stats_events_list.py test/test_storage_config.py test/test_storage_uri.py test/test_tag_creation.py diff --git a/clients/python/README.md b/clients/python/README.md index 779404a8bcf..9d641ff5d3a 100644 --- a/clients/python/README.md +++ b/clients/python/README.md @@ -196,6 +196,7 @@ Class | Method | HTTP request | Description *RetentionApi* | [**set_garbage_collection_rules**](docs/RetentionApi.md#set_garbage_collection_rules) | **POST** /repositories/{repository}/gc/rules | *StagingApi* | [**get_physical_address**](docs/StagingApi.md#get_physical_address) | **GET** /repositories/{repository}/branches/{branch}/staging/backing | get a physical address and a return token to write object to underlying storage *StagingApi* | [**link_physical_address**](docs/StagingApi.md#link_physical_address) | **PUT** /repositories/{repository}/branches/{branch}/staging/backing | associate staging on this physical address with a path +*StatisticsApi* | [**post_stats_events**](docs/StatisticsApi.md#post_stats_events) | **POST** /statistics | post stats events, this endpoint is meant for internal use only *TagsApi* | [**create_tag**](docs/TagsApi.md#create_tag) | **POST** /repositories/{repository}/tags | create tag *TagsApi* | [**delete_tag**](docs/TagsApi.md#delete_tag) | **DELETE** /repositories/{repository}/tags/{tag} | delete tag *TagsApi* | [**get_tag**](docs/TagsApi.md#get_tag) | **GET** /repositories/{repository}/tags/{tag} | get tag @@ -270,6 +271,8 @@ Class | Method | HTTP request | Description - [StagingLocation](docs/StagingLocation.md) - [StagingMetadata](docs/StagingMetadata.md) - [Statement](docs/Statement.md) + - [StatsEvent](docs/StatsEvent.md) + - [StatsEventsList](docs/StatsEventsList.md) - [StorageConfig](docs/StorageConfig.md) - [StorageURI](docs/StorageURI.md) - [TagCreation](docs/TagCreation.md) diff --git a/clients/python/docs/StatisticsApi.md b/clients/python/docs/StatisticsApi.md new file mode 100644 index 00000000000..d699fcdd660 --- /dev/null +++ b/clients/python/docs/StatisticsApi.md @@ -0,0 +1,116 @@ +# lakefs_client.StatisticsApi + +All URIs are relative to *http://localhost/api/v1* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**post_stats_events**](StatisticsApi.md#post_stats_events) | **POST** /statistics | post stats events, this endpoint is meant for internal use only + + +# **post_stats_events** +> post_stats_events(stats_events_list) + +post stats events, this endpoint is meant for internal use only + +### Example + +* Basic Authentication (basic_auth): +* Api Key Authentication (cookie_auth): +* Bearer (JWT) Authentication (jwt_token): +* Api Key Authentication (oidc_auth): + +```python +import time +import lakefs_client +from lakefs_client.api import statistics_api +from lakefs_client.model.error import Error +from lakefs_client.model.stats_events_list import StatsEventsList +from pprint import pprint +# Defining the host is optional and defaults to http://localhost/api/v1 +# See configuration.py for a list of all supported configuration parameters. +configuration = lakefs_client.Configuration( + host = "http://localhost/api/v1" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure HTTP basic authorization: basic_auth +configuration = lakefs_client.Configuration( + username = 'YOUR_USERNAME', + password = 'YOUR_PASSWORD' +) + +# Configure API key authorization: cookie_auth +configuration.api_key['cookie_auth'] = 'YOUR_API_KEY' + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['cookie_auth'] = 'Bearer' + +# Configure Bearer authorization (JWT): jwt_token +configuration = lakefs_client.Configuration( + access_token = 'YOUR_BEARER_TOKEN' +) + +# Configure API key authorization: oidc_auth +configuration.api_key['oidc_auth'] = 'YOUR_API_KEY' + +# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed +# configuration.api_key_prefix['oidc_auth'] = 'Bearer' + +# Enter a context with an instance of the API client +with lakefs_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = statistics_api.StatisticsApi(api_client) + stats_events_list = StatsEventsList( + events=[ + StatsEvent( + _class="_class_example", + name="name_example", + count=1, + ), + ], + ) # StatsEventsList | + + # example passing only required values which don't have defaults set + try: + # post stats events, this endpoint is meant for internal use only + api_instance.post_stats_events(stats_events_list) + except lakefs_client.ApiException as e: + print("Exception when calling StatisticsApi->post_stats_events: %s\n" % e) +``` + + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **stats_events_list** | [**StatsEventsList**](StatsEventsList.md)| | + +### Return type + +void (empty response body) + +### Authorization + +[basic_auth](../README.md#basic_auth), [cookie_auth](../README.md#cookie_auth), [jwt_token](../README.md#jwt_token), [oidc_auth](../README.md#oidc_auth) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**204** | reported successfully | - | +**400** | bad request | - | +**401** | Unauthorized | - | +**0** | Internal Server Error | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/clients/python/docs/StatsEvent.md b/clients/python/docs/StatsEvent.md new file mode 100644 index 00000000000..2f313005d4a --- /dev/null +++ b/clients/python/docs/StatsEvent.md @@ -0,0 +1,14 @@ +# StatsEvent + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**_class** | **str** | stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\") | +**name** | **str** | stats event name (e.g. \"put_object\", \"create_repository\", \"<experimental-feature-name>\") | +**count** | **int** | number of events of the class and name | +**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/clients/python/docs/StatsEventsList.md b/clients/python/docs/StatsEventsList.md new file mode 100644 index 00000000000..3f5a230b766 --- /dev/null +++ b/clients/python/docs/StatsEventsList.md @@ -0,0 +1,12 @@ +# StatsEventsList + + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**events** | [**[StatsEvent]**](StatsEvent.md) | | +**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/clients/python/lakefs_client/api/statistics_api.py b/clients/python/lakefs_client/api/statistics_api.py new file mode 100644 index 00000000000..89d1a3f3f4a --- /dev/null +++ b/clients/python/lakefs_client/api/statistics_api.py @@ -0,0 +1,160 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from lakefs_client.api_client import ApiClient, Endpoint as _Endpoint +from lakefs_client.model_utils import ( # noqa: F401 + check_allowed_values, + check_validations, + date, + datetime, + file_type, + none_type, + validate_and_convert_types +) +from lakefs_client.model.error import Error +from lakefs_client.model.stats_events_list import StatsEventsList + + +class StatisticsApi(object): + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None): + if api_client is None: + api_client = ApiClient() + self.api_client = api_client + self.post_stats_events_endpoint = _Endpoint( + settings={ + 'response_type': None, + 'auth': [ + 'basic_auth', + 'cookie_auth', + 'jwt_token', + 'oidc_auth' + ], + 'endpoint_path': '/statistics', + 'operation_id': 'post_stats_events', + 'http_method': 'POST', + 'servers': None, + }, + params_map={ + 'all': [ + 'stats_events_list', + ], + 'required': [ + 'stats_events_list', + ], + 'nullable': [ + ], + 'enum': [ + ], + 'validation': [ + ] + }, + root_map={ + 'validations': { + }, + 'allowed_values': { + }, + 'openapi_types': { + 'stats_events_list': + (StatsEventsList,), + }, + 'attribute_map': { + }, + 'location_map': { + 'stats_events_list': 'body', + }, + 'collection_format_map': { + } + }, + headers_map={ + 'accept': [ + 'application/json' + ], + 'content_type': [ + 'application/json' + ] + }, + api_client=api_client + ) + + def post_stats_events( + self, + stats_events_list, + **kwargs + ): + """post stats events, this endpoint is meant for internal use only # noqa: E501 + + This method makes a synchronous HTTP request by default. To make an + asynchronous HTTP request, please pass async_req=True + + >>> thread = api.post_stats_events(stats_events_list, async_req=True) + >>> result = thread.get() + + Args: + stats_events_list (StatsEventsList): + + Keyword Args: + _return_http_data_only (bool): response data without head status + code and headers. Default is True. + _preload_content (bool): if False, the urllib3.HTTPResponse object + will be returned without reading/decoding response data. + Default is True. + _request_timeout (int/float/tuple): timeout setting for this request. If + one number provided, it will be total request timeout. It can also + be a pair (tuple) of (connection, read) timeouts. + Default is None. + _check_input_type (bool): specifies if type checking + should be done one the data sent to the server. + Default is True. + _check_return_type (bool): specifies if type checking + should be done one the data received from the server. + Default is True. + _host_index (int/None): specifies the index of the server + that we want to use. + Default is read from the configuration. + async_req (bool): execute request asynchronously + + Returns: + None + If the method is called asynchronously, returns the request + thread. + """ + kwargs['async_req'] = kwargs.get( + 'async_req', False + ) + kwargs['_return_http_data_only'] = kwargs.get( + '_return_http_data_only', True + ) + kwargs['_preload_content'] = kwargs.get( + '_preload_content', True + ) + kwargs['_request_timeout'] = kwargs.get( + '_request_timeout', None + ) + kwargs['_check_input_type'] = kwargs.get( + '_check_input_type', True + ) + kwargs['_check_return_type'] = kwargs.get( + '_check_return_type', True + ) + kwargs['_host_index'] = kwargs.get('_host_index') + kwargs['stats_events_list'] = \ + stats_events_list + return self.post_stats_events_endpoint.call_with_http_info(**kwargs) + diff --git a/clients/python/lakefs_client/apis/__init__.py b/clients/python/lakefs_client/apis/__init__.py index eafb7c84b2f..457b10bf695 100644 --- a/clients/python/lakefs_client/apis/__init__.py +++ b/clients/python/lakefs_client/apis/__init__.py @@ -27,5 +27,6 @@ from lakefs_client.api.repositories_api import RepositoriesApi from lakefs_client.api.retention_api import RetentionApi from lakefs_client.api.staging_api import StagingApi +from lakefs_client.api.statistics_api import StatisticsApi from lakefs_client.api.tags_api import TagsApi from lakefs_client.api.templates_api import TemplatesApi diff --git a/clients/python/lakefs_client/model/stats_event.py b/clients/python/lakefs_client/model/stats_event.py new file mode 100644 index 00000000000..90d9316c1c5 --- /dev/null +++ b/clients/python/lakefs_client/model/stats_event.py @@ -0,0 +1,274 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from lakefs_client.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) +from ..model_utils import OpenApiModel +from lakefs_client.exceptions import ApiAttributeError + + + +class StatsEvent(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + return { + '_class': (str,), # noqa: E501 + 'name': (str,), # noqa: E501 + 'count': (int,), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + '_class': 'class', # noqa: E501 + 'name': 'name', # noqa: E501 + 'count': 'count', # noqa: E501 + } + + read_only_vars = { + } + + _composed_schemas = {} + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, _class, name, count, *args, **kwargs): # noqa: E501 + """StatsEvent - a model defined in OpenAPI + + Args: + _class (str): stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\") + name (str): stats event name (e.g. \"put_object\", \"create_repository\", \"\") + count (int): number of events of the class and name + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self._class = _class + self.name = name + self.count = count + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, _class, name, count, *args, **kwargs): # noqa: E501 + """StatsEvent - a model defined in OpenAPI + + Args: + _class (str): stats event class (e.g. \"s3_gateway\", \"openapi_request\", \"experimental-feature\", \"ui-event\") + name (str): stats event name (e.g. \"put_object\", \"create_repository\", \"\") + count (int): number of events of the class and name + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self._class = _class + self.name = name + self.count = count + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") diff --git a/clients/python/lakefs_client/model/stats_events_list.py b/clients/python/lakefs_client/model/stats_events_list.py new file mode 100644 index 00000000000..e22ac5ed721 --- /dev/null +++ b/clients/python/lakefs_client/model/stats_events_list.py @@ -0,0 +1,268 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import re # noqa: F401 +import sys # noqa: F401 + +from lakefs_client.model_utils import ( # noqa: F401 + ApiTypeError, + ModelComposed, + ModelNormal, + ModelSimple, + cached_property, + change_keys_js_to_python, + convert_js_args_to_python_args, + date, + datetime, + file_type, + none_type, + validate_get_composed_info, +) +from ..model_utils import OpenApiModel +from lakefs_client.exceptions import ApiAttributeError + + +def lazy_import(): + from lakefs_client.model.stats_event import StatsEvent + globals()['StatsEvent'] = StatsEvent + + +class StatsEventsList(ModelNormal): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + + Attributes: + allowed_values (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + with a capitalized key describing the allowed value and an allowed + value. These dicts store the allowed enum values. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + discriminator_value_class_map (dict): A dict to go from the discriminator + variable value to the discriminator class name. + validations (dict): The key is the tuple path to the attribute + and the for var_name this is (var_name,). The value is a dict + that stores validations for max_length, min_length, max_items, + min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum, + inclusive_minimum, and regex. + additional_properties_type (tuple): A tuple of classes accepted + as additional properties values. + """ + + allowed_values = { + } + + validations = { + } + + @cached_property + def additional_properties_type(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + """ + lazy_import() + return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501 + + _nullable = False + + @cached_property + def openapi_types(): + """ + This must be a method because a model may have properties that are + of type self, this must run after the class is loaded + + Returns + openapi_types (dict): The key is attribute name + and the value is attribute type. + """ + lazy_import() + return { + 'events': ([StatsEvent],), # noqa: E501 + } + + @cached_property + def discriminator(): + return None + + + attribute_map = { + 'events': 'events', # noqa: E501 + } + + read_only_vars = { + } + + _composed_schemas = {} + + @classmethod + @convert_js_args_to_python_args + def _from_openapi_data(cls, events, *args, **kwargs): # noqa: E501 + """StatsEventsList - a model defined in OpenAPI + + Args: + events ([StatsEvent]): + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + self = super(OpenApiModel, cls).__new__(cls) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.events = events + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + return self + + required_properties = set([ + '_data_store', + '_check_type', + '_spec_property_naming', + '_path_to_item', + '_configuration', + '_visited_composed_classes', + ]) + + @convert_js_args_to_python_args + def __init__(self, events, *args, **kwargs): # noqa: E501 + """StatsEventsList - a model defined in OpenAPI + + Args: + events ([StatsEvent]): + + Keyword Args: + _check_type (bool): if True, values for parameters in openapi_types + will be type checked and a TypeError will be + raised if the wrong type is input. + Defaults to True + _path_to_item (tuple/list): This is a list of keys or values to + drill down to the model in received_data + when deserializing a response + _spec_property_naming (bool): True if the variable names in the input data + are serialized names, as specified in the OpenAPI document. + False if the variable names in the input data + are pythonic names, e.g. snake case (default) + _configuration (Configuration): the instance to use when + deserializing a file_type parameter. + If passed, type conversion is attempted + If omitted no type conversion is done. + _visited_composed_classes (tuple): This stores a tuple of + classes that we have traveled through so that + if we see that class again we will not use its + discriminator again. + When traveling through a discriminator, the + composed schema that is + is traveled through is added to this set. + For example if Animal has a discriminator + petType and we pass in "Dog", and the class Dog + allOf includes Animal, we move through Animal + once using the discriminator, and pick Dog. + Then in Dog, we will make an instance of the + Animal class but this time we won't travel + through its discriminator because we passed in + _visited_composed_classes = (Animal,) + """ + + _check_type = kwargs.pop('_check_type', True) + _spec_property_naming = kwargs.pop('_spec_property_naming', False) + _path_to_item = kwargs.pop('_path_to_item', ()) + _configuration = kwargs.pop('_configuration', None) + _visited_composed_classes = kwargs.pop('_visited_composed_classes', ()) + + if args: + raise ApiTypeError( + "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % ( + args, + self.__class__.__name__, + ), + path_to_item=_path_to_item, + valid_classes=(self.__class__,), + ) + + self._data_store = {} + self._check_type = _check_type + self._spec_property_naming = _spec_property_naming + self._path_to_item = _path_to_item + self._configuration = _configuration + self._visited_composed_classes = _visited_composed_classes + (self.__class__,) + + self.events = events + for var_name, var_value in kwargs.items(): + if var_name not in self.attribute_map and \ + self._configuration is not None and \ + self._configuration.discard_unknown_keys and \ + self.additional_properties_type is None: + # discard variable. + continue + setattr(self, var_name, var_value) + if var_name in self.read_only_vars: + raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate " + f"class with read only attributes.") diff --git a/clients/python/lakefs_client/models/__init__.py b/clients/python/lakefs_client/models/__init__.py index 2604353f3b6..fe64d9fd33b 100644 --- a/clients/python/lakefs_client/models/__init__.py +++ b/clients/python/lakefs_client/models/__init__.py @@ -74,6 +74,8 @@ from lakefs_client.model.staging_location import StagingLocation from lakefs_client.model.staging_metadata import StagingMetadata from lakefs_client.model.statement import Statement +from lakefs_client.model.stats_event import StatsEvent +from lakefs_client.model.stats_events_list import StatsEventsList from lakefs_client.model.storage_config import StorageConfig from lakefs_client.model.storage_uri import StorageURI from lakefs_client.model.tag_creation import TagCreation diff --git a/clients/python/test/test_statistics_api.py b/clients/python/test/test_statistics_api.py new file mode 100644 index 00000000000..d77a05ccf61 --- /dev/null +++ b/clients/python/test/test_statistics_api.py @@ -0,0 +1,36 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import unittest + +import lakefs_client +from lakefs_client.api.statistics_api import StatisticsApi # noqa: E501 + + +class TestStatisticsApi(unittest.TestCase): + """StatisticsApi unit test stubs""" + + def setUp(self): + self.api = StatisticsApi() # noqa: E501 + + def tearDown(self): + pass + + def test_post_stats_events(self): + """Test case for post_stats_events + + post stats events, this endpoint is meant for internal use only # noqa: E501 + """ + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/clients/python/test/test_stats_event.py b/clients/python/test/test_stats_event.py new file mode 100644 index 00000000000..472c7f27209 --- /dev/null +++ b/clients/python/test/test_stats_event.py @@ -0,0 +1,36 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import sys +import unittest + +import lakefs_client +from lakefs_client.model.stats_event import StatsEvent + + +class TestStatsEvent(unittest.TestCase): + """StatsEvent unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testStatsEvent(self): + """Test StatsEvent""" + # FIXME: construct object with mandatory attributes with example values + # model = StatsEvent() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/clients/python/test/test_stats_events_list.py b/clients/python/test/test_stats_events_list.py new file mode 100644 index 00000000000..87fb8ff9d95 --- /dev/null +++ b/clients/python/test/test_stats_events_list.py @@ -0,0 +1,38 @@ +""" + lakeFS API + + lakeFS HTTP API # noqa: E501 + + The version of the OpenAPI document: 0.1.0 + Contact: services@treeverse.io + Generated by: https://openapi-generator.tech +""" + + +import sys +import unittest + +import lakefs_client +from lakefs_client.model.stats_event import StatsEvent +globals()['StatsEvent'] = StatsEvent +from lakefs_client.model.stats_events_list import StatsEventsList + + +class TestStatsEventsList(unittest.TestCase): + """StatsEventsList unit test stubs""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def testStatsEventsList(self): + """Test StatsEventsList""" + # FIXME: construct object with mandatory attributes with example values + # model = StatsEventsList() # noqa: E501 + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/docs/assets/js/swagger.yml b/docs/assets/js/swagger.yml index 87163987f6d..0686bb56c76 100644 --- a/docs/assets/js/swagger.yml +++ b/docs/assets/js/swagger.yml @@ -1138,6 +1138,33 @@ components: description: The id of the created metarange type: string + StatsEvent: + type: object + properties: + class: + description: stats event class (e.g. "s3_gateway", "openapi_request", "experimental-feature", "ui-event") + type: string + name: + description: stats event name (e.g. "put_object", "create_repository", "") + type: string + count: + description: number of events of the class and name + type: integer + required: + - class + - name + - count + + StatsEventsList: + type: object + required: + - events + properties: + events: + type: array + items: + $ref: "#/components/schemas/StatsEvent" + paths: /setup_comm_prefs: post: @@ -3783,3 +3810,29 @@ paths: $ref: "#/components/responses/NotFound" default: $ref: "#/components/responses/ServerError" + + /statistics: + post: + tags: + - statistics + operationId: postStatsEvents + summary: post stats events, this endpoint is meant for internal use only + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/StatsEventsList" + responses: + 204: + description: reported successfully + 400: + description: bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + $ref: "#/components/responses/Unauthorized" + default: + $ref: "#/components/responses/ServerError" diff --git a/pkg/actions/service_test.go b/pkg/actions/service_test.go index e48e77e85d5..359646fd591 100644 --- a/pkg/actions/service_test.go +++ b/pkg/actions/service_test.go @@ -43,7 +43,11 @@ func NewActionStatsMockCollector() ActionStatsMockCollector { } func (c *ActionStatsMockCollector) CollectEvent(ev stats.Event) { - c.Hits[ev.Name]++ + c.CollectEvents(ev, 1) +} + +func (c *ActionStatsMockCollector) CollectEvents(ev stats.Event, count uint64) { + c.Hits[ev.Name] += int(count) } func (c *ActionStatsMockCollector) CollectMetadata(_ *stats.Metadata) {} diff --git a/pkg/api/controller.go b/pkg/api/controller.go index aace2405df4..b38245e211d 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -3612,6 +3612,7 @@ func (c *Controller) GetLakeFSVersion(w http.ResponseWriter, r *http.Request) { writeError(w, r, http.StatusUnauthorized, ErrAuthenticatingRequest) return } + // set upgrade recommended based on last security audit check var ( upgradeRecommended *bool @@ -3633,6 +3634,53 @@ func (c *Controller) GetLakeFSVersion(w http.ResponseWriter, r *http.Request) { }) } +func (c *Controller) PostStatsEvents(w http.ResponseWriter, r *http.Request, body PostStatsEventsJSONRequestBody) { + ctx := r.Context() + user, err := auth.GetUser(ctx) + if err != nil { + writeError(w, r, http.StatusUnauthorized, ErrAuthenticatingRequest) + return + } + + for _, statsEv := range body.Events { + if statsEv.Class == "" { + writeError(w, r, http.StatusBadRequest, "invalid value: class is required") + return + } + + if statsEv.Name == "" { + writeError(w, r, http.StatusBadRequest, "invalid value: name is required") + return + } + + if statsEv.Count < 0 { + writeError(w, r, http.StatusBadRequest, "invalid value: count must be a non-negative integer") + return + } + } + + client := httputil.GetRequestLakeFSClient(r) + for _, statsEv := range body.Events { + ev := stats.Event{ + Class: statsEv.Class, + Name: statsEv.Name, + UserID: user.Username, + Client: client, + } + c.Collector.CollectEvents(ev, uint64(statsEv.Count)) + + c.Logger.WithContext(ctx).WithFields(logging.Fields{ + "class": ev.Class, + "name": ev.Name, + "count": statsEv.Count, + "user_id": ev.UserID, + "client": ev.Client, + }).Debug("sending stats events") + } + + writeResponse(w, r, http.StatusNoContent, nil) +} + func IsStatusCodeOK(statusCode int) bool { return statusCode >= 200 && statusCode <= 299 } @@ -3817,6 +3865,7 @@ func (c *Controller) LogAction(ctx context.Context, action string, r *http.Reque if err != nil { ev.UserID = user.Username } + c.Logger.WithContext(ctx).WithFields(logging.Fields{ "class": ev.Class, "name": ev.Name, diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index 885a45549ac..0dd7f3dbe05 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -3500,3 +3500,216 @@ func TestController_ClientDisconnect(t *testing.T) { t.Fatalf("Metric for client request closed: %d, expected: %d", clientRequestClosedCount, expectedCount) } } + +func TestController_PostStatsEvents(t *testing.T) { + clt, deps := setupClientWithAdmin(t) + ctx := context.Background() + + type key struct { + class string + name string + } + + tests := []struct { + name string + events []api.StatsEvent + expectedEventCounts map[key]int + expectedStatusCode int + }{ + { + name: "single_event_count_1", + events: []api.StatsEvent{ + { + Class: "single_event_count_1", + Name: "name", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "single_event_count_1", name: "name"}: 1, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "single_event_count_gt_1", + events: []api.StatsEvent{ + { + Class: "single_event_count_gt_1", + Name: "name", + Count: 3, + }, + }, + expectedEventCounts: map[key]int{ + {class: "single_event_count_gt_1", name: "name"}: 3, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "multiple_events", + events: []api.StatsEvent{ + { + Class: "class_multiple_events_ev_1", + Name: "name1", + Count: 1, + }, + { + Class: "class_multiple_events_ev_2", + Name: "name2", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "class_multiple_events_ev_1", name: "name1"}: 1, + {class: "class_multiple_events_ev_2", name: "name2"}: 1, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "multiple_events_same_class", + events: []api.StatsEvent{ + { + Class: "class_multiple_events_same_class", + Name: "name1", + Count: 1, + }, + { + Class: "class_multiple_events_same_class", + Name: "name2", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "class_multiple_events_same_class", name: "name1"}: 1, + {class: "class_multiple_events_same_class", name: "name2"}: 1, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "multiple_events_same_name", + events: []api.StatsEvent{ + { + Class: "multiple_events_same_name_1", + Name: "same_name", + Count: 1, + }, + { + Class: "multiple_events_same_name_2", + Name: "same_name", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "multiple_events_same_name_1", name: "same_name"}: 1, + {class: "multiple_events_same_name_2", name: "same_name"}: 1, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "multiple_events_same_class_same_name", + events: []api.StatsEvent{ + { + Class: "multiple_events_same_class_same_name", + Name: "same_name", + Count: 1, + }, + { + Class: "multiple_events_same_class_same_name", + Name: "same_name", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "multiple_events_same_class_same_name", name: "same_name"}: 2, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "empty_usage_class", + events: []api.StatsEvent{ + { + Class: "", + Name: "name", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "", name: "name"}: 0, + }, + expectedStatusCode: http.StatusBadRequest, + }, + { + name: "empty_usage_name", + events: []api.StatsEvent{ + { + Class: "class_empty_usage_name", + Name: "", + Count: 1, + }, + }, + expectedEventCounts: map[key]int{ + {class: "class_empty_usage_name", name: ""}: 0, + }, + expectedStatusCode: http.StatusBadRequest, + }, + { + name: "zero_usage_count", + events: []api.StatsEvent{ + { + Class: "class_zero_usage_count", + Name: "name", + Count: 0, + }, + }, + expectedEventCounts: map[key]int{ + {class: "class_zero_usage_count", name: "name"}: 0, + }, + expectedStatusCode: http.StatusNoContent, + }, + { + name: "negative_usage_count", + events: []api.StatsEvent{ + { + Class: "class_negative_usage_count", + Name: "name", + Count: -23, + }, + }, + expectedEventCounts: map[key]int{ + {class: "class_negative_usage_count", name: "name"}: 0, + }, + expectedStatusCode: http.StatusBadRequest, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := clt.PostStatsEventsWithResponse(ctx, api.PostStatsEventsJSONRequestBody{ + Events: tt.events, + }) + if err != nil { + t.Fatalf("PostStatsEvents failed: %s", err) + } + + if resp.StatusCode() != tt.expectedStatusCode { + t.Fatalf("PostStatsEvents status code: %d, expected: %d", resp.StatusCode(), tt.expectedStatusCode) + } + + for _, sentEv := range tt.events { + var collectedEventsToCount = map[key]int{} + var k = key{class: sentEv.Class, name: sentEv.Name} + _, isMapContainKey := collectedEventsToCount[k] + if isMapContainKey { + continue + } + for _, collectedMetric := range deps.collector.Metrics { + if collectedMetric.Event.Class == sentEv.Class && collectedMetric.Event.Name == sentEv.Name { + collectedEventsToCount[k] += int(collectedMetric.Value) + } + } + if collectedEventsToCount[k] != tt.expectedEventCounts[k] { + t.Fatalf("PostStatsEvents events for class %s and name: %s, count: %d, expected: %d", sentEv.Class, sentEv.Name, collectedEventsToCount[k], tt.expectedEventCounts[k]) + } + } + }) + } +} diff --git a/pkg/api/serve_test.go b/pkg/api/serve_test.go index f283464c619..a7db4f35fa7 100644 --- a/pkg/api/serve_test.go +++ b/pkg/api/serve_test.go @@ -51,16 +51,20 @@ type dependencies struct { // memCollector in-memory collector stores events and metadata sent type memCollector struct { - Events []*stats.Event + Metrics []*stats.Metric Metadata []*stats.Metadata InstallationID string mu sync.Mutex } -func (m *memCollector) CollectEvent(ev stats.Event) { +func (m *memCollector) CollectEvents(ev stats.Event, count uint64) { m.mu.Lock() defer m.mu.Unlock() - m.Events = append(m.Events, &ev) + m.Metrics = append(m.Metrics, &stats.Metric{Event: ev, Value: count}) +} + +func (m *memCollector) CollectEvent(ev stats.Event) { + m.CollectEvents(ev, 1) } func (m *memCollector) CollectMetadata(metadata *stats.Metadata) { diff --git a/pkg/stats/collector.go b/pkg/stats/collector.go index 77e5dcdbba2..0bb8f9d0dd6 100644 --- a/pkg/stats/collector.go +++ b/pkg/stats/collector.go @@ -26,6 +26,7 @@ const ( type Collector interface { CollectEvent(ev Event) + CollectEvents(ev Event, count uint64) CollectMetadata(accountMetadata *Metadata) CollectCommPrefs(email, installationID string, featureUpdates, securityUpdates bool) SetInstallationID(installationID string) @@ -104,7 +105,7 @@ func (t *TimeTicker) Tick() <-chan time.Time { type BufferedCollector struct { cache keyIndex - writes chan Event + writes chan Metric sender Sender sendTimeout time.Duration flushTicker FlushTicker @@ -127,7 +128,7 @@ type BufferedCollectorOpts func(s *BufferedCollector) func WithWriteBufferSize(bufferSize int) BufferedCollectorOpts { return func(s *BufferedCollector) { - s.writes = make(chan Event, bufferSize) + s.writes = make(chan Metric, bufferSize) } } @@ -180,7 +181,7 @@ func NewBufferedCollector(installationID string, c *config.Config, opts ...Buffe } s := &BufferedCollector{ cache: make(keyIndex), - writes: make(chan Event, collectorEventBufferSize), + writes: make(chan Metric, collectorEventBufferSize), runtimeStats: map[string]string{}, flushTicker: &TimeTicker{ticker: time.NewTicker(flushDuration)}, flushSize: flushSize, @@ -218,7 +219,11 @@ func (s *BufferedCollector) getInstallationID() string { } func (s *BufferedCollector) incr(k Event) { - s.cache[k]++ + s.update(Metric{Event: k, Value: 1}) +} + +func (s *BufferedCollector) update(k Metric) { + s.cache[k.Event] += k.Value } func (s *BufferedCollector) send(metrics []Metric) { @@ -235,7 +240,7 @@ func (s *BufferedCollector) send(metrics []Metric) { }() } -func (s *BufferedCollector) CollectEvent(ev Event) { +func (s *BufferedCollector) CollectEvents(ev Event, count uint64) { if s.isCtxCancelled() { return } @@ -248,7 +253,11 @@ func (s *BufferedCollector) CollectEvent(ev Event) { } else { ev = ev.ClearExtended() } - s.writes <- ev + s.writes <- Metric{Event: ev, Value: count} +} + +func (s *BufferedCollector) CollectEvent(ev Event) { + s.CollectEvents(ev, 1) } func (s *BufferedCollector) isCtxCancelled() bool { @@ -265,7 +274,7 @@ func (s *BufferedCollector) Run(ctx context.Context) { select { case w := <-s.writes: // collect events, and flush if needed by size - s.incr(w) + s.update(w) if len(s.cache) >= s.flushSize { s.flush() } @@ -315,7 +324,7 @@ func (s *BufferedCollector) Close() { // drain writes close(s.writes) for w := range s.writes { - s.incr(w) + s.update(w) } metrics := makeMetrics(s.cache) s.send(metrics) diff --git a/pkg/stats/nullcollector.go b/pkg/stats/nullcollector.go index 9b1b3b5fefd..d09b906b386 100644 --- a/pkg/stats/nullcollector.go +++ b/pkg/stats/nullcollector.go @@ -6,6 +6,8 @@ func (m *NullCollector) CollectMetadata(_ *Metadata) {} func (m *NullCollector) CollectEvent(_ Event) {} +func (m *NullCollector) CollectEvents(ev Event, count uint64) {} + func (m *NullCollector) SetInstallationID(_ string) {} func (m *NullCollector) CollectCommPrefs(_, _ string, _, _ bool) {}